avatar

Catalog
SSDT Hook

经过IRP那一章,发现如果文章篇幅过长(那篇我粘了很多结构定义的代码),网页端是无法显示出来博客内容的,只能分篇,看起来会比较麻烦,以后会尽量讲的精炼,也方便自己以后查看。虽然现在依然是4月19日,但是代码是4月17日写的,就算作是那天的博客吧。这一篇主要讲解一下SSDT Hook的手法,并实现一个简单的SSDT Hook

系统服务表&SSDT

关于系统服务表和SSDT的细节可以参考这里,本篇不再赘述,仅作简要介绍。

  • 系统服务表(SystemServiceTable):之前在3环进0环时有讲过,因为3环调用0环函数时,需要提供一个系统服务号,就是在系统服务表里进行寻址用的。

  • SSDT(System Service Desciptor Table):系统服务描述符表,是一张已经导出的包含了4张系统服务表的表。其中只有第一张系统服务表可见 通过如下语句声明,即可获取到SSDT表的首地址

    c
    1
    extern PKSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;
  • SSDT Shadow(System Service Desciptor Table Shadow):一张未导出的表,与SSDT类似,不同的在于,SSDT Shadow两张表均可见。由于未导出,需要通过其它方式进行查找,例如内存搜索。

SSDT Hook原理

Hook总的来说可以分为两类:表Hook(例如IAT Hook)和地址Hook(例如Inline Hook,下一篇会讲)

  • 表Hook:主要是通过修改函数表中的函数,替换为自己的函数从而达到Hook的目的
  • 地址Hook:通过修改函数内部的部分字节,从而跳转到指定地方,改变程序执行流程,从而达到Hook目的

本篇中介绍的SSDT Hook属于表Hook。SSDT Hook的原理在于,通过SSDT找对应的系统服务表,在系统服务表中找到指定的内核函数,将其替换为自己的函数,从而达到Hook的目的。

SSDT Hook思路

找到系统服务表

由于SSDT是导出的,声明后即可获取SSDT地址,但是内核文件并未提供SSDT及系统服务表的结构体,这里我们需要自己先定义SSDT和系统服务表的结构体,并声明SSDT,代码如下:

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//定义系统服务表结构
typedef struct _KSYSTEM_SERVICE_TABLE {
PULONG ServiceTableBase; //服务函数地址表基址
PULONG Count;
ULONG ServiceLimit; //服务函数的个数
PULONG ArgmentTableBase; //服务函数参数表地址
}KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

//定义系统服务描述符表(SSDT)结构
typedef struct _KSERVICE_DESCRIPTOR_TABLE {
KSYSTEM_SERVICE_TABLE ntoskrnl; //ntoskrnl.exe的服务函数
KSYSTEM_SERVICE_TABLE win32k; //win32k.sys的服务函数,(GDI32.dll/User32.dll 的内核支持)
KSYSTEM_SERVICE_TABLE notUsed1;
KSYSTEM_SERVICE_TABLE notUsed2;
}KSERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;

extern PKSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;

构建自己的函数

本次实验采用对NtOpenProcess进行Hook,所以我们需要构建一个自己的MyNtOpenProcess函数。构建自己的函数有如下注意事项:

  1. 参数必须与原函数一致,否则执行时会崩溃
  2. 实现自己的功能,例如打印参数等
  3. 调用原本的函数,若不调用原本的函数,该函数将无法执行下去实现原本的功能,也会造成程序崩溃。

所以需要先定义一个函数指针,并用一个全局变量保存原函数的地址(在系统服务表中找到NtOpenProcess,该函数的系统服务号是0x7A),在自己的MyNtOpenProcess中执行完自身的功能调用原函数完成整个程序的执行。代码如下:

c
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
//定义调用NtOpenProcess的函数指针
typedef NTSTATUS(*NTOPENPROCESS)(
PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId
);

