通過PEB的Ldr參數(結構體定義為_PEB_LDR_DATA),遍歷當前進程加載的模塊信息鏈表,找到目標模塊。
摘自PEB LDR DATA:
typedef struct _PEB_LDR_DATA
{
0x00 ULONG Length; /* Size of structure, used by ntdll.dll as structure version ID */
0x04 BOOLEAN Initialized; /* If set, loader data section for current process is initialized */
0x08 PVOID SsHandle;
0x0c LIST_ENTRY InLoadOrderModuleList; /* Pointer to LDR_DATA_TABLE_ENTRY structure. Previous and next module in load order */
0x14 LIST_ENTRY InMemoryOrderModuleList; /* Pointer to LDR_DATA_TABLE_ENTRY structure. Previous and next module in memory placement order */
0x1c LIST_ENTRY InInitializationOrderModuleList; /* Pointer to LDR_DATA_TABLE_ENTRY structure. Previous and next module in initialization order */
} PEB_LDR_DATA, *PPEB_LDR_DATA; // +0x24
_PEB_LDR_DATA結構體中的InLoadOrderModuleList
、InMemoryOrderModuleList
、InInitializationOrderModuleList
指向一個當前進程加載模塊的鏈表,鏈表的每個結點都被定義為_LIST_ENTRY
類型的結構體,三條鏈表以不同方式串連,加載順序、內存分布順序、初始化順序。
_LIST_ENTRY:
0:000> dt ntdll!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY
其中Flink
指向下一結點,尾部結點的Flink
則指向頭部;Blink
指向前一結點,首部節點指向尾部結點;所以該鏈表結構就是一個雙向循環鏈表。
除頭結點外,_LIST_ENTRY
結構體中的兩個指針都指向一個_LDR_DATA_TABLE_ENTRY
結構體,看這情況也就是說_LDR_DATA_TABLE_ENTRY
頭部為_LIST_ENTRY
咯?該結構體含有當前結點對應的模塊的許多信息,根據成員BaseDllName匹配需要的已加載模塊,再由DllBase得到句柄。
在通過InLoadOrderLinks
進行模塊查找時,Flink
或者Blink
可直接作為_LDR_DATA_TABLE_ENTRY
地址;如果通過InMemoryOrderLinks
或InInitializationOrderLinks
進行匹配時,需要將F(B)link
地址偏移-0x08
或-0x10
作為地址,與兩者在_LDR_DATA_TABLE_ENTRY
結構體中的偏移相對應。
0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x018 DllBase : Ptr32 Void
+0x01c EntryPoint : Ptr32 Void
+0x020 SizeOfImage : Uint4B
+0x024 FullDllName : _UNICODE_STRING
+0x02c BaseDllName : _UNICODE_STRING
+0x034 FlagGroup : [4] UChar
+0x034 Flags : Uint4B
+0x034 PackagedBinary : Pos 0, 1 Bit
+0x034 MarkedForRemoval : Pos 1, 1 Bit
+0x034 ImageDll : Pos 2, 1 Bit
+0x034 LoadNotificationsSent : Pos 3, 1 Bit
+0x034 TelemetryEntryProcessed : Pos 4, 1 Bit
+0x034 ProcessStaticImport : Pos 5, 1 Bit
+0x034 InLegacyLists : Pos 6, 1 Bit
+0x034 InIndexes : Pos 7, 1 Bit
+0x034 ShimDll : Pos 8, 1 Bit
+0x034 InExceptionTable : Pos 9, 1 Bit
+0x034 ReservedFlags1 : Pos 10, 2 Bits
+0x034 LoadInProgress : Pos 12, 1 Bit
+0x034 LoadConfigProcessed : Pos 13, 1 Bit
+0x034 EntryProcessed : Pos 14, 1 Bit
+0x034 ProtectDelayLoad : Pos 15, 1 Bit
+0x034 ReservedFlags3 : Pos 16, 2 Bits
+0x034 DontCallForThreads : Pos 18, 1 Bit
+0x034 ProcessAttachCalled : Pos 19, 1 Bit
+0x034 ProcessAttachFailed : Pos 20, 1 Bit
+0x034 CorDeferredValidate : Pos 21, 1 Bit
+0x034 CorImage : Pos 22, 1 Bit
+0x034 DontRelocate : Pos 23, 1 Bit
+0x034 CorILOnly : Pos 24, 1 Bit
+0x034 ChpeImage : Pos 25, 1 Bit
+0x034 ReservedFlags5 : Pos 26, 2 Bits
+0x034 Redirected : Pos 28, 1 Bit
+0x034 ReservedFlags6 : Pos 29, 2 Bits
+0x034 CompatDatabaseProcessed : Pos 31, 1 Bit
+0x038 ObsoleteLoadCount : Uint2B
+0x03a TlsIndex : Uint2B
+0x03c HashLinks : _LIST_ENTRY
+0x044 TimeDateStamp : Uint4B
+0x048 EntryPointActivationContext : Ptr32 _ACTIVATION_CONTEXT
+0x04c Lock : Ptr32 Void
+0x050 DdagNode : Ptr32 _LDR_DDAG_NODE
+0x054 NodeModuleLink : _LIST_ENTRY
+0x05c LoadContext : Ptr32 _LDRP_LOAD_CONTEXT
+0x060 ParentDllBase : Ptr32 Void
+0x064 SwitchBackContext : Ptr32 Void
+0x068 BaseAddressIndexNode : _RTL_BALANCED_NODE
+0x074 MappingInfoIndexNode : _RTL_BALANCED_NODE
+0x080 OriginalBase : Uint4B
+0x088 LoadTime : _LARGE_INTEGER
+0x090 BaseNameHashValue : Uint4B
+0x094 LoadReason : _LDR_DLL_LOAD_REASON
+0x098 ImplicitPathOptions : Uint4B
+0x09c ReferenceCount : Uint4B
+0x0a0 DependentLoadFlags : Uint4B
+0x0a4 SigningLevel : UChar
測試不調用系統API,利用PEB尋找模塊,並通過模塊尋找目標函數;這種情況大多是在Shellcode中用到,比方說惡意程序、病毒等;在許多情況下shellcode通常作為獨立代碼執行,不被加載器基址重定位,也無法直接調用API,所以通過PEB查找目標模塊,進而查找目標函數,通常首先都會獲取LoadLibraryA
和GetProcAddress
地址,便於之后直接加載指定模塊,獲取導出函數並調用。
寫的時候我發現從函數序數表
得到的函數序號減去序號基數base
會得到不正確結果,不減則正確,代碼調試時得到base
值為1
。
導出表結構:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
測試代碼,寫的時候在通過模塊獲取函數地址的時候沒用匯編,要提二進制碼還得重寫這個部分;不過順便溫習一下導出表結構。
#include "windows.h"
#include "stdio.h"
//typedef void(*func)();
VOID WINAPI Lower(WCHAR* s) {
WCHAR* pos = s;
for (; *pos; pos++) {
if (*pos <= 'Z' && *pos >= 'A')
*pos |= 0x20;
}
//printf("\t==lower string : %ws\n", s);
}
BOOL WINAPI __strcmpW(WCHAR* a, WCHAR *b) {
//printf("\tcompared dll name: %ws\n\n", b);
int i = 0;
for (i = 0; a[i] || b[i]; i++)
if (a[i] != b[i])
return FALSE;
return TRUE;
}
HMODULE WINAPI FindModuleByPeb(WCHAR* targetModule) {
WCHAR dllName[50] = { 0 };
BOOL foundModule = FALSE;
DWORD dllBase = NULL;
printf("[#] start get module handle\n");
/*
通過PEB結構中的Ldr尋找到InLoadOrderModuleList,遍歷尋找已加載的模塊,通過模塊名進行尋找
*/
__asm {
push targetModule
call Lower
mov eax, fs:[30h] // eax <- peb
mov eax, [eax + 0ch] // eax <- Ldr _PEB_LDR_DATA
mov eax, [eax + 0ch] // eax <- first Flink address, InLoadOrderModuleList [Type: _LIST_ENTRY]
_LOOP :
push eax
mov eax, [eax + 2ch + 4] // dll name string address
cmp eax, 0
jz _END // 字符串為NULL,說明尋找完畢,退出
lea ebx, dllName
push ebx // for calling compare
push ebx // for calling lower string
_COPYNAME :
mov dl, byte ptr[eax]
mov byte ptr[ebx], dl // copy name
add ebx, 2
add eax, 2
cmp[eax], 0
jnz _COPYNAME
mov[ebx], 0
call Lower // lower dll name string
push targetModule
call __strcmpW // compare dll name
cmp al, 1
jz _FOUND
pop eax
mov eax, [eax] // next Flink
jmp _LOOP // if not found, go to next flink and loop again
_FOUND :
pop eax
push DWORD ptr[eax + 18h] // save dllBase
pop dllBase
mov foundModule, 1 // found target dll
_END :
}
if (foundModule) {
printf("\t[ok] Have found target module :)\n");
printf("\t\tDllBase : %#x\n\t\tDll Name: %ws\n\n", dllBase, targetModule);
}
else
printf("\t[no] Not found :(\n\n");
return (HMODULE)dllBase;
}
func WINAPI GetProcByhMod(HMODULE hMod, WCHAR* procName) {
PIMAGE_DOS_HEADER pIDH = NULL; //DOS 頭
PIMAGE_NT_HEADERS pINH = NULL; // NT頭
PIMAGE_DATA_DIRECTORY pIDD = NULL; // 數據目錄表
PIMAGE_EXPORT_DIRECTORY pIED = NULL; // 導出表
INT i = 0, length = 0;
WORD ordinal = -1;
DWORD funcAddr = NULL;
WCHAR funcName[60] = { 0 }; // 函數名字
CHAR *name = NULL;
pIDH = (PIMAGE_DOS_HEADER)hMod;
printf("[#]start Get Library By found module handle\n");
if ((WORD)pIDH->e_magic == 0x5a4d) // magic值 MZ
printf("\tMatch \"MZ\" magic :)\n");
else
printf("\tNot Match \"MZ\" magic :(\n");
pINH = (PIMAGE_NT_HEADERS)(pIDH->e_lfanew+(DWORD)hMod);
/*
printf("offset : %#x\n", pIDH->e_lfanew);
printf("Image Base : %#x\n", hMod);
printf("PIMAGE_NT_HEADERS value : %#x\n", pINH);
*/
if ((WORD)pINH->Signature == 0x4550) // 簽名 PE
printf("\tMatch \"PE\" signature :)\n");
else
printf("\tNot Match \"PE\" signature :(\n");
pIDD = (PIMAGE_DATA_DIRECTORY)((pINH->OptionalHeader).DataDirectory); // 數據目錄表
pIED = (PIMAGE_EXPORT_DIRECTORY)(pIDD[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (DWORD)hMod);
printf("\texport table VA : %#x\n\tfunction names array address : %#x\n", (DWORD)pIED, pIED->AddressOfNames + (DWORD)hMod);
Lower(procName); //
for (i = 0; i < pIED->NumberOfNames; i++) {
name = (CHAR*)(*((DWORD*)(pIED->AddressOfNames + (DWORD)hMod) + i) + (DWORD)hMod);
for (length = 0; name[length]; length++); // 函數名長度
/*printf("==> %s\n", name);
通過functionames數組獲取下標,根據該下標(輸出函數名表和輸出序號表一一對應)在輸出序號表
獲取函數地址表中的序號,將序號減去基數作為下標尋找到函數地址RVA。
*/
MultiByteToWideChar(CP_ACP, NULL, name, ++length, funcName, length);
//printf("\tcompared function name : %ws\n", funcName);
Lower(funcName);
if (__strcmpW(procName, funcName)) {
printf("\t[ok] succeedfound function name :)\n");
ordinal = *((WORD*)(pIED->AddressOfNameOrdinals + (DWORD)hMod) + i); // WORD
printf("\t\tindex of target function : %#x\n\t\tordinal number : %#x\n\t\torinal base : %#x\n", i, ordinal, pIED->Base);
funcAddr = *((DWORD*)(pIED->AddressOfFunctions + (DWORD)hMod) + (ordinal/* - pIED->Base加上之后不對*/)) + (DWORD)hMod;
printf("\tGet function address : %#x\n", funcAddr);
break;
}
}
if (!funcAddr)
printf("\t[no] not Found target function :(");
return (func)funcAddr;
}
INT main(INT argc, CHAR* argv[]) {
WCHAR searchMod[] = { L"Kernel32.dll" };
WCHAR procLoadlib[] = { L"LoadLibraryA" };
WCHAR procGetProc[] = { L"GetProcAddress" };
//func procAddr = NULL;
//
CHAR tarMod[] = { "User32.dll" };
CHAR targFunc[] = { "MessageBoxA" }; // 測試彈窗
CHAR test[] = { "test" };/////
/*HMODULE hMod = LoadLibraryA(tarMod);
typedef int (*msgBoxProc)(HWND, LPCTSTR, LPCTSTR, UINT);
msgBoxProc f = (msgBoxProc)GetProcAddress(hMod, targFunc);
f(NULL, (LPCTSTR)"test", (LPCTSTR)"test", MB_OK);*/
HMODULE hMod = FindModuleByPeb(searchMod);
if (hMod) {
__asm {
lea eax, procLoadlib
push eax //LoadLibraryA
push hMod
call GetProcByhMod
cmp eax, 0
jz _END2
mov ebx,eax
lea eax, tarMod // target mod; user32.dll
push eax
call ebx // call LoadLibraryA
cmp eax,0
jz _END2
push eax // save hInstance value
lea eax,procGetProc // string GetProcAddress
push eax
push hMod
call GetProcByhMod
cmp eax, 0
jz _END2
mov ebx, eax
lea eax, targFunc
pop edx
push eax // messageboxa
push edx // target hMod
call ebx // call getprocaddress
cmp eax, 0
jz _END2
mov ebx, eax
push MB_OK
lea eax, test
push eax
push eax
push 0 // param for messagebox
call ebx // call got api - messageboxA
_END2:
}
}
}