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

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

2025-7-17 21:02
2924

网上找了很多代码,但都不能直接用。然后缝缝补补就用了。原本是想通过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不支持








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

上传的附件:
收藏
免费 7
支持
分享
最新回复 (15)
雪    币: 188
活跃值: (1691)
能力值: ( 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
1
雪    币: 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
雪    币: 235
活跃值: (190)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
杰森66988   pte hook 要隔离一个PML4E  ,一个PML4E控制512G空间 如果系统在你隔离PML4E后在这个512G空间中又分配了新的内存给 ...
大哥问题好犀利哦。怎么我没想过这个问题?
2025-12-28 11:24
0
雪    币: 235
活跃值: (190)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
mb_kbmkpcnd 这个可以先隔离。然后当自己用的时候再判断修改部分内容是否更新,更新就重新隔离,确保新内存分配的实时同步。
hook了后要每个页表比对吗?那太麻烦了吧。
如果监控分配内存的函数也不对,工作量太大了。
是不是我的PTE HOOK一直报错就是这个原因呢?
2025-12-28 11:27
0
雪    币: 235
活跃值: (190)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
mb_kbmkpcnd 这个可以先隔离。然后当自己用的时候再判断修改部分内容是否更新,更新就重新隔离,确保新内存分配的实时同步。

楼主大哥,这个代码我摘到我的工程中,一样的蓝屏。 可不可以帮我看看我的代码哪里有问题哦,谢谢。 

我的代码

2025-12-28 11:35
0
雪    币: 33
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
老年人学C._. 楼主大哥,这个代码我摘到我的工程中,一样的蓝屏。 可不可以帮我看看我的代码哪里有问题哦,谢谢。 我的代码
每个版本内核数据结构排布都有可能有问题,你运行一段时间就报错,你确定获取的结构没问题?先通过windbg确定结构没问题。我这个你也运行报错的原因大概是没替换内核数据结构?毕竟我这个是适用于win10 10240版本。
2025-12-28 21:31
0
雪    币: 33
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
老年人学C._. 楼主大哥,这个代码我摘到我的工程中,一样的蓝屏。 可不可以帮我看看我的代码哪里有问题哦,谢谢。 我的代码
先暂定你代码有问题跟你电脑环境无关,先检测你内核结构是否符合版本,再检测你分配页表和分割页表操作没问题。你现在最好是给你的代码添加调试信息函数来验证你的操作是否正确再怀疑是否是电脑环境的问题
2025-12-28 21:53
0
雪    币: 33
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
老年人学C._. 楼主大哥,这个代码我摘到我的工程中,一样的蓝屏。 可不可以帮我看看我的代码哪里有问题哦,谢谢。 我的代码
报错误码0x4E,0x1A,0x3B都大概率是分配页表出问题的
2025-12-28 21:54
0
雪    币: 235
活跃值: (190)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
mb_kbmkpcnd 先暂定你代码有问题跟你电脑环境无关,先检测你内核结构是否符合版本,再检测你分配页表和分割页表操作没问题。你现在最好是给你的代码添加调试信息函数来验证你的操作是否正确再怀疑是否是电脑环境的问题
好的好的,谢谢大哥提供的思路
2026-1-1 09:24
0
雪    币: 235
活跃值: (190)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
mb_kbmkpcnd 每个版本内核数据结构排布都有可能有问题,你运行一段时间就报错,你确定获取的结构没问题?先通过windbg确定结构没问题。我这个你也运行报错的原因大概是没替换内核数据结构?毕竟我这个是适用于win10 ...

大哥,我不理解你的代码中这几句是什么意思

	// 创建新的PML4E
	PML4E* new_pml4 = (PML4E*)VaPdpt; // 重用VaPdpt内存
	PML4E* old_pml4 = (PML4E*)pml4_va;
	RtlCopyMemory(new_pml4, old_pml4, PAGE_SIZE);

	new_pml4[pml4e_index].AsUlonglong = 0;
	new_pml4[pml4e_index].Present = 1;
	new_pml4[pml4e_index].ReadWrite = 1;
	new_pml4[pml4e_index].PageFrameNumber = virtual_to_physical(VaPdpt) >> 12;
	
// 保存当前CR3
	QWORD old_cr3 = __readcr3();

	// 写入新CR3
	QWORD new_cr3 = virtual_to_physical(new_pml4);
	__writecr3(new_cr3);

	// 立即恢复原CR3
	__writecr3(old_cr3);

为什么要重建PML4这个表呢,不是说好了PTE HOOK只修改PML4中的pdpte表项吗?


还有重建了写入新的CR3后,立即要恢复到原来的CR3呢?


我执行到这里就双重错误了。

2026-1-1 17:19
0
雪    币: 33
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
老年人学C._. 大哥,我不理解你的代码中这几句是什么意思 // 创建新的PML4E PML4E* new_pml4 = (PML4E*)Va ...
代码中重建PML4的原因是为了实现进程内页表隔离过PG,双重错误可能是 PML4E* new_pml4 = (PML4E*)VaPdpt; 中的VaPdpt这个问题
1天前
0
雪    币: 33
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
写错了,同一个物理页 VaPdpt 既作为PML4,又作为PDPTE发生了自引用,分配内存写错了
1天前
0
雪    币: 33
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16
老年人学C._. 大哥,我不理解你的代码中这几句是什么意思 // 创建新的PML4E PML4E* new_pml4 = (PML4E*)Va ...
我是虚拟机测试的,估计报错被忽略了
1天前
0
游客
登录 | 注册 方可回帖
返回