SSDT、PEB、TEB & Hook

SSDT

概述

SSDT全称是”System Services Descriptor Table”(系统服务描述符表),在内核中的实际名称是”KeSeriveDescriptorTable“。这个表已通过内核ntoskrnl.exe导出(在x64里不导出)。

SSDT用于处理应用层通过kernel32.dll下发的各个API操作请求。ntdll.dll中的API是一个简单的包装函数,当kernel32.dll中的API通过ntdll.dll时,会先完成对参数的检查,在调用一个中断(int 2Eh 或者 Sys Enter指令),从而实现从R3层进入R0层,并将要调用的服务号(也就是SSDT数组中的索引号index值)存放到寄存器EAX中,最后根据存放在EAX中的索引值在SSDT数组中调用指定的服务(Nt*系列函数)如下图所示。

1550663088705

定义

SSDT表的结构定义如下

1
2
3
4
5
6
7
8
9
10
#pragma pack(1)
typedef struct ServiceDescriptorEntry{
unsigned int *ServiceTableBase; //Table Base Address
unsigned int *ServiceCounterTableBase;
unsigned int NumberOfServices;// Count of Service function in table
unsigned char *ParamTableBas;
}
ServiceDescriptorTableEntry_t,
*PServiceDescriptorTableEntry_t;
#pragma pack()

其中最重要的2个成员为ServiceTableBase(SSDT表的基地址)和 NumberOfServices(表示系统中SSDT服务函数的个数)。SSDT表其实就是一个连续存放这个函数指针的数组。

原理与Hook

SSDT表的导入方法如下。

1
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;

由此可以知道SSDT表的基地址(数组的首地址)和SSDT函数的索引号(index),从而求出对应的服务函数的地址。在x86平台上,它们之间满足如下规则。

1
FuncAddr = KeServiceDescriptortable + 4 * index

与x86平台上直接在SSDT中存放SSDT函数地址不同,在x64平台上,SSDT中存放的时索引号所对应SSDT函数地址和SSDT表基地址的偏移 * 16 的值,计算公式如下。

1
FuncAddr = ([KeServiceDescriptortable + index * 4] >> 4 + KeServiceDescriptorTable)

通过这个公式,只要知道SSDT表的首地址和对应函数的索引号,就可以将对应位置的服务函数替换成自己的函数,从而完成SSDT Hook过程了。

Shadow SSDT的原理比SSDT类似,它对应的表名为KeServiceDescriptorTableShadow,是内核未导出的另一张表,包含Ntoskrnel.exe和win32k.sys服务函数,主要处理来自User32.dll和GDI32.dll的系统调用。与SSDT不同,Shadow SSDT是未导出的,因此不能在自己的模块中导入和直接引用。

挂钩该表中的NtGdiBitBlt、NtGdiStretchBlt 可以实现截屏保护。挂钩NtUserSetWindowsHookEx函数可以防止或保护键盘钩子,挂钩与按键相关的函数NtUserSendInput可以防止模拟按键,挂钩NtUserFindWindowEx函数可以防止或保护键盘钩子,挂钩与按键相关的函数NtUserSendInput可以防止模拟按键,挂钩NtUserFindWindowEx函数可以防止搜索窗口,挂钩与窗口相关的函数NtUserPostMessage、NtUserQueryWindow可以防止窗口被关闭。

Shadow SSDT的管沟原理和SSDT的挂钩原理一样,只不过由于未导出,需要使用不同的方法来获取该表的地址及服务函数的索引号。例如,硬编码与KeServiceDescriptorTable在不同系统中的位置偏移,搜索KeAddSystemServiceTable、KTHREAD.ServiceTable,以及有效内存搜索等。

KeServiceDescriptorTableShadow实际上也是一个SSDT结构数组,也就是说,KeServiceDescriptorTableShadow是一组系统描述表。在Windows XP中,KeServiceDescriptorTableShadow表位于KeServiceDescriptorTable表上方便宜0x40处。

KeServiceDescriptorTableShadow包含四个子结构,示例如下。第一个子结构是”ntoskrnl.exe(native api)”,与KeServiceDescriptorTable的指向相同。真正需要获得的是第二个子结构,即”win32k.sys(gdi/user support)”。第三个和第四个子结构一般不使用。

