通过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: } } }