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:在原始函数体内修改指令(通常是前几条指令),插入跳转指令

实现要点

  1. 需要至少5个字节的硬编码空间(用于E8 call或E9 jmp)
  2. 不能选择涉及全局变量和重定位地址的指令进行修改
  3. 需要临时关闭内存页保护才能修改内核代码
  4. 必须保存被覆盖的原始指令以便恢复

实现步骤

1. 定位目标函数

NtOpenFile为例,通过以下步骤定位:

  1. 使用Windbg查找ZwOpenFile函数偏移(示例中为0x74)
  2. 通过SSDT表找到内核函数地址
  3. 计算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. 计算跳转地址

  1. 确定Hook位置(示例中选择偏移3处)
  2. 计算返回地址(原始地址+偏移+5)
  3. 计算跳转偏移量
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;
}

关键注意事项

  1. 指令选择:选择修改的指令不能是涉及全局变量或重定位地址的指令
  2. 内存保护:修改内核代码前必须关闭CR0的WP位(写保护)
  3. 寄存器保存:在跳转代码中要保存和恢复所有寄存器状态
  4. 原始指令保存:必须保存被覆盖的原始指令以便正确恢复
  5. 偏移计算:跳转偏移量计算要准确,确保能正确跳转和返回

总结

Ring0下的Inline Hook技术通过直接修改内核函数代码实现挂钩,具有较高的隐蔽性。实现过程中需要注意内存保护机制、指令选择和偏移计算等关键点。这种技术可用于内核监控、安全防护等多种场景,但使用时需遵守相关法律法规。

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 的实际地址 2. 准备Hook代码 定义过滤函数 定义SSDT结构体 编写跳转代码 3. 计算跳转地址 确定Hook位置(示例中选择偏移3处) 计算返回地址(原始地址+偏移+5) 计算跳转偏移量 4. 修改内存保护 关闭页保护 执行Hook 恢复页保护 5. 卸载Hook 完整实现代码 关键注意事项 指令选择 :选择修改的指令不能是涉及全局变量或重定位地址的指令 内存保护 :修改内核代码前必须关闭CR0的WP位(写保护) 寄存器保存 :在跳转代码中要保存和恢复所有寄存器状态 原始指令保存 :必须保存被覆盖的原始指令以便正确恢复 偏移计算 :跳转偏移量计算要准确,确保能正确跳转和返回 总结 Ring0下的Inline Hook技术通过直接修改内核函数代码实现挂钩,具有较高的隐蔽性。实现过程中需要注意内存保护机制、指令选择和偏移计算等关键点。这种技术可用于内核监控、安全防护等多种场景,但使用时需遵守相关法律法规。