搜索内存中的API有许多方法,主要的是要找到KERNEL32.DLL的加载地址,常用的基本方法也就几种。
1、暴力搜索KERNEL32.DLL,同时需要处理内存访问异常
2、通过线程初始化时压入堆栈的ExitThread的地址来得到KERNEL32.DLL的地址。
3、通过SEH异常链表(Windows Xp)
4、通过TEB得到PEB结构的地址,然后获得PEB_LDR_DATA的地址,然后遍历模块链表,得到KERNEL32.DLL的地址。
一、一般DLL加载的地址会在0x70000000到0x80000000之间所以我们可以直接搜索这段区域的内存,但是在这段区域中有些内存区域是访问异常的,所以会用到SEH异常处理的解决方法。(winxp 正常 ,win7 , win8 debug模式下正常,原因 win7下的SEH(结构化异常),解决办法 SEH和SafeSEH)
typedef struct _EXCEPTION_INFO
{
unsigned long Eip;
unsigned long ExceptionCode;
}EXCEPTIONINFO, *PEXCEPTIONINFO;
typedef struct _EXCEPTION_TASK
{
unsigned long prev;//FS:[0]
unsigned long handler;
PEXCEPTIONINFO pExceptInfo;
}EXCEPTION_TASK, *PEXCEPTION_TASK;
EXCEPTION_DISPOSITION __cdecl ExceptionHandler(
struct _EXCEPTION_RECORD *ExceptionRecord, // 异常发生时的异常码
void * EstablisherFrame, // 异常时的ESP值
struct _CONTEXT *ContextRecord , // 异常发生时的各个寄存器的值
void * DispatcherContext)
{
PEXCEPTION_TASK pExceptTask = (PEXCEPTION_TASK)EstablisherFrame; //堆栈的结构 与 ExceptionInfo结构相对应, 异常时的ESP
pExceptTask->pExceptInfo->ExceptionCode = ExceptionRecord->ExceptionCode;
ContextRecord->Esp = (DWORD)EstablisherFrame;
ContextRecord->Eip = pExceptTask->pExceptInfo->Eip;
return ExceptionContinueExecution;
}
BOOL IsReadable(DWORD dwModuleBase)
{
EXCEPTIONINFO ExceptionInfo = { 0 };
ExceptionInfo.ExceptionCode = -1;
DWORD pExceptionEip = (DWORD)&ExceptionInfo.Eip;
/*
在堆栈中构造EXCEPTION_TASK结构,通过异常传递ESP指针,
可以访问其他函数的局部变量
*/
__asm
{
pushad
mov eax, pExceptionEip
lea ebx, ErrRetAddr
mov dword ptr[eax], ebx //给Eip赋出异常时的返回值
lea eax, ExceptionInfo
push eax
push ExceptionHandler //异常回调函数
push dword ptr fs:[0] //上一个异常链表头指针
mov fs:[0], esp //安装异常处理例程
//测试基址处的内存是否可读,异常发生处
mov esi, dwModuleBase
mov ecx, 10
rep lodsb
ErrRetAddr:
pop dword ptr fs:[0] //卸载已经安装的异常处理例程
pop eax //add esp, 8 平衡堆栈
pop eax
popad
}
if (-1 == ExceptionInfo.ExceptionCode)
{
return TRUE;//未出现异常
}
else
{
return FALSE;//出现异常
}
}
BOOL IsDLL(DWORD dwModuleBase)
{
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwModuleBase;
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
if ((pDosHeader->e_magic == IMAGE_DOS_SIGNATURE) && (pNtHeader->Signature == IMAGE_NT_SIGNATURE))
{
if (pNtHeader->FileHeader.Characteristics & 0x2000)//DLL文件特点
{
return TRUE;
}
}
return FALSE;
}
//暴力搜索KERNEL32.DLL ---- API
DWORD searchApi()
{
int i = 0;
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeader;
PIMAGE_EXPORT_DIRECTORY pExprotDirectory;
DWORD dwModuleBase = 0x7FFF0000;
while (dwModuleBase >= 0x70000000)
{
//判断内存是否可以读写 并处理读写产生的异常
if (!IsReadable(dwModuleBase))
{
dwModuleBase -= 0x10000;
continue;
}
//判断是否是dll文件
if (!IsDLL(dwModuleBase))
{
dwModuleBase -= 0x10000;
continue;
}
pDosHeader = (PIMAGE_DOS_HEADER)dwModuleBase;
pNtHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
// 3查找Kernel32.DLL地址
if ((pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size) &&\
(pNtHeader)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress)
{
pExprotDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)pDosHeader +\
pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
char *szDllName = (char*)((DWORD)pDosHeader + pExprotDirectory->Name);
if (0 == _strcmpi(szDllName, "Kernel32.dll"))
{
//printf("%s\n", szDllName);
return dwModuleBase;
}
}
dwModuleBase -= 0x10000;
}
}
二、线程被初始化时ExitThread被压人堆栈,以便线程通过ret返回时可以执行ExitThread退出线程。而ExitThread是从KERNEL32.DLL中导出的。
dwKernel32Base dw 0
mov edi, [esp] ; edi = 堆栈顶
and edi, 0ffff0000h ; 用 AND 获得初始页
.while TRUE
.if word ptr [edi] == IMAGE_DOS_SIGNATURE ; 等于“MZ”吗?
mov esi, edi ; Yes, next...
add esi, [esi + IMAGE_DOS_HEADER.e_lfanew] ; 就是 esi + 3ch
.if word ptr [esi] == IMAGE_NT_SIGNATURE ; 等于“PE”吗?
mov dwKernel32Base, edi ; Yes, we got it.
.break
.endif
.endif
;以下等同于sub edi, 010000h,即每次减少64k:
dec edi
xor di, di
.break .if edi < 070000000h ; 基地址一般不可能小于70000000h
.endw
三、遍历SEH异常链表,获得EXCEPTION_REGISTRATION结构prev为-1的异常处理过程地址,这个异常处理过程地址是位于kernel32.dll,通过它搜索得到kernel32.dll的基地址。我们都知道[fs:0]的ExceptionList 指向EXCEPTION_REGISTRATION结构,所以通过[fs:0]获得EXCEPTION_REGISTRATION结构后,判断prev成员是否是-1,如果是的话则取异常处理过程地址,然后进行搜索。此方法在Windows 7、Windows8 下查找到的是ntdll.dll,而不再是KERNEL32.DLL
struct EXCEPTION_REGISTRATION
prev dd ?
handler dd ?
ends
#include "windows.h"
unsigned int GetKernelBase()
{
struct EXCEPTION_REGISTRATION
{
unsigned int prev;
unsigned int handler;
};
unsigned int Exception;
__asm
{
push eax
mov eax, dword ptr fs:[0]
mov Exception, eax
pop eax
}
EXCEPTION_REGISTRATION *pExceptioRegist = (EXCEPTION_REGISTRATION*)Exception;
while(-1 != pExceptioRegist->prev)
{
pExceptioRegist = (EXCEPTION_REGISTRATION*)pExceptioRegist->prev;
}
unsigned handler = pExceptioRegist->handler;
unsigned kernelBase = handler & 0xFFFF0000;
PIMAGE_DOS_HEADER pDosHeader = 0;
PIMAGE_NT_HEADERS pNtHeader = 0;
while(1)
{
pDosHeader = (PIMAGE_DOS_HEADER)kernelBase;
pNtHeader = (PIMAGE_NT_HEADERS)(pDosHeader->e_lfanew + (unsigned)pDosHeader);
if (IMAGE_DOS_SIGNATURE == pDosHeader->e_magic && IMAGE_NT_SIGNATURE == pNtHeader->Signature) break;
kernelBase -= 0x10000;
}
return kernelBase;
}
通过Windbg的dt命令查看TEB的结构
nt!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
TEB结构的0x30偏移处存储的我们的PEB结构的地址,PEB结构如下:
nt!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 SpareBool : UChar
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
-
struct _LIST_ENTRY prev dd ? handler dd ?
nt!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 Void
+0x00c InLoadOrderModuleList : _LIST_ENTRY
+0x014 InMemoryOrderModuleList : _LIST_ENTRY
+0x01c InInitializationOrderModuleList : _LIST_ENTRY
+0x024 EntryInProgress : Ptr32 Void
我们看到这个结构中的模块立标有3个_LIST_ENTRY结构,它们分别是
InLoadOrderModuleList (加载顺序模块列表)
InMemoryOrderModuleList(内存顺序模块排列)
InInitializationOrderModuleList(初始化顺序模块列表)。
并且这三个链表的结点是均是指向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
.....
}LDR_MODULE, *PLDR_MODULE;
我们一般取它的初始化顺序结构(InInitializationOrderModuleList)的Flink成员指向的_LDR_MODULE结构的BaseAddress成员则为我们需要的基地址,Window Xp 及以前的系统第二个为KERNEL32.DLL,从Windows 7开始第三个链表为KERNEL32.DLL。
在Win8下:
unsigned KernelBase;
__asm
{
push eax
mov eax, fs:[30h] ;Get Peb
mov eax, [eax+0ch] ;Get _PEB_LDR_DATA
mov eax, [eax+1ch];
mov eax, [eax]
mov eax, [eax]
mov eax, [eax+8] ;
mov KernelBase, eax
pop eax
}