绕过 AMSI 与 ETW:使用 C# 构建自定义 PowerShell 运行空间
aeverj
aeverj
红队工坊
2026年10月15日 06:00
北京
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
原文标题: Amsi bypass Bypassing AMSI & ETW: Building a Custom PowerShell Runspace in C#
■前言
在红队(Red Team)渗透测试中,成功进入目标系统往往只是第一步。真正棘手的挑战,是如何在后渗透(Post-Exploitation)阶段应对微软层层设防的安全防护体系。
其中,有两道防线尤其让攻击者头疼:
• AMSI(Antimalware Scan Interface,反恶意软件扫描接口):负责检查 PowerShell 即将执行的脚本和命令,识别潜在的恶意内容。
• ETW(Event Tracing for Windows,Windows 事件跟踪):负责收集程序运行时的行为数据,让安全人员能够追踪 PowerShell 执行过的操作。
可以把它们理解成两道不同的防线。
AMSI 就像机场安检员。 无论脚本从哪里来,只要准备执行,就可能先接受安全检查。
ETW 则像监控摄像头。 即使脚本顺利通过检查,它仍然可能记录执行过程,为安全人员留下调查线索。
对于依赖 PowerShell 的攻击工具而言,这两套机制都是重要障碍。一段恶意脚本即使成功进入目标系统,也可能在执行前被 AMSI 拦截,或者在执行过程中被 ETW 记录。
那么,如果不直接启动 powershell.exe,而是在自己的 C# 程序里创建一个 PowerShell 环境,情况会有什么不同?
这正是本文要讨论的技术思路。
作者使用 C# 编写了一个工具,通过自定义 PowerShell 运行空间(Runspace),尝试同时干扰 AMSI 和 ETW,并让 PowerShell 运行在\\完整语言模式(Full Language Mode)\\下。
最终目标是将这些功能封装进一个编译后的可执行程序,为操作者提供一个限制更少、监控更弱的 PowerShell 环境。
下面,我们先弄清楚 AMSI、ETW 和 PowerShell 语言模式分别是什么,再看看作者如何将这些技术组合起来。
■什么是 AMSI?
\\AMSI(反恶意软件扫描接口)\\是微软从 Windows 10 开始引入的一套 Windows 应用程序编程接口(Application Programming Interface,API)。
它的作用并不复杂:在应用程序和杀毒软件之间搭建一座桥梁。
当 PowerShell 或其他支持 AMSI 的应用程序准备执行脚本时,可以先把脚本内容交给 AMSI 检查。
AMSI 再将内容转交给系统中的反恶意软件,由后者判断代码是否存在安全风险。
整个流程可以分成五步:
-
用户或脚本向 PowerShell 提交一条命令。
-
PowerShell 引擎在执行前,将原始脚本内容发送给
amsi.dll。 -
AMSI 把内容交给已注册的反恶意软件提供程序,例如 Microsoft Defender。
-
安全软件进行分析,并返回安全或恶意的检测结果。
-
如果内容被判定为恶意,执行可能被阻止,同时产生安全警报。
这里最重要的一点是:AMSI 关心的不是脚本从哪里来,而是脚本准备执行什么。
举个例子。
假设攻击者已经成功把一段恶意代码写入目标计算机的磁盘,甚至直接加载进内存。
这是不是意味着代码就能顺利运行?
未必。
因为在 PowerShell 真正执行这段代码之前,AMSI 仍然可能对其进行检查。
换句话说,即使攻击载荷(Payload)已经进入系统,也不代表它成功通过了最后一道安全检查。
为什么 AMSI 对红队如此重要?
以 Invoke-Mimikatz 为例。
在现代 PowerShell 环境中,直接运行这类已知的攻击工具,往往会触发 Microsoft Defender 的特征检测。
原因就在于,当相关脚本进入 AMSI 的扫描流程时,安全软件可以根据已知的恶意特征识别它。
这也是为什么在红队测试中,理解 AMSI 的工作方式非常重要。
对于依赖 PowerShell 的攻击工具来说,AMSI 可能在代码真正执行之前,就阻止整个操作。
■什么是 ETW?
如果说 AMSI 负责检查代码能不能执行,那么 ETW 的任务就是记录程序执行过程中发生了什么。
\\ETW(Windows 事件跟踪)\\是 Windows 内置的一套高性能事件跟踪框架,与操作系统底层紧密集成。
它可以收集系统和应用程序运行时产生的详细遥测数据(Telemetry)。
PowerShell 自身就集成了多个 ETW 事件提供程序(Event Provider),因此安全人员能够通过这些数据了解 PowerShell 的运行活动。
当 PowerShell 会话运行时,Microsoft-Windows-PowerShell 等事件提供程序可以产生以下记录:
• 脚本块日志(Script Block Logging):记录执行的脚本块内容。
• 模块加载(Module Loading):记录加载了哪些 PowerShell 模块。
• 命令调用(Command Invocation):记录命令调用活动。
• 管道执行信息(Pipeline Execution):记录 PowerShell 管道的执行细节。
这些事件可以写入 Windows 事件日志,并进一步发送到安全信息与事件管理平台(Security Information and Event Management,SIEM),例如 Microsoft Sentinel、Splunk 或 Elastic。
对于负责防守的蓝队(Blue Team)来说,ETW 是极其重要的数据来源。
因为它不仅能够帮助安全人员了解磁盘上存放了哪些文件,还能够提供程序实际运行时的行为线索。
为什么 ETW 对红队如此重要?
假设攻击者已经成功绕过 AMSI,恶意脚本也顺利执行了。
是不是就意味着攻击不会被发现?
当然不是。
因为即使 AMSI 没有阻止脚本,ETW 仍然可能记录它执行时的行为。
其中,脚本块日志尤其值得关注。
攻击者为了躲避安全检测,常常会使用代码混淆(Obfuscation)技术,把原本容易识别的脚本改写成复杂、难懂的形式。
但 PowerShell 在处理这些脚本时,可能会记录经过解析或解混淆后的内容。
这就意味着,即使原始脚本看起来像一团乱码,安全人员仍然可能从日志中发现其真正的执行逻辑。
因此,代码混淆并不能保证攻击行为不被记录。
这也是为什么一些攻击工具会尝试干扰 ETW,以减少操作过程中留下的取证痕迹。
不过,ETW 并不是一个单独的日志开关。不同事件提供程序之间相互独立,干扰某个 PowerShell 日志来源,并不意味着 Windows 上所有监控都会失效。
■理解 PowerShell 的语言模式
PowerShell 并不是在所有环境中都拥有相同的执行能力。
为了适应不同的安全需求,PowerShell 提供了多种语言模式(Language Mode),用于控制脚本可以使用哪些功能。
| 语言模式 | 说明 |
|---|---|
| FullLanguage | 完整语言模式,可以访问所有语言元素、命令以及允许使用的 .NET 类型。 |
| ConstrainedLanguage | 受限语言模式,限制敏感 .NET 类型和高级语言功能,允许使用受到约束的命令。 |
| RestrictedLanguage | 限制语言模式,只允许使用部分受限语法,不允许任意脚本块或对象成员访问。 |
| NoLanguage | 无语言模式,不允许使用 PowerShell 脚本语言语法,但仍可直接调用允许执行的命令。 |
在安全要求较高的企业环境中,\\受限语言模式(Constrained Language Mode,CLM)\\是一种常见的防护措施。
它通常与 Windows Defender 应用程序控制(Windows Defender Application Control,WDAC)或 AppLocker 等应用程序控制策略配合使用。
这种模式会限制脚本使用一些敏感的 .NET 功能,例如:
• System.Reflection:用于在运行时检查和访问程序集内部成员的反射机制。
• Add-Type:用于编译或加载 .NET 类型的 PowerShell 命令。
• 通过相关高级功能调用底层 Win32 API 的能力。
为什么要限制这些功能?
原因很简单:大量攻击工具依赖 .NET 的动态调用能力。
例如,一些工具需要在运行时操作程序集、调用系统底层 API,或者动态加载代码。
而 CLM 恰好会限制这些功能。
所以,对于红队操作者来说,CLM 往往是一道很难绕开的障碍。
■代码原理拆解
了解了前面这些概念,我们就可以看看作者的工具是如何将它们组合起来的。
整个程序是一个 C# 控制台应用,主要包含四个环节:
-
尝试干扰当前进程中的 AMSI 扫描机制。
-
尝试抑制 PowerShell 的 ETW 遥测事件。
-
创建一个使用完整语言模式的 PowerShell 运行空间。
-
提供交互式命令循环,让用户像使用普通 PowerShell 一样输入命令。
第一步:检查程序运行架构
程序启动后,首先检查当前进程是否运行在 64 位环境中。
if (!Environment.Is64BitProcess)
{
Console.WriteLine(\"[-] CRITICAL ERROR: This process MUST run in 64-bit mode.\");
return;
}
这段代码很好理解。
Environment.Is64BitProcess 用于判断当前进程是不是 64 位进程。
如果不是,程序就会输出错误信息并直接退出。
为什么作者要做这个限制?
因为 x86(32 位)和 x64(64 位)环境在内存布局、字段偏移量以及部分内部数据结构方面可能存在差异。
尤其是涉及 System.Management.Automation.dll 这样的 PowerShell 核心程序集时,如果程序对底层环境作出了错误假设,就可能导致操作失败,甚至直接崩溃。
因此,作者选择只允许程序在 64 位模式下运行。
需要说明的是,这属于该工具自身的运行要求,并不代表所有 PowerShell 运行空间都必须使用 64 位进程。
第二步:通过反射干扰 AMSI
接下来进入整个工具的核心部分:AMSI 绕过。
传统的 AMSI 绕过方法,通常会直接修改 amsi.dll 中的内存代码。
但作者选择了另一种思路。
不直接修改 DLL 的机器码,而是通过 .NET 反射访问 PowerShell 内部的状态信息。
什么是 .NET 反射(.NET Reflection)?
可以把它理解成一种让程序在运行过程中查看自身或其他程序集内部结构的能力。
通过反射,程序可以检查类型、字段、方法等信息,并在运行环境允许的情况下访问某些非公开成员。
本文涉及 PowerShell 核心程序集 System.Management.Automation.dll 中的内部类:
System.Management.Automation.AmsiUtils
其中有一个名为 amsiInitFailed 的私有静态字段。
它与 AMSI 的初始化状态有关。
作者的思路,是利用反射改变这一内部状态,让 PowerShell 误以为 AMSI 初始化失败,从而影响后续扫描流程。
这类方法最早由安全研究人员 Matt Graeber 等人公开研究,后来逐渐成为 AMSI 绕过领域中的经典技术。
与传统的内存补丁方法相比,这种技术具有几个特点:
• 不需要直接修改 amsi.dll 中的机器码。
• 不需要通过 VirtualProtect 修改内存页面保护属性。
• 不依赖外壳代码(Shellcode)。
• 主要通过托管 .NET 代码访问 PowerShell 内部状态。
最重要的是,相关操作发生在当前进程内部(In-Process)。
作者认为,相比直接修改 AmsiScanBuffer 函数,这种实现可能更难被某些终端检测与响应系统(Endpoint Detection and Response,EDR)发现。
不过,这并不意味着它一定能够绕过现代安全软件。
微软已经针对常见 AMSI 绕过方式加入了检测能力,而且 PowerShell 内部实现也可能随着版本变化而改变。
因此,这类技术是否有效,仍取决于实际运行环境和安全产品的防护能力。
第三步:干扰 ETW 事件记录
处理 AMSI 之后,作者把类似的反射思路用于 ETW。
文章研究了 PowerShell 程序集中的另一个内部类:
System.Management.Automation.EtwActivity
这个类与 PowerShell 引擎的 ETW 活动有关。
其中,isEtwEnabled 字段与部分事件跟踪功能的启用状态相关。
作者试图通过改变内部状态,减少 PowerShell 引擎产生的 ETW 事件。
其设计目标包括干扰以下记录:
• 脚本块日志。
• 模块加载记录。
• 命令调用信息。
这样,即使脚本已经执行,部分原本应该产生的 PowerShell 日志也可能受到影响。
但这里有一个非常重要的区别:
干扰 PowerShell 的 ETW 事件,不等于关闭整个 Windows 的 ETW 系统。
Windows 中还有许多其他事件提供程序。
例如,.NET 公共语言运行时(Common Language Runtime,CLR)就可以独立生成程序集加载等事件。
即使 PowerShell 层面的部分事件受到影响,这些其他来源仍然可能留下记录。
另外,不同版本的 PowerShell 对内部 ETW 状态的处理方式也可能有所不同。
因此,原文中关于所有 PowerShell ETW 日志都会被完全关闭的描述,应理解为作者的设计目标,而不能视为在所有环境中都必然成立的结果。
第四步:创建自定义 Runspace,并使用完整语言模式
接下来是另一个关键环节。
作者不再依赖传统的 powershell.exe 来执行命令,而是直接在 C# 程序内部创建 PowerShell 运行空间。
所谓 Runspace,可以把它理解成一个嵌入应用程序内部的 PowerShell 执行环境。
它负责保存会话状态、管理变量、执行命令,并维护当前工作目录等信息。
我们平时使用 PowerShell,通常是打开:
powershell.exe
或者:
pwsh.exe
但 PowerShell 的执行引擎并不只能通过这两个程序使用。
开发者同样可以通过 PowerShell SDK,将引擎嵌入自己的 .NET 程序。
作者使用的基础代码如下:
InitialSessionState iss = InitialSessionState.CreateDefault();
using (Runspace runspace = RunspaceFactory.CreateRunspace(iss))
{
runspace.Open();
// ...
}
这段代码主要做了三件事。
首先,通过 InitialSessionState.CreateDefault() 创建默认的 PowerShell 会话配置。
然后,通过 RunspaceFactory.CreateRunspace() 创建一个新的运行空间。
最后,调用 runspace.Open(),让这个运行空间进入可用状态。
到这里,一个由 C# 程序承载的 PowerShell 执行环境就建立起来了。
原文接下来还尝试将语言模式设置为 FullLanguage,希望让运行空间获得完整的 PowerShell 语言能力。
作者认为,自定义宿主可以避开 powershell.exe 上的受限语言模式约束。
不过,这里需要特别澄清一个技术细节。
CLM 并不只是由 powershell.exe 这个宿主程序负责执行。
在受到 WDAC、AppLocker 等系统级应用程序控制策略保护的环境中,PowerShell 引擎自身也可能根据系统策略实施语言限制。
因此,创建自定义 Runspace,并不必然意味着能够绕过 CLM。
也就是说,自定义 PowerShell 宿主确实提供了更灵活的执行方式,但它并不是一个在所有 Windows 环境下都有效的安全策略绕过手段。
第五步:实现交互式 PowerShell 终端
完成前面的准备之后,还剩最后一个问题:
如何让用户像操作普通 PowerShell 一样,持续输入命令并查看结果?
作者采用了一个简单直接的办法:编写无限循环,不断读取用户输入。
核心代码如下:
while (true)
{
string currentPath = runspace.SessionStateProxy.Path.CurrentLocation.ToString();
Console.Write(\"PS \" + currentPath + \"> \");
string input = Console.ReadLine();
// ...
ExecutePowerShellScript(ps => {
ps.Runspace = runspace;
ps.AddScript(input);
ps.AddCommand(\"Out-String\");
}, runspace);
}
运行之后,终端会显示我们熟悉的 PowerShell 提示符:
PS C:\\>
用户输入命令后,程序将其交给当前运行空间执行。
其中:
• Console.ReadLine():负责接收用户输入。
• ps.Runspace:指定命令使用哪个 PowerShell 运行空间。
• ps.AddScript(input):将输入的内容添加为待执行脚本。
• Out-String:将执行结果转换为便于在控制台显示的字符串。
程序还会捕获错误输出,并将错误信息显示为红色,尽量还原原生 PowerShell 的交互体验。
这里还有一个值得注意的设计。
所有命令都在同一个 Runspace 中执行。
因此,程序并不是每输入一条命令就启动一个新的 PowerShell 环境,而是持续复用现有会话。
这样一来,用户就可以在同一个环境中连续执行命令,体验上更接近真正的 PowerShell 终端。
■为什么这种方法有效?
从红队角度来看,这种方案的特殊之处,在于它试图避开传统攻击方式容易暴露的检测信号。
我们可以把几种常见技术放在一起比较。
| 技术方式 | 主要检测信号 | 本文工具的设计思路 |
|---|---|---|
修改 AmsiScanBuffer |
对 amsi.dll 进行可疑内存写入、调用 VirtualProtect |
使用 .NET 反射,而不是直接修改函数机器码 |
powershell.exe -ep bypass |
进程创建日志、命令行审计 | 从独立编译的 C# 程序中运行 PowerShell 引擎 |
| PowerShell 下载执行器(Download Cradle) | 网络活动、AMSI 检查、脚本块日志 | 尝试在脚本运行前干扰 AMSI 和 ETW |
| 代码混淆 | PowerShell 引擎解析后的内容仍可能进入脚本块日志 | 尝试通过干扰 ETW 减少相关日志 |
这套方案最大的特点,就是把 PowerShell 的执行能力隐藏在一个普通的 C# 可执行程序内部。
它不需要通过传统的 powershell.exe 进程启动命令,因此可以避开部分专门监控 PowerShell 进程创建行为的检测规则。
同时,作者还试图在脚本运行之前,改变 AMSI 和 ETW 的相关状态。
按照作者的设计,这有助于减少执行过程中的部分安全监控记录。
不过,需要注意的是,没有启动 powershell.exe,并不意味着 PowerShell 引擎的活动就无法被发现。
现代 EDR 产品不仅可以监控进程名称,还能观察程序集加载、内存行为、运行时调用和其他系统活动。
因此,这种方法更准确的定位,是改变攻击活动暴露出来的检测特征,而不是让攻击活动彻底消失。
■防御方面的考虑
如果你是一名蓝队安全人员,应该如何识别类似行为?
作者提出了四个值得关注的方向。
1. 监控异常进程加载 PowerShell 程序集
首先,需要关注非标准程序加载以下程序集的行为:
System.Management.Automation.dll
通常情况下,PowerShell 引擎会通过 powershell.exe 或 pwsh.exe 运行。
如果某个陌生的可执行文件突然加载了 PowerShell 核心程序集,就值得进一步调查。
当然,这并不意味着所有自定义 PowerShell 宿主都是恶意程序。
合法的自动化工具、运维软件和管理平台,同样可能在内部使用 PowerShell SDK。
因此,更合理的检测方式,是结合程序来源、数字签名、运行路径及后续行为综合判断。
2. 关注 CLR 和 .NET 程序集加载事件
即使攻击工具能够影响部分 PowerShell ETW 日志,也不代表其他事件来源一定受到影响。
例如:
• CLR 分析和跟踪信息。
• .NET 程序集加载事件。
• 其他与进程和模块活动有关的系统日志。
这些数据可以帮助安全人员从 PowerShell 引擎之外的角度,观察程序的实际行为。
3. 留意突然出现的日志空白
有时候,发现异常并不一定需要看到明显的恶意日志。
本来应该持续产生的日志突然消失,也可能是一个重要信号。
假设某台服务器平时一直会产生 PowerShell 执行记录。
但从某个时间点开始,相关日志突然中断,而系统中仍然存在 PowerShell 活动。
这种情况就值得进一步调查。
当然,日志中断也可能由配置变更、采集故障或其他正常原因导致。
因此,不能仅凭日志缺失就判定系统遭到攻击,而应结合其他遥测信息进行分析。
4. 检测异常反射操作
本文介绍的攻击思路,涉及访问 PowerShell 程序集中的内部类,例如:
• AmsiUtils
• EtwActivity
防守方可以通过 EDR 行为分析、内存扫描和运行时监控,寻找针对这些类的异常访问模式。
特别是当某个未知程序加载 PowerShell 引擎后,又尝试访问与安全检测相关的非公开成员时,这种组合行为就值得重点关注。
与其只依赖单个特征字符串,不如把程序集加载、反射行为、进程活动和日志变化结合起来分析。
这样往往能够建立更可靠的检测机制。
■总结
本文展示了一种将多种 PowerShell 安全机制研究技术组合起来的思路:
AMSI 绕过、ETW 遥测干扰,以及完整语言模式下的自定义 PowerShell Runspace。
作者尝试将这些功能封装到一个独立的 C# 可执行程序中,让操作者不必直接启动传统 PowerShell 进程,也能获得一个交互式脚本执行环境。
这一方案真正值得关注的地方,不只是某个特定的绕过技巧,而是它体现了一种思路:
攻击者不一定要正面突破每一道防线,也可能通过改变程序的运行方式,尝试避开部分检测路径。
对于红队人员来说,理解这些技术,有助于在授权测试中更真实地模拟攻击者可能采用的手段。
对于蓝队人员来说,了解这些技术同样重要。
如果企业只依赖 AMSI 或某一种 PowerShell 日志进行安全检测,那么当这些数据来源受到干扰时,就可能失去部分关键线索。
更加可靠的防御体系,需要综合利用进程行为、程序集加载、运行时事件、内存分析和多种日志来源。
同时也要认识到,自定义 PowerShell 宿主并不天然具备绕过所有安全策略的能力。实际效果仍然取决于 PowerShell 版本、系统配置和安全产品的检测能力。
理解攻击技术的原理,是为了建立更完善的防御体系,而不是削弱安全防护。
如果你对网络安全、红队攻防技术充满热情,渴望学习更多实战技巧,例如渗透测试、自动化脚本编写、免杀技术等, 欢迎关注我的公众号
在这里,我会持续分享更多高质量的技术文章,与你一同探索网络安全的奥秘,提升实战技能! 让我们一起在队攻防的道路上,不断精进,突破边界!
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论