首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
2
1
[分享]HOOK SSDT NtReadVirtualMemory实操以及遇到的问题
发表于: 2026-2-23 20:11
2614
[分享]HOOK SSDT NtReadVirtualMemory实操以及遇到的问题
mb_binusgki
2026-2-23 20:11
2614
# 前言 SSDT HOOK已经是非常老的技术了, 但是作为入门的新手还是要走一下流程了解原理, 本次实验的对象为NtReadVirtualMemory函数, 平台为win10x64, 过程中遇到了不少问题, 也换了几种方式实现, 但总体上没有脱离SSDT HOOK, 没有使用inline Hook. # 第一步: 获取NtReadVirtualMemory函数 ``` //获取NtReadVirtualMemory函数 UINT_PTR GetNtReadVirtualMemory() { //获取内核基址 UINT_PTR NtoskrnlBase = GetNtoskrnlBase(); //取NtReadVirtualMemory函数 UINT_PTR NtReadVirtualMemory = NtoskrnlBase + 0x622A80; return NtReadVirtualMemory; } //========调度方法========= //获取NtReadVirtualMemory地址 SysNtReadVirtualMemory = GetNtReadVirtualMemory(); if (!SysNtReadVirtualMemory) { PZY_PRINT("获取NtReadVirtualMemory地址失败"); return FALSE; } ``` 我用的偷懒的方法, 直接根据内核偏移进行了硬编码定位(这种方法版本并不通用), 后续可改为特征值方式查找 # 第二步: 获取SSDT表 ``` //获取SSDT PKSERVICE_DESCRIPTOR_TABLE GetSSDT() { //获取内核基址 UINT64 NtoskrnlBase = GetNtoskrnlBase(); //取KiSystemServiceRepeat函数 UINT64 KiSystemServiceRepeat = NtoskrnlBase + 0x1D2B94; //计算偏移 INT32 offset = *(INT32*)(KiSystemServiceRepeat + 3); UINT64 nextCode = KiSystemServiceRepeat + 7; return nextCode + offset; } //========调度方法========= //获取SSDT PKSERVICE_DESCRIPTOR_TABLE ssdt = GetSSDT(); if (!ssdt) { PZY_PRINT("获取SSDT失败"); return FALSE; } ``` ssdt表全称为系统服务表: system_service_descriptor_table, 在`<ntifs.h>`中就有`PKSERVICE_DESCRIPTOR_TABLE`类型可以直接使用. 获取ssdt方式我是通过取KiSystemServiceRepeat函数在此基础上计算偏移得出, 因为KiSystemServiceRepeat就是系统服务从r3到r0必经的函数, 所以跟踪此函数必然可得出ssdt的位置. # 第三步: 在SSDT中寻找NtReadVirtualMemory函数 ``` //在SSDT中寻找指定函数 BOOLEAN FindNtFunctionInSSDT( _In_ PKSERVICE_DESCRIPTOR_TABLE KeSsdt, _In_ PVOID TargetNtFunction, _Out_ PULONG FoundIndex, _Out_ PULONG_PTR* FoundEntryAddress ) { //检查参数是否合格 if (!KeSsdt || !TargetNtFunction) return FALSE; //取出服务表基址 PUCHAR serviceTableBase = (PUCHAR)KeSsdt->ServiceTableBase; //取出服务表数量 ULONG numberOfServices = KeSsdt->NumberOfServices; //循环遍历 for (ULONG index = 0; index < numberOfServices; index++) {//解密算法参考KiSystemServiceRepeat // movsxd r11, dword ptr [r10+rax*4] INT32 entry = *(INT32*)(serviceTableBase + index * sizeof(INT32)); // sar r11, 4 例子:0x01fde701 -> 01fde70 entry >>= 4; //add r10, r11 PUCHAR func = serviceTableBase + entry; //判断 if ((PVOID)func == TargetNtFunction) { //写入index if (FoundIndex) *FoundIndex = index; //写入fun地址 if (FoundEntryAddress) *FoundEntryAddress = (PVOID)(serviceTableBase + index * sizeof(INT32)); return TRUE; } } return FALSE; } //========调度方法========= //在SSDT中寻找指定函数 ULONG index; PULONG entry; BOOLEAN FindNtFunctionInSSDTState = FindNtFunctionInSSDT(ssdt, SysNtReadVirtualMemory, &index, &entry); if (!FindNtFunctionInSSDTState) { PZY_PRINT("在SSDT中寻找指定函数失败"); return FALSE; } ``` 这里特别说下ssdt与对应函数的映射算法: 首先ssdt是一个结构存储着ssdt表的信息, 里面的常用字段一般为: ServiceTableBase 这就是ssdt列表的地址, NumberOfServices 这个就是ssdt列表中有多少个服务对象 也就是多少个系统函数, 其他字段暂时没有用到. ``` KMDFDriver2!_KSERVICE_DESCRIPTOR_TABLE +0x000 ServiceTableBase : Ptr64 Uint8B +0x008 ServiceCounterTableBase : Ptr64 Uint4B +0x010 NumberOfServices : Uint8B +0x018 ParamTableBase : Ptr64 Char ``` 然后继续说, ssdt的每个服务是4个字节, 并不是8个字节, 所以取出的服务也并不是直接可以使用的地址, 需要一定的解密过程, 算法为: ServiceTableBase + [ServiceTableBase + 服务号*4] >> 4, 举个例子: ServiceTableBase: 0xfffff8072d8ccc10 服务号: 0x3f [ServiceTableBase + 0x3f*4]=46743f00 那么服务方法就是: 0xfffff8072d8ccc10+[ServiceTableBase + 0x3f*4]>>4 =0xfffff8072d8ccc10+46743f00>>4 =0xfffff8072d8ccc10+46743f0 验证一下: ``` 1: kd> u 0xfffff807`2d8ccc10+46743f0 KMDFDriver2!MyNtReadVirtualMemory [D:\pzy\个人练习\C++\KMDF Driver2\KMDF Driver2\MyFunction.asm @ 9]: fffff807`31f41000 4883ec28 sub rsp,28h fffff807`31f41004 ff157e710000 call qword ptr [KMDFDriver2!SysNtReadVirtualMemory (fffff807`31f48188)] fffff807`31f4100a 4883c428 add rsp,28h fffff807`31f4100e c3 ret ``` # 第四步: 关闭写保护与开启保护 ``` //关闭写保护 VOID DisableWP() { __writecr0(__readcr0() & ~0x10000); } //开启写保护 VOID EnableWP() { __writecr0(__readcr0() | 0x10000); } //========调度方法========= //关闭写保护 DisableWP(); //开启写保护 EnableWP(); ``` 这里的开关读写保护__writecr0方法也是`<ntifs.h>`官方提供好的 直接用即可 就不用手搓汇编了 # 第五步: 保存原NtReadVirtualMemory在SSDT表的值并写入写入HOOK函数 ``` //========调度方法========= //保存原NtReadVirtualMemory在SSDT表的值 oldEntry = *entry; oldIndex = index; //写入HOOK INT32 newEntry = (INT32)((PUCHAR)MyNtReadVirtualMemory - ssdt->ServiceTableBase); // SSDT 要求 << 4 newEntry <<= 4; // 写回 4 字节 *entry = newEntry; ``` 这里就是保存原ssdt的值还原的时候要用, 然后将自己的方法写入ssdt, 算法也是逆向还原为ssdt服务的算法, 就不细说了 # 第六步: HOOK函数的构造 这一步也是坑最多的一步, 并且有些问题我也没解决 原本刚开始我是直接写C的代码的, 但是在排查问题的过程中不清晰, 所以改写汇编了(我要确保栈不出问题) 于是拥有x32编程经验的我顺手就开始写__asm{}的代码, 然后发现编译器通不过, 一查发现: 原来x64不给用, 就只能乖乖写.asm文件 ``` OPTION CASEMAP:NONE EXTERN SysNtReadVirtualMemory:PROC EXTERN SysMiReadWriteVirtualMemory:PROC EXTERN HookNtReadVirtualMemoryXx:PROC PUBLIC MyNtReadVirtualMemory .code MyNtReadVirtualMemory PROC ;自己的代码 sub rsp, 28h call HookNtReadVirtualMemoryXx add rsp, 28h ;尝试方法一 ;NtReadVirtualMemory原来的代码 sub rsp, 38h mov rax, [rsp + 60h] mov dword ptr [rsp + 28h], 10h mov [rsp + 20h], rax call qword ptr [SysMiReadWriteVirtualMemory] add rsp, 38h ret ;尝试方法二 ;直接调用NtReadVirtualMemory ;sub rsp, 28h ;call qword ptr [SysNtReadVirtualMemory] ;add rsp, 28h ;ret MyNtReadVirtualMemory ENDP END ``` 这里简单说下`.asm`的语法: `EXTERN` 就是要从外部导入的函数 `PUBLIC` 就是要导出的方法 `.code` 就是代码段 `.data` 就是数据段 但我没用到 `OPTION CASEMAP:NONE`是大小写关闭,防止出错 `END` 最后记得协商结束符号 `SysNtReadVirtualMemory` 这是原系统NtReadVirtualMemory方法, 为了不与系统函数重名我加了sys前缀 `SysMiReadWriteVirtualMemory` 这个也是原系统MiReadWriteVirtualMemory方法 `HookNtReadVirtualMemoryXx` 这个是我自己的方法, 我就简单的输出了日志用于观察 **然后导出的方法在C中声明即可使用** ``` //汇编声明 extern NTSTATUS MyNtReadVirtualMemory( HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, SIZE_T Size, PSIZE_T NumberOfBytesRead ); ``` **nt!NtReadVirtualMemory** ``` nt!NtReadVirtualMemory: fffff803`65ce2a80 4883ec38 sub rsp,38h fffff803`65ce2a84 488b442460 mov rax,qword ptr [rsp+60h] fffff803`65ce2a89 c744242810000000mov 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 ``` # 第七步: HOOK函数的构造问题与思考 我的想法是在调用完自己的方法后直接原封不动执行`NtReadVirtualMemory`的代码, 确实我就是这么写的, 刚开始栈不对或者参数没传对会造成蓝屏, 但换成汇编后完全没这些担心了, 我能保证代码完全和系统的一致, 并且跟踪了调用过程也是一致的  修改后的ssdt: ``` 1: kd> u 0xfffff801`08ec8c10+46583f0 KMDFDriver2!MyNtReadVirtualMemory [D:\pzy\个人练习\C++\KMDF Driver2\KMDF Driver2\MyFunction.asm @ 9]: fffff801`0d521000 4883ec28 sub rsp,28h fffff801`0d521004 e8b7170000 call KMDFDriver2!HookNtReadVirtualMemoryXx (fffff801`0d5227c0) fffff801`0d521009 4883c428 add rsp,28h fffff801`0d52100d 4883ec38 sub rsp,38h fffff801`0d521011 488b442460 mov rax,qword ptr [rsp+60h] fffff801`0d521016 c744242810000000 mov dword ptr [rsp+28h],10h fffff801`0d52101e 4889442420 mov qword ptr [rsp+20h],rax fffff801`0d521023 ff156f710000 call qword ptr [KMDFDriver2!SysMiReadWriteVirtualMemory (fffff801`0d528198)] ``` 修改后可以看到日志效果: 凡是读取内存经过NtReadVirtualMemory都有记录  但是此时打开任何应用都会有c0000005错误提示:  我排除了栈不平 栈取值错误以及寄存器不对的可能(将HookNtReadVirtualMemoryXx注释掉就完全和系统函数一致了, 但错误还是存在), 唯一有可能出错的地方就在于系统检查了返回地址, 判断了返回地址是否在内核范围内, 于是我做了测试将直接调用`call qword ptr [SysNtReadVirtualMemory]` 这样如果检查的是NtReadVirtualMemory中的MiReadWriteVirtualMemory那么我就能通过 但结果还是失败, 那就只有一种可能: 校验是在`KiSystemServiceRepeat`中进行的, 后续我跟踪了`MiReadWriteVirtualMemory`证实了`0xc0000005`错误确实在`MiReadWriteVirtualMemory`中发生:   但判断实在`KiSystemServiceRepeat`中 # 第八: 总结 这次ssdt整体流程走下来了, 但是在最后一步没有成功, 我后续又想了一阵 觉得暂时没有必要深究 毕竟这是人尽皆知的技术 微软严防也是正常, 而且这个技术一用就是名牌, 相当于公开裸奔所以价值不大, 但是没搞出来还是不太爽, 有无大神帮忙解答...
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于
2026-3-17 17:29 被mb_binusgki编辑 ,原因: 补充内容
#调试逆向
#系统底层
#问题讨论
#其他内容
收藏
・
2
点赞
・
1
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
我的小拇指啊
这个讨论对我很有帮助,谢谢!
2026-2-24 11:28
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
3
)
逆向爱好者
雪 币:
3536
活跃值:
(7804)
能力值:
( LV3,RANK:20 )
在线值:
发帖
37
回帖
280
粉丝
24
关注
私信
逆向爱好者
2
楼
图片的是c0000005,
2026-2-24 09:44
0
mb_binusgki
雪 币:
390
能力值:
( LV1,RANK:0 )
在线值:
发帖
29
回帖
6
粉丝
22
关注
私信
mb_binusgki
3
楼
逆向爱好者
图片的是c0000005,
是c0000005 我笔误写错了 多谢提醒已修改
2026-2-24 12:31
0
mb_gmajzygm
雪 币:
20
能力值:
( LV1,RANK:0 )
在线值:
发帖
4
回帖
37
粉丝
1
关注
私信
mb_gmajzygm
4
楼
放弃ssdthook吧win10
2026-6-6 17:14
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
mb_binusgki
29
发帖
6
回帖
0
RANK
关注
私信
他的文章
[分享]注册表回调内核通信
379
[分享]简单编译器实现
1869
[分享]Lexer简单词法解析器入门
1233
[分享]vm解释器入门2
1288
[分享]vm解释器入门
1587
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部