首页
社区
课程
招聘
[原创]PTE hook 可hook内核函数不pg
发表于: 2025-7-17 21:02 1643

[原创]PTE hook 可hook内核函数不pg

2025-7-17 21:02
1643

网上找了很多代码,但都不能直接用。然后缝缝补补就用了。原本是想通过ptebase计算pte地址的,这个方法可以通用很多版本,但还是通过CR3寄存器逐级计算页表项地址,因为我没弄明白ptebase  ,反正一直计算结果错误,有人能完善就好了。这个代码适用于win10 10240版本。


代码原理很简单,通过回调函数检查是否有人调用要hook的函数(这里是NtCreateFile),然后拦截调用进程并检查是否是系统进程如果是就隔离并hook全局内核函数,但假如是一般进程就隔离这个进程的页表然后hook。后面调用这个被hook函数都会跳转到hook函数执行一系列操作(可以修改内核函数参数或者返回值),最后执行正常内核函数。

整体执行流程

  1. 驱动加载(DriverEntry):初始化驱动,获取目标函数地址(如NtCreateFile),安装钩子。

  2. 钩子安装(pte_hook):隔离目标函数所在的内存页,修改其内容以实现跳转。

  3. 拦截执行:当目标函数被调用时,跳转到自定义处理函数(hook_nt_create_file)。

  4. 驱动卸载:恢复原始函数代码,释放资源。

函数作用详解

1. 辅助函数

  • read_cr3(): 读取CR3寄存器,获取当前进程的页目录物理地址。

  • invalidate_page(void* addr): 使用__invlpg指令刷新TLB缓存,确保内存更改生效。

  • physical_to_virtual(QWORD pa): 将物理地址转换为虚拟地址(通过MmGetVirtualForPhysical)。

  • virtual_to_physical(void* va): 将虚拟地址转换为物理地址(通过MmGetPhysicalAddress)。

  • get_pte_base(): 获取PTE基地址(通过遍历PML4表找到CR3对应的页表基址)。

  • get_pages_table(PTE_TABLE* Table): 根据线性地址(虚拟地址)获取其对应的各级页表项(PML4E, PDPTE, PDE, PTE)。

  • split_large_pages(PDE* in_pde, PDE* out_pde): 将大页(2MB)分割为4KB小页,便于单独修改目标页。

  • isolate_page_table(CR3 cr3_reg, void* replaceAlignAddr, PDE* splitPDE): 核心函数,为目标地址创建独立的页表结构,使其指向新的物理页(实现写时复制)。

  • isolate_pages(HANDLE pid, void* iso_address): 为指定进程的地址隔离页表(包括大页分割、禁用写保护、调用isolate_page_table等)。

  • mdl_write_memory(void* address, void* buffer, size_t size): 通过MDL(内存描述符列表)安全写入内核内存。

  • get_instruction_length(void* address): 获取指令长度,用于复制原始函数的前几条指令。

2. 钩子核心函数

  • pte_hook(HANDLE pid, void** oFuncAddr, void* targetFuncAddr): 安装钩子的主函数。

    1. 初始化:获取PTE基地址,分配跳板内存(trampoline)。

    2. 隔离目标函数所在页(isolate_pages)。

    3. 复制原始函数前N条指令到跳板(按指令边界复制)。

    4. 在跳板末尾添加跳回指令(跳转到原始函数后半部分)。

    5. 修改原始函数开头为跳转到目标钩子函数的指令。

    6. 保存原始字节和进程信息(用于卸载时恢复)。

3. 钩子示例函数

  • hook_nt_create_file(...): 自定义的NtCreateFile钩子函数,用于拦截文件打开操作。如果打开的是C:\test.txt,则返回拒绝访问。

4. 驱动入口和卸载函数

  • DriverEntry():

    1. 获取NtCreateFile的地址。

    2. 调用pte_hook安装钩子(目标进程为系统进程,PID=4)。

    3. 设置驱动卸载函数。

  • 驱动卸载函数(DriverUnload):

    1. 遍历所有钩子记录,恢复原始函数代码(使用保存的原始字节)。

    2. 释放跳板内存。

代码执行流程详细步骤

步骤1: 驱动加载

  1. DriverEntry被调用。

  2. 通过MmGetSystemRoutineAddress获取NtCreateFile地址。

  3. 调用pte_hook,传入目标函数地址(NtCreateFile)和自定义钩子函数(hook_nt_create_file)。

  4. pte_hook中:

    • 初始化全局变量(PTE基地址、跳板内存)。

    • 附加到目标进程(PID=4,即系统进程)。

    • 隔离目标函数所在页(isolate_pages)。

    • 复制原始指令到跳板,生成跳回指令。

    • 修改原始函数开头为跳转指令(跳转到自定义钩子函数)。

    • 保存原始字节和进程信息。

  5. 保存原始函数的指针(g_OriginNtCreateFile)用于后续调用。

步骤2: 拦截函数调用

  1. 当系统调用NtCreateFile时,执行流程跳转到hook_nt_create_file

  2. 在自定义钩子函数中:

    • 检查文件名,如果是C:\test.txt则返回STATUS_ACCESS_DENIED

    • 否则,调用原始函数(g_OriginNtCreateFile)。

步骤3: 驱动卸载

  1. 调用驱动卸载函数。

  2. 遍历所有钩子记录(m_info数组):

    • 附加到对应进程。

    • 使用MDL写入恢复原始函数代码。

    • 刷新TLB(invalidate_page)。

  3. 释放跳板内存(m_trampLine)。

关键修复点总结

  1. 大页分割处理:在isolate_page_table中,正确处理大页分割后的页表。

  2. 指令边界复制:在pte_hook中,按指令长度复制原始函数指令,避免截断指令导致崩溃。

  3. 跳回指令生成:在跳板末尾添加正确的跳回指令(跳回原始函数后半部分)。

  4. 安全刷新TLB:在isolate_page_table中,通过临时切换CR3刷新整个TLB。

§§相关问题§§:

  1. 在Windows内核中,如何防止PTE Hook被PatchGuard检测?

  2. 在多处理器(SMP)系统中,如何确保所有CPU核心的TLB都被刷新?

  3. 除了MDL写入,还有哪些安全的内核内存修改方法?

  4. 如何扩展此代码以支持非x64架构(如ARM64)?

现在只支持2mb大页分割,1gb不支持








传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

上传的附件:
收藏
免费 6
支持
分享
最新回复 (4)
雪    币: 188
活跃值: (1631)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
Thanks for share!
2025-7-21 11:00
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3



  pte hook 要隔离一个PML4E  ,一个PML4E控制512G空间 如果系统在你隔离PML4E后在这个512G空间中又分配了新的内存给系统使用 被隔离进程怎么同步?



最后于 2025-8-3 23:16 被杰森66988编辑 ,原因:
2025-8-3 23:14
0
雪    币: 33
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
杰森66988   pte hook 要隔离一个PML4E  ,一个PML4E控制512G空间 如果系统在你隔离PML4E后在这个512G空间中又分配了新的内存给 ...
这个可以先隔离。然后当自己用的时候再判断修改部分内容是否更新,更新就重新隔离,确保新内存分配的实时同步。
2025-8-7 17:48
0
雪    币: 33
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
毕竟这个只是用来一次性的函数钩子,可以考虑其他方法,比如:apc调用劫持(我也忘记什么名了,流程是修改apc执行后回去的地址指向自己写的地址,当执行指定地址的时候调用apc然后apc返回到hook的地方),回调hook,异常,ept等等,但大都是通过回调机制(我了解的技术,不深)
2025-8-7 17:58
0
游客
登录 | 注册 方可回帖
返回