logo
Методические указания для АЗ-ИБ

Переполнение в стеке

В рассмотренном примере при вызове функции overflow_func() в стек помещается кадр стека. При первом вызове функции кадр стека может выглядеть примерно так:

buf

указатель кадра стека (sfp)

адрес возврата (ret)

*str (аргумент функции)

остальной стек


Однако когда функция пытается записать 128 байт данных в buf длиной 20 байт, лишние 108 байт запишутся за пределы буфера, затирая указатель кадра стека, адрес возврата и аргумент функции указатель str. Затем, когда функция завершает свою работу, порграмма пытается перейти по адресу возврата, который теперь заполнен буквами "А" (0х41 в шестнадцатеричном виде). Программа пытается выполнить возврат по этому адресу, заставляя EIP перейти на 0x41414141 некоторый случайный адрес, который либо относится к недопустимому пространству памяти, либо содержит недопустимые команды, что приводит к аварийному завершению программы. Это называется переполнением в стеке, потому что оно происходит в стековом сегменте памяти.

Переполнения могут происходить и в других сегментах памяти (об этом позже), но переполнения в стеке более разнообразны и интересны, поскольку могут изменять адрес возврата. Аварийное завершение программы при переполнении в стеке не столь интересно, как причина, по которой оно происходит. Если адрес возврата можно было бы контролировать и записать в него какой-нибудь адрес, где находится реально исполняемый код, то тогда программа "вернулась" бы и выполнила этот код, а не завершилась аварийно. А если данные, переписывающие адрес возврата, зависят от данных, введённых пользователем, например от текста в поле для ввода имени пользователя, то он сможет управлять адресом возврата и последующим выполнением программы.

Если можно модифицировать адрес возврата и изменить порядок выполнения путём переполнения буфера, то всё, что требуется, это какой-нибудь полезный код, который хотелось бы выполнить. Здесь мы сталкиваемся с инжекцией байт-кода. Байт-код это искусно написанный на ассемблере код, являющийся законченной программой, которую можно вредить в буфер. На байт-код накладываются некоторые ограничения: он должен быть законченной программой и в нём не должно быть некоторых специальных символов, потому что он должен иметь вид обычных данных, помещаемых в буфер.

Самый распространённый пример байт-кода это шеллкод. Это код, который запускает оболочку. Если suid-программу (Set UID — программа, у которой установлен этот бит, выполняется с правами хозяина файла программы. Имеет смысл только при установке на исполняемые файлы) с правами root удастся заставить выполнить шеллкод, то атакующий получит пользовательскую оболочку с правами root, при этом система будет считать, что suid-программа продолжает делать то, что ей положено.