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

kas1e

Xakep, номер #050, стр. 050-062-6


Как ты, возможно, уже понял - переполнение стека именно в этом и заключается - переполнить переменными какой-то буфер в стеке так, чтобы перезаписался адрес возврата из функции (в данном случае из main()).

Рассмотрим пример.

Допустим, при запуске стек начинается по адресу 0xbffffa9f. Автоматически при запуске программы в ESP будет 0xbffffa9c, т.к. 4 байта уже "забраны" под адрес возврата из main(). В EBP регистре при запуске может быть какой угодно хлам, т.к. он не определен. Как я говорил, EBP регистр создан для простоты навигации по стеку, и мы можем делать с этим регистром всякие арифметические и логические операции, как и с обычными регистрами, и при этом иметь "direct access" к стеку.

Для того чтобы мы могли бродить по стеку, в ebp должен быть адрес этого стека. Просто сделать mov %esp,%ebp мы, конечно, можем, но в случае написания именно на ассемблере. А вот разработчики C-компилятора сразу себя обезопасили, и даже в функции main() сохраняют %ebp (т.к. и всякие обработчики исполняемого файла могут использовать %ebp). После того как %ebp сохранили, можно сделать и mov %esp,%ebp. Это объяснение двух первых и двух последних инструкций любой С-функции/подпрограммы/процедуры.

Итак, после выполнения первых двух инструкций наш стек выглядит так:

0xbffffa9f 0xbffffa9c

по этому адресу по этому адресу

4 байта адреса сохраненный на

возврата из main всякий случай EBP.

В результате, после первых двух инструкций в ESP у нас 0xbffffa98. И в EBP тоже. Т.е. перезаписывать нам придется 4 байта сохраненного EBP, а после него уже будет перезаписываться адрес возврата. Нам это все равно, так как, если мы сможем перезаписывать эти области, то мы просто будем добавлять еще 4 байта и делать грязное дело.

Конечно, мы можем просто обратиться по этому адресу и написать то, что нам надо. НО! Не надо забывать, что все это разжевывается в контексте использования данной уязвимости в уже готовой/чужой программе.

Идем дальше. Допустим, у нас есть некий буфер (локальная переменная), который, естественно, расположен в стеке. Скажем char little_buffer[4];

В стеке под этот буфер должно выделиться место. Т.е. после первых двух инструкций (push %ebp и mov %esp,%ebp) идет sub $0x4,%esp. Этой инструкцией мы выделяем буфер на 4 байта в стеке. Сделаем little_buffer[8] - соответственно sub $0x8,%esp и буфер в 8 байт.

И вот мы подошли к самому главному. Нам нужно переполнить этот буфер, т.е. вписать вместо 4 зарезервированных байт какую-то инфу, чтобы заполнить буфер, еще 4 - чтобы перезаписать сохраненный ebp, и дальше уже 4 байта на адрес возврата. Вот! Это самый важный момент. Во второй части статьи мы будем подсовывать наш код, и возврат будет происходить именно на него!

Теперь рассмотрим это на практике. Для этого напишем еще одну программу:

main (int argc, char *argv[ ]) // будем брать символы с командной строки

{

char little_buffer[4]; // сделаем буфер на 4 байта

Назад на стр. 050-062-5  Содержание  Вперед на стр. 050-062-7