//定义全局变量保存原函数地址
ULONG OriginFunctionAddr = (KeServiceDescriptorTable->ServiceTableBase)[0x7A];

//实现自己的MyNtOpenProcess函数
NTSTATUS MyNtOpenProcess(
PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId
) {
//执行自己的功能,这里是打印NtOpenProcess的参数
DbgPrint("ProcessHandle: %x, DesiredAccess: %x, ObjectAttributes: %x, ClientId: %x\n",
ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);

NTOPENPROCESS pOpenProcess = (NTOPENPROCESS)OriginFunctionAddr;
pOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);

return STATUS_SUCCESS;
}

修改系统服务表地址

NtOpenProcess的系统服务号是0x7A,这一步看上去很简单,只需要修改系统服务表中,下标为0x7A那个函数的地址为自己MyNtOpenProcess函数的地址即可。但是有一点需要注意的是,系统服务表所对应的物理页是只读的,所以在修改前我们需要先修改物理页的属性。有如下两种方式:

  1. 通过页表基址修改物理页属性(注:这里的演示代码仅修改了PTE的属性,没有修改PDE的属性)

    c
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    if(RCR4 & 0x00000020)
    {//说明是2-9-9-12分页
    KdPrint(("2-9-9-12分页 %p\n",RCR4));
    KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8))));
    *(DWORD64*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8)) |= 0x02;
    KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 9) & 0x007FFFF8))));
    }
    else
    {//说明是10-10-12分页
    KdPrint(("10-10-12分页\n"));
    KdPrint(("PTE1 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC))));
    *(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC)) |= 0x02;
    KdPrint(("PTE2 %p\n",*(DWORD*)(0xC0000000 + ((HookFunAddr >> 10) & 0x003FFFFC))));
    }
  2. 通过修改CR0寄存器 代码如下:

    Code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    VOID PageProtectOff() {
    __asm {
    cli
    mov eax, cr0
    and eax, not 0x10000
    mov cr0, eax
    }
    }

    VOID PageProtectOn() {
    __asm {
    mov eax, cr0
    or eax, 0x10000
    mov cr0, eax
    sti
    }
    }

本次实验采用的是第二种方式,修改Cr0寄存器达成Hook的目的,代码如下:

c
1
2
3
4
5
6
VOID HookNtOpenProcess() {
PageProtectOff();
(KeServiceDescriptorTable->ServiceTableBase)[0x7A] = &MyNtOpenProcess;
DbgPrint("Hook Starting...\n");
PageProtectOn();
}

设置Unhook函数

Hook完实现自身功能,达成目的之后,需要设置一个Unhook函数,把系统服务表再改回来,所以还需要设置一个Unhook函数,这个函数就比较简单了,只需要把原函数的地址再写回去即可,需要通过先前代码设置一个全局变量保存原函数的地址。代码如下:

c
1
2
3
4
5
6
7
8
9
10
//设置全局变量,保存原函数的地址
ULONG OriginFunctionAddr = (KeServiceDescriptorTable->ServiceTableBase)[0x7A];

//Unhook函数
VOID UnHookNtOpenProcess() {
PageProtectOff();
(KeServiceDescriptorTable->ServiceTableBase)[0x7A] = OriginFunctionAddr;
DbgPrint("UnHook Finished!\n");
PageProtectOn();·
}

功能演示

在执行驱动前,查看PC Hunter,可以发现SSDT并没有函数被挂钩

执行后,在DebugView中可以看到,不断有NtOpenProcess的参数被写入,说明不断有新的进程被打开,我们打开一个记事本文件,同样会增加一些参数的打印

这时,我们再次查看PC Hunter,发现PC Hunter已经被检测到了SSDT表被挂钩子了,也说明这次实验SSDT Hook成功了

完整代码

附上完整代码作为参考

c
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include "ntddk.h"

//定义调用NtOpenProcess的函数指针
typedef NTSTATUS(*NTOPENPROCESS)(
PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId
);

//定义系统服务表结构
typedef struct _KSYSTEM_SERVICE_TABLE {
PULONG ServiceTableBase; //服务函数地址表基址
PULONG Count;
ULONG ServiceLimit; //服务函数的个数
PULONG ArgmentTableBase; //服务函数参数表地址
}KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;

//定义系统服务描述符表(SSDT)结构
typedef struct _KSERVICE_DESCRIPTOR_TABLE {
KSYSTEM_SERVICE_TABLE ntoskrnl; //ntoskrnl.exe的服务函数
KSYSTEM_SERVICE_TABLE win32k; //win32k.sys的服务函数,(GDI32.dll/User32.dll 的内核支持)
KSYSTEM_SERVICE_TABLE notUsed1;
KSYSTEM_SERVICE_TABLE notUsed2;
}KSERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;

extern PKSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;

//函数声明
NTSTATUS MyNtOpenProcess(
PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId
);
VOID HookNtOpenProcess();
VOID UnHookNtOpenProcess();
VOID PageProtectOff();
VOID PageProtectOn();
VOID Driver_Unload(PDRIVER_OBJECT pDriverObj);

//定义原函数为全局变量
ULONG OriginFunctionAddr;

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObj, PUNICODE_STRING RegistryPath) {
DbgPrint("Driver is running!\n");

OriginFunctionAddr = (KeServiceDescriptorTable->ServiceTableBase)[0x7A];

/* DbgPrint("%x, %x, %x, %x\n",
KeServiceDescriptorTable->ServiceTableBase,
KeServiceDescriptorTable->Count,
KeServiceDescriptorTable->ServiceLimit,
KeServiceDescriptorTable->ArgmentTableBase);
*/
// DbgPrint("OriginFunctionAddr: %x", OriginFunctionAddr);
HookNtOpenProcess();

pDriverObj->DriverUnload = Driver_Unload;
return STATUS_SUCCESS;
}

