# Simple Reverse - 0x24(2023 Lab - WinMalware - Dynamic API Resolution Background) ## Background * Process Environment Block (PEB) * 紀錄許多 Process 相關資訊的 OS 資料結構 * 存在於 user land * x86 環境下,可以從 fs:[0x30] 取得 * x64 環境下,可以從 gs:[0x60] 取得 * ==0x18: Ldr== * 指向 \_PEB\_LDR\_DATA 結構 * [\_PEB](https://www.vergiliusproject.com/kernels/x64/Windows%2011/22H2%20(2022%20Update)/_PEB) (注意 x64 和 x86 結構不同) ![](https://hackmd.io/_uploads/HyIcW4CfT.png) * \_PEB\_LDR\_DATA * 紀錄 Process 中載入模組的相關資訊 * 模組 module:PE 或 DLL * ==0x10: InLoadOrderModuleList== * 指向 _LDR_DATA_TABLE_ENTRY * 依載入順序串起的雙向 linked list * [\_PEB\_LDR\_DATA](https://www.vergiliusproject.com/kernels/x64/Windows%2011/22H2%20(2022%20Update)/_PEB_LDR_DATA) ![](https://hackmd.io/_uploads/BJFj-NAGp.png) * \_LDR\_DATA\_TABLE\_ENTRY * 紀錄一個載入模組的相關資訊 * ==0x00: InLoadOrderModuleList== * 依載入順序串起的雙向 linked list * Flink:指向下一個 entry * ==0x30: DllBase== * 此載入模組的 ImageBase * ==0x58: BaseDllName== * 此載入模組的檔案名稱 * \_LDR\_DATA\_TABLE\_ENTRY ![](https://hackmd.io/_uploads/BJ5JfVAMp.png) ## Exploit * Defense Evasion — Dynamic API Resolution * 用途: > 駭客常用的手法往往倚賴特定的 API 來達成,例如:`Injection` = `VirtualAllocEx` + `WriteProcessMemory` + `CreateRemoteThread`,因此資安產品只要監控這些 API,就很容易偵測到惡意行為 > Shellcode 沒有 loader 幫你把 API 連結起來 * 目的: 不靠 loader,在 runtime 自行爬取系統結構,取得所需的 Windows API(其實就有點像是打pwn的時候會看glibc有沒有import其他library的感覺一樣,如果有就可以用一些方法把它拿來用,像是leak一些資訊或是拿到shell之類的) * 困難的地方:要達成這個條件就需要有==system dll==的export address table,而system dll在哪裡呢?就是在process的memory中,所以我們只要拿到該dll的imagebase就可以直接拿到我們想要的API ![](https://hackmd.io/_uploads/SkIngERMp.png) * 如何達成?完全就是利用前面background提到的PEB結構慢慢parse,最後就可以parse到想要的dll,簡單來說就是,先找到PEB,在0x18的地方抓到\_PEB\_LDR\_DATA的RVA,再從該表的0x10抓到目前已經載入的DLL或PE的地址,並且比對現在我們想要的DLL是不是目前抓到的DLL table(就是從該表的BaseDllName去比對名稱),如果不是就繼續往下搜尋;如果是,就利用DllBase儲存的ImageBase抓到他在process中的地址 ![](https://hackmd.io/_uploads/H1N1rEAza.png) ## Source Code :::spoiler IDA Source Code(解析後的版本) ```cpp __int64 my_start() { // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND] for ( i = &loc_20281179F; *i != 'ZM' || getNtHdrs(i)->OptionalHeader.Magic != 0x20B; i = (i - 1) ) ; p_InLoadOrderModuleList = &NtCurrentPeb()->Ldr->InLoadOrderModuleList; for ( j = p_InLoadOrderModuleList; j->InLoadOrderLinks.Flink != p_InLoadOrderModuleList; j = j->InLoadOrderLinks.Flink ) { dll_name = j->BaseDllName.Buffer; dll_base = j->DllBase; if ( dll_name ) { dll_name_1 = *dll_name; if ( (*dll_name == 'k' || dll_name_1 == 'K')// find "kernel32.dll" from BaseDllName && ((v3 = *(dll_name + 1), v3 == 'e') || v3 == 'E') && ((v4 = *(dll_name + 2), v4 == 'r') || v4 == 'R') && ((v5 = *(dll_name + 3), v5 == 'n') || v5 == 'N') && ((v6 = *(dll_name + 4), v6 == 'e') || v6 == 'E') && ((v7 = *(dll_name + 5), v7 == 'l') || v7 == 'L') && *(dll_name + 6) == '3' && *(dll_name + 7) == '2' ) { exportTable = (dll_base + getNtHdrs(j->DllBase)->OptionalHeader.DataDirectory[0].VirtualAddress);// get kernel32.dll's Export Address Table num_of_names = exportTable->NumberOfNames; name_array = (dll_base + exportTable->AddressOfNames); name_ordinal = (dll_base + exportTable->AddressOfNameOrdinals); func_array = (dll_base + exportTable->AddressOfFunctions); for ( k = 0i64; k < num_of_names; ++k ) { api_name = dll_base + name_array[k]; hash = 0; do hash += __ROL4__(hash, 11) + 1187 + *api_name++;// do self-defined hash function while ( *api_name ); switch ( hash ) { case 0x5F00766C: v59 = dll_base + func_array[name_ordinal[k]]; break; case 0x6D555364: v60 = dll_base + func_array[name_ordinal[k]]; break; case 0x42B4FA0: v61 = dll_base + func_array[name_ordinal[k]]; break; case 0xC473C85A: v64 = dll_base + func_array[name_ordinal[k]]; break; } } } else if ( (dll_name_1 == 'm' || dll_name_1 == 'M') && ((v16 = *(dll_name + 1), v16 == 's') || v16 == 'S') && ((v17 = *(dll_name + 2), v17 == 'v') || v17 == 'V') && ((v18 = *(dll_name + 3), v18 == 'c') || v18 == 'C') && ((v19 = *(dll_name + 4), v19 == 'r') || v19 == 'R') && ((v20 = *(dll_name + 5), v20 == 't') || v20 == 'T') ) { exportTable_1 = (dll_base + getNtHdrs(j->DllBase)->OptionalHeader.DataDirectory[0].VirtualAddress); num_of_names_1 = exportTable_1->NumberOfNames; name_array_1 = (dll_base + exportTable_1->AddressOfNames); name_ordinal_1 = (dll_base + exportTable_1->AddressOfNameOrdinals); func_array_1 = (dll_base + exportTable_1->AddressOfFunctions); for ( m = 0i64; m < num_of_names_1; ++m ) { api_name_1 = dll_base + name_array_1[m]; hash_1 = 0; do hash_1 += *api_name_1++ + __ROL4__(hash_1, 11) + 1187; while ( *api_name_1 ); if ( hash_1 == 0xCD841E17 ) v62 = dll_base + func_array_1[name_ordinal_1[m]]; } } else if ( dll_name_1 == 'u' || dll_name_1 == 'U' ) { v29 = *(dll_name + 1); if ( v29 == 's' || v29 == 'S' ) { v33 = *(dll_name + 2); if ( v33 == 'e' || v33 == 'E' ) { v34 = *(dll_name + 3); if ( (v34 == 'r' || v34 == 'R') && *(dll_name + 4) == '3' && *(dll_name + 5) == '2' ) { exportTable_2 = (dll_base + getNtHdrs(j->DllBase)->OptionalHeader.DataDirectory[0].VirtualAddress); num_of_names_2 = exportTable_2->NumberOfNames; name_array_2 = (dll_base + exportTable_2->AddressOfNames); name_ordinal_2 = (dll_base + exportTable_2->AddressOfNameOrdinals); func_array_2 = (dll_base + exportTable_2->AddressOfFunctions); for ( n = 0i64; n < num_of_names_2; ++n ) { api_name_2 = dll_base + name_array_2[n]; hash_2 = 0; do hash_2 += *api_name_2++ + __ROL4__(hash_2, 11) + 1187; while ( *api_name_2 ); if ( hash_2 == 0x416F607 ) v63 = dll_base + func_array_2[name_ordinal_2[n]]; } } } } } } } ... ``` ::: ## Recon 1. #5的for-loop就是找 exported_next-stage.dll 的檔案起點 2. #7到後面的for-llop結束就是取得 PEB 並遍歷 \_LDR\_DATA\_TABLE\_ENTRY 3. 基本上經過解析之後就會非常清楚這一段在做的事情就是和上面的攻擊手法一模一樣,接下來當找到想要的dll之後就會開始找想要的API,以目前的例子來說,攻擊者主要想要找==kernel32.dll==, ==msvcrt.dll==和==user32.dll== 4. 找API的過程和想像中不一樣,他不是直接明文去找,而是把目前爬到的API name做自定義的hash之後再去比對,如果對了就放到變數中