内核攻防:致盲EDR技术详解
需求背景
在APT攻击中,使用驱动致盲EDR(Endpoint Detection and Response)的意义在于通过加载恶意或修改的驱动程序,直接操作内核数据结构,禁用或清除EDR的回调和监控机制,从而绕过安全检测。这种方法能够隐藏攻击行为、规避日志记录和实时响应,确保攻击活动在受害系统中悄无声息地进行,提高持久性和隐蔽性。
驱动解析
内存读写驱动实现
项目代码: https://github.com/k3lpi3b4nsh33/rwdriver
devctrl_RwMemory函数是一个典型的驱动层代码,用于设备驱动程序的I/O控制请求处理,特别是内存读写操作相关的情况。
功能描述:
- 在内核模式下执行内存读写操作
- 用于在同一进程内复制内存数据
参数说明:
DeviceObject: 设备对象指针(未使用)irp: I/O请求包指针irpSp: IRP栈位置指针
主要流程:
- 获取并验证系统缓冲区
- 验证内存操作参数(源地址、目标地址、大小)
- 执行内存复制操作
- 完成IRP请求
返回值:
- 成功: STATUS_SUCCESS
- 失败: STATUS_INVALID_PARAMETER或STATUS_UNSUCCESSFUL
使用示例:
通过设备I/O控制代码调用此函数,传入MEMORY_OPERATION结构体,包含源地址、目标地址和复制大小。
可能的应用场景
-
内存读写工具:
- 用户模式程序通过IOCTL调用该函数,向驱动请求读写当前进程的特定内存区域
- 用于开发调试工具或与进程交互的应用程序
-
内核态内存编辑:
- 函数内部调用
MmCopyVirtualMemory,直接操作进程的虚拟内存空间 - 用途包括调试器工具、数据注入(如游戏外挂)、内存复制操作的驱动辅助实现
- 函数内部调用
-
反病毒软件或内存分析工具:
- 用于安全领域,如反病毒引擎或内存分析工具,从特定进程中提取敏感信息
驱动利用Exe解析
项目代码: https://github.com/k3lpi3b4nsh33/BlindEdr
核心原理
所有操作的原理是:通过控制驱动的内存读写函数(DriverMemoryOperation),修改相关EDR内核数据结构的地址。
在Common.c中,DriverMemoryOperation函数是与驱动交互的关键函数。它传入一个PMemOp结构体,在驱动中使用MmCopyVirtualMemory进行操作。从R3的角度来说,相当于使用了一个处于驱动层的Memcpy函数。
API哈希动态获取
实现的主要目的和意义:
-
反检测和反调试
- 避免在程序中直接存储API函数名称字符串
- 规避静态分析和特征码检测
- 增加逆向分析的难度
-
动态解析
- 使用哈希值代替明文字符串
- 运行时动态计算和匹配API函数
- 绕过导入表(IAT)监控
实现方法:
- 编写
CityHash函数,使用三个不同的哈希值进行计算(FIRST_HASH、SECOND_HASH、THIRD_HASH) - API函数解析流程(
GetProcAddressH):- 遍历模块导出表
- 计算每个导出函数名的哈希值
- 与预定义哈希值比对
- 处理转发函数情况
- 返回匹配函数地址
- 模块句柄的获取(
GetModuleHandleH):- 支持内核模块和用户模块的处理
- 使用
NtQuerySystemInformation获取系统模块信息 - 大小写不敏感的哈希匹配
EDR相关模块分析
FLTMGR.SYS的作用:
- 文件操作监控:EDR通过Windows文件系统过滤器框架拦截文件的创建、读取、修改和删除等操作
- 行为记录:记录终端上的文件活动日志,为威胁分析和回溯提供数据支持
- 实时拦截:在文件操作发生前拦截威胁操作
NTOSKRNL.exe的作用:
- 作为系统核心,提供基本的内核服务
- 所有内核模式驱动程序都依赖其提供的接口
- 用户程序通过系统调用访问其功能(如文件读写、网络通信)
内核函数地址解析机制
- 获取目标内核模块的实际基地址
- 通过哈希匹配从
kernel32.dll中获取LoadLibraryExA/LoadLibraryA函数 - 对于
FLTMGR.SYS,使用LoadLibraryExA并指定DONT_RESOLVE_DLL_REFERENCES标志加载驱动文件作为模板 - 对于
ntoskrnl.exe,使用普通的LoadLibraryA加载 - 计算目标函数在用户态加载的模块中的偏移,加上实际的内核模块基地址,得到函数在内核空间中的真实地址
这种方法避免了直接读取内核内存,同时通过哈希隐藏了敏感的函数名和模块名。
EDR特征检测与清除技术
EDR特征检测
IsEDRHash函数:
- 基本原理:通过对驱动程序名称(DriverName)的哈希值或名称前缀匹配来检测特定的安全防护软件驱动程序
- 哈希值匹配:
- 将驱动程序名称转换为唯一哈希值
- 与内置的已知安全驱动程序的哈希列表(
AVDriverHashes)比较 - 匹配则返回TRUE
- 字符串匹配:
- 检查驱动程序名称是否包含某些已知字符串(
PrefixKESDriver) - 包含则返回TRUE
- 检查驱动程序名称是否包含某些已知字符串(
模式匹配技术
FindPattern函数:
- 在内存中按字节逐地址扫描,寻找符合指定模式的指令或数据结构
- 模式匹配由外部定义的验证函数(如
ValidateLeaPattern、ValidateCallJmpPattern等)提供逻辑 - 适配不同的模式和用途:
- 寻找特定的汇编指令(如LEA或CALL)
- 定位内核中的特定回调函数或数据结构
实现细节:
- 验证输入参数
- 初始化搜索状态
- 逐地址搜索
- 调用验证函数
- 继续搜索
- 释放资源
示例:匹配LEA RAX, [RIP+Offset]指令:
DriverMemoryOperation依次读取内存数据到buffer- 调用
ValidateLeaRipPattern检查 - 匹配则返回地址
CalculateOffset函数:
- 功能:计算内存中的偏移量
- 实现:
- 从指定内存地址的某一偏移位置开始逐字节读取数据
- 按照字节顺序将每个字节拼接成一个64位偏移量
- 如果高位包含符号位,则进行符号扩展
- 返回最终计算的偏移量
回调清除技术
CallbackManager.c
实现Windows内核回调的检测、管理和清理功能,主要处理三种回调:
- 进程创建回调(
PsSetCreateProcessNotifyRoutine_CH):监控进程创建和终止 - 线程创建回调(
PsSetCreateThreadNotifyRoutine_CH):监控线程创建和终止 - 镜像加载回调(
PsSetLoadImageNotifyRoutine_CH):监控DLL和驱动加载
关键函数:
-
ProcessDriverCallback:- 检查特定驱动回调函数是否与EDR相关,若是则清除回调
- 计算内存地址,读取回调信息
- 验证驱动程序名称,判断是否为已知的EDR驱动
- 通过内核内存写入操作清除回调
-
GetPspNotifyRoutineArrayH:- 定位
PspNotifyRoutineArray地址(存储内核回调函数的数组) - 根据Windows版本判断如何解析地址
- 使用模式匹配和指令偏移提取目标地址
- 兼容不同版本的Windows
- 定位
-
PrintAndClearCallBack:- 扫描回调数组中的每个条目,打印相关信息,并清除EDR回调
- 遍历回调数组,按索引逐一读取回调地址
- 通过位操作解析回调函数的实际地址
- 验证驱动程序名称,若是EDR驱动则清除其回调条目
-
ClearThreeCallBack:- 批量处理与进程、线程和模块加载相关的三种回调
- 使用结构体加循环的方法进行清除回调函数
- 获取各回调的数组地址并逐一清理
FilterCallBackManager.c
RemoveInstanceCallback函数:
- 遍历和处理
FLT_FILTER对应的所有FLT_INSTANCE实例,清除其关联的回调节点 - 核心逻辑:
- 初始化偏移量(根据操作系统版本动态确定关键偏移量)
- 遍历实例列表,访问每个实例并处理其内容
- 处理回调节点数组,清除存在的回调节点
- 处理完所有节点后遍历到下一个实例
ClearMiniFilterCallBack函数:
- 枚举系统中所有MiniFilter驱动,进行回调清理
- 核心逻辑:
- 定位全局MiniFilter数据结构
- 处理MiniFilter驱动列表,清除EDR驱动的回调实例
- 处理卷(Volume)回调,清除EDR相关的节点
- 输出处理结果
ObjectCallBackManager.c
清除对象回调的核心逻辑:
- 获取回调列表的头指针
- 根据对象类型(
PsProcessType或PsThreadType),找到回调链表头地址 - 使用特定偏移量(取决于操作系统版本)解析内核结构
- 根据对象类型(
- 遍历回调链表
- 读取每个节点的
PreOperation和PostOperation回调函数地址
- 读取每个节点的
- 判断是否为EDR回调
- 检查回调函数所属的驱动程序名称是否属于已知的EDR驱动
- 清除EDR回调
- 清空对应节点的回调地址(写入0)
关键函数:
ClearObRegisterCallbacks:- 入口函数,负责调用其他函数枚举和清理回调
- 定义两种对象类型(进程和线程),逐个处理
GetPsProcessAndProcessTypeAddr:- 根据对象类型获取回调链表头的地址
- 通过模式搜索定位地址指针
RemoveObRegisterCallbacks:- 清除指定类型对象的回调列表中与EDR相关的回调
- 遍历链表节点,统计回调节点数量并清理
ProcessCallback:- 处理每个回调函数地址,检查是否属于EDR驱动并清理
RegistryCallbackManager.c
主要功能:
- 清理与
CmRegisterCallback注册的注册表回调函数 - 枚举和打印当前已注册的驱动名称
- 通过修改回调链表的头节点地址绕过Windows内核的PatchGuard保护机制
实现步骤:
- 获取回调链表地址
- 使用
CmUnRegisterCallback函数地址定位链表头 - 通过模式匹配和偏移量计算得到链表头
- 使用
- 遍历回调链表
- 读取每个节点,提取注册的回调函数地址
- 获取回调函数所属驱动名称并打印
- 清空回调链表
- 修改链表的头节点地址,使所有回调失效
- 仅修改头节点以避免触发PatchGuard检测
常用的规避EDR技巧
IAT Camouflage技术
IatCamouflage函数的主要功能是通过混淆手段隐藏真实的意图或行为:
-
动态内存分配和随机化:
- 使用伪随机的种子和时间戳生成随机大小的内存缓冲区
- 在缓冲区中存储伪随机值,增加调试和分析的复杂度
-
引入不可预测性:
- 使用
__rdtsc()(获取CPU时间戳计数器)和随机化的哈希值生成不可预测的执行路径
- 使用
-
混淆IAT使用痕迹:
- 调用一系列Windows API函数(如
GetTickCount64、GetSystemInfo等) - 引入大量看似无关的操作干扰逆向分析工具
- 调用一系列Windows API函数(如
-
隐藏执行逻辑:
- 将随机生成的哈希值与动态计算值(基于时间戳和系统信息)混合
- 干扰静态和动态分析
-
内存清理:
- 在执行完伪代码逻辑后释放分配的随机缓冲区
- 减少内存残留信息
操作演示
- 开启Windows测试模式,然后重启系统
- 创建驱动服务,开启驱动服务
项目背景
这个项目很大程度上受到RealBlindingEDR的启发。从简单的小驱动写起,然后再进行驱动函数调用。