首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
逆向工程
发新帖
0
1
[分享]注册表回调内核通信
发表于: 2026-8-8 11:34
376
[分享]注册表回调内核通信
mb_binusgki
2026-8-8 11:34
376
# 注册表回调内核通信 R3 与 R0 通信最常规的方式是 `DeviceIoControl`,但驱动必须 `IoCreateDevice` 创建设备对象、暴露符号链接(`\\.\xxx`),导致驱动可被遍历发现、通信特征固定。而注册表回调通信正是打破传统通信的方法之一,其特点是:**不创建设备,用注册表回调做通道**。 ## 核心原理 Windows 提供 `CmRegisterCallbackEx` 注册回调,此后系统每一次注册表操作都会先经过该回调。回调可以查看操作内容、返回状态码决定放行或阻止。 利用这一点,我们约定: 1. R3 程序以 `REG_BINARY` 类型调用 `RegSetValueExW` 写入一个结构体,触发回调 2. 回调从待写入数据中解析出结构体,执行对应的内核操作 3. 回调返回 `STATUS_UNSUCCESSFUL`,阻止该数据真正落盘 4. R3 收到写入失败,但命令已执行、结果已到内存 数据全程在内存传递,不落盘、不建设备。 ## 协议:CrComm.h ```c typedef struct _DriverParameter { ULONG64 VCode; // 校验码:必须为 0 才触发处理 ULONG64 ControlCode; // 命令号 PVOID InputBuffer; // 内核读这里(用户态内存) PVOID OutputBuffer; // 内核写这里(用户态内存) } DriverParameter, * PDriverParameter; typedef struct _RWMEMDATA { ULONG64 ProcessId; // 目标进程 PID ULONG64 Address; // 目标虚拟地址 ULONG64 Size; // 读写字节数 ULONG64 Buffer; // 当前进程缓冲区地址 ULONG64 Type; // 0=读 1=写 } RWMEMDATA, * PRWMEMDATA; ``` `VCode` 相当于暗号,非 0 时回调直接放行,不影响系统正常写注册表。`InputBuffer`/`OutputBuffer` 可以直接传用户态指针,因为回调运行在调用者进程上下文,内核视角下这些地址可直接读写。 命令码定义: ```c #define COMM_ApiReadandwrite 0x0 // 内存读写 #define COMM_Judgmentloading 0x11 // 驱动加载检测 ``` ## 内核侧:回调与命令分发 ### 注册与卸载 ```c // DriverEntry 中注册回调 CmRegisterCallbackEx( (PEX_CALLBACK_FUNCTION)RegistryCallback, &altitude, &altitude, &RegistryCallback, &g_RegCookie, NULL); // DriverUnload 中注销回调 CmUnRegisterCallback(g_RegCookie); ``` `altitude`("429628")是回调排序高度,系统用来决定多个回调的调用顺序。卸载时必须注销,否则回调指向已释放代码 → 蓝屏。 ### 回调分发 ```c NTSTATUS RegistryCallback(PVOID a1, PVOID a2, PVOID a3) { REG_NOTIFY_CLASS Notify = (REG_NOTIFY_CLASS)(ULONG64)a2; switch (Notify) { case RegNtSetValueKey: { PREG_SET_VALUE_KEY_INFORMATION RegInfo = (PREG_SET_VALUE_KEY_INFORMATION)a3; // 第一道过滤:必须是 REG_BINARY 且大小等于 DriverParameter if (RegInfo->Type != REG_BINARY || RegInfo->DataSize != sizeof(DriverParameter)) break; // 不是我们的数据,放行 PDriverParameter Parameter = (PDriverParameter)RegInfo->Data; // 第二道过滤:VCode 必须为 0 if (Parameter->VCode != 0) return STATUS_SUCCESS; NTSTATUS Status = STATUS_UNSUCCESSFUL; switch (Parameter->ControlCode) { case COMM_Judgmentloading: // 0x11 加载检测 *(ULONG64*)Parameter->InputBuffer = 6; Status = STATUS_SUCCESS; break; case COMM_ApiReadandwrite: // 0x0 内存读写 { PRWMEMDATA Data = (PRWMEMDATA)Parameter->InputBuffer; HANDLE ProcessId = (HANDLE)Data->ProcessId; PVOID Address = (PVOID)Data->Address; PVOID Buffer = (PVOID)Data->Buffer; ULONG Size = (ULONG)Data->Size; UCHAR Type = (UCHAR)Data->Type; USHORT Ret = RaWMem(ProcessId, Address, Size, Buffer, Type); RtlCopyMemory(Parameter->OutputBuffer, &Ret, sizeof(USHORT)); Status = STATUS_SUCCESS; break; } } // 返回值反转:成功 → 阻止落盘,失败 → 放行 return NT_SUCCESS(Status) ? STATUS_UNSUCCESSFUL : STATUS_SUCCESS; } } return STATUS_SUCCESS; } ``` **返回值反转**是核心: - 命令处理**成功** → 返回 `STATUS_UNSUCCESSFUL` → 写入被阻止,数据不落盘 - 命令处理**失败** → 返回 `STATUS_SUCCESS` → 放行正常写入 所以 `RegSetValueExW` 返回 `0x1f` 不代表失败,恰恰说明命令已被内核成功处理。 ### 跨进程内存读写 ```c static USHORT RaWMem(HANDLE ProcessId, PVOID Address, ULONG Size, PVOID Buffer, UCHAR Type) { PEPROCESS TargetProcess = NULL; NTSTATUS Status = PsLookupProcessByProcessId(ProcessId, &TargetProcess); if (!NT_SUCCESS(Status)) return FAILURE; PEPROCESS CurrentProcess = IoGetCurrentProcess(); SIZE_T CopiedBytes = 0; if (Type == 0) // 读:目标进程 → 当前进程 Status = MmCopyVirtualMemory(TargetProcess, Address, CurrentProcess, Buffer, Size, KernelMode, &CopiedBytes); else // 写:当前进程 → 目标进程 Status = MmCopyVirtualMemory(CurrentProcess, Buffer, TargetProcess, Address, Size, KernelMode, &CopiedBytes); ObDereferenceObject(TargetProcess); // 释放引用计数 return NT_SUCCESS(Status) ? SUCCESS : FAILURE; } ``` `PsLookupProcessByProcessId` 将 PID 转为 `PEPROCESS`,`IoGetCurrentProcess` 取当前进程,`MmCopyVirtualMemory` 完成跨进程拷贝,最后 `ObDereferenceObject` 释放引用(否则内核对象泄漏)。 ## 用户侧:发送命令 ```cpp static BOOL SendCommand(PDriverParameter Param) { HKEY hKey; if (RegOpenKeyExW(HKEY_CURRENT_USER, REG_TEST_PATH, 0, KEY_SET_VALUE, &hKey) != ERROR_SUCCESS) return FALSE; RegSetValueExW(hKey, L"TestValue", 0, REG_BINARY, (const BYTE*)Param, sizeof(DriverParameter)); RegCloseKey(hKey); return TRUE; // 不检查 RegSetValueExW 返回值 } ``` 路径选 `HKCU\Software\Microsoft\Windows\CurrentVersion\Run` 只是为了让操作看起来更常见,实际上回调拦截所有注册表写,路径不限。`SendCommand` 只负责投递,结果通过 `InputBuffer`/`OutputBuffer` 的内容判断。 ### 两个测试用例 ```cpp // 测试1:驱动加载检测 TestJudgmentloading(); // 构造:VCode=0, ControlCode=0x11, InputBuffer=&Result // 内核写 6 到 Result,用户检查 Result==6 确认驱动在位 // 测试2:内存读写 TestApiReadAndWrite(); // 构造:RWMEMDATA 描述读自身进程 TestData 变量 // 内核 MmCopyVirtualMemory 拷贝到 ReadBuffer // 用户检查 Ret==SUCCESS && ReadBuffer==0x12345678 ``` ## 完整时序 ``` R3 R0 1. 填充 DriverParameter 2. RegOpenKeyExW 3. RegSetValueExW(REG_BINARY) ────▶ 4. 触发回调 5. 校验 Type/Size/VCode 6. switch 分发执行 7. 成功 → 返回 STATUS_UNSUCCESSFUL 4'. 返回 0x1f ◀─────────────────────── (写入被阻止) 5'. 检查 InputBuffer/OutputBuffer 结果 ``` ## 这样设计隐蔽在哪里 | 对比项 | IOCTL 通信 | 注册表回调通信 | |--------|-----------|---------------| | 设备对象 | 有,可被遍历 | 无 | | 符号链接 | 有,可被打开 | 无 | | 通信特征 | DeviceIoControl + IRP | 普通注册表写 | | 数据残留 | 可能留日志/缓存 | 不落盘,无痕迹 | | 干扰系统 | 专属通道 | 非本方命令全放行 | ## 注意事项 - 卸载驱动必须 `CmUnRegisterCallback(g_RegCookie)`,否则回调残留 → 蓝屏 - `PsLookupProcessByProcessId` 后必须 `ObDereferenceObject`,否则内核对象泄漏 - 该技术为双刃剑,仅用于学习研究,切勿用于恶意用途 # 缺点总结 注册表回调通信虽然隐蔽性不错,但缺点同样明显,注册表拦截本身确实会成为检测特征。 其一是频率异常,正常程序写注册表通常是一次性或低频,而通过注册表回调方式通信会出现注册表写操作频率异常成为其的特征。 其二是数据特征,RegSetValueExW 写入的数据是 sizeof(DriverParameter) 的 REG_BINARY,而普通程序写 REG_BINARY 很少见(大多是 REG_SZ/REG_DWORD),即使写 REG_BINARY,大小也几乎不可能是固定的 32 字节(sizeof(DriverParameter) 的精确值)内容看起来像指针值(InputBuffer/OutputBuffer 是地址),正常注册表值不会有进程地址 其三是返回值异常(0x1f),RegSetValueExW 成功写入应该返回 ERROR_SUCCESS(0),但采用注册表回调通信的程序每次通信都会返回 STATUS_UNSUCCESSFUL → 用户态拿到 0x1f (ERROR_ACCESS_DENIED)。一个正常程序反复写注册表,每次都被拒绝但还在继续写——行为上就很可疑。 # 五、改进思路 如果要在现有项目基础上改进,可以考虑: 数据加密+随机填充:DriverParameter 内容加密,VCode 不在内存中以明文 0 出现 随机注册表路径:不在固定路径写,每次通信随机选一个合法路径 低频通信 + 时间抖动:避免连续高频写注册表,加入随机延迟 混合通信:低频命令用注册表(如心跳检测),大量数据通过共享内存 换 altitude:找一个合法的、属于某个已卸载产品的 altitude 来冒充 > ⚠️ 本文仅用于学习与学术研究目的。
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
收藏
・
0
点赞
・
1
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
阿辉阿
你的帖子非常有用,感谢分享!
2026-8-9 17:13
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
0
)
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
mb_binusgki
29
发帖
6
回帖
0
RANK
关注
私信
他的文章
[分享]注册表回调内核通信
376
[分享]简单编译器实现
1867
[分享]Lexer简单词法解析器入门
1231
[分享]vm解释器入门2
1288
[分享]vm解释器入门
1587
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部