Již není mnoho základních věcí, které by měl začínající tvůrce shellcodů znát. V poslední dílu z minisérie pro začátečníky si ukážeme, jakým způsobem pracovat s funkcemi z externích DLL knihoven. Praktickou ukázkou je pak tvorba shellcodu, který využívá API funkci MessageBox z DLL knihovny user32.dll. Opět nebude chybět nezbytná teorie 🙂

Obsah:
——————————————
i. Úvod
ii. Nezbytná teorie
iii. Používáme funkce z externích knihoven
iv. Závěr
Odkazy
——————————————

i. Úvod
V předchozím dílu jsme si ukázali dva koncepty, jak pracovat s textovými řetězci – první za pomoci zásobníku, druhý za pomoci .text sekce. Vytvořili jsme si také dvě varianty shellcodu spouštějící Příkazový řádek (Command Line). Tento díl by měl uzavřít základní část seriálu, která je nezbytná pro další práci. Jedná se o práci s API funkcemi z externích DLL knihoven. Jako ukázkový shellcode zvolíme kód, který vyvolá MessageBox s definovaným textem a ukončí se pomocí API funkce ExitProcess. Tento díl by měl patřit k těm kratším, nikoliv však méně hodnotným.

ii. Nezbytná teorie
Opět zde máme nezbytnou teorii, ve které se dnes zaměřím na některé pokročilejší myšlenky, které budou podrobně a prakticky vysvětleny v dalších dílech seriálu. To, že shellcode by neměl obsahovat NULL bajty, jsem zdůrazňoval více než mnohokrát. Je výzvou pro programátory takové shellcody vytvořit (za všechny jmenujme skupinu Last Stage of Delirium, která na tomto poli kolem roku 2003 excelovala). Ovšem v současné době je stále populárnější enkódování celého shellcodu bez toho, aby se NULL bajty odstranily. Na zásobník se tak dostane kód bez NULL bajtů, který se nejdříve dekóduje a následně vykoná. Je to časově úspornější na vývoj, avšak náročnější na výslednou velikost.

Nejen při vzdálené, ale i při lokální exploitaci programů s využitím shellcodů se může snadno stát, že útočníka odhalí IDS v závislosti na použitých ASCII znacích (hexadecimální hodnoty opcódů, hodnot a adres) tvořících shellcode. Proto byla vyvinuta technika tvorby alphabetic shellcodů, které tvoří tzv. „bezpečné znaky“. Těmito znaky jsou myšleny takové znaky, které jsou používány v běžném textu, tedy velká a malá písmena, čísla, vykřičníky, tečky a podobně. Konstrukce takového shellcodu je sice mnohem náročnější, ale výsledek stojí určitě za to 🙂

Asi největším dosavadním vrcholem jsou tzv. english shellcode. Jedná se o shellcody tvořící anglická slova nebo dokonce celé věty. Prakticky jsem se s nimi setkal pouze v podobě jediné laboratorní testovací ukázky. Každopádně lze do budoucna předpokládat, že se objeví i „In The Wild“.

iii. Používáme funkce z externích knihoven
Ač se to nezdá, dělali jsme to už v předchozích dílech 🙂 Volali jsme totiž funkce obsažené v DLL knihovně kernel32.dll. Společně s ntdll.dll se jedná o jediné dvě knihovny, které se nahrávají do paměti při spouštění libovolného programu, a to vždy na stejné adresy. Proto je můžeme bez váhání použít. V případě ostatních DLL knihoven je tato situace poněkud komplikovanější. I když program bude využívat námi požadovanou DLL knihovnu, nemáme záruky, že se v paměti objeví na stejné adrese jako v jiných programech. Pokud bychom na tuto skutečnost spoléhali, volání funkce by mohlo vést k zacyklení aplikace nebo k jejímu pádu v důsledku přístupu na adresu, kde by funkce měla být, ale není.

Jednoduchý Cčkový kód, který vyhodí MessageBox a ukončí se pomocí ExitProcess by mohl vypadat třeba takhle:

1
2
3
4
5
6
#include <windows.h>
 
int main(){
	MessageBox(NULL, "RubberDuck", "RubberDuck", MB_OK);
	ExitProcess(0);
}

Dva řádky kódu. To by mohla být brnkačka, ne? 😉 Pojďme si to zkusit přepsat do assembleru. Řetězce budu ukládat na zásobník. V případě použitých konstant (MB_OK) je potřeba zjistit si, jakou hodnotu reprezentují.

1
2
3
4
5
6
7
8
9
10
11
12
13
push 000006b63h
push 075447265h
push 062627552h
mov edx, esp ; edx = zacatek stringu
 
push 0
push edx
push edx
push 0
call MessageBoxA ; volame ANSI verzi funkce
 
push 0
call ExitProcess

Takhle by mohla vypadat hodně odlehčená ASM verze kódu. Vidíme, že se v kódu nachází nejméně 5 NULL bajtů a stringy identifikující názvy API funkcí je potřeba zaměnit za jejich adresy, což už pro nás není problém 🙂 API funkce MessageBoxA[1] se nachází v DLL knihovně user32.dll a API funkce ExitProcess tradičně v kernel32.dll.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
xor ecx, ecx ; ecx = 0
 
