探索黑客技术攻防,实战研究与安全创新

导航菜单

搜索kernel32.dll:TEB与PEB之旅

每个Windows进程都由一个Active进程EPROCESS表示,此结构体除了包含许多与此进程相关的属性,还包含部分指向其他结构体的指针。如每个进程都存在一个或多个线程,每个线程又通过一个可执行线程ETHREAD结构体表示。EPROCESS结构体及其大多数相关的数据结构都存储在系统地址空间,唯一例外的是进程环境块PEB。因为结构体PEB一部分组件是需要用户模式访问的,故而它是系统结构群中为数不多驻留在用户空间的对象。

Windows加载器、堆管理器以及子系统DLLs中使用的数据都驻留在PEB。

TEB(ThreadEnvironmentBlock,线程环境块)位于用户地址空间。进程中的每个线程都有自己的一个TEB。一个进程的所有TEB都以堆栈方式存放在从0x7FFDE000处开始的线性内存中,每4KB为一个完整的TEB,不过该内存区域是向下扩展的。在用户模式下,当前线程的TEB位于独立的4KB段,可通过CPU的FS寄存器来访问该段,一般存储在[FS:0]。在用户态下,WinDbg中可用命令$thread取得TEB地址。

结构体PEB的定义在头文件winternl.h中给出:


typedefstruct_PEB{
BYTE
Reserved1[2];
BeingDebugged;
Reserved2[1];
Reserved3[2];
Ldr;
BYTE
BYTE
PVOID
PPEB_LDR_DATA
PRTL_USER_PROCESS_PARAMETERSProcessParameters;
BYTE
Reserved4[104];
Reserved5[52];
PVOID
PPS_POST_PROCESS_INIT_ROUTINEPostProcessInitRoutine;
BYTE
Reserved6[128];
Reserved7[1];
SessionId;
PVOID
ULONG
}PEB,*PPEB;


从微软官方SDK文档给出的定义可知,大量的成员变量都隐藏了,无法知晓这些变量的具体含义,不过我们可以使用调试器打印出此结构体的详细信息,如图1所示。

001.png

由图1可知,PEB结构的偏移0xC处保存着指针Ldr,该指针指向PEB_LDR_DATA结构体,如图2所示。

002.png

该结构的后三个成员是指向LDR_MODULE链表结构中相应三条双向链表头的指针,分别是按照加载顺序、在内存中地址顺序和初始化顺序排列的模块信息结构的指针。双向链表指针结构LIST_ENTRY在双端队列中以一个成员节点形式存在,即这些结构体被嵌入在一个更大的结构体内,如图3所示。

003.png

为了更清楚的了解LDR_DATA_TABLE_ENTRY结构体,可以使用dt命令查看结构体成员,如图4所示。

004.png

从结构体定义可知,InMemoryOrderLinksLIST_ENTRY结构体相对于上述结构体地址偏移8字节位置。要注意的是,LIST_ENTRY结构体不同于自定义的双向链表,其相邻的节点并不是相邻结构体的首字节处,而是偏移一定位置。LDR_DATA_TABLE_ENTRY结构体中包含了进程地址空间中加载的所有模块元数据信息,如每个模块的基地址,且这些结构体链表中的第三个节点即是kernel32.dll库。下面我们用Windbg调试工具来验证这点。首先定位到TEB偏移0x30处,即FS:[0x30]地址处保存的一个指针,指向PEB,如图5所示。

005.png

然后再通过_PEB_LDR_DATA的InMemoryOrderModuleList成员获取_LIST_ENTRY结构,再由_LIST_ENTRY的Flink成员获取_LDR_DATA_TABLE_ENTRY结构,如图6所示。

006.png

通过上述命令即可列出偏移地址0x341ed0位置下的所有_LIST_ENTRY结构体中Flink的内存地址值。此时,我们可以进一步打印出第二个节点对应的大结构体_LDR_DATA_TABLE_ENTRY对应的元数据信息,如图7所示。更简单明了的确认方式,可直接输入如图8所示的命令。

007.png

008.png

从上图给出的kernel32.dll地址可知,前面给出的逻辑分析正确。即给定一个TEB地址,我们可以获取到PEB地址,而PEB结构中包含一个PEB_LDR_DATA结构体,此结构体中的InMemoryOrderModuleList成员对应的Flink成员又指向了_LDR_DATA_TABLE_ENTRY结构体,此结构体链表的第三个节点即存放了kernel32.dll库文件。整个调用过程可如图9所示进行描述。

009.png

有了上面的基础,通过下面的代码即可实现DLL库文件的遍历。


#includewindows.h
#defineCONTAINING_RECORD(address,type,field)((type*)(/
(PCHAR)(address)-/
(ULONG_PTR)(((type*)0)-field)))
/*其中结构体_PEB_LDR_DATA、_LDR_DATA_TABLE_ENTRY、_PEB自行定义*/
voidmain(void)
{
PLDR_DATA_TABLE_ENTRYpLdrDataEntry=NULL;
PLIST_ENTRYpListEntryStart=NULL,pListEntryEnd=NULL;
PPEB_LDR_DATApPebLdrData=NULL;
PPEBpPeb=NULL;
//任意加载一个DLL文件,获取当前进程的PEB结构体指针地址
LoadLibrary(blackbox.dll);
__asm
{
//通过fs:[30h]获取当前进程的_PEB结构
moveax,dwordptrfs:[30h];
movpPeb,eax
}
//通过_PEB的Ldr成员获取_PEB_LDR_DATA结构
pPebLdrData=pPeb-Ldr;
//通过_PEB_LDR_DATA的InMemoryOrderModuleList成员获取_LIST_ENTRY结构
pListEntryStart=pListEntryEnd=pPebLdrData-InMemoryOrderModuleList.Flink;
//查找所有已载入到内存中的模块
while(pListEntryStart!=pListEntryEnd){
//通过_LIST_ENTRY的Flink成员获取_LDR_DATA_TABLE_ENTRY结构
pLdrDataEntry=
(PLDR_DATA_TABLE_ENTRY)CONTAINING_RECORD(pListEntryStart,LDR_DATA_TAB
LE_ENTRY,InMemoryOrderLinks);
//输出_LDR_DATA_TABLE_ENTRY的BaseDllName或FullDllName成员信息
printf(%x%s/n,pLdrDataEntry-BaseDllName.DllBase,BaseDllName.Buffer);
pListEntryStart=pListEntryStart-Flink;
}
}


通过分析PEB和TEB结构体,我们可以利用PEB结构体中的成员变量实现进程中特定DLL模块的隐藏,即从PEB的Ldr链中摘除指定的DLL模块即可。更多的Rootkit是通过上述方法获取到kernel32.dll基址,之后搜索Kernel32.dll的导出表地址,接下来就是hacker构造自己的程序导入表,实现恶意Rookit的加载等操作。