彙編:定位Windows API地址

架不住“文探”的一再“邀請”,在《今日頭條》上開了個鋪子,同名賬號。有點摸不著頭腦,是因為上一篇文章“彙編:定位kernel32的基地址”突然在《頭條》上“火”了起來,展現量達到了10000多,我暫時還不知道展現量是什麼意思、有什麼用,但我發現我的公眾號的關注人數增加了好幾十。可那篇文章公眾號上的閱讀量才300不到啊,可見頭條的定向推送確實厲害。

再來篇姐妹篇。文章有點長,但如果你熟悉其中的過程,將會一氣呵成,有點嫌短了。對這種內容,要一句一句地摳,才會覺得有意思。最後,又是對比了x86和x64下的不同。

以下彙編程式將定位LoadLibraryA函式的地址並將其儲存在EBP指向的地址處。程式碼有點長。別擔心,我們將在主要列表之後分解每個部分正在做什麼。

[SECTION 。text]BITS 32_start: jmp main ; Constantswin32_library_hashes: call win32_library_hashes_return ; LoadLibraryA dd 0xEC0E4E8E ; Function: find_kernel32find_kernel32: push esi xor eax, eax mov eax, [fs:eax+0x30] mov eax, [eax+0x0C] mov esi, [eax+0x1C] lodsd mov eax, [eax+0x08] pop esi ret ; Function: find_functionfind_function: pushad mov ebp, [esp+0x24] mov eax, [ebp+0x3C] mov edx, [ebp+eax+0x78] add edx, ebp mov ecx, [edx+0x18] mov ebx, [edx+0x20] add ebx, ebpfind_function_loop: jecxz find_function_finished dec ecx mov esi, [ebx+ecx*4] add esi, ebp compute_hash: xor edi, edi xor eax, eax cldcompute_hash_again: lodsb test al, al jz compute_hash_finished ror edi, 0x0D add edi, eax jmp compute_hash_againcompute_hash_finished:find_function_compare: cmp edi, [esp+0x28] jnz find_function_loop mov ebx, [edx+0x24] add ebx, ebp mov cx, [ebx+2*ecx] mov ebx, [edx+0x1C] add ebx, ebp mov eax, [ebx+4*ecx] add eax, ebp mov [esp+0x1C], eaxfind_function_finished: popad ret ; Function: resolve_symbols_for_dllresolve_symbols_for_dll: lodsd push eax push edx call find_function mov [edi], eax add esp, 0x08 add edi, 0x04 cmp esi, ecx jne resolve_symbols_for_dllresolve_symbols_for_dll_finished: ret main: sub esp, 0x88 ; Allocate space on stack for function addresses mov ebp, esp ; Set ebp as frame ptr for relative offset on stack call find_kernel32 ; Find base address of kernel32。dll mov edx, eax ; Store base address of kernel32。dll in EDX jmp win32_library_hasheswin32_library_hashes_return: pop esi lea edi, [ebp+0x04] ; This is where we store our function addresses mov ecx, esi add ecx, 0x04 ; Length of kernel32 hash list call resolve_symbols_for_dll

程式碼清單 1:完整的 32 位函式定位程式集清單

主要功能

設定堆疊和儲存

我們從第6行開始,跳轉到位於第83行的main函式。前幾行在堆疊上留出一些空間並設定一個幀指標EBP,它將在整個彙編程式碼中用於引用儲存的資料。這很重要,因為一旦我們找到某物的值或地址,我們就需要一種稍後引用它的方法。

找到Kernel32的基地址

第88行,我們呼叫find_kernel32函式在記憶體中查詢kernel32。dll的基地址。找到基地址後,它會儲存在第90行的EDX暫存器中以進行安全儲存。

獲取編碼函式名稱的位置

第92行的win32_library_hashes又反過來call win32_library_hashes_return。然後將返回eip彈出到ESI 中。

第95-98行正在設定我們的儲存位置。ESI和EDI將用於指向我們需要引用的位置。ESI將用於指向雜湊函式名稱的位置,EDI將用於指向我們將儲存解析函式地址的位置。

解析符號

在第100行,呼叫了resolve_symbols_for_dll函式。重要的是要記住kernel32的基地址當前儲存在EDX暫存器中。這將在以後很重要。

目前,我們將只關注這個函式。我們將參考“程式碼清單2”以避免來回滾動太多。

