TEB和PEB


TEB

基本知识

TEB 指线程环境块,该结构体包含进程中运行线程的各种信息,进程中的每个线程都对应一个 TEB 结构体。不同系统中 TEB 结构体的形态略微不同。

TEB 结构体的定义

1
2
3
4
5
6
7
8
9
10
typedef struct _TEB {
BYTE Reserved1[1952];
PVOID Reserved2[412];
PVOID TlsSlots[64];
BYTE Reserved3[8];
PVOID Reserved4[26];
PVOID ReservedForOLE;
PVOID Reserved5[4];
PVOID TlsExpansionSlots;
} TEB, *PTEB;

TEB 结构体成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
+0x000 NtTib            : _NT_TIB
+0x01c EnvironmentPointer : Ptr32 Void
+0x020 ClientId : _CLIENT_ID :当前进程ID
+0x028 ActiveRpcHandle : Ptr32 Void
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB 当前进程的PEB指针
+0x034 LastErrorValue : Uint4B
+0x038 CountOfOwnedCriticalSections : Uint4B
+0x03c CsrClientThread : Ptr32 Void
+0x040 Win32ThreadInfo : Ptr32 Void
+0x044 User32Reserved : [26] Uint4B
+0x0ac UserReserved : [5] Uint4B
+0x0c0 WOW32Reserved : Ptr32 Void
+0x0c4 CurrentLocale : Uint4B
+0x0c8 FpSoftwareStatusRegister : Uint4B
+0x0cc SystemReserved1 : [54] Ptr32 Void
+0x1a4 ExceptionCode : Int4B
+0x1a8 ActivationContextStack : _ACTIVATION_CONTEXT_STACK
+0x1bc SpareBytes1 : [24] UChar
+0x1d4 GdiTebBatch : _GDI_TEB_BATCH
+0x6b4 RealClientId : _CLIENT_ID
+0x6bc GdiCachedProcessHandle : Ptr32 Void
+0x6c0 GdiClientPID : Uint4B
+0x6c4 GdiClientTID : Uint4B
+0x6c8 GdiThreadLocalInfo : Ptr32 Void
+0x6cc Win32ClientInfo : [62] Uint4B
+0x7c4 glDispatchTable : [233] Ptr32 Void
+0xb68 glReserved1 : [29] Uint4B
+0xbdc glReserved2 : Ptr32 Void
+0xbe0 glSectionInfo : Ptr32 Void
+0xbe4 glSection : Ptr32 Void
+0xbe8 glTable : Ptr32 Void
+0xbec glCurrentRC : Ptr32 Void
+0xbf0 glContext : Ptr32 Void
+0xbf4 LastStatusValue : Uint4B
+0xbf8 StaticUnicodeString : _UNICODE_STRING
+0xc00 StaticUnicodeBuffer : [261] Uint2B
+0xe0c DeallocationStack : Ptr32 Void
+0xe10 TlsSlots : [64] Ptr32 Void
+0xf10 TlsLinks : _LIST_ENTRY
+0xf18 Vdm : Ptr32 Void
+0xf1c ReservedForNtRpc : Ptr32 Void
+0xf20 DbgSsReserved : [2] Ptr32 Void
+0xf28 HardErrorsAreDisabled : Uint4B
+0xf2c Instrumentation : [16] Ptr32 Void
+0xf6c WinSockData : Ptr32 Void
+0xf70 GdiBatchCount : Uint4B
+0xf74 InDbgPrint : UChar
+0xf75 FreeStackOnTermination : UChar
+0xf76 HasFiberData : UChar
+0xf77 IdealProcessor : UChar
+0xf78 Spare3 : Uint4B
+0xf7c ReservedForPerf : Ptr32 Void
+0xf80 ReservedForOle : Ptr32 Void
+0xf84 WaitingOnLoaderLock : Uint4B
+0xf88 Wx86Thread : _Wx86ThreadState
+0xf94 TlsExpansionSlots : Ptr32 Ptr32 Void
+0xf98 ImpersonationLocale : Uint4B
+0xf9c IsImpersonating : Uint4B
+0xfa0 NlsCache : Ptr32 Void
+0xfa4 pShimData : Ptr32 Void
+0xfa8 HeapVirtualAffinity : Uint4B
+0xfac CurrentTransactionHandle : Ptr32 Void
+0xfb0 ActiveFrame : Ptr32 _TEB_ACTIVE_FRAME
+0xfb4 SafeThunkCall : UChar
+0xfb5 BooleanSpare : [3] UChar