NTSTATUS MyNtOpenProcess(
PHANDLE ProcessHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PCLIENT_ID ClientId
) {
DbgPrint("ProcessHandle: %x, DesiredAccess: %x, ObjectAttributes: %x, ClientId: %x\n",
ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);

NTOPENPROCESS pOpenProcess = (NTOPENPROCESS)OriginFunctionAddr;
pOpenProcess(ProcessHandle, DesiredAccess, ObjectAttributes, ClientId);

return STATUS_SUCCESS;
}

VOID HookNtOpenProcess() {
PageProtectOff();
(KeServiceDescriptorTable->ServiceTableBase)[0x7A] = &MyNtOpenProcess;
DbgPrint("Hook Starting...\n");
PageProtectOn();
}

VOID UnHookNtOpenProcess() {
PageProtectOff();
(KeServiceDescriptorTable->ServiceTableBase)[0x7A] = OriginFunctionAddr;
DbgPrint("UnHook Finished!\n");
PageProtectOn();
}

VOID PageProtectOff() {
__asm {
cli
mov eax, cr0
and eax, not 0x10000
mov cr0, eax
}
}

VOID PageProtectOn() {
__asm {
mov eax, cr0
or eax, 0x10000
mov cr0, eax
sti
}
}

VOID Driver_Unload(PDRIVER_OBJECT pDriverObj) {
UnHookNtOpenProcess();

DbgPrint("Unload Success!\n");
}

总结

通过PC Hunter我们可以发现,SSDT Hook是很容易被检测出来的,也就容易有反制措施,下一篇会介绍另一种Hook的方式:Inline Hook来达成同样的功能或者目的。

参考资料

参考教程:https://www.bilibili.com/video/BV1NJ411M7aE?p=62

参考文章:https://blog.csdn.net/qq_41988448/article/details/103527830

参考笔记:馍馍的笔记

Author: cataLoc
Link: http://cata1oc.github.io/2020/04/17/SSDT_Hook/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