暴力搜索
32位在4G内存搜索有一定可行性,但是处理起来其实还是比较麻烦的,因为内存不可读会触发异常,需要对这些异常问题进行处理。
优化思路:缩小范围、增大搜索步长
(1)不优化,原始内存特征匹配,容易出错,利用复杂。
(2)优化暴力搜索,有三种方法
方法一
只要系统没有做模块基址重定位,那么32位下kernel32的加载地址在0x70000000-0x80000000之间,然后Kernel32.dll加载是64k对齐的,所以查找次数<256MB/64K+1= 4097次,就可以找到。
#include <Windows.h>
#include <stdio.h>int main()
{HANDLE kernelA = LoadLibrary(L"kernel32.dll");printf("0x:%p\n", kernelA);system("pause");
}
但是这里判断定位成功条件仍然需要采取两重判断,先判断MZ头再解析PE结构来获取DLL名称进行判断,从而来降低在其他环境出现地址错误的概率。
导入表与exe实际加载顺序:
ntdll.dll->kernel32.dll->ucrtbase.dll->kernelbase.dll->…
可以看到关键的系统模块都分配在了0x70000000上面,故单一匹配MZ头不是100%准确。
方法二
什么是异常处理链表
当异常发生时,系统从fs:[0]指向的内存地址处取出ExceptionList字段,然后从ExceptionList字段指向的_EXCEPTION_REGISTRATION_RECORD结构中取出handler字段,并根据其中的地址去调用异常处理程序(回调函数)。
异常处理链表是提到的由_EXCEPTION_REGISTRATION_RECORD结构构成的单链表
typedef struct _EXCEPTION_REGISTRATION_RECORD
{PEXCEPTION_REGISTRATION_RECORD Next;PEXCEPTION_DISPOSITION Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;
Next指向异常处理程序的地址,prev 则指向下一个 _EXCEPTION_REGISTRATION_RECORD 结构体,来构成一个单向链表。
异常处理链表有什么特点
当异常发生时,系统会遍历异常处理链表,直到找到正确的异常处理程序。链表最后一项的prev值为0xFFFFFFFF,说明链表已经遍历完毕。
最后一项指向的是系统默认的位于Kernel32.dll的UnhandledExceptionFilter顶层异常处理程序的过滤函数,该过滤函数的地址是存在于Kernel32.dll内存空间的
查找Kernel32.dll加载基址
1)取fs:[0]的值即ExceptionList指针指向的地址赋予给edx寄存器
2)判断Next指针指向的值是否为0xffffffff计算机负数用补码表示即-1,是的话
mov edx, [edx],将值传递到edx寄存器中,接着mov edx,[edx+4]将Handler指向的值赋值给edx,此时edx就在Kernel32.dll内存空间中,然后开始逐一递减dec edx来回溯PE头<-cmp word ptr [edx], ‘ZM’,数值存储比较采用小端字节序,CPU读取从低地址读到高地址,所以这里是’ZM’而不是’MZ’,网上有些代码是错的,如果不是-1,那么就遍历下一个mov edx, [edx]
代码实现
#include <stdio.h>
#include <windows.h>
int main()
{unsigned int kernelAddr;__asm {mov edx, fs: [0] ;Foreach:cmp [edx], 0xffffffffje Handle; //if equal : jump mov edx, [edx];jmp Foreach;Handle:mov edx, [edx + 4];_Loop:cmp word ptr[edx], 'ZM';jz Kernel;dec edx;xor dx, dx;jmp _LoopKernel :mov kernelAddr, edx;}printf(TEXT("Kernel32.dll address: %x\r\n"), kernelAddr);printf(TEXT("LoadLibrary Kernel32.dll address: %x\r\n"),LoadLibrary(TEXT("kernel32.dll")));return 0;
}
基于PEB搜索
TEB->PEB
TEB(Thread Environment Block,线程环境块)系统在此TEB中保存频繁使用的线程相关的数据。位于用户地址空间,在比 PEB 所在地址低的地方。用户模式下,当前线程的TEB位于独立的4KB段(页),可通过CPU的FS寄存器来访问该段,一般存储在[FS:0]
PEB(Process Environment Block,进程环境块)存放进程信息,每个进程都有自己的PEB信息。位于用户地址空间。可在TEB结构地址偏移0x30处获得PEB的地址位置。
查看结构Windbg 命令:
TEB: !teb
、dt -r1 ntdll!_teb
PEB: !peb
、dt -r1 ntdll!_peb
我们已经知道可以通过fs:[0]寄存器访问到TEB的地址,这里我们又知道了可以通过TEB结构偏移0x30处指向的地址是PEB结构地址,即fs:[0]->TEB->PEB,在这一步完成PEB地址的定位。
PEB结构
微软文档:https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb
typedef struct _PEB {BYTE Reserved1[2];BYTE BeingDebugged;BYTE Reserved2[1];PVOID Reserved3[2];PPEB_LDR_DATA Ldr;PRTL_USER_PROCESS_PARAMETERS ProcessParameters;PVOID Reserved4[3];PVOID AtlThunkSListPtr;PVOID Reserved5;ULONG Reserved6;PVOID Reserved7;ULONG Reserved8;ULONG AtlThunkSListPtr32;PVOID Reserved9[45];BYTE Reserved10[96];PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;BYTE Reserved11[128];PVOID Reserved12[1];ULONG SessionId;
} PEB, *PPEB;
文档中很多是保留(Reserved)字段,这里我们关注下其中一个成员Ldr,其结构为PPEB_LDR_DATA。
微软文档:https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data
Contains information about the loaded modules for the process.
包含有关该过程的加载模块的信息。
typedef struct _PEB_LDR_DATA {BYTE Reserved1[8];PVOID Reserved2[3];LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA, *PPEB_LDR_DATA;
第三个参数InMemoryOrderModuleList
The head of a doubly-linked list that contains the loaded modules for the process. Each item in the list is a pointer to an LDR_DATA_TABLE_ENTRY structure. For more information, see Remarks.
双向链表的头部包含进程的加载模块。链表的每一个都是指向LDR_DATA_TABLE_ENTRY结构的指针
这个链表到底有什么信息
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;
文档是不够全面的,下面我们用Windbg来看下具体的结构和值
!peb->dt -r1 0x774bdca0 _PEB_LDR_DATA
!peb
dt -r1 0x778a5d80 _PEB_LDR_DATA
可以看到这里,除了文档InMemoryOrderModuleList,实际还有两个:
InLoadOrderModuleList
InMemoryOrderModuleList
InInitializationOrderModuleLists
这个其实是模块在不同状态的顺序
InLoadOrderModuleList 指的是模块加载的顺序
InMemoryOrderModuleList指的是在内存的排列顺序
InInitializationOrderModuleLists 指的是模块初始化装载顺序。
这里选择跟进InLoadOrderModuleList指向的结构
1)dt -r1 _LIST_ENTRY 0x00485bc0(这里取第二个,第一个是exe本身)->dt -r1 0x00485ab8 _LDR_DATA_TABLE_ENTRY
2)lm 列举出加载的模块信息
从这图可以得出两个信息,Flink总是指向下一个_LDR_DATA_TABLE_ENTRY结构对应加载顺序的Flink指针,_LDR_DATA_TABLE_ENTRY在0x2c处是加载模块的名称,在0x18偏移处,是该模块的加载基地址。
基于上述认识,使用Windbg遍历一下InMemoryOrderModuleList加载顺序的完整链结构:
(1)dt -r1 0x004f5bc0-0x8 _LDR_DATA_TABLE_ENTRY
第一个结构是:*.exe
(2)dt -r1 0x004f5ab8-0x8 _LDR_DATA_TABLE_ENTRY
第二个模块:ntdll.dll
(3)dt -r1 0x004f5fa8-0x8 _LDR_DATA_TABLE_ENTRY
第三个模块:kerner32.dll
程序,遍历三个链表内容
#include<stdio.h>
#include<windows.h>typedef struct UNICODE_STRING
{USHORT _ength;USHORT MaximumLength;PWSTR Buffer;
}UNICODE_STRING, * PUNICODE_STRING;typedef struct PEB_LDR_DATA {ULONG Length;BOOLEAN initialized;PVOID SsHandle;LIST_ENTRY InLoadOrderModuleList;LIST_ENTRY InMemoryOrderModuleList;LIST_ENTRY InInitializationOrderModuleList;}PEB_LDR_DATA, * PPEB_LDR_DATA;typedef struct LDR_DATA_TABLE_ENTRY
{LIST_ENTRY InLoadOrderModuleList;LIST_ENTRY InMemoryOrderModuleList;LIST_ENTRY InInitializationOrderModuleList;void* BaseAddress;void* EntryPoint;ULONG SizeOfImage;UNICODE_STRING FullDllName;UNICODE_STRING BaseDllName;ULONG Flags;SHORT LoadCount;SHORT TlsIndex;HANDLE SectionHandle;ULONG CheckSum;ULONG TimeDateStamp;
}MY_LDR_MODULE, * PLDR_MODULE;int main()
{PEB_LDR_DATA* pEBLDR;MY_LDR_MODULE* pLdrMod;PLDR_MODULE PLdr;LIST_ENTRY* pNext, * pStart;_asm{mov eax, fs: [0x30]mov eax, [eax + 0xC]mov pEBLDR, eax}printf("GetModuleHandle Kernel32:0x%08x\n", GetModuleHandle(L"Kernel32"));printf("GetModuleHandle ntdll:0x%08x\n", GetModuleHandle(L"ntdll"));printf("--------------------------------------------------------------------------\n");printf("PEB_LDR_DATA:0x%08x\n", pEBLDR);printf("LDR->InLoadOrderModuleList:\t\t0x%08x\n", pEBLDR->InLoadOrderModuleList);printf(">>>InLoadOrderModuleList<<<\n");printf("BaseAddress\t\t BaseDllName\n================================================\n");pNext = (LIST_ENTRY*)&(pEBLDR->InLoadOrderModuleList);pStart = pNext;do{pNext = pNext->Flink;pLdrMod = (MY_LDR_MODULE*)pNext;printf("0x%08x\t\t", pLdrMod->BaseAddress);wprintf(L"%s\n", pLdrMod->BaseDllName.Buffer);} while (pNext != pStart);printf("LDR->InMemoryOrderModuleList:\t\t0x%08x\n", pEBLDR->InMemoryOrderModuleList);printf("BaseAddress\t\t BaseDllName\n================================================\n");pNext = (LIST_ENTRY*)&(pEBLDR->InMemoryOrderModuleList);pStart = pNext;do{pNext = pNext->Flink;pLdrMod = CONTAINING_RECORD(pNext, LDR_DATA_TABLE_ENTRY, InMemoryOrderModuleList);printf("0x%08x\t\t", pLdrMod->BaseAddress);wprintf(L"%s\n", pLdrMod->BaseDllName.Buffer);} while (pNext != pStart);printf("LDR->InInitializationOrderModuleList:\t0x%08x\n", pEBLDR->InInitializationOrderModuleList);printf("BaseAddress\t\t BaseDllName\n================================================\n");pNext = (LIST_ENTRY*)&(pEBLDR->InInitializationOrderModuleList);pStart = pNext;do{pNext = pNext->Flink;pLdrMod = CONTAINING_RECORD(pNext, LDR_DATA_TABLE_ENTRY, InInitializationOrderModuleList);printf("0x%08x\t\t", pLdrMod->BaseAddress);wprintf(L"%s\n", pLdrMod->BaseDllName.Buffer);} while (pNext != pStart);getchar();
}
搜索思路
1)xor eax, eax清零,mov eax, fs:[0x30] 获取PEB地址
2)mov eax, [eax + 0x0c] 获取LDR地址,0x30和0x0c上面都有讲的,偏移量。
mov esi, [eax + 0Ch] //则指向InLoadOrderModuleList
mov esi, [eax + 14h] //则指向InMemoryOrderModuleList
4)遍历Flink,找到Kernel32.dll的位置
位置在第3个,这里需要简单计算下。
指向InLoadOrderModuleList 的同时就是第一个了。
再指向一次mov esi, [esi],就是第二个了。
lodsd或者mov esi,[esi];mov eax, esi,就是第三个了
5)获取Kernel地址,这里也需要小小计算一下。
mov eax,[eax+08h] //InLoadOrderModuleList 顺序
mov eax, [eax+18h] //InMemoryOrderModuleList 顺序
6)完成赋值,mov address, eax; 最后输入验证结果。
代码实现
//InLoadOrderModuleList
#include <Windows.h>
#include <stdio.h>int main()
{unsigned int address;__asm {xor eax, eaxmov eax, fs: [eax + 30h] ; 指向PEB的指针mov eax, [eax + 0ch]; 指向PEB_LDR_DATA的指针mov eax, [eax + 0ch]; 根据PEB_LDR_DATA得出InLoadOrderModuleList的Flink字段mov esi, [eax];lodsd;mov eax, [eax + 18h]; Kernel.dll的基地址mov address, eax;}printf("0x:%p\n", address);HANDLE kernelA = LoadLibrary(L"kernel32.dll");printf("0x:%p\n", kernelA);system("pause");return 0;
}
//InMemoryOrderModuleList
#include <Windows.h>
#include <stdio.h>int main()
{unsigned int address;__asm {xor eax, eax;mov eax, fs: [eax + 30h] ; 指向PEB的指针mov eax, [eax + 0ch]; 指向PEB_LDR_DATA的指针mov eax, [eax + 14h]; 根据PEB_LDR_DATA得出InMemoryOrderModuleList的Flink字段mov esi, [eax];lodsd;mov eax, [eax + 10h]; Kernel.dll的基地址mov address, eax;}printf("0x:%p\n", address);HANDLE kernelA = LoadLibrary(L"kernel32.dll");printf("0x:%p\n", kernelA);system("pause");return 0;
}