重要成员

TEB 结构体的成员多而复杂,在用户模式调试中起着重要作用的成员有 2 个,如下所示。

1
2
3
+0x000 NtTib            : _NT_TIB
...
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
  • ProcessEnvironmenBlock成员

先看偏移 30 处的ProcessEnvironmenBlock成员,它是指向 PEB(Process Environment Block,进程环境块)结构体的指针。PEB 是进程环境块,每个进程对应 1 个 PEB 结构体。

  • NtTib成员

TEB 结构体的第一个成员为_NT_TIB结构体(TIB 是 Thread information Block的简称,意为“线程信息块”),_NT_TIB结构体的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct _NT_TIB {
struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;
PVOID StackBase;
PVOID StackLimit;
PVOID SubSystemTib;
union {
PVOID FiberData;
DWORD Vresion;
};
PVOID ArbitraryUserPointer;
struct _NT_TIB *Self;
} NT_TIB;
typedef NT_TIB *PNT_TIB;

ExceptionList成员指向_EXCEPTION_REGISTRATION_RECORD结构体组成的链表,它用于 Windows OS 的 SEH。Self成员是_NT_TIB结构体的自引用指针,也是 TEB 结构体的指针(因为TEB结构体的第一个成员就是_NT_TIB结构体)。接下来的问题是,该如何在用户模式下访问 TEB 结构体呢?

TEB访问方法

我们通过 Windbg 内核调试器可以很容易地访问 TEB 结构体,但是在用户模式下我们就需要通过另一种方式了,就是通过系统提供的相关 API 访问。

Ntdll.NtCurrentTeb()

Ntdll.NtCurrentTeb() API 用来返回当前线程的 TEB 结构体的地址。我们通过调试工具来查看该函数内部是如何实现的。

在 OD 中右键菜单选择 Search for Name in all modules 菜单,在对话框中查找ntdll.NtCurrentTeb() API。

双击跳转到该 API 的代码处:

从上图中可以看到,NtCurrentTeb()函数的内部代码非常简单,只返回FS:[18]地址值。在上图的代码注释窗口可以看到,FS:[18]的实际地址为 0x317018。在内存窗口中进入 0x317018 地址,发现其值为 0x317000,即NtCurrentTeb() API 返回 0x317018,该地址就是当前线程的 TEB 的地址。咨询观察 TEB 结构体的地址(0x317000),发现它与 FS 段寄存器所指的段内存的基址是一样的。也就是说,TEB 与 FS 段寄存器有着某种关联。

FS 段寄存器

  • SDT

其实,FS 段寄存器用来指示当前线程的 TEB 结构体。

32 位系统中进程的虚拟内存大小为 4GB,因而需要 32 位的指针才能访问整个内存空间。但是 FS 寄存器的大小只有 16 位,那么它如何表示进程内存空间中的 TEB 结构体的地址呢?实际上,FS 寄存器并非直接指向 TEB 结构体的地址,它持有 SDT 的索引,而该索引持有实际 TEB 地址。

注:SDT 位于内核内存区域,其地址存储在特殊的寄存器 GDTR(Global Descriptor Table Resiger,全局描述符表寄存器)中。

由于段寄存器实际存储的是 SDT 的索引,所以它也被称为 “段选择符”(Segment Selector)。上图可以看到,TEB 结构体位于 FS 段选择符所指的段内存的起始地址(base address)处。

  • FS:[0x18]=TEB 起始地址

如果掌握了上述内容,那么就很容易理解下面公式的含义。

