Tuesday, October 16, 2012

Пример простого переполнения

Нужно мне объяснить людям, которые не совсем в теме, что такое переполнение стека длинной строкой и как это работает в 32-битных программах под Windows. Ну я прикидываю что надо рассказать по теории - про адресное пространство, стек и т.п. Потом конечно начинаю думать какой бы пример переполнения показать. Решаю что сначала лучше показать "на кошках", т.е. на каком-то искусственном примере. Недолго думая, я открываю MS Visual C++ 2010 Express, создаю проект консольного приложения и по памяти накидываю в main.cpp известный всем код:
#include <stdio.h>

void victimFunction() {
 char text[80];
 printf("Enter your text: ");
 scanf("%s", text);
 printf("Your text is '%s'", text);
}

int main() {
 victimFunction();
 return 0;
}
Код - практически классика жанра. Под строку text на стеке выделяется буфер в 80 байтов. Если этот буфер переполнить слишком длинной строкой - эта строка затрет адрес возврата на стеке и управление из функции victimFunction() вернется не в main(), а "куда надо" ©. "Все очевидно! Любой поймет!" - думаю я. "Какой же я молодец!" - думаю я.

Любуясь простотой творения рук своих, я уже прикидываю что и как я буду с важным видом показывать и объяснять. Запускаю компиляцию в Release Solution Configuration... и тут прилетает маленькая, но жестокая птичка обломинго. Disassembly показывает:
#include <stdio.h>

void victimFunction() {
 char text[80];
 printf("Enter your text: ");
 scanf("%s", text);
 printf("Your text is '%s'", text);
}

int main() {
01261000  push        ebp  
01261001  mov         ebp,esp  
01261003  sub         esp,54h  
01261006  mov         eax,dword ptr [___security_cookie (1263000h)]  
0126100B  xor         eax,ebp  
0126100D  mov         dword ptr [ebp-4],eax  
01261010  push        esi  
 victimFunction();
01261011  mov         esi,dword ptr [__imp__printf (126209Ch)]  
01261017  push        offset string "Enter your text: " (12620F4h)  
0126101C  call        esi  
0126101E  lea         eax,[ebp-54h]  
01261021  push        eax  
01261022  push        offset string "%s" (1262108h)  
01261027  call        dword ptr [__imp__scanf (12620A4h)]  
0126102D  lea         ecx,[ebp-54h]  
01261030  push        ecx  
01261031  push        offset string "Your text is '%s'" (126210Ch)  
01261036  call        esi  
 return 0;
}
01261038  mov         ecx,dword ptr [ebp-4]  
0126103B  add         esp,14h  
0126103E  xor         ecx,ebp  
01261040  xor         eax,eax  
01261042  pop         esi  
01261043  call        __security_check_cookie (126104Ch)  
01261048  mov         esp,ebp  
0126104A  pop         ebp  
0126104B  ret  
То есть во-первых компилятор Visual C++ начиная ещё по-моему с 2003-го по умолчанию считает /GS, а значит автоматически применяет защиту стека на основе canary word (видите в коде вызовы security_*_cookie? - это оно!). Во вторых victimFunction() вызывается единожды, а значит отличный кандидат что бы стать inline в процессе оптимизации кода. Ну она собственно и становится inline.

В результате этот код для простой демонстрации переполнения буфера получается явно непригодным. Почему я не вспомнил про эти элементарные в общем-то вещи до того как скомпилировал код? Наверное какое-то временное помутнение рассудка (ну или я просто дебил, но развивать эту тему как-то не особенно хочется - поэтому лучше пусть будет помутнение :)).

Ладно, постепенно у меня в голове проясняется. В настройках проекта отключаю
и

В результате получается вполне годный для демонстрации простого переполнения код:
#include <stdio.h>

void victimFunction() {
010C1000  push        ebp  
010C1001  mov         ebp,esp  
010C1003  sub         esp,50h  
 char text[80];
 printf("Enter your text: ");
010C1006  push        offset ___xi_z+30h (10C20F4h)  
010C100B  call        dword ptr [__imp__printf (10C209Ch)]  
010C1011  add         esp,4  
 scanf("%s", text);
010C1014  lea         eax,[text]  
010C1017  push        eax  
010C1018  push        offset ___xi_z+44h (10C2108h)  
010C101D  call        dword ptr [__imp__scanf (10C20A4h)]  
010C1023  add         esp,8  
 printf("Your text is '%s'", text);
010C1026  lea         ecx,[text]  
010C1029  push        ecx  
010C102A  push        offset ___xi_z+48h (10C210Ch)  
010C102F  call        dword ptr [__imp__printf (10C209Ch)]  
010C1035  add         esp,8  
}
010C1038  mov         esp,ebp  
010C103A  pop         ebp  
010C103B  ret 

int main() {
010C1040  push        ebp  
010C1041  mov         ebp,esp  
 victimFunction();
010C1043  call        victimFunction (10C1000h)  
 return 0;
010C1048  xor         eax,eax  
}
010C104A  pop         ebp  
010C104B  ret 
Это я к чему все пишу? Да ни к чему, просто так :) Не отключайте /GS, а то придет бабайка-переполняйка, вирусов свежих принесёт - вот!

No comments:

Post a Comment