|
Переполнение буфера в стеке. Часть 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 байта |