内核初级对抗技术分析
zishen 发表于 河南 二进制安全 1703浏览 · 2025-06-07 06:13

内核 初级对抗

前言

基础知识

防御

注册表监控

关键API:

以下为最常用的REG_NOTIFY_CLASS的值

image-20250606125631225.png
图片加载失败


关键代码:

image-20250606130248640.png
图片加载失败


如图所示,分别使用R3Demo和手动创建方式创建指定的注册表项,我们在回调函数中设置RegNtRenameKey和RegNtPreCreateKeyEx用来拦截注册表项的创建操作,这样当创建指定名称的注册表项时,我们就能够识别并拦截,达到阻止创建的效果(注:如果是手动创建注册表项,就会先创建【新项 #1】,再重命名为你创建的项,所以手动创建指定项要使用RegNtRenameKey拦截)

进程保护监控

在windows体系中,应用层是不能直接操作内核对象的,而是通过句柄来索引,详细的说内核对象通过API来创建,每个内核对象是一个数据结构,它对应一块内存,由操作系统内核分配,并且只能由操作系统内核访问。 在此数据结构中少数成员如安全描述符和使用计数是所有对象都有的,但其他大多数成员都是不同类型的对象特有的。 内核对象的数据结构只能由操作系统提供的API访问,应用程序在内存中不能访问。 调用创建内核对象的函数后,该函数会返回一个句柄,它标识了所创建的对象。 它可以由进程的任何线程使用。

通过ObRegisterCallbacks实现进程保护的核心是利用 Windows 内核的对象回调机制,在进程对象的关键操作(如句柄创建、权限获取)发生时介入校验,动态修改访问权限,可以有效拦截R3下的常规攻击

关键API:

关键代码:

其中关键部分是:OperationInformation->Parameters->CreateHandleInformation.DesiredAccess = 0;

在 Windows 系统里,若一个进程要访问另一个进程,需要先获取目标进程的句柄(handle)。而获取句柄通常会调用像OpenProcess这样的函数,这些函数最终会在内核中触发句柄创建操作。句柄代表着对某个对象(这里指进程)的引用,并且会关联一系列访问权限,比如读取内存、写入内存、终止进程等。这些权限是通过DesiredAccess参数来指定的,它是一组标志位的组合,像PROCESS_VM_READ(读取内存)、PROCESS_TERMINATE(终止进程)等都属于常见的标志位。

image-20250606152207699.png
图片加载失败


在代码中,我们设置DesiredAccess为0,意味着请求的访问权限被全部清除。这样后续若进程试图通过这个句柄执行任何操作(如读取内存),系统会检查句柄权限。由于权限为 0,所有操作都会因权限不足而失败。这样我们就可以达到如图阻止CE读写内存的效果等

进攻

对抗注册表保护和进程保护我们需要用windbg/vs进行双机调试,这方面的教程有很多大家可以自行搜索参考

对抗注册表保护

要过掉注册表保护,我们首先要明确方向,主要有以下几种方法:

通过CmUnRegisterCallback函数找到Cookie直接调用卸载

找到回调函数首地址,直接ret

将CmpCallBackCount设置为0

先记录Cookie

image-20250606170710184.png


回调函数入口

image-20250606170723918.png
图片加载失败


在写代码之前,我们先手动找一下CmUnRegisterCallback

image-20250606163033978.png
图片加载失败


其中有一个很关键的成员CallbackListHead,我们先dq进去看一下

image-20250606165948732.png
图片加载失败


一直dq下去可以验证发现是一个双向环状链表

image-20250606170816797.png
图片加载失败


其中该结构体的第一个成员指向下一个结构体,第二个成员指向上一个结构体,通过之前记录的Cookie和回调函数入口发现,有一个结构体的第四、五、六个成员分别是Cookie,Context,回调函数入口地址,那么最关心的成员我们就找到了,现在就是要想办法通过代码的方式找到它

那我们就可以自行构建一下该结构体:

image-20250606201326768.png
图片加载失败


先通过MmGetSystemRoutineAddress找到CmUnRegisterCallback的地址,然后提取特征码,根据特征定位关键位置

代码:

这样通过该函数我们就可以查找到我们需要的关键信息,这样使用Cookies调用卸载函数或在函数头写补丁直接ret则可直接过掉注册表保护

image-20250606194748207.png
图片加载失败


CmpCallBackCount在CmUnRegisterCallback的尾部,找寻方法同理

注意

在上述代码中,我们不能在循环中直接调用CmUnRegisterCallback,因为这个函数内部会从注册表回调链表中移除当前节点,并释放或标记无效,而我们在循环中使用plist = plist->Flink遍历链表,假设我们有以下下链表

HEAD <-> A <-> B <-> C 当遍历到A并移除后链表变成 HEAD <-> B <-> C 但我们的下一步是 plist = plist->Flin 也就是 plist = A->Flink;可此时A的内容已不可靠,所以会直接导致蓝屏

所以我们要想使用CmUnRegisterCallback过注册表监控需要先在循环中记录cookies,再循环移除

对抗进程保护

要过掉进程保护,方法与对抗注册表保护类似:

通过ObUnRegisterCallbacks找到_HANDLE值,再调用ObUnRegisterCallbacks

找到回调函数入口,再ret

将Altitude注册更高,进行拦截

同上我们先记录一下_HANDLE和回调函数入口地址

image.png


image.png
图片加载失败


对抗进程保护我们需要先找到对象类型,也就是PsProcessType,它是指针的指针

image-20250606212341260.png
图片加载失败


所以我们需要先拿到PsProcessType的值,再拿该地址的值来dt _OBJCET_TYPE

image-20250606212938210.png
图片加载失败


查看CallbackList

image-20250606213238705.png
图片加载失败


image-20250606213513260.png
图片加载失败


我们一直dq下去不难发现这个链表类似于_CMPCALLBACKLIST,都是双向环状链表,在链表的一个成员中,发现_HANDLE和ProtectProcess都在该结构体中,按照老样子,整理一下该结构体

代码比EnumCmpCallback要简单,因为我们不需要找特征码了,PsProcessType是全局的,可以直接获取

屏幕截图 2025-06-07 131949.png
图片加载失败


总结

内核回调机制是内核安全防护的重要组成部分,但在面对有一定逆向能力和 Ring0 编写能力的攻击者时,其对抗能力仍存在上限。通过合理的回调管理和策略(如白名单、路径精确匹配、进程行为分析)可以显著提高防护效果。但同时也必须考虑回调滥用可能带来的系统不稳定和兼容性问题,因此在实际应用中应权衡性能、安全与稳定性。

1 条评论
某人
表情
可输入 255
图小喵
2025-06-21 10:46 1 回复
感谢分享,帖子深入剖析了内核回调机制在进程保护和注册表监控中的应用与局限,对安全防护有重要参考价值。查看思维导图更直观理解技术脉。
内核初级对抗技术分析.jpeg
图片加载失败