FS:[0x18][7FFDF018](->7FFDF000)具有相同含义。由结构体代码中的_NT_TIB结构体的定义得知,结构体的最后一个Self成员恰好位于从 TEB 结构体偏移018的位置。Self指针变量指向_NT_TIB结构体的起始地址,也就是 TEB 的起始地址。

  • FS:[0x30]=PEB 起始地址

FS:[0x30][7FFDF030](->7FFD3000)具有相同含义。也就是说,通过 TEB 的ProcessEnvironment Block成员可以获取 PEB 结构体的起始地址。

  • FS:[0]=SEH 起始地址

PEB

基本知识

PEB 是存放进程信息的结构体,尺寸非常大,这里我们只需要它的几个重要字段。

PEB 访问方法

先了解访问 PEB 结构体的方法。在前面 TEB 结构体的学习中我们已经知道,TEB.Process-EnvironmentBlock成员就是 PEB 结构体的地址。

TEB 结构体位于 FS 段选择符所指的段内存的起始地址处,且ProcessEnvironmentBlock成员位于距 TEB 结构体偏移的 30 的位置。所以有如下等式成立:

1
FS:[30] = TEB.ProcessEnvironmentBlock = address of PEB

用如下汇编代码表示上述等式:

  • 方法1:直接获取 PEB 地址
1
mov eax,dword ptr FS:[30]  ;FS[30] = address of PEB
  • 方法2:先获取 TEB 地址,再通过ProcessEnvironmentBlock(+30偏移)获取 PEB 地址
1
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 结构体的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct _PEB{
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Idr;
PPTL_USER_PROCESS_PARAMETERS ProcessParameters;
BYTE Reserved4[104];
PVOID Reserved5[52];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved6[128];
PVOID Reserved7[1];
ULONG SessionId;
} PEB,*PPEB;

PEB 结构体的成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
+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 进程加载模块链表
+0x010 ProcessParameters : Ptr32 _RTL_USER_PROCESS_PARAMETERS
+0x014 SubSystemData : Ptr32 Void
+0x018 ProcessHeap : Ptr32 Void
+0x01c FastPebLock : Ptr32 _RTL_CRITICAL_SECTION
+0x020 FastPebLockRoutine : Ptr32 Void
+0x024 FastPebUnlockRoutine : Ptr32 Void
+0x028 EnvironmentUpdateCount : Uint4B
+0x02c KernelCallbackTable : Ptr32 Void
+0x030 SystemReserved : [1] Uint4B
+0x034 AtlThunkSListPtr32 : Uint4B
+0x038 FreeList : Ptr32 _PEB_FREE_BLOCK
+0x03c TlsExpansionCounter : Uint4B
+0x040 TlsBitmap : Ptr32 Void
+0x044 TlsBitmapBits : [2] Uint4B
+0x04c ReadOnlySharedMemoryBase : Ptr32 Void
+0x050 ReadOnlySharedMemoryHeap : Ptr32 Void
+0x054 ReadOnlyStaticServerData : Ptr32 Ptr32 Void
+0x058 AnsiCodePageData : Ptr32 Void
+0x05c OemCodePageData : Ptr32 Void
+0x060 UnicodeCaseTableData : Ptr32 Void
+0x064 NumberOfProcessors : Uint4B
+0x068 NtGlobalFlag : Uint4B
+0x070 CriticalSectionTimeout : _LARGE_INTEGER
+0x078 HeapSegmentReserve : Uint4B
+0x07c HeapSegmentCommit : Uint4B
+0x080 HeapDeCommitTotalFreeThreshold : Uint4B
+0x084 HeapDeCommitFreeBlockThreshold : Uint4B
+0x088 NumberOfHeaps : Uint4B
+0x08c MaximumNumberOfHeaps : Uint4B
+0x090 ProcessHeaps : Ptr32 Ptr32 Void
+0x094 GdiSharedHandleTable : Ptr32 Void
+0x098 ProcessStarterHelper : Ptr32 Void
+0x09c GdiDCAttributeList : Uint4B
+0x0a0 LoaderLock : Ptr32 Void
+0x0a4 OSMajorVersion : Uint4B
+0x0a8 OSMinorVersion : Uint4B
+0x0ac OSBuildNumber : Uint2B
+0x0ae OSCSDVersion : Uint2B
+0x0b0 OSPlatformId : Uint4B
+0x0b4 ImageSubsystem : Uint4B
+0x0b8 ImageSubsystemMajorVersion : Uint4B
+0x0bc ImageSubsystemMinorVersion : Uint4B
+0x0c0 ImageProcessAffinityMask : Uint4B
+0x0c4 GdiHandleBuffer : [34] Uint4B
+0x14c PostProcessInitRoutine : Ptr32 void
+0x150 TlsExpansionBitmap : Ptr32 Void
+0x154 TlsExpansionBitmapBits : [32] Uint4B
+0x1d4 SessionId : Uint4B
+0x1d8 AppCompatFlags : _ULARGE_INTEGER
+0x1e0 AppCompatFlagsUser : _ULARGE_INTEGER
+0x1e8 pShimData : Ptr32 Void
+0x1ec AppCompatInfo : Ptr32 Void
+0x1f0 CSDVersion : _UNICODE_STRING
+0x1f8 ActivationContextData : Ptr32 Void
+0x1fc ProcessAssemblyStorageMap : Ptr32 Void
+0x200 SystemDefaultActivationContextData : Ptr32 Void
+0x204 SystemAssemblyStorageMap : Ptr32 Void
+0x208 MinimumStackCommit : Uint4B

