+0x000 NtTib :_NT_TIB
.
.
.
+0x30 ProcessEnvironmentBlock :Ptr32_PEB
TEB结构体的第一个成员为_NT_TIB结构体(TIB意为线程信息块)
_NT_TIB结构体
typedef struct _NT_TIB{
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
PVOID StackBase;
PVOID StackLimit;
PVOID SubSystemTib;
union {
PVOID FiberData;
DWORD Version;
};
PVOID ArbitraryUserPointer;
struct _NT_TIB *Self;
} NT_TIB;
typedef NT_TIB *PNT_TIB;
ExceptionList成员指向_EXCEPTION_REGISTRATION_RECORD结构体组成的链表,它用于WindowsOS的SEH, SEH是Wiondows操作系统中的结构化异常处理机制,常用于反调试技术。Self成员是_NT_TIB结构体的自引用指针,它指向_NT_TIB结构体,又因为_NT_TIB是TEB结构体的第一个成员,所以它也是指向TEB结构体的指针(它里面存着TEB结构体的地址)。
用户模式下使用Ntdll.NtCurrentTeb()API访问TEB结构体,它返回当前线程的的TEB结构体的地址,它的实现方法就是返回FS:[0x18]处的地址值(FS段寄存器用来指示当前线程的TEB结构体),而FS:0x18处正是Self指针,其含有的也正是TEB结构体的地址。
用一个公式总结:
FS:[0x18] = TEB.NtTib.Self = address of TIB = address of TEB = FS:0(注意:xx:[]是该处含有的值)
使用汇编获取PEB地址的两种方法
方法1
MOV EAX,DWORD PTR FS:[30] ;FS:[30] = address of PEB
方法2
MOV EAX,DWORD PTR FS:[18] ;FS[18] = address of TEB
MOV EAX,DWORD PTR DS:[EAX+30] ;DS[EAX+30] = address of PEB
PEB中重要的几个成员
+002 BeingDebugged ;Uchar(可用于反调试技术)
...
+008 ImageBaseAddress ;Ptr32 Void
...
+00c Ldr ;Ptr32 _PEB_LDR_DATA(可用于反调试技术)
...
+018 ProcessHeap ;Ptr32 Void(可用于反调试技术)
...
+068 NtGlobalFlag ;uint4B(可用于反调试技术)
BeingDebugged的作用就是标识当前进程是否处于调试状态,Kernel32.dll中的IsDebuggerPresent() API就是用来获取该处的值的(是,则返回1;否,则返回0)。
IsDebuggerPresent() API的汇编代码形式
MOV EAX,DWORD PTR FS:[18]
MOV EAX,DWORD PTR DS:[EAX+30]
MOVZX EAX,BYTE PTR DS:[EAX+2]
RETN
该值在代码逆向分析领域主要用于反调试技术。检测该值,若进程处于调试中,则终止进程。
PEB.ImageBaseAddress成员用来表示进程的ImageBase
GetModuleHandle()API用来获取ImageBase。
HMODULE WINAPI GetModuleHandle(
__in_opt LPCTSTR lpModuleName
);
将lpModuleName参数赋值为NULL,调用GetModuleHandle()函数将返回进程被加载的ImageBase
GetModuleHandle() API的部分代码
MOV EAX,DWORD PTR FS:[18] ;FS:[18] = TEB
MOV EAX,DWORD PTR DS:[EAX+30] ;DS:[EAX+30] = PEB
MOV EAX,DWORD PTR DS:[EAX+8] ;DS:[EAX+8] = PEB.ImageBaseAddress
函数的最终返回值将被存到EAX中
PEB.Ldr成员是指向_PEB_LDR_DATA结构体的指针,_PEB_LDR_DATA结构体如下。
+000 Length :Uint4B
+004 Initialized :UChar
+008 SsHandle :Ptr32 Void
+00c InLoadOrderModulelist :_LIST_ENTRY
+014 InMemoryOrderModulelist :_LIST_ENTRY
+01c InInitializationOrderModulelist:_LIST_ENTRY
+024 EntryInProgress :Ptr32 Void
+028 ShutdownInProgress :UChar
+02c ShutdownThreadId :Ptr32 Void
当模块(DLL)加载到进程后,通过PEB.Ldr成员可以直接获得该模块的加载基址,_PEB_LDR_DATA结构体中含有3个_LIST_ENTRY类型的成员,_LIST_ENTRY结构体如下。
typedef struct LIST_ENTRY{
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Bink;
}LIST_ENTRY,*PLIST_ENTRY;
从上述结构体可以看出,_LIST_ENTRY结构体提供双向链表机制。链表中保存的是_LDR_DATA_TABLE_ENTRY结构体的信息,给结构体如下。
typedef struct _LDR_DATA_TABLE_ENTRY {
PVOID Reserved1[2];
LIST_ENTRY InMemoryOrderLinks;
PVOID Reserved2[2];
PVOID D1lBase;
PVOID EntryPoint;
PVOID Reserved3;
Unicode_STRING Ful1D1lName;
BYTE Reserved4[8];
PVOID Reserved5[3];
union{
ULONG CheckSum;
PVOID Reserved6;
};
ULONG TimeDateStamp;
}LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY;
每个加载到进程中的DLL模块都对应一个_LDR_DATA_TABLE_ENTRY结构体,这些结构体相互链接,最终形成了_LIST_ENTRY双向链表。_PEB_LDR_DATA结构体中存在3种_LIST_ENTRY双向链表,也就是说,存在多个_LDR_DATA_TABLE_ENTRY结构体,并且有三种链接方法可以将它们链接起来。
PEB.ProcessHeap与PEB.NtGlobalFlag成员(像PEB.BeingDebugged成员一样)应用于反调试技术。若进程处于调试状态,则ProcessHeap与NtGlobalFlag成员就持有特定值。由于它们具有这一个特征,所以常常应用于反调试技术。
PEB.ProcessHeap成员是指向HEAP结构体的指针,HEAP结构体如下。
+0×000 Entry :_HEAP_ENTRY
+0×008 Signature :Uint4B
+0×00c Flags :Uint4B
+0×010 ForceFlags :Uint4B
+0×014 VirtualMemoryThreshold :Uint4B
+0×018 SegmentReserve :Uint4B
+0×01c SegmentCommit :Uint4B
+0×020 DeCommitFreeBlockThreshold :Uint4B
...
进程处于被调试状态时,Flags(+0xC)与Force Flags(+ox10)成员被设置成特定的值。
PEB.ProcessHeap(PEB结构体中偏移0x18的位置)成员既可以从PEB结构体中直接获得,也可以通过GetProcessHeap() API获得。
GetProcessHeap() API代码的汇编形式基本类似于IsDebuggerPresent(),按照TEB→PEB→PEB.ProcessHeap顺序访问的ProcessHeap
汇编代码如下
MOV EAX,DWORD PTR FS:[18] ;FS:[18] = TEB
MOV EAX,DWORD PTR DS:[EAX+30] ;DS:[EAX+30] = PEB
MOV EAX,DWORD PTR DS:[EAX+18] ;DS:[EAX+18] = PEB.ProcessHeap
RETN
当进程运行正常时Heap.Flagsh成员的值为0x2,Heap. ForceFlags成员的值位0x0,进程处于被调试状态时这些值也会随之改变