1 技术简介
“利用硬件断点来规避EDR检测”技术又被称为Blindside技术,该技术通过加载一个不受监控且未被挂钩的 DLL文件,且不挂钩特定函数,并运用允许运行任意代码的调试技术来实现EDR检测的规避。该调试技术一般在调试器中被广泛应用。
2 Blindside技术原理
▶ 2.1 硬件断点
在Blindside技术中需要使用硬件断点的相关知识,而且硬件断点是和CPU紧密相关的。接下来就先介绍硬件断点和硬件断点寄存器。OS/CPU提供了8个寄存器来供硬件断点使用,其中DR0-DR7是系统提供的硬件断点寄存器。与软件断点不同,硬件断点可用于设置“内存断点”或当任何指令尝试读取、写入或执行特定内存地址时触发的断点(取决于断点配置)。硬件断点的数量是有限的,允许最多一次设置4个硬件断点。硬件断点寄存器,也叫做调试寄存器,如下图所示。
其中,DR0 - DR3 用于保存断点的线性地址,也叫做“调试地址寄存器”。当其中一个寄存器中的地址与指令匹配时,将触发断点。DR4 - DR5是保留调试寄存器。
DR6寄存器解析
DR6是调试状态寄存器,主要作用是当CPU检测到匹配断点条件的断点或有其他调试事件发生时,用来向调试器的断点异常处理程序传递断点异常的详细信息。只有在生成异常时才会更新此寄存器。
该寄存器用于表示进入陷阱的原因,各个位的含义如下:
● B0~B3位:表示的是如果其中任何一个位置位,则表示的是相应的DR0 - DR3断点引发的调试陷阱
● BD位:表示的是GD(DR7)位置位情况下访问调试寄存器引发的陷阱
● BT位:表示的是TSS任务切换时,若设置了T标志位,会引起调试异常,并使得BT位置位
● BS位:表示的是单步中断引发的断点。即EFLAGS的TF置位时引发的调试陷阱。硬件断点抛出的异常也是单步异常
DR7寄存器解析
DR7 被称为“调试控制寄存器”。其中,L0、L1、L2、L3分别代表DR0、DR1、DR2、DR3是否启用,G0-G3可以忽略。
L/G:局部/全局。
GD:保护标志。
LE和GE:为了兼容性,Intel建议使用精确断点时把LE和GE都设置为1。(使用精确断点标志,P6及之后的CPU不支持该标志)
R/W0和LEN0 - R/W3和LEN3 描述的是硬件断点的类型以及长度信息。
R/W0到R/W3:指定各个断点的触发条件。它们对应于DR0到DR3中的地址以及DR6中的4个断点条件标志。
// 00 只执行
// 01 写入数据断点
// 10 I/O端口断点(只用于pentium+,需设置CR4的DE位,DE是CR4的第3位 )
// 11 读或写数据断点
LEN0到LEN3:指定在调试地址寄存器DR0到DR3中指定的地址位置的大小。
注意事项:
如果R/Wx位为0,则LENx位也必须为0,否则会产生不确定的行为。R/Wx位为0是硬件执行断点。
设置的地址要和长度对齐。
// 00 1字节
// 01 2字节
// 10 保留
// 11 4字节
对于 Blindside 技术,DR7 是最关键的寄存器,因为它控制每个断点并设置断点条件。
▶ 2.2 调试异常
当涉及到硬件断点异常时,会出现两种情况:调试异常(#DB)和断点异常(#BP)。
调试异常(#DB):当除INT 3指令以外的调试事件发生时,会导致此异常
断点异常(#BP):INT 3指令执行时会导致此异常,CPU转到该异常的处理例程。异常处理例程会进一步将异常分发给调试器软件
对于 Blindside 技术,调试异常 (#DB) 是最重要的。当触发断点时,执行将被重定向到处理程序。Blindside 技术中的异常只有在单步异常时才会被触发。
首先需要建立断点的处理程序。
handler函数首先检查EXCEPTION_POINTERS结构的ExceptionRecord成员中的ExceptionCode是否为EXCEPTION_SINGLE_STEP(单步异常)。如果是单步异常,则将检查ExceptionInfo结构的 ContextRecord 成员中的指令指针 (Rip) 是否等于Dr0的值。如果也是这样,该函数将会执行一些操作,比如打印异常信息等。
最后,该函数设置恢复标志 (RF),并返回
EXCEPTION_CONTINUE_EXECUTION 以指示应继续执行。如果ExceptionCode不是EXCEPTION_SINGLE_STEP,则该函数返回 EXCEPTION_CONTINUE_SEARCH 以指示应继续搜索处理程序。
接下来需要设置硬件断点。
3 Blindside实现过程
▶ 3.1 Blindside实现原理
Blindside技术的实现原理是通过在调试模式下创建进程,在 LdrLoadDll 设置硬件断点,并强制加载 ntdll.dll,而不加载其他的dll文件,强制加载的 ntdll.dll 是没有被hook的,最后将强制加载的 ntdll.dll 内存复制到现有进程并卸载所有被hook的函数调用。
通过利用Blindside技术,使用硬件断点来hook LdrLoadDLL 来阻止加载额外的 dll 文件,并创建一个只有 ntdll 且未被hook的进程。
▶ 3.2 Blindside实现过程
调试模式创建进程
定位 LdrLoadDll 进程地址
创建的新进程是目标进程的子进程,和目标进程具备相同的 ntdll 基址以及 LdrLoadDll 的地址也是相同的。
设置硬件断点
定位到 LdrLoadDll 地址后设置硬件断点。
等待断点触发
接下来,该函数调用 SetThreadContext() 函数将更新的上下文设置到线程。然后它进入一个无限循环,使用 WaitForDebugEvent() 函数等待调试事件。
当接收到调试事件时,函数会检查ExceptionCode是否为 EXCEPTION_SINGLE_STEP 的异常调试事件。如果是,该函数将使用 GetThreadContext() 函数检索线程的当前上下文,并检查异常地址是否与指定地址匹配。
如果异常地址与指定地址匹配,函数将重置 Dr0、Dr6 和 Dr7 寄存器并且不返回任何内容,这样做是为了阻止 LdrLoadDll 加载其他 DLL。否则,它会重置断点并使用 DBG_CONTINUE 参数调用 ContinueDebugEvent() 函数来继续执行。这个循环一直持续到 WaitForDebugEvent() 返回 0,表示没有更多的调试事件。
加载内存并覆盖hook
将 ntdll 的内存复制到目标进程中并取消hook任何系统调用。
首先获取到了NtReadVirtualMemory函数和VirtualProtect函数地址,通过NtReadVirtualMemory函数读取未被hook的ntdll内存,接下来就是遍历找到ntdll的.text段的虚拟地址,将保护改为PAGE_EXECUTE_READWRITE,将新映射缓冲区(freshNtdll)的.text段复制到被hook版本的ntdll中,这将导致挂钩被覆盖。
清理并恢复
恢复内存保护以及结束不再使用的进程。
4 结合其他技术实现dump lsass
▶ 4.1 结合RPC技术
RPC技术原理
RPC,全称“Remote Procedure Call”,即远程过程调用,RPC在Windows上的设计是一种强大、健壮、高效且“安全”的进程间通信 (IPC) 机制,它支持数据交换和调用驻留在不同进程中的功能。
RPC技术主要是通过RPC控制lsass进程来加载SSP DLL实现dump lsass。其中SSP(Security Support Provider)是一个DLL,允许开发者提供一些回调函数,以便在特定认证和授权事件期间调用,而通过模拟AddSecurityPackage函数调可以实现让RPC向lsass发送信号加载我们自己定义的SSP DLL文件,通过自定义的SSP DLL文件来dump lsass内存。
在使用RPC技术需要使用一个API函数,即AddSecurityPackage函数,该函数主要的作用是向lsass发送RPC调用信号加载一个新的SSP DLL。而AddSecurityPackage函数注册SSP的具体过程是在Secur_32.dll和sspcli.dll文件中完成的。在AddSecurityPackage函数中主要通过NdrClientCall3函数来实现RPC调用。
下面就具体分析真实的调用过程:
sub_1800141D0函数调用sub_180004304函数。
sub_180004304函数调用sub_180006760函数。
最终在sub_180006760函数调用的NdrClientCall3函数,由下图可知NdrClientCall3函数的参数信息。
下面的代码用来构造SecurityPackage包。
用来创建RPC连接字符串以及RPC句柄。其中需要SSPI RPC服务器在LSASS过程中使用的具体端点,该字符串是保存在sspisrv.dll文件中的SspiSrvInitialize()导出函数。
SspiSrvInitialize函数调用RpcServerUseProtseqEpW和RpcServerUseProtseqEp函数告诉RPC运行时库使用具有指定端点组合指定的协议序列,主要用于接收远程过程调用,如下图所示,lsasspirpcSSPI RPC服务器在LSASS过程中使用的具体端点就是lsasspirpc。
通过调用Proc0_SspirConnectRpc和Proc3_SspirCallRpc函数完成对AddSecurityPackage函数的直接调用。
其中Proc0_SspirConnectRpc和Proc3_SspirCallRpc函数是对NdrClientCall3函数的封装,NdrClientCall3函数主要负责RPC的调用。
其中自定义SSP DLL文件使用了两种不同的函数分别进行测试
第一种:MiniDump函数
第二种:MiniDumpWriteDump函数
免杀测试
火绒和360安全卫士为最新版。
静态查杀以及动态执行火绒没有报警。
静态查杀以及动态执行火绒没有报警,云查杀也没有报警。
测试没有使用Blindside技术,直接利用RPC技术则会直接拦截和查杀。
5 缓解措施
▶ 5.1SetThreadContext函数
监视 SetThreadContext 函数的使用。通过检查攻击者是否把地址写入到调试地址寄存器 (DR0 - DR3) 中,来确定硬件断点是否被滥用。
▶ 5.2 检测调试功能
当使用调试功能时,可以检测调试寄存器 (DR0 - DR3) 是否存在可疑功能。如果既有调试功能,在调试寄存器中又存在数据内容,则可能存在恶意行为。
需要通过对多种行为监控来确定是否使用Blindside技术,而不是单纯的通过某一种行为确定恶意。
6 模拟攻击库更新
塞讯安全度量验证平台已将此技术纳入模拟攻击库,在平台内搜索“Blindside”即可获得相关攻击模拟实验,从而验证安全防御体系是否能够有效应对这一攻击手段。