; Function: resolve_symbols_for_dll resolve_symbols_for_dll: 3 lodsd 4 push eax 5 push edx 6 call find_function 7 mov [edi], eax 8 add esp, 0x08 9 add edi, 0x04 10 cmp esi, ecx 11 jne resolve_symbols_for_dll 12 resolve_symbols_for_dll_finished: 13 ret

程式碼清單 2:32 位解析符號函式

1。在第3行,lodsd指令將儲存在ESI 中的值載入到EAX暫存器中,並遞增ESI以指向下一個地址。如果我們的程式碼正在尋找多個函式,ESI將準備就緒並指向下一個編碼的函式名稱。

2。EAX現在包含我們的第一個函式名稱的編碼值,而EDX包含kernel32的地址,然後在第4-5行被壓入堆疊。

3。現在我們將跳過對find_function的呼叫。重要的是要知道EAX現在包含我們在返回後要查詢的函式的地址。

4。在第7行,解析函式的地址儲存在EDI當前指向的地址處。

5。第8行將堆疊恢復到EAX和EDX被推送到它之前的原始狀態。

6。在第9行,EDI遞增以指向可以儲存函式地址的下一個位置。

7。然後比較ESI和ECX以檢視是否已到達雜湊函式列表的末尾。如果沒有,函式將迴圈直到到達列表的末尾。如果已到達結尾,則函式返回。

查詢函式地址

現在將在本部分中介紹彙編程式碼的兩個最重要的部分。彙編程式碼第一部分透過PE Structure來定位_IMAGE_EXPORT_DIRECTORY。一旦找到_IMAGE_EXPORT_DIRECTORY結構,程式碼清單4中彙編程式碼第二部分將遍歷函式名稱列表,以找到與正在搜尋的函式匹配的函式。最後,定位的地址將儲存到一個位置,以後可以引用它來進行函式呼叫。

查詢匯出的函式名稱

程式碼清單3中的第一部分程式碼將定位_IMAGE_EXPORT_DIRECTORY結構。找到結構後,將收集儲存在NumberOfFunctions變數中的匯出函式的數量和儲存在AddressOfNames變數中的匯出函式名稱列表的 RVA 。需要這兩個值來遍歷匯出的函式名稱以找到與正在搜尋的內容匹配的函式。一旦找到匹配的函式,AddressOfNameOrdinals將用於獲取匯出的函式地址。

;Function: find_functionfind_function: pushad mov ebp, [esp+0x24] mov eax, [ebp+0x3C] mov edx, [ebp+eax+0x78] add edx, ebp mov ecx, [edx+0x18] mov ebx, [edx+0x20] add ebx, ebp

程式碼清單 3:32 位查詢函式:定位 _IMAGE_EXPORT_DIRECTORY

以下分步分析

程式碼清單 3

1。第3行,PUSHAD指令用於將所有當前暫存器儲存在堆疊上。PUSHAD按以下順序(自頂向下)入在棧中:EAX,ECX,EDX,EBX,原始ESP,EBP,ESI和EDI

2。kernel32。dll的基地址儲存在堆疊中的ESP + 0x24(36 位元組)然後被移動到第4行的EBP暫存器。

3。第5行的 move 指令將位於kernel32。dll基址偏移0x3C處的值載入到EAX暫存器中。根據PE Structure圖,PE Header的基地址位於PE檔案基地址的0x3C偏移處。EAX現在包含從kernel32。dll基址到PE Header地址的偏移量。

4。第6行的移動指令將EBP(kernel32。dll 的基地址)、EAX(PE 頭偏移)和0x78相加,並將結果儲存在EDX暫存器中。回頭看PE Structure圖,ExportTable結構的偏移值就在那個位置。EDX現在包含匯出表相對於kernel32。dll基址的偏移值。

5。EBP(kernel32。dll 的基地址)被新增到第7行的EDX(_IMAGE_EXPORT_DIRECTORY 的偏移量)。EDX現在指向_IMAGE_EXPORT_DIRECTORY結構。結構佈局見下圖。

typedefstruct

