Drunkmars's Blog

ring0下的Inline hook

字数统计: 1.5k阅读时长: 6 min
2022/02/16

Inline hook是直接在以前的函数替里面修改指令,用一个跳转或者其他指令来达到挂钩的目的。 这是相对普通的hook来说,因为普通的hook只是修改函数的调用地址,而不是在原来的函数体里面做修改。一般来说,普通的hook比较稳定使用。 inline hook 更加高级一点,一般也跟难以被发现。ring3的Inline hook在之前已经实现过了,再看看ring0的Inline hook该如何实现。

这里本来调用链应该是OpenFile -> ZwOpenFile,这里在od里面应该可以看到,这里我就用windbg直接找到这个ring0函数。

首先在windbg里面定位到ZwOpenFile函数,可以看到它的偏移为0x74

image-20220216160848788

通过SSDT表找到所有的内核函数地址,再通过0x74偏移定位到ZwOpenFile函数

1
2
3
kd> dd KeServiceDescriptorTable
kd> dd 80505450 + 74 * 4
kd> u 8057b182

image-20220216160922723

我这里因为windbg的原因没有显示函数名称,到pchunter里面确认一下,地址确实是一样的

image-20220216160932275

那么我们要实现Inline hook,无论是使用E8 call,还是E9 jmp,都需要至少5个硬编码才能实现,所以这里我们找5个硬编码进行填入我们代码的操作,这里注意不能够找全局变量和重定位的地址,否则在进行还原的过程中可能地址已经发生改变导致程序不能够正常运行

1
2
3
mov ebp,esp
xor eax,eax
push eax

image-20220216161051303

首先我们写一个FilterNtOpenFile函数用来打印我们Inline hook后的一些信息,这里用到PsGetCurrentProcess获取进程的EPROCESS结构,在0x174偏移存放着进程名,我们通过打印进程名来查看一下哪些进程调用了NtOpenFile这个函数

image-20220216175426208

1
2
3
4
5
6
char* p = "r0 InlineHook";
void FilterNtOpenFile(char* p)
{
KdPrint(("%s\r\n", p));
KdPrint(("name:%s\r\n", (char*)PsGetCurrentProcess() + 0x174));
}

然后提供ServiceDescriptorEntry这个结构体并定义KeServiceDescriptorTablentoskrnl.exe 所导出的全局变量

1
2
3
4
5
6
7
8
typedef struct ServiceDescriptorEntry {
unsigned int* ServiceTableBase;
unsigned int* ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char* ParamTableBase;
} ServiceDescriptorTableEntry_t, * PServiceDescriptorTableEntry_t;

__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;

这里我们再利用汇编来执行我们的汇编代码之后再jmp到原覆盖地址+5的地方,先用pushadpushfd保存寄存器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void _declspec(naked) NewNtOpenFile()
{
__asm
{
pushad
pushfd
push p
call FilterNtOpenFile
popfd
popad
mov ebp, esp
xor eax, eax
push eax
jmp ReAddress
}
}

首先定义一个数组,用来存放E9jmp跳转的代码

1
UCHAR jmp_code[5] = "";

然后因为我们在8057b185这个地址开始覆盖,函数的起始地址为8057b182,所以偏移为3

1
ULONG ChangeAddr = 3;

然后通过KeServiceDescriptorTableServiceTableBase属性定位到NtOpenFile的起始地址,这里在PCHunter里面可以看到NtOpenFile的索引号为116

image-20220216181246599

1
StartAddr = KeServiceDescriptorTable.ServiceTableBase[116];

定义返回地址,用函数的开始地址+偏移+5即可得到返回地址

1
ReAddress = StartAddr + ChangeAddr + 5;

通过E9 jmp的计算公式还需要计算我们自己定义的函数newNtOpenKey相对于NtOpenFile的偏移量

1
ULONG jmpAddr = (ULONG)NewNtOpenFile - StartAddr - ChangeAddr - 5;

将跳转代码写入数组

1
2
3
jmp_code[0] = 0xE9;

*(ULONG*)&jmp_code[1] = jmpAddr;

这里就需要写入内存了,这里需要关闭页的只读保护,定义一个ShutPageProtect函数将CR0寄存器的WP位置0

1
2
3
4
5
6
7
8
9
10
//关闭页只读保护
__asm
{
push eax;
mov eax, cr0;
and eax, ~0x10000; // 与0x10000想与后取反
mov cr0, eax;
pop eax;
ret;
}

关闭页保护之后首先将之前的硬编码保存,再进行覆盖

1
2
3
ShutPageProtect();
RtlCopyMemory(Old_code, (PVOID)(StartAddr + ChangeAddr), 5);
RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), jmp_code, 5);

