ring0下的Inline hook
字数 874 2025-08-29 08:31:35
Ring0下的Inline Hook技术详解
前言
Inline Hook是一种直接在目标函数内部修改指令的技术,通过插入跳转或其他指令来实现挂钩功能。与普通Hook(仅修改函数调用地址)相比,Inline Hook更加高级且难以被发现。本文详细讲解在Ring0(内核层)实现Inline Hook的技术原理和实现方法。
技术原理
Inline Hook与普通Hook的区别
- 普通Hook:修改函数的调用地址,指向自定义函数
- Inline Hook:在原始函数体内修改指令(通常是前几条指令),插入跳转指令
实现要点
- 需要至少5个字节的硬编码空间(用于E8 call或E9 jmp)
- 不能选择涉及全局变量和重定位地址的指令进行修改
- 需要临时关闭内存页保护才能修改内核代码
- 必须保存被覆盖的原始指令以便恢复
实现步骤
1. 定位目标函数
以NtOpenFile为例,通过以下步骤定位:
- 使用Windbg查找
ZwOpenFile函数偏移(示例中为0x74) - 通过SSDT表找到内核函数地址
- 计算
NtOpenFile的实际地址
kd> dd KeServiceDescriptorTable
kd> dd 80505450 + 74 * 4
kd> u 8057b182
2. 准备Hook代码
定义过滤函数
char *p = "r0 InlineHook";
void FilterNtOpenFile(char *p) {
KdPrint(("%s \r\n", p));
KdPrint(("name:%s \r\n", (char*)PsGetCurrentProcess() + 0x174));
}
定义SSDT结构体
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 _declspec(naked) NewNtOpenFile() {
__asm {
pushad
pushfd
push p
call FilterNtOpenFile
popfd
popad
mov ebp, esp
xor eax, eax
push eax
jmp ReAddress
}
}
3. 计算跳转地址
- 确定Hook位置(示例中选择偏移3处)
- 计算返回地址(原始地址+偏移+5)
- 计算跳转偏移量
UCHAR jmp_code[5] = "";
ULONG ChangeAddr = 3;
StartAddr = KeServiceDescriptorTable.ServiceTableBase[116];
ReAddress = StartAddr + ChangeAddr + 5;
ULONG jmpAddr = (ULONG)NewNtOpenFile - StartAddr - ChangeAddr - 5;
jmp_code[0] = 0xE9;
*(ULONG*)&jmp_code[1] = jmpAddr;
4. 修改内存保护
关闭页保护
void _declspec(naked) ShutPageProtect() {
__asm {
push eax;
mov eax, cr0;
and eax, ~0x10000;
mov cr0, eax;
pop eax;
ret;
}
}
执行Hook
ShutPageProtect();
RtlCopyMemory(Old_code, (PVOID)(StartAddr + ChangeAddr), 5);
RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), jmp_code, 5);
OpenPageProtect();
恢复页保护
void _declspec(naked) OpenPageProtect() {
__asm {
push eax;
mov eax, cr0;
or eax, 0x10000;
mov cr0, eax;
pop eax;
ret;
}
}
5. 卸载Hook
void UnHookNtOpenFile() {
ULONG ChangeAddr = 3;
ShutPageProtect();
RtlCopyMemory((PVOID)(StartAddr + ChangeAddr), Old_code, 5);
OpenPageProtect();
}
完整实现代码
#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;
}
关键注意事项
- 指令选择:选择修改的指令不能是涉及全局变量或重定位地址的指令
- 内存保护:修改内核代码前必须关闭CR0的WP位(写保护)
- 寄存器保存:在跳转代码中要保存和恢复所有寄存器状态
- 原始指令保存:必须保存被覆盖的原始指令以便正确恢复
- 偏移计算:跳转偏移量计算要准确,确保能正确跳转和返回
总结
Ring0下的Inline Hook技术通过直接修改内核函数代码实现挂钩,具有较高的隐蔽性。实现过程中需要注意内存保护机制、指令选择和偏移计算等关键点。这种技术可用于内核监控、安全防护等多种场景,但使用时需遵守相关法律法规。