1
2
3
4
5
6
7
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
SYSTEM_SERVICE_TABLE ntoskrnl; //ntoskrnl.exe(native api)
SYSTEM_SERVICE_TABLE win32k; //win32k.sys(gdi/user support)
SYSTEM_SERVICE_TABLE Table3; //Not used
SYSTEM_SERVICE_TABLE Table4; //Not used
}

TEB

TEB和PEB一样,不在系统内核空间中,而是之外应用层中的结构。TEB结构比较重要。

概述

TEB 全称 Thread environment block,线程环境块,结构中包含了系统频繁使用的一些与线程相关的数据。进程中的每个线程(系统线程除外)都有一个自己的TEB。一个进程的所有TEB都存放在0x7FFDE00开始的线性内存中,,每4KB未一个完整的TEB。

1. TEB结构体

与EPROCESS类似(其他博文已有描述),在不同的Windows中,TEB结构略有差异。例如,在R3级的应用程序中,fs:[0]的地址指向TEB结构,这个结构的开头是一个NT_TIB结构,具体如下。

1
2
3
4
5
6
7
8
9
10
0:000> dt _nt_tib
ntdll!_NT_TIB
+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : Ptr32 Void
+0x008 StackLimit : Ptr32 Void
+0x00c SubSystemTib : Ptr32 Void
+0x010 FiberData : Ptr32 Void
+0x010 Version : Uint4B
+0x014 ArbitraryUserPointer : Ptr32 Void
+0x018 Self : Ptr32 _NT_TIB

(摘自 Windbg)

NT_TIB结构的0x18偏移处是一个Self指针,指向这个结构自身,也就是TEB结构的开头。TEB结构的0x30偏移处是指向PEB的指针。

利用Windbg的本地调试可以查看系统中的TEB结构。启动WinDbg,选择 File –> Kernel Debug –> Local 选项,然后在弹出的对话框中单击 Local 标签,就可以打开WinDbg的本机调试功能。在 Windows Vista 及以后的版本中会弹出信息,提示系统不支持本地内核调试,这时可以使用管理员模式打开cmd ,输入命令 bcdedit -debug on,重新启动计算机,在以管理员身份打开WinDbg。

输入 !teb 即可查看TEB结构数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0:000> !teb
TEB at 00620000
ExceptionList: 0093f6f4
StackBase: 00940000
StackLimit: 0093c000
SubSystemTib: 00000000
FiberData: 00001e00
ArbitraryUserPointer: 00000000
Self: 00620000
EnvironmentPointer: 00000000
ClientId: 00003220 . 00002294
RpcHandle: 00000000
Tls Storage: 0062002c
PEB Address: 0061d000
LastErrorValue: 0
LastStatusValue: 0
Count Owned Locks: 0
HardErrorMode: 0

继续查看,代码如下。

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
0:000> dt _teb 00620000
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x01c EnvironmentPointer : (null)
+0x020 ClientId : _CLIENT_ID
+0x028 ActiveRpcHandle : (null)
+0x02c ThreadLocalStoragePointer : 0x0062002c Void
+0x030 ProcessEnvironmentBlock : 0x0061d000 _PEB
+0x034 LastErrorValue : 0
+0x038 CountOfOwnedCriticalSections : 0
+0x03c CsrClientThread : (null)
+0x040 Win32ThreadInfo : (null)
+0x044 User32Reserved : [26] 0
+0x0ac UserReserved : [5] 0
+0x0c0 WOW32Reserved : 0x77196000 Void
+0x0c4 CurrentLocale : 0x804
+0x0c8 FpSoftwareStatusRegister : 0
+0x0cc ReservedForDebuggerInstrumentation : [16] (null)
+0x10c SystemReserved1 : [26] (null)
+0x174 PlaceholderCompatibilityMode : 0 ''
+0x175 PlaceholderHydrationAlwaysExplicit : 0 ''
+0x176 PlaceholderReserved : [10] ""
+0x180 ProxiedProcessId : 0
+0x184 _ActivationStack : _ACTIVATION_CONTEXT_STACK
+0x19c WorkingOnBehalfTicket : [8] ""
+0x1a4 ExceptionCode : 0n0
+0x1a8 ActivationContextStackPointer : 0x00620184 _ACTIVATION_CONTEXT_STACK
+0x1ac InstrumentationCallbackSp : 0
//更多代码略

在TEB中,0x30偏移处时PEB结构,地址为0x00620000。可以使用dt命令进一步查看PEB结构成员中的值。

2. 访问TEB

可以通过NtCurrentTeb函数调用和FS段寄存器访问这两种方法访问TEB结构。