_IMAGE_EXPORT_DIRECTORY{

0:DWORD Characteristics; 4:DWORD TimeDateStamp; 8:WORD MajorVersion; A:WORD MinorVersion; C:DWORD Name; 10:DWORD Base; 14:DWORD NumberOfFunctions; 18:DWORD NumberOfNames; 1C:DWORD AddressOfFunctions; // RVA from base of image 20:DWORD AddressOfNames; // RVA from base of image 24:DWORD AddressOfNameOrdinals; // RVA from base of image} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

圖 1:匯出表

ExportTable

結構

6。0x18被新增到EDX並存儲在第8行的ECX 中。根據圖1中的_IMAGE_EXPORT_DIRECTORY結構,NuberOfNames變數位於偏移量0x18 處。ECX現在儲存匯出函式的數量。

7。0x20被新增到EDX並存儲在第9行的EBX 中。根據圖1中的_IMAGE_EXPORT_DIRECTORY結構,AddressOfNames變數位於偏移量0x20 處。此變數包含從kernel32。dll的基址到匯出的函式名稱列表的相對偏移量。

8。在第10行,將EBP新增到EDX 中。EDX現在指向匯出的函式名稱列表

迭代函式名稱以找到匹配項

下一部分程式碼將遍歷EBX指向的AddressOfNames列表。它將向後遍歷列表,對每個名稱進行雜湊處理並將它們與儲存在我們的彙編程式碼中的雜湊值進行比較。當找到匹配項時,在呼叫find_function函式之前,該值將儲存在EDI指向的位置。EDI 的先前值當前位於堆疊中以安全儲存,因為此函式將重用EDI來儲存正在檢查的當前函式名稱的計算雜湊值。以下分步分析程式碼清單4中的程式碼。

find_function_loop: jecxz find_function_finished dec ecx mov esi, [ebx+ecx*4] add esi, ebp compute_hash: xor edi, edi xor eax, eax cldcompute_hash_again: lodsb test al, al jz compute_hash_finished ror edi, 0x0D add edi, eax jmp compute_hash_againcompute_hash_finished:find_function_compare: cmp edi, [esp+0x28] jnz find_function_loop mov ebx, [edx+0x24] add ebx, ebp mov cx, [ebx+2*ecx] mov ebx, [edx+0x1C] add ebx, ebp mov eax, [ebx+4*ecx] add eax, ebp mov [esp+0x1C], eaxfind_function_finished: popad ret

程式碼清單 4:32 位迭代函式列表

第2行檢查ECX是否為零。如果ECX已達到零,該函式將透過恢復暫存器並返回來完成。

在第3行,ECX減 1,這表明程式碼將反向迭代列表。

在第4行,將EBX(函式名稱列表)指向的值新增到ECX(匯出名稱數量)乘以4的總和並存儲在ESI 中。ESI現在包含從kernel32的基址到 list 中最後一個字串的偏移量。

EBP(kernel32。dll 的基地址)在第5行新增到ESI。ESI現在指向最後匯出的函式名稱,一個以 NULL 結尾的字串。

在第7-18行,函式名稱的雜湊被計算並存儲在EDI 中。

計算出的雜湊儲存在EDI暫存器中,在第20行與儲存在 [ ESP + 0x28 ] 中的值進行比較。雜湊函式名稱儲存在該位置。它在呼叫find_function函式之前被壓入堆疊。

如果值不匹配,則迴圈移動到下一個函式名稱。如果找到匹配項,則繼續。

接下來的幾個步驟(第22-28行)可能有點難以理解,請參閱圖2以幫助您繼續學習。0x24被新增到EDX(_IMAGE_EXPORT_DIRECTORY 結構)並存儲在第22行的EBX 中。根據圖1中的_IMAGE_EXPORT_DIRECTORY 結構,

AddressOfOrdinals

位於偏移量

0x24

處。此變數包含與匯出函式對應的有序值列表的相對偏移量。

彙編:定位Windows API地址

圖 2:匯出表視覺化

9。在第23行,將EBP(kernel32。dll 的基地址)新增到EBX 中。EBX現在指向由AddressOfOrdinals變數引用的序數列表。

10。EBX(序數列表地址)被新增到ECX(函式編號)的總和*2 並存儲在ECX 中。ECX現在包含AddressOfFunctions列表中的偏移量,該列表將包含正在搜尋的函式地址。

11。0x1C被新增到EDX(_IMAGE_EXPORT_DIRECTORY 結構)並存儲在第25行的EBX中。EBX現在在_IMAGE_EXPORT_DIRECTORY結構中包含AddressOfFunctions變數的偏移量。

12。在第26行,將EBP(kernel32。dll 的基地址)新增到EBX 中,使EBX指向函式地址列表。

13。在第27行,ECX(函式編號)乘以 4 並新增到EBX(函式地址列表)並存儲在EAX 中。EAX現在包含從kernel32。dll的基址到正在搜尋的函式地址的偏移量。

14。在第28行,EAX(函式地址偏移)被新增到EBP(kernel32。dll 的基地址)。EAX現在包含正在搜尋的函式的地址。

15。該值儲存在 [ ESP + 0x1C ] 中,其中包含在函式開始時由PUSHAD命令壓入堆疊的EDI值。這儲存了函式地址,我們以後可以根據需要引用它。

16。POPAD函式從堆疊暫存器恢復到其以前的狀態,並執行返回到resolve_symbols_for_dll功能。

64 位

下面的彙編程式將定位兩個函式的地址:LoadLibraryA和CreateProcessA。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293

[SECTION 。text]BITS 64_start: jmp main ; Constants win32_library_hashes: call win32_library_hashes_return ; LoadLibraryA R13 dd 0xEC0E4E8E ; CreateProcessA R13 + 0x08 dd 0x16B3FE72 ; ======== Function: find_kernel32 find_kernel32: push rsi mov rax, [gs:0x60] mov rax, [rax+0x18] mov rax, [rax+0x20] mov rax, [rax] mov rax, [rax] mov r11, [rax+0x20] ; Kernel32 Base Stored in R11 pop rsi ret ; ======= Function: find_function find_function: mov eax, [r11+0x3C] mov edx, [r11+rax+0x88] add rdx, r11 ; RDX now points to the IMAGE_DATA_DIRECTORY structure mov ecx, [rdx+0x18] ; ECX = Number of named exported functions mov ebx, [rdx+0x20] add rbx, r11 ; RBX = List of exported named functions find_function_loop: jecxz find_function_finished dec ecx ; Going backwards lea rsi, [rbx+rcx*4] ; Point RSI at offset value of the next function name mov esi, [rsi] ; Put the offset value into ESI add rsi, r11 ; RSI now points to the exported function name compute_hash: xor edi, edi ; Zero EDI xor eax, eax ; Zero EAX cld ; Reset direction flag compute_hash_again: mov al, [rsi] ; Place the first character from the function name into AL inc rsi ; Point RSI to the next character of the function name test al, al ; Test to see if the NULL terminator has been reached jz compute_hash_finished ror edi, 0x0D ; Rotate the bits of EDI right 13 bits add edi, eax ; Add EAX to EDI jmp compute_hash_again compute_hash_finished: find_function_compare: cmp edi, r12d ; Compare the calculated hash to the stored hash jnz find_function_loop mov ebx, [rdx+0x24] ; EBX contains the offset to the AddressNameOrdinals list add rbx, r11 ; RBX points to the AddressNameOrdinals list mov cx, [rbx+2*rcx] ; CX contains the function number matching the current function mov ebx, [rdx+0x1C] ; EBX contains the offset to the AddressOfNames list add rbx, r11 ; RBX points tot he AddressOfNames List mov eax, [rbx+4*rcx] ; EAX contains the offset of the desired function address add rax, r11 ; RAX contains the address of the desired function find_function_finished: ret ; ======== Function: resolve_symbols_for_dll resolve_symbols_for_dll: mov r12d, [r8d] ; Move the next function hash into R12 add r8, 0x04 ; Point R8 to the next function hash call find_function mov [r15], rax ; Store the resolved function address add r15, 0x08 ; Point to the next free space cmp r9, r8 ; Check to see if the end of the hash list was reached jne resolve_symbols_for_dll resolve_symbols_for_dll_finished: ret main: sub rsp, 0x110 ; Allocate space on stack for function addresses mov rbp, rsp ; Set ebp as frame ptr for relative offset on stack call find_kernel32 ; Find base address of kernel32。dll jmp win32_library_hashes win32_library_hashes_return: pop r8 ; R8 is the hash list location mov r9, r8 add r9, 0x08 ; R9 marks the end of the hash list lea r15, [rbp+0x10] ; This will be a working address used to store our function addresses mov r13, r15 ; R13 will be used to reference the stored function addresses call resolve_symbols_for_dll int3

缺少命令?

當使用 Assembly 在 64 位上執行時,有一些命令不再存在或不再有用。PUSHAD和POPAD不會在64位暫存器工作。在這種情況下,它們沒有用,我們需要找到替代方案。該LODSB命令也不會在64位暫存器工作。有必要將其替換為MOV AL、[RSI]和INC RSI序列。它做完全相同的事情,只是有更多的位元組和指令。

暫存器差異

64位程式集有更多可用的暫存器。添加了暫存器R8-R15。這為我們提供了8個新的地方來存放我們需要的東西。有了這個額外的儲存空間是相當容易的補償缺少PUSHAD和POPAD命令。不過也有一些考慮。為了對我們現在找到的函式地址做任何事情,我們需要呼叫這些函式。根據文件,64位stdcall函式呼叫有點不同。根據引數傳遞部分,R8和R9暫存器用於將第三個和第四個引數傳遞給函式。我們應該避免在其中長期存放任何東西。這同樣適用於在RCX和RDX引數。

PE頭偏移值仍處於KERNEL32。DLL的0x3c偏移量上。在這裡,查詢_IMAGE_EXPORT_DIRECTORY的偏移量有點不同。檢查下圖3中WinDbg中的_IMAGE_DOS_HEADER和_IMAGE_NT_HEADERS64的錶轉儲,我們可以看到佈局只是略有不同。_IMAGE_DATA_DIRECTORY位於OptionalHeader偏移量0x18的0x70處。這意味著總偏移量需要是0x88,而不是32位模式下的0x78。好訊息是IMAGE_DATA_DIRECTORY的佈局仍然與圖1相同。

0:001> dt _IMAGE_DOS_HEADER 77260000ntdll!_IMAGE_DOS_HEADER +0x000 e_magic : 0x5a4d +0x002 e_cblp : 0x90 +0x004 e_cp : 3 +0x006 e_crlc : 0 +0x008 e_cparhdr : 4 +0x00a e_minalloc : 0 +0x00c e_maxalloc : 0xffff +0x00e e_ss : 0 +0x010 e_sp : 0xb8 +0x012 e_csum : 0 +0x014 e_ip : 0 +0x016 e_cs : 0 +0x018 e_lfarlc : 0x40 +0x01a e_ovno : 0 +0x01c e_res : [4] 0 +0x024 e_oemid : 0 +0x026 e_oeminfo : 0 +0x028 e_res2 : [10] 0 +0x03c e_lfanew : 0n2240:001> dt -r _IMAGE_NT_HEADERS64 772600eCntdll!_IMAGE_NT_HEADERS64 +0x000 Signature : 0 +0x004 FileHeader : _IMAGE_FILE_HEADER +0x000 Machine : 0 +0x002 NumberOfSections : 0 +0x004 TimeDateStamp : 0x202200f0 +0x008 PointerToSymbolTable : 0x9020b +0x00c NumberOfSymbols : 0x9b000 +0x010 SizeOfOptionalHeader : 0x1000 +0x012 Characteristics : 8 +0x018 OptionalHeader : _IMAGE_OPTIONAL_HEADER64 +0x000 Magic : 0 +0x002 MajorLinkerVersion : 0 ‘’ +0x003 MinorLinkerVersion : 0 ‘’ +0x004 SizeOfCode : 0x15340 +0x008 SizeOfInitializedData : 0x1000 +0x00c SizeOfUninitializedData : 0x77260000 +0x010 AddressOfEntryPoint : 0 +0x014 BaseOfCode : 0x1000 +0x018 ImageBase : 0x00010006`00000200 +0x020 SectionAlignment : 0x10006 +0x024 FileAlignment : 0x10006 +0x028 MajorOperatingSystemVersion : 0 +0x02a MinorOperatingSystemVersion : 0 +0x02c MajorImageVersion : 0xf000 +0x02e MinorImageVersion : 0x11 +0x030 MajorSubsystemVersion : 0x400 +0x032 MinorSubsystemVersion : 0 +0x034 Win32VersionValue : 0x122d97 +0x038 SizeOfImage : 0x1400003 +0x03c SizeOfHeaders : 0x40000 +0x040 CheckSum : 0 +0x044 Subsystem : 0x1000 +0x046 DllCharacteristics : 0 +0x048 SizeOfStackReserve : 0x00100000`00000000 +0x050 SizeOfStackCommit : 0x00001000`00000000 +0x058 SizeOfHeapReserve : 0 +0x060 SizeOfHeapCommit : 0x0009fffc`00000010 +0x068 LoaderFlags : 0xad25 +0x06c NumberOfRvaAndSizes : 0xf8bbc +0x070 DataDirectory : [16] _IMAGE_DATA_DIRECTORY +0x000 VirtualAddress : 0x1f4 +0x004 Size : 0x116000

圖 3:在 WinDbg 中查詢匯出表