Переполнение буфера в стеке. Шествие второе

kas1e

Xakep, номер #051, стр. 051-060-1

Из предыдущей части статьи ты узнал, что же собой представляет переполнение в стеке и каким образом оно получается. Узнал, что такое регистры, инструкции и весь необходимый теоретический минимум. Сегодня же мы займемся непосредственно реализацией. Делать это будем под linux на x86-ом процессоре. Но сначала немного теории.

Атрибуты файлов

Все файлы в любом unix имеют помимо прав доступа (комбинации г, w, x) еще и атрибуты: sticky bit, suid/sgid и блокирование.

sticky bit - в современных осях практически не используется, но раньше юзался для уменьшения времени загрузки наиболее часто запускаемых программ. Механизм действия таков: после завершения программы ее образ остается в памяти, и последующие запуски программы производятся быстрее.

suid/sgid - это то, что нас больше всего интересует. Эти атрибуты (или флаги) позволяют менять привилегии с текущего пользователя на владельца файла. Например, у тебя есть некая программа, на которой стоит SUID-флаг, владелец и группа файла - root. Если пользователь запустит такую программу, то процесс будет работать с правами рута. Интересно еще и то, что процессы, порожденные из такого "суидного" файла, также наследуют рута. И что же получается? А то, что если переполняется буфер в стеке суидной программы, то, в принципе, ты можешь сделать нечто незапланированное в программе на root-уровне.

Третий атрибут - блокирование. Он позволяет устранить проблему возникновения конфликтов в том случае, когда с данным файлом работают несколько задач одновременно.

Из всех атрибутов в нашем случае важны suid/sgid. Почему? Потому что ты можешь, находясь в пользовательском процессе, юзать переполнение в стеке любой суидной программы и получить, скажем, новый шелл, но уже с root-привилегиями. Вот именно для этого и пишутся специальные куски кода, которые делают такие вещи.

Shellcode

Что такое shellcode? Это код, выдающий шелл. Написан он будет в машинных кодах. Почему именно так? Во-первых, наши переполнения базируются на организации стека и регистрах. А там только байты и машинные коды. Самый простой (и распространенный) способ создания шеллкода - написание его на ассемблере, а потом перевод в машинный код (к примеру, objdump'ом). Шелл, в понимании unix, дают программы /bin/sh, /bin/ksh, /bin/bash и другие. Т.е. все, что тебе нужно - запустить /bin/sh на ассемблере. Ассемблеров под unix много, но мы возьмем стандартный "as" с at&t-синтаксисом.

At&T-синтаксис кардинально отличается от intel'овского (tasm/nasm/masm). Вот основные нюансы:

Перед регистрами всегда ставится знак '%' (%ebp,%eax).

Перед непосредственными операндами символ '$' (push $1).

Директивы всегда начинаются с точки (.text,.data).

Если после каких-то символов стоит двоеточие, то это означает метку (как и в intel).

К командам, имеющим операнды, добавляются такие суффиксы:

суффикс описание пример

b байт movb $1,%al

w 2 байта movw $1,%eax

l 4 байта movl $0xbfffffff,%eax

Это три наиболее часто используемых суффикса (есть еще и s, t, q и т.д.). Сама ассемблерная программа должна начинаться с метки _start. В отличие от intel-ассемблеров, метка end не нужна:

Содержание  Вперед на стр. 051-060-2

ttfb: 3.0710697174072 ms