写入内存完毕之后再定义一个OpenPageProtect函数将CR0寄存器的WP恢复为1

1
2
3
4
5
6
7
8
9
10
11
12
void _declspec(naked) OpenPageProtect()
{
__asm
{
push eax;
mov eax, cr0;
or eax, 0x10000;
mov cr0, eax;
pop eax;
ret;
}
}

那么到这里我们的hook代码就已经完成,因为我们已经将原来的硬编码存入了Old_code这个数组,这里我们编写UnHookNtOpenFile时利用RtlCopyMemory写会到原内存即可

1
2
3
4
5
6
7
8
9
void UnHookNtOpenFile()
{
ULONG ChangeAddr = 3;

ShutPageProtect();
RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), Old_code, 5);
OpenPageProtect();

}

再就是加载驱动和卸载驱动,在加载驱动中调用HookNtOpenFile,在卸载驱动中调用UnHookNtOpenFile即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//卸载驱动
void DriverUnload(DRIVER_OBJECT* obj)
{
//卸载钩子
UnHookNtOpenFile();

KdPrint(("驱动卸载成功!\n"));
}

/***驱动入口主函数***/
NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* path)
{
KdPrint(("驱动启动成功!\n"));

//安装钩子
HookNtOpenFile();

driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

完整代码如下

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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#include <ntddk.h>

typedef struct ServiceDescriptorEntry {
unsigned int* ServiceTableBase;
unsigned int* ServiceCounterTableBase;
unsigned int NumberOfServices;
unsigned char* ParamTableBase;
} ServiceDescriptorTableEntry_t, * PServiceDescriptorTableEntry_t;

__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;

// 关闭页只读保护
void ShutPageProtect();
// 开启页只读保护
void OpenPageProtect();
// 测试函数
void FilterNtOpenFile(char* p);
// 新NtOpenFile
void NewNtOpenFile();
// hook NtOpenFile
void HookNtOpenFile();
// unhook NtOpenFile
void UnHookNtOpenFile();


//关闭页只读保护
void _declspec(naked) ShutPageProtect()
{
__asm
{
push eax;
mov eax, cr0;
and eax, ~0x10000;
mov cr0, eax;
pop eax;
ret;
}
}

//开启页只读保护
void _declspec(naked) OpenPageProtect()
{
__asm
{
push eax;
mov eax, cr0;
or eax, 0x10000;
mov cr0, eax;
pop eax;
ret;
}
}

ULONG StartAddr;
ULONG ReAddress;
UCHAR Old_code[5];

char* p = "r0 InlineHook";
void FilterNtOpenFile(char* p)
{
KdPrint(("%s\r\n", p));
KdPrint(("name:%s\r\n", (char*)PsGetCurrentProcess() + 0x174));
}

void _declspec(naked) NewNtOpenFile()
{
__asm
{
pushad
pushfd
push p
call FilterNtOpenFile
popfd
popad
mov ebp, esp
xor eax, eax
push eax
jmp ReAddress
}
}

void HookNtOpenFile()
{
// 存放跳转指令的数组
UCHAR jmp_code[5] = "";
// 在入口0x3处hook
ULONG ChangeAddr = 3;
// NtOpenFile函数的开始地址
StartAddr = KeServiceDescriptorTable.ServiceTableBase[116];
// 返回地址
ReAddress = StartAddr + ChangeAddr + 5;
// newNtOpenKey相对于NtOpenKey的偏移量
ULONG jmpAddr = (ULONG)NewNtOpenFile - StartAddr - ChangeAddr - 5;

// 使用jmp指令跳转,jmp = 0xE9
jmp_code[0] = 0xE9;
// 填入偏移地址
*(ULONG*)&jmp_code[1] = jmpAddr;

ShutPageProtect();

RtlCopyMemory(Old_code, (PVOID)(StartAddr + ChangeAddr), 5);
RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), jmp_code, 5);

OpenPageProtect();

}

void UnHookNtOpenFile()
{
ULONG ChangeAddr = 3;

ShutPageProtect();
RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), Old_code, 5);
OpenPageProtect();

}

//卸载驱动
void DriverUnload(DRIVER_OBJECT* obj)
{
//卸载钩子
UnHookNtOpenFile();

KdPrint(("驱动卸载成功!\n"));
}

/***驱动入口主函数***/
NTSTATUS DriverEntry(DRIVER_OBJECT* driver, UNICODE_STRING* path)
{
KdPrint(("驱动启动成功!\n"));

//安装钩子
HookNtOpenFile();

driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

实现效果如下

InlineHook

CATALOG