push 001016b63h
mov byte ptr [esi + 2], cl ; NULL bajt
push 075447265h
push 062627552h
mov edx, esp ; edx = zacatek stringu
 
push ecx
push edx
push edx
push ecx
mov ebx, 07e3a07eah ; ebx = MessageBoxA
call ebx
 
xor ecx, ecx
push ecx
mov ebx, 07c81cb12h ; ebx = ExitProcess
call ebx

Celý kód vypadá, že už neobsahuje jediný NULL bajt. Tak ho zkusíme zkompilovat a spustit. Teď záleží na aktuální situaci. Někomu kód bude fungovat a někomu ne. Záleží, zda tento proces má nebo nemá načtenou DLL knihovnu user32.dll. Ty, kterým kód funguje asi nepotěším, když jim řeknu, že je to zcela irelevantní a budou ho muset předělat 🙂 A ti, kterým nefunguje, mají aspoň motivaci přepracovat ho.

Protože je API funkce MessageBoxA v knihovně user32.dll, potřebujeme tuto knihovnu nejdříve nahrát do paměťového prostoru procesu. K tomu využijeme funkci LoadLibraryA[2]. Tato funkce nám vrátí handle modulu, který využijeme jako argument funkce GetProcAddress[3] a získáme adresu námi požadované funkce MessageBoxA. Na první pohled se tento kód může zdát jednoduchý, takže si ho nejdříve napíšeme v Cčku:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <windows.h>
 
int main(){
	HMODULE hModule;
	FARPROC MyMessageBoxA;
 
	hModule = LoadLibraryA("user32.dll"); 
	// tady by se hodila kontrola
	MyMessageBoxA = (FARPROC)GetProcAddress(hModule, "MessageBoxA");
	// opet by to chtelo kontrolu
 
	MyMessageBoxA(NULL, "RubberDuck", "RubberDuck", MB_OK);
 
	ExitProcess(0);
}

Myslím, že celý kód je srozumitelný, a proto můžeme přistoupit k jeho přepsání do assembleru:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
xor ecx, ecx
xor esi, esi
 
push 001016c6ch
mov byte ptr [esp + 2], cl
push 0642e3233h
push 072657375h
push esp
mov ebx, 07c801d7bh ; ebx = LoadLibraryA
call ebx
 
mov ecx, esi
push 00141786fh
mov byte ptr [esp + 3], cl
push 042656761h
push 07373654dh
push esp
push eax
mov ebx, 07c80ae40h ; ebx = GetProcAddress
call ebx
 
mov ecx, esi
push 001016b63h
mov byte ptr [esp + 2], cl
push 075447265h
push 062627552h
mov edx, esp
 
push esi
push esp
push esp
push esi
call eax ; eax = MessageBoxA
 
push esi
mov ebx, 07c81cb12h ; ebx = ExitProcess
call ebx

messagebox_1

Vypadá to, že mě kód funguje skvěle 🙂 Teď již zbývá poslední tradiční krok: Vytvořit samotný kód shellcodu a otestovat ho.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
 
int main(){
    unsigned char shellcode[]=
    "\x33\xC9\x33\xF6\x68\x6C\x6C\x01\x01\x88"
    "\x4C\x24\x02\x68\x33\x32\x2E\x64\x68\x75"
    "\x73\x65\x72\x54\xBB\x7B\x1D\x80\x7C\xFF"
    "\xD3\x8B\xCE\x68\x6F\x78\x41\x01\x88\x4C"
    "\x24\x03\x68\x61\x67\x65\x42\x68\x4D\x65"
    "\x73\x73\x54\x50\xBB\x40\xAE\x80\x7C\xFF"
    "\xD3\x8B\xCE\x68\x63\x6B\x01\x01\x88\x4C"
    "\x24\x02\x68\x65\x72\x44\x75\x68\x52\x75"
    "\x62\x62\x8B\xD4\x56\x52\x52\x56\xFF\xD0"
    "\x56\xBB\x12\xCB\x81\x7C\xFF\xD3";
 
    void (* pfunc)();
 
    pfunc = shellcode;
 
    pfunc();
 
    return 0;
}

messagebox_2

iv. Závěr
Shellcode pro můj systém funguje, takže úkol byl úspěšně splněn 🙂 S doposud získanými znalostmi by každý čtenář měl být schopen vytvořit shellcode podle svých vlastních představ. Tímto oficiálně ukončuji úvodní část seriálu věnovanou základům návrhu a psaní shellcodu a s předstihem napovídám, že další díly se již ponesou v duchu pokročilejších technik 🙂 Rád bych apeloval na všechny čtenáře, aby se nebáli zpětné vazby a případnými komentáři, připomínkami, výtkami, nápady etc. pomohli co možná nejvíce zvýšit celkovou úroveň seriálu, který je psán právě pro ně.

Odkazy
[1] – http://msdn.microsoft.com/en-us/library/ms645505%28VS.85%29.aspx
[2] – http://msdn.microsoft.com/en-us/library/ms684175%28VS.85%29.aspx
[3] – http://msdn.microsoft.com/en-us/library/ms683212%28VS.85%29.aspx