1、CreateProcess函数在完成装载应用程序后,会先将一个返回地址压入到堆栈顶端,而这个返回地址恰好在Kernel32.dll中,利用这个原理我们可以顺着这个返回地址按64KB大小往地址搜索,那么我们一定可以找到Kernel32模块的基地址,废话少说,代码如下:
GetK32Base:
mov eax, [esp+04h] ;得到kernel32的返回地址
and eax, ffff0000h
Search:
cmp WORD ptr [eax], IMAGE_DOS_SIGNATURE ;判断是否为ImageBase
jnz @f
mov edx, [eax+3ch]
add edx, eax
cmp WORD ptr [edx], IMAGE_NT_SIGNATURE ;判断是否为PE头
jnz @f
ret
@@:
dec eax ;按64KB递减搜索
xor ax, ax
cmp eax, 70000000h
ja Search
ret
2、通过PEB枚举当前进程空间中用户模块列表也可以获取Kernel32模块的基地址,fs:[0]指向TEB,fs:[30h]指向PEB,PEB偏移0ch是LDR指针,以下可以分别通过加载顺序、内存顺序、初始化顺序获取Kernel32模块的基地址,这里以初始化顺序为例:
未公开的LDR_MODULE数据结构如下:
typedef struct _LDR_MODULE
{
LIST_ENTRY InLoadOrderModuleList; // +0x00
LIST_ENTRY InMemoryOrderModuleList; // +0x08
LIST_ENTRY InInitializationOrderModuleList; // +0x10
PVOID BaseAddress; // +0x18
PVOID EntryPoint; // +0x1c
ULONG SizeOfImage; // +0x20
UNICODE_STRING FullDllName; // +0x24
UNICODE_STRING BaseDllName; // +0x2c
ULONG Flags; // +0x34
SHORT LoadCount; // +0x38
SHORT TlsIndex; // +0x3a
LIST_ENTRY HashTableEntry; // +0x3c
ULONG TimeDateStamp; // +0x44
// +0x48
} LDR_MODULE, *PLDR_MODULE;
以下是WinDbg显示的PEB_LDR_DATA的数据结构
+0x00c Ldr : Ptr32 to struct _PEB_LDR_DATA, 7 elements, 0x28 bytes
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 to Void
+0x00c InLoadOrderModuleList : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x000 Flink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x004 Blink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x014 InMemoryOrderModuleList : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x000 Flink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x004 Blink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x01c InInitializationOrderModuleList : struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x000 Flink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x004 Blink : Ptr32 to struct _LIST_ENTRY, 2 elements, 0x8 bytes
+0x024 EntryInProgress : Ptr32 to Void
通过上面两个结构我们可以通过3种方式获取Kernel32模块的基地址
1)加载顺序的方式:
GetK32Base:
assume fs:nothing
push esi
xor eax,eax
mov eax,fs:[eax+30h] ;指向PEB的指针
mov eax,[eax+0ch] ;指向PEB_LDR_DATA的指针
mov esi,[eax+0ch] ;根据PEB_LDR_DATA得出InLoadOrderModuleList的Flink字段
lodsd
mov eax, [eax] ;指向下一个节点
mov eax,[eax+18h] ;Kernel.dll的基地址
pop esi
ret
2)内存顺序的方式:
GetK32Base:
assume fs:nothing
push esi
xor eax,eax
mov eax,fs:[eax+30h] ;指向PEB的指针
mov eax,[eax+0ch] ;指向PEB_LDR_DATA的指针
mov esi,[eax+14h] ;根据PEB_LDR_DATA得出InMemoryOrderModuleList的Flink字段
lodsd
mov eax, [eax] ;指向下一个节点
mov eax,[eax+10h] ;Kernel.dll的基地址
pop esi
ret
3)初始化顺序的方式:
GetK32Base:
assume fs:nothing
push esi
xor eax,eax
mov eax,fs:[eax+30h] ;指向PEB的指针
mov eax,[eax+0ch] ;指向PEB_LDR_DATA的指针
mov esi,[eax+1ch] ;根据PEB_LDR_DATA得出InInitializationOrderModuleList的Flink字段
lodsd
mov eax,[eax+08h] ;Kernel.dll的基地址
pop esi
ret
3、通过遍历SEH链的方法,在SEH链中查找成员prev的值为0xFFFFFFFFh的EXCEPTION_REGISTER结构。该结构中的handler值是系统异常处理例程,总是位于kernerl32.dll中。当前线程的TIB保存在fs段选择器指定的数据段的0偏移处,所以fs:[0] 的地方就是TIB结构中的ExceptionList字段。而ExceptionList指向一个EXCEPTION_REGISTRATION结构,SEH异常处理回调函数的入口地址就是由EXCEPTION_REGISTRATION结构指定。
TIB结构如下:
NT_TIB STRUCT
ExceptionList dd ?
StackBase dd ?
StackLimit dd ?
SubSystemTib dd ?
union
FiberData dd ?
Version dd ?
ends
ArbitraryUserPointer dd ?
Self dd ?
NT_TIB ENDS
EXCEPTION_REGISTRATION STRUCT
prev dd ? ;前一个EXCEPTION_REGISTRATION结构的地址
handler dd ? ;异常处理回调函数地址
EXCEPTION_REGISTRATION ends
GetK32Base:
assume fs:nothing
xor esi, esi
mov esi, fs:[esi]
lodsd
@@:
inc eax
jz @f
dec eax
xchg esi, eax
lodsd
jmp @b
@@:
lodsd
@@:
dec eax
xor ax, ax
cmp DWORD ptr [eax], 5A4D
jnz @b
ret