在正常情况下,要想使用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, *P