在正常情况下,要想使用GetProcAddress函数,需要首先调用LoadLibraryA函数获取到kernel32.dll动态链接库的内存地址,接着在调用GetProcAddress函数时传入模块基址以及模块中函数名即可动态获取到特定函数的内存地址,但在有时这个函数会被保护起来,导致我们无法直接调用该函数获取到特定函数的内存地址,此时就需要自己编写实现LoadLibrary以及GetProcAddress函数,该功能的实现需要依赖于PEB线程环境块,通过线程环境块可遍历出kernel32.dll模块的入口地址,接着就可以在该模块中寻找GetProcAddress函数入口地址,当找到该入口地址后即可直接调用实现动态定位功能。
首先通过PEB/TEB找到自身进程的所有载入模块数据,获取TEB也就是线程环境块。在编程的时候TEB始终保存在寄存器 FS 中。- 0:000> !teb
- TEB at 00680000
- ExceptionList: 008ff904
- StackBase: 00900000
- StackLimit: 008fc000
- RpcHandle: 00000000
- Tls Storage: 0068002c
- PEB Address: 0067d000
- 0:000> dt _teb 00680000
- ntdll!_TEB
- +0x000 NtTib : _NT_TIB
- +0x01c EnvironmentPointer : (null)
- +0x020 ClientId : _CLIENT_ID
- +0x028 ActiveRpcHandle : (null)
- +0x02c ThreadLocalStoragePointer : 0x0068002c Void
- +0x030 ProcessEnvironmentBlock : 0x0067d000 _PEB // 偏移为30,PEB
复制代码 从该命令的输出可以看出,PEB 结构体的地址位于 TEB 结构体偏移0x30 的位置,该位置保存的地址是 0x0067d000。也就是说,PEB 的地址是 0x0067d000,通过该地址来解析 PEB并获得 LDR结构。- 0:000> dt nt!_peb 0x0067d000
- ntdll!_PEB
- +0x000 InheritedAddressSpace : 0 ''
- +0x001 ReadImageFileExecOptions : 0 ''
- +0x002 BeingDebugged : 0x1 ''
- +0x003 BitField : 0x4 ''
- +0x003 ImageUsesLargePages : 0y0
- +0x003 IsProtectedProcess : 0y0
- +0x003 IsImageDynamicallyRelocated : 0y1
- +0x003 SkipPatchingUser32Forwarders : 0y0
- +0x003 IsPackagedProcess : 0y0
- +0x003 IsAppContainer : 0y0
- +0x003 IsProtectedProcessLight : 0y0
- +0x003 IsLongPathAwareProcess : 0y0
- +0x004 Mutant : 0xffffffff Void
- +0x008 ImageBaseAddress : 0x00f30000 Void
- +0x00c Ldr : 0x774c0c40 _PEB_LDR_DATA // LDR
复制代码 从如上输出结果可以看出,LDR 在 PEB 结构体偏移的 0x0C 处,该地址保存的地址是 0x774c0c40 通过该地址来解析 LDR 结构体。WinDBG 输出如下内容:- 0:000> dt _peb_ldr_data 0x774c0c40
- ntdll!_PEB_LDR_DATA
- +0x000 Length : 0x30
- +0x004 Initialized : 0x1 ''
- +0x008 SsHandle : (null)
- +0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x9e3208 - 0x9e5678 ]
- +0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x9e3210 - 0x9e5680 ]
- +0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x9e3110 - 0x9e35f8 ]
- +0x024 EntryInProgress : (null)
- +0x028 ShutdownInProgress : 0 ''
- +0x02c ShutdownThreadId : (null)
- 0:000> dt _LIST_ENTRY
- ntdll!_LIST_ENTRY
- +0x000 Flink : Ptr32 _LIST_ENTRY
- +0x004 Blink : Ptr32 _LIST_ENTRY
复制代码 现在来手动遍历第一条链表,输入命令0x9e3208:在链表偏移 0x18 的位置是模块的映射地址,即 ImageBase;在链表
偏移 0x28 的位置是模块的路径及名称的地址;在链表偏移 0x30 的位置是模块名称的地址。- 0:000> dd 0x9e3208
- 009e3208 009e3100 774c0c4c 009e3108 774c0c54
- 009e3218 00000000 00000000 00f30000 00f315bb
- 009e3228 00007000 00180016 009e1fd4 00120010
- 009e3238 009e1fda 000022cc 0000ffff 774c0b08
- 0:000> du 009e1fd4
- 009e1fd4 "C:\main.exe"
- 0:000> du 009e1fda
- 009e1fda "main.exe"
复制代码 读者可自行验证,如下所示的确是模块的名称。既然是链表,就来下一条链表的信息,009e3100保存着下一个链表结构。依次遍历就是了。- 0:000> dd 009e3100
- 009e3100 009e35e8 009e3208 009e35f0 009e3210
- 009e3110 009e39b8 774c0c5c 773a0000 00000000
- 009e3120 0019c000 003c003a 009e2fe0 00140012
- 0:000> du 009e2fe0
- 009e2fe0 "C:\Windows\SYSTEM32\ntdll.dll"
复制代码 上述地址009e3100介绍的结构,是微软保留结构,只能从网上找到一个结构定义,然后自行看着解析就好了。- typedef struct _LDR_DATA_TABLE_ENTRY
- {
- PVOID Reserved1[2];
- LIST_ENTRY InMemoryOrderLinks;
- PVOID Reserved2[2];
- PVOID DllBase;
- PVOID EntryPoint;
- PVOID Reserved3;
- UNICODE_STRING FullDllName;
- BYTE Reserved4[8];
- PVOID Reserved5[3];
- union {
- ULONG CheckSum;
- PVOID Reserved6;
- };
- ULONG TimeDateStamp;
- } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
复制代码 根据如上流程,想要得到kernel32.dll模块的入口地址,我们可以进行这几步,首先得到TEB地址,并在该地址中寻找PEB线程环境块,并在该环境块内得到LDR结构,在该结构中获取第二条链表地址,输出该链表中的0x10以及0x20即可得到当前模块的基地址,以及完整的模块路径信息,该功能的实现分为32位于64位,如下代码则是实现代码。
[code]#include #include // 将不同的节压缩为单一的节#pragma comment(linker, "/merge:.data=.text") #pragma comment(linker, "/merge:.rdata=.text")#pragma comment(linker, "/section:.text,RWE")// 得到32位模式下kernel32.dll地址DWORD GetModuleKernel32(){ DWORD *PEB = NULL, *Ldr = NULL, *Flink = NULL, *p = NULL; DWORD *BaseAddress = NULL, *FullDllName = NULL; __asm { mov eax, fs:[0x30] // FS保存着TEB mov PEB, eax // +30定位到PEB } // 得到LDR Ldr = *((DWORD **)((unsigned char *)PEB + 0x0c)); // 在LDR基础上找到第二条链表 Flink = *((DWORD **)((unsigned char *)Ldr + 0x14)); p = Flink; p = *((DWORD **)p); // 计数器 int count = 0; while (Flink != p) { BaseAddress = *((DWORD **)((unsigned char *)p + 0x10)); FullDllName = *((DWORD **)((unsigned char *)p + 0x20)); if (BaseAddress == 0) break; // printf("镜像基址 = %08x \r\n 模块路径 = %S \r\n", BaseAddress, (unsigned char *)FullDllName); // 第二个模块是kernel32.dll if (count == 1) { // printf("address =%x \n", BaseAddress); return reinterpret_cast(BaseAddress); } p = *((DWORD **)p); count = count + 1; } // 未找到Kernel32模块 return 0;}// 获取64位模式下的kernel32.dll基址ULONGLONG GetModuleKernel64(){ ULONGLONG dwKernel32Addr = 0; // 获取TEB的地址 _TEB* pTeb = NtCurrentTeb(); // 获取PEB的地址 PULONGLONG pPeb = (PULONGLONG)*(PULONGLONG)((ULONGLONG)pTeb + 0x60); // 获取PEB_LDR_DATA结构的地址 PULONGLONG pLdr = (PULONGLONG)*(PULONGLONG)((ULONGLONG)pPeb + 0x18); // 模块初始化链表的头指针InInitializationOrderModuleList PULONGLONG pInLoadOrderModuleList = (PULONGLONG)((ULONGLONG)pLdr + 0x10); // 获取链表中第一个模块信息,exe模块 PULONGLONG pModuleExe = (PULONGLONG)*pInLoadOrderModuleList; //printf("EXE Base = > %X \n", pModuleExe[6]); // 获取链表中第二个模块信息,ntdll模块 PULONGLONG pModuleNtdll = (PULONGLONG)*pModuleExe; //printf("Ntdll Base = > %X \n", pModuleNtdll[6]); // 获取链表中第三个模块信息,Kernel32模块 PULONGLONG pModuleKernel32 = (PULONGLONG)*pModuleNtdll; //printf("Kernel32 Base = > %X \n", pModuleKernel32[6]); // 获取kernel32基址 dwKernel32Addr = pModuleKernel32[6]; return dwKernel32Addr;}int main(int argc, char *argv[]){ // 输出32位kernel32 DWORD kernel32BaseAddress = GetModuleKernel32(); std::cout AddressOfFunctions); // 获取导出名称表 PDWORD pENT = (PDWORD)(dwBase + pExport->AddressOfNames); // 获取导出序号表 PWORD pEIT = (PWORD)(dwBase + pExport->AddressOfNameOrdinals); for (DWORD dwOrdinal = 0; dwOrdinal < dwFunCount; dwOrdinal++) { if (!pEAT[dwOrdinal]) { continue; } // 获取序号 DWORD dwID = pExport->Base + dwOrdinal; // 获取导出函数地址 ULONGLONG dwFunAddrOffset = pEAT[dwOrdinal]; for (DWORD dwIndex = 0; dwIndex < dwFunNameCount; dwIndex++) { // 在序号表中查找函数的序号 if (pEIT[dwIndex] == dwOrdinal) { // 根据序号索引到函数名称表中的名字 ULONGLONG dwNameOffset = pENT[dwIndex]; char* pFunName = (char*)((ULONGLONG)dwBase + dwNameOffset); if (!strcmp(pFunName, "GetProcAddress")) { // 根据函数名称返回函数地址 return dwBase + dwFunAddrOffset; } } } } return 0;}// 定义名称指针typedef ULONGLONG(WINAPI *fnGetProcAddress)(_In_ HMODULE hModule, _In_ LPCSTR lpProcName);typedef HMODULE(WINAPI *fnLoadLibraryA)(_In_ LPCSTR lpLibFileName);int main(int argc, char *argv[]){ DWORD kernel32BaseAddress = GetModuleKernel32(); if (kernel32BaseAddress == 0) { return 0; } // 获取kernel32基址/获取GetProcAddress的基址 fnGetProcAddress pfnGetProcAddress = (fnGetProcAddress)MyGetProcAddress(); std::cout |