首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
1
1
[分享]InlineHook NtReadVirtualMemory实现过程
发表于: 2026-2-24 13:08
2180
[分享]InlineHook NtReadVirtualMemory实现过程
mb_binusgki
2026-2-24 13:08
2180
# 前言 昨天做了ssdthook搞到挺晚最终还是出现一些bug导致程序无法正常打开, 于是今天修改思路使用inlineHook实现, inlineHook相比ssdthook是更加实用的技术, 也能做到更加隐蔽, 比ssdthook上限高了很多, 用起来也舒服. 大体思路为: 在`NtReadVirtualMemory`中的`call nt!MiReadWriteVirtualMemory`下手, 将其跳转到自己的方法, 实现自己的功能后在原封不动的`jmp`到`MiReadWriteVirtualMemory`中, 提前透露说下今天的inlinehook相比于ssdthook非常顺利, 直接开始 # 第一步 获取NtReadVirtualMemory和MiReadWriteVirtualMemory地址 和ssdthook做法一模一样. 直接上代码: ``` //获取NtReadVirtualMemory函数 UINT_PTR GetNtReadVirtualMemory() { //获取内核基址 UINT_PTR NtoskrnlBase = GetNtoskrnlBase(); //取NtReadVirtualMemory函数 UINT_PTR NtReadVirtualMemory = NtoskrnlBase + 0x622A80; return NtReadVirtualMemory; } //获取MiReadWriteVirtualMemory函数 UINT_PTR GetMiReadWriteVirtualMemory() { //获取内核基址 UINT_PTR NtoskrnlBase = GetNtoskrnlBase(); //取MiReadWriteVirtualMemory函数 UINT_PTR MiReadWriteVirtualMemory = NtoskrnlBase + 0x622AB0; return MiReadWriteVirtualMemory; } //===============调度方法================= //赋值MiReadWriteVirtualMemory SysMiReadWriteVirtualMemory = GetMiReadWriteVirtualMemory(); if (!SysMiReadWriteVirtualMemory) { PZY_PRINT("获取MiReadWriteVirtualMemory地址失败"); return FALSE; } //获取NtReadVirtualMemory地址 SysNtReadVirtualMemory = GetNtReadVirtualMemory(); if (!SysNtReadVirtualMemory) { PZY_PRINT("获取NtReadVirtualMemory地址失败"); return FALSE; } ``` # 第二步: inlineHOOK初始化过程 NtReadVirtualMemory原汇编代码: ``` nt!NtReadVirtualMemory: fffff803`65ce2a80 4883ec38 sub rsp,38h fffff803`65ce2a84 488b442460 mov rax,qword ptr [rsp+60h] fffff803`65ce2a89 c744242810000000 mov dword ptr [rsp+28h],10h fffff803`65ce2a91 4889442420 mov qword ptr [rsp+20h],rax fffff803`65ce2a96 e815000000 call nt!MiReadWriteVirtualMemory (fffff803`65ce2ab0) fffff803`65ce2a9b 4883c438 add rsp,38h fffff803`65ce2a9f c3 ret ``` 可以计算得出, 从开头到`call nt!MiReadWriteVirtualMemory`距离16字节 ``` // call 指令偏移 g_CallAddress = (PUCHAR)SysNtReadVirtualMemory + 0x16; ``` 保存原始字节以便还原 ``` // 保存原始 5 字节 RtlCopyMemory(g_OriginalBytes, g_CallAddress, 5); ``` 计算新的偏移 ``` // 计算新的相对偏移 LONG64 diff = (LONG64)MyNtReadVirtualMemory - ((LONG64)g_CallAddress + 5); ``` 插入指令是32位的 加上判断防止出错 ``` //判断是否超出范围 if (diff > 0x7fffffffLL || diff < -0x80000000LL) return STATUS_NOT_SUPPORTED; // 超出 rel32 范围 ``` 制作新偏移 ``` LONG disp32 = (LONG)diff; UCHAR patch[5]; patch[0] = 0xE8; // call *(PLONG)&patch[1] = disp32; // 写入新偏移 ``` 开关保护写入偏移 ``` //关闭写保护 DisableWP(); RtlCopyMemory(g_CallAddress, patch, 5); //开启写保护 EnableWP(); ``` # 第三步: 构造自己的MyNtReadVirtualMemory MyNtReadVirtualMemory也是在ssdt写的基础上修改的, 相比之前添加了保存通用寄存器这步, 以便确保寄存器的正确性 ``` OPTION CASEMAP:NONE EXTERN SysNtReadVirtualMemory:PROC EXTERN SysMiReadWriteVirtualMemory:PROC EXTERN HookNtReadVirtualMemoryXx:PROC PUBLIC MyNtReadVirtualMemory .code MyNtReadVirtualMemory PROC ;自己的代码 ; -------- 保存通用寄存器 -------- push rcx push rdx push r8 push r9 push r10 push r11 sub rsp, 28h call HookNtReadVirtualMemoryXx add rsp, 28h ; -------- 还原寄存器(逆序)-------- pop r11 pop r10 pop r9 pop r8 pop rdx pop rcx ;调用原MiReadWriteVirtualMemory jmp qword ptr [SysMiReadWriteVirtualMemory] MyNtReadVirtualMemory ENDP END ``` `HookNtReadVirtualMemoryXx`这个方法就是自己的算法, 框架写好后算法都可以放上加, 可以记一下默认NtReadVirtualMemory的参数, 直接用形参取即可 ``` //原系统函数NtReadVirtualMemory typedef NTSTATUS(NTAPI* NtReadVirtualMemory)( HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T BufferSize, PSIZE_T NumberOfBytesRead ); ``` 大概是这样, 但好像有些版本是六个参数, 我就取了五个参数, 因为栈值都是8字节一个参数所以并不存在对齐问题, 还是很舒服的 其实我就上了个日志的功能, 大致就是取了第一个参数就是句柄, 根据句柄获取eprocess, 然后继续获取pid和程序名称, 其实推荐用pid和handle获取就好, 用文件名获取会消耗一些内存 第五步: 效果展示及总结 依旧是拿ce做演示, 可以正常获取并且顺利打开了程序没有昨天的内存访问失败错误  当打开进程列表也能看到ce在访存的记录, 一切正常  后续要添加过滤或者监控都非常方便, 只需要在r3传入pid, 就可以在r0做相应处理 最后就是对于根据句柄获取eprocess的`ObReferenceObjectByHandle`函数有个坑, 就是我们默认都是要进程句柄, 所以传入的参数都是`PsProcessType` 相当于一个过滤, 如果不是进程句柄则返回失败, 失败了切记要直接返回, 之前我就没注意这个点, 即便是返回失败了还继续用空的eprocess获取pid等后续操作, 换来的自然就是疯狂蓝屏 然后又花了时间排查.. 但是总的来说 inlinehook确实非常好用 简单效果强, 并且微软也确实没有对`NtReadVirtualMemory`的字节码做扫描, 毕竟对`NtReadVirtualMemory`访问频率高的函数定时扫描那对性能损耗非常高. 对于对句柄降权方式的保护无非就是看谁hook的函数更加底层, 其实也可以直接通过映射物理地址等方式直接重塑`NtReadVirtualMemory`进行访存, 但是对于`CE` `OD`这些工具就不是很友好, 要在r3层进行拦截分流到自己的接口, 并且并不止`NtReadVirtualMemory`一个函数, 通常保护软件会拦截所有与内存 句柄 等相关的函数, 如果全部重塑实在不是好的办法...
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
最后于
2026-3-18 10:15 被mb_binusgki编辑 ,原因: 补充内容
#调试逆向
#系统底层
#问题讨论
#其他内容
收藏
・
1
点赞
・
1
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
我的小拇指啊
为你点赞!
2026-2-26 15:06
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
2
)
Saileaxh
雪 币:
3110
活跃值:
(2919)
能力值:
( LV6,RANK:80 )
在线值:
发帖
18
回帖
49
粉丝
108
关注
私信
Saileaxh
1
2
楼
PatchGuard会保护的
2026-2-25 10:02
0
mb_binusgki
雪 币:
390
能力值:
( LV1,RANK:0 )
在线值:
发帖
29
回帖
6
粉丝
22
关注
私信
mb_binusgki
3
楼
之前用的ssdthook似乎被PatchGuard保护了, 但直接在函数内部inlinehook似乎是成功了, 但是我是在测试模式免签名环境下调试的, 不懂正常环境是否也会被PatchGuard
2026-2-26 10:59
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
mb_binusgki
29
发帖
6
回帖
0
RANK
关注
私信
他的文章
[分享]注册表回调内核通信
379
[分享]简单编译器实现
1869
[分享]Lexer简单词法解析器入门
1233
[分享]vm解释器入门2
1288
[分享]vm解释器入门
1587
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部