(1)NtCurrentTeb函数调用

从ntdll.dll中道出了一个函数NtCurrentTeb函数,该函数可以返回当前线程的TEB结构体的地址。通过喜爱按的代码,就i可以从ntdll.dll中找到对应的NtCurrentTeb函数地址并调用它,返回TEB结构的地址。

1
2
3
4
5
6
7
8
9
10
typedef struct _TEB{
NT_TIB Tib;
PVOID EnvironmentPointer;
CLIENT_ID Cid;
PVOID ActiveRpcInfo;
PPEB Peb;
}TEB , *PTEB;
typedef PTEB (NTAPI *NtCurrentTeb)();
NtCurrentTeb fnNtCurrentTeb = (NtCurrentTeb)GetProocAddress(GetModuleHandle(L"ntdll.dll")), "NtCurrentTeb");
PTEB pTeb = fnNtCurrentTeb();
(2)FS段寄存器访问

FS是段寄存器,当代码运行在R3时,基地址即为当前线程的线程环境块(TEB),所以该段也称为 TEB段 。运行如下代码可获得TEB的指针。

1
mov eax, dword ptr fs:[18h]

PEB

概述

PEB 全称 ProcessEnvironment Block,进程环境块,存在于用户地址空间中,记录了进程的相关信息。每个进程有自己的PEB信息。

PEB访问

TEB中的ProcessEnvironmentBlock 就是PEB结构的地址,其结构的0x30偏移处是一个指向PEB的指针。PEB的0x2偏移处是一个UChar成员,名叫“BeginDebugged”,进程被调试时值为1,否则为0.因此访问PEB有两种方法。

  • 直接获取

    1
    mov eax , dword ptr fs:[30]  ;fs[30]里面存放的即为PEB地址
  • 通过TEB获取

    1
    2
    mov eax,dword ptr fs:[18h]   ;此时eax里为TEB的指针
    mov eax,dword ptr [eax + 30h] ;此时eax里为PEB的指针

此外,在内核结构对象EPROCESS结构中,同样记录了PEB结构的地址。因此,可以通过查看EPROCESS找到进程的PEB信息。

PEB结构体

与TEB一样,PEB也是随着Windows系统版本的变化而略有差异的结构。可以查阅MSDN或winternl.h,获取TEB结构定义。

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

MSDN中定义的PEB是不完整的,微软隐藏了很多细节,其他结构也是一样,下面是windbg获得的结果

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
0:000> dt _PEB 0X0061d000
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 ''
+0x003 BitField : 0x4 ''
+0x003 ImageUsesLargePages : 0y0
+0x003 IsProtectedProcess : 0y0
+0x003 IsImageDynamicallyRelocated : 0y1
+0x003 SkipPatchingUser32Forwarders : 0y0
+0x003 IsPackagedProcess : 0y0
+0x003 IsAppContainer : 0y0
+0x003 IsProtectedProcessLight : 0y0
+0x003 IsLongPathAwareProcess : 0y0
+0x004 Mutant : 0xffffffff Void
+0x008 ImageBaseAddress : 0x01140000 Void
+0x00c Ldr : 0x772c0c40 _PEB_LDR_DATA
+0x010 ProcessParameters : 0x009a2320 _RTL_USER_PROCESS_PARAMETERS
+0x014 SubSystemData : (null)
+0x018 ProcessHeap : 0x009a0000 Void
+0x01c FastPebLock : 0x772c09e0 _RTL_CRITICAL_SECTION
+0x020 AtlThunkSListPtr : (null)
+0x024 IFEOKey : (null)
+0x028 CrossProcessFlags : 2
//更多代码略

其中,BeingDebugged成员适用于指定该进程是否处于被调试状态CheckRemoteDebuggerPrecset()函数用于判断进程是否处于调式状态。ProcessParameters是一个RTL_USER_PROCESS_PARAMMETERS,即用于记录进程的参数信息(例如命令行参数等)。

下图表示EPROCESS、ETHREAD、PEB、TEB的关系,可以看出,EPROCESS和ETHREAD结构处于内核空间中,它们分别拥有一个指针,指向处于应用层空间的PEB结构和TEB结构,而在TEB中也有一个指针指向PEB结构。

1550668838988

​ EPROCESS、ETHREAD、TEB、PEB之间的关系

1571159972513

Author: YuanBi
Link: https://www.basicbit.cn/2017/06/13/2017-07-15-SSDT,PEB,TEB/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.