TEB/PEB定位PE文件导入导出表
基本思路:
TEB/PEB定位PE文件基址
- 通过FS寄存器找到当前的TEB
- 通过[TEB+0x30]找到PEB的地址
- 通过[PEB+0x0C]找到PEB_LDR_DATA的结构体指针
- 通过[PEB_LDR_DATA+0x1C]找到此结构体的成员InInitializationOrderModuleList,此成员保存着模块链表的头部地址
- InInitializationOrderModuleList按照顺序保存进程加载的模块基址,其中第一个始终为ntdll.dll,第二个视系统的不同可能保存有Kernel32.dll或KernelBase.dll的信息
- 不管Kernel32.dll或KernelBase.dll都导出有我们需要的函数GetprocAddress和LoadLibraryEx
- LoadLibrary只在Kernel32.dll导出
PE文件定位导入导出表
- [ImageBase+0x3C]是是DOS头指向NT头的偏移:e_lfanew
- NT头的地址:pNt=ImageBase+e_lfanew,数据目录表[0]的偏移:DirectoryRVA=[pNt+0x78]
- 数据目录表的地址:pDataDirectory=ImageBase+DirectoryRVA
- 导出表地址:pExport=ImageBase+VirtualAddress
4.1 导入地址表:pEAT=[pExport+0x1C]
4.2 导入名称表:pENT=[pExport+0x20]
4.3 导入序号表:pEOT=[pExport+0x24]
4.4 导出表名称数量:NumberOfNames=[pExport+0x18] - 导入表地址:pImport=[pDataDirectory+0x8]+ImageBase
TEB-线程环境块
TEB保存了和一个线程相关的大量的信息,Windows将TEB
结构的变量保存在内存中的一个位置上. 这个地址是无法直接获取到的。TIB(Thread Information Block)线程信息块是保存线程基本信息的数据结构,在用户模式下,它位于TEB的头部。
-
x86系统:
TEB总是由FS:[0]指向的,TEB的偏移0x18是指向自己的指针,偏移0x30是PEB
-
x64系统:
TEB是由GS:[0]指向的,TEB的偏移0x30是指向自己的指针,偏移0x60是PEB
PEB-进程环境块
PEB保存进程相关信息,通过PEB找到模块链表,获取模块基址
-
[PEB+0xC]找到DllList成员,此成员指向PEB_LDR_DATA 结构体
-
[PEB_LDR_DATA+0x1C]找到InInitializationOrderModuleList,此成员保存模块链表的头部地址
// PEB_LDR_DATA结构
typedef struct _PEB_LDR_DATA
{
ULONG Length; // +0x00
BOOLEAN Initialized; // +0x04
PVOID SsHandle; // +0x08
LIST_ENTRY InLoadOrderModuleList; // +0x0c
LIST_ENTRY InMemoryOrderModuleList; // +0x14
LIST_ENTRY InInitializationOrderModuleList; // +0x1c
} PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
InLoadOrderModuleList 、InMemoryOrderModuleList、InInitializationOrderModuleList都是双向链表的头部,访问模块顺序的不同,列表中的每一项都是指向LDR_DATA_TABLE_ENTRY结构的指针。
- [InInitializationOrderModuleList + 0x8] 取出模块基址
[InInitializationOrderModuleList + 0x14] 取出模块名偏移
[InInitializationOrderModuleList] 指向下一个模块
// struct _LIST_ENTRY结构体
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;
// LDR_DATA_TABLE_ENTRY结构体
typedef struct _LDR_DATA_TABLE_ENTRY {
/*0x00*/ PVOID Reserved1[2];
/*0x08*/ LIST_ENTRY InMemoryOrderLinks; // 模块基址
/*0x0C*/ PVOID Reserved2[2];
/*0x14*/ PVOID DllBase; // 模块名字
/*0x18*/ PVOID EntryPoint;
/*0x1C*/ PVOID Reserved3;
/*0x20*/ UNICODE_STRING FullDllName;
/*0x24*/ BYTE Reserved4[8];
/*0x2C*/ PVOID Reserved5[3];
/*0x38*/ union {
ULONG CheckSum;
PVOID Reserved6;
};
/*0x40*/ ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
- 示例程序
DWORD GetKernel32Addr()
{
_asm {
push esi
mov esi, dword ptr fs : [0x30] //esi=PEB地址
mov esi, [esi + 0x0C] //esi=PEB_LDR_DATA结构体的地址
mov esi, [esi + 0x1C] //esi=模块链表指针InInitializationOrderModuleList
mov esi, [esi] //esi=访问链表的第二个条目
mov eax, [esi + 0x08] //ebx=Kernel32.dll模块基址
pop esi
}
}
定位导入导出表
- 在Dos头部找到NT头偏移
- 偏移加模块基址定位到NT头后,在扩展头找到数据目录表RVA=[pNtHeader+0x78]
- 数据目录表的地址=模块基址+RVA,每个数据目录表成员大小占8字节。
- 导出表地址:pExport=模块基址+VirtualAddress
4.1 导入地址表:pEAT=[pExport+0x1C]
4.2 导入名称表:pENT=[pExport+0x20]
4.3 导入序号表:pEOT=[pExport+0x24]
4.4 导出表名称数量:NumberOfNames=[pExport+0x18]
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 0
DWORD TimeDateStamp; // 0x4
WORD MajorVersion; // 0x6
WORD MinorVersion; // 0x8
DWORD Name; // 0xC 指向该导出表文件名字字符串
DWORD Base; // 0x10 导出函数起始序号
DWORD NumberOfFunctions; // 0x14 地址表成员个数 导出序号最大-最小
DWORD NumberOfNames; // 0x18 以函数名字导出的函数个数
DWORD AddressOfFunctions; // 0x1C 导出函数地址表RVA
DWORD AddressOfNames; // 0x20 导出函数名称表RVA
DWORD AddressOfNameOrdinals; // 0x24 导出函数序号表RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
- 导入表地址:pImport=[pDataDirectory+0x8]+ImageBase