PEB 的重要成员

PEB 结构体非常庞大,且结构复杂,这里我们只学习几个重要的成员。

PEB.BeingDebugged

Kernel32.dll中有个名为Kernel32!IsDebuggerPresent()的 API,但普通的应用程序开发中并不常用。

1
BOOL WINAPI IsDebuggerPresent(void);

顾名思义,该 API 函数用于判断当前进程是否处于调试状态,并返回判断结果。该 API 通过检测PEB.IsDebuggerPresent成员来确定是否正在调试进程(是,则返回1;否,则返回0)。

PEB.ImageBaseAddress

PEB.ImageBaseAddress成员用来表示进程的 ImageBase。

GetMouleHandle() API 用来获取 imageBase,向lpModuleName参数赋值为 NULL,调用GetModuleHandle()函数将返回进程被加载的 ImageBase。

1
2
3
HMODULE WINAPI GetModuleHandle(
__in_opt LPCTSTR lpModuleName
);

PEB.Ldr

PEB.Ldr成员是指向_PEB_LDR_DATA结构体的指针。借助 WinDbg 调试器可以查看_PEB_LDR_DATA结构体成员。

1
2
3
4
5
6
7
8
9
+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是非常重要的成员。_PEB_LDR_DATA结构体成员中有 3 个_LIST_ENTRY类型的成员(InLoadOrderModuleListInMemoryOrderModuleListInInitializationOrderModuleList),_LIST_ENTRY结构体的定义如下:

1
2
3
4
typedef struct _LIST_ENTRY{
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
}_LIST_ENTRY,*PLIST_ENTRY;

从上述结构体的定义可以看到,_LIST_ENTRY结构体提供了双向链表机制。链表中保存着_LDR_DATA_TABLE_ENTRY结构体的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;

每个加载到进程中的 DLL 模块都有与之对应的_LDR_DATA_TABLE_ENTRY结构体,这些结构体相互链接,最终形成_LIST_ENTRY双向链表。需要注意的是,_PEB_LDR_DATA结构体中存在 3 种链表。也就是说,存在多个_LDR_DATA_TABLE_ENTRY结构体,并且有 3 种链接方法可以将它们链接起来。

PEB.ProcessHeap & PEB.NtGlobalFlag

PEB.ProcessHeapPEB.NtGlobalFlag成员应用于反调试技术。若进程处于调试状态,则ProcessHeapNtGlobalFlag成员就持有特定值。由于它们具有这么一个特征,所以常常应用于反调试技术。