type
status
date
slug
summary
tags
category
icon
password
0x00 混淆代码
对于 powershell 绕过 AMSI 来说,混淆代码是绕过 AMSI 最基础的步骤,因为大部分绕过方法还是要执行 AMSI 语句,这个用来绕过的语句本身也是要被 AMSI 检测的,所以要对这个绕过语句进行混淆处理。
这块严格来说不是绕过 AMSI,而是绕过 AMSI 对接的安全产品的规则,自带的 windows defender 的规则非常容易绕过。
攻击姿势
经过简单的分割测试,可以确定有如下规则
- 字符串
'AmsiUtils'
- 字符串
'amsiInitFailed'
- 同时出现
[Ref].Assembly.GetType
GetField
SetValue($null,$true)
powershell 语法极为灵活,下面简单列举几种绕过的方法。
使用 like 避免出现完整字符串 + 拆分成多条语句避免同时出现关键字
对方法名进行字符串拼接
拆分成变量
除了手动混淆,也可以使用专门的混淆工具,如:https://github.com/danielbohannon/Invoke-Obfuscation
不过要注意,一些混淆手法是对命令进行加密,再利用
Invoke-Expression
执行解密的命令,这样是无法绕过 AMSI 的,因为Invoke-Expression
执行的命令将会被 AMSI 检测,效果如下: 
检测方法
对于分割绕过,可以将同一 powershell 进程执行的多条命令合并后进行检测。
因为 AMSI 需要快速响应,所以很难使用高级检测方案,我的想法是阻断规则和告警规则可以分开,阻断规则就是高性能的正则表达式或者关键字,检测完成后直接返回给 AMSI,避免程序卡死,然后再进行相对耗时的高级检测,将检测结果发往服务端。
- 使用语法树判断 powershell 代码是否混淆,正常业务一般不会混淆 powershell 代码,所以检测到混淆行为就可以直接告警个低危。(也许需要对一些业务混淆加白)
- 静态反混淆
https://github.com/pan-unit42/public_tools/tree/master/powershellprofiler
- 动态反混淆 - powershell 沙箱
静态反混淆容易被一些动态特性击败,可以对 powershell 进行二开,做成 powershell 沙箱,真正的获取语句执行的操作。
0x01 降级攻击
降级PowerShell版本到 2.0 可以绕过 AMSI,因为这个版本的 powershell 还没有引入 AMSI 机制。
PowerShell 2.0 基于.NET CLR 2.0,对应的 .NET 版本是 2/3/3.5,这在现在的系统上预装的并不多,可以用以下命令查看安装的 .NET 版本。
早期的部分 win10 预装了 .NET 3.5
攻击姿势
使用
powershell.exe -version 2
改变版本。在脚本开头加入
#requires -version 2
,可以指定脚本使用 PowerShell 2.0 执行。检测方法
检测进程启动事件是否有
-version 2
参数。检测 powershell 脚本中是否有
#requires -version 2
。感觉只要有这种行为,就可以给个中低危告警了,现在正常业务用 powershell 2 的情况太少了。
0x02 修改注册表
对于 powershell、.NET 之外的 AMSI 检测,不少可以被注册表配置影响,比如 Jscript 对应的
Software\\Microsoft\\Windows Script\\Settings\\AmsiEnable
,wmi 对应的 Software\\Microsoft\\WBEM\\AmsiEnable
。需要注意的是,这块微软做过多次修改,能不能生效要针对具体的windows版本来分析。
需要注意的是,在早期 win10 中,读取的是
HKEY_CURRENT_USER
,现在读取的是HKEY_LOCAL_MACHINE
,修改相关的注册表需要管理员权限。此外,还可以直接删除注册表中注册的
Providers
来影响 AMSI 正常运作。HKLM:\SOFTWARE\Microsoft\AMSI\Providers
检测方法
监控修改相关注册表的行为。
0x03 反射
攻击姿势
PowerShell 将有关 AMSI 的信息存储在
System.Management.Automation.AmsiUtils
类内。通过反射与其进行交互。前面几篇分析过,powershell 在进行 AMSI 扫描时会对
amsiInitFailed
进行判断,如果为 true
则直接跳过扫描。那么可以使用反射访问 AmsiUtils
类中的amsiInitFailed
,将其直接设置为 true
。混淆后
前文还提到 AmsiOpenSession 的参数检查,如果
amsiSession
的指针或 amsiContext
为 NULL
,或者 amsiContext
的第二和第三个 _QWORD
为0,则返回错误代码 -2147024809
。那么就可以通过反射获取
amsiContext
的地址进行修改,将第二_QWORD
修改为0。在 win10上,还有个判断验证的是amsiContext
的第一个 DWORD 值是否为'AMSI'
,这个判断在 win11上被去除了。
在 win10 可以对amsiSession
赋值为NULL
进行绕过, 在 win11 上并不可以,使用 dnspy 分析代码可以发现 win11 上每次的amsiSession
都是从新从amsiContext
中取出的。
检测方法
- 使用反混淆分析语句
- hook
SetValue
,检测设置amsiInitFailed
值的行为。
0x04 修补
从内存中修补 Amsi.dll 也是常用的绕过的手法。
AmsiInitialize
AmsiInitialize
在执行命令前就已经执行,但可以修补后执行 AmsiUninitialize
重新触发 AmsiInitialize
。AmsiInitialize
较为复杂且版本差异大,攻击者一般不会考虑修补这个函数的逻辑来绕过,以下仅为研究使用。 (实战要修补的话应该也是修补为强制失败,见后文)
AmsiInitialize
存在如下汇编,可以通过修补满足两个跳转条件,强制跳转返回 80070057h
。以下脚本内容为获取内存地址、修改内存权限、修补对应内存
触发重新初始化,完成绕过
更通用的绕过脚本,强制返回错误代码
AmsiOpenSession
这是一个比较好的修补对象,在win10和win11上的变化很小,且逻辑较为简单,只要满足第一个跳转条件就可以直接返回报错。
将
test rdx,rdx
修补为 xor rax,rax
脚本如下
AmsiScanBuffer
逻辑比较复杂,可以将函数开头修补为以下内容,以强制其返回错误代码,稍微混淆了下避免特征被匹配。
检测方法
- 去混淆后识别攻击代码特征
- 扫描 Amsi.dll 中特定地址
性能比扫描整个 Amsi.dll 好点,不过攻击者能修改的位置是非常多的,比较容易被绕过。
- 内存完整性检查
- 对 Amsi.dll 的代码部分进行哈希处理,而不是对整个Amsi.dll模块进行哈希处理,与硬盘上 Amsi.dll 的代码部分进行比较。
- 考虑到性能问题,扫描频率不可能太高,攻击者有可能在完成攻击后将 amsi 修改回去(仅对短时间就能完成目的的攻击有效)。
- 瞬间修改 Amsi.dll 造成永久失效 (在win10上修改后再改回来可以永久失效,但 win11上不行,win11 每次执行时发现AMSI失败会重新初始化。
经过研究,只要修补AmsiInitialize
保证初始化成功但没有执行加载杀毒软件的dll,还是可以在win11瞬间修改造成永久失效的,但这样修补不仅复杂且很容易受 amsi.dll 版本影响。
- api监控,检测修改 Amsi.dll 内存权限、内存内容的调用。
0x05 ScriptBlock Shaulling
攻击姿势
简单来说,就是 PowerShell 中的所有的安全功能都只传递
ScriptBlock
的 Extent
,但实际上还可以通过 BeginBlock
和 EndBlock
执行代码。可以用以下方法来创建
BeginBlock
或 EndBlock
和 Extent
不一致的代码块利用方式
可以看到 AMSI 接收的结果和实际执行的命令并不一致

虽然这样也需要传入恶意命令,但与混淆那里的绕过不同的是,这里传入的恶意命令是字符串,不需要基于 powershell 语法混淆,可以随意进行混淆,解密后的代码不是通过Invoke-Expression
执行的,也就不用担心
检测方法
检测操控
ScriptBlockAst
的行为,正常业务程序几乎不可能用到这个特性。0x06 硬件断点
攻击姿势
参考文章:
检测方法(待验证)
参考文章:
0x07 修补其他程序
因为对 amsi.dll 的修改检测比较严格,所以出现了这种绕过思路。
修补杀毒软件的 amsi 提供程序对应的 dll。
修补调用 amsi.dll 的程序。
比如修补powershell:
参考文章
- 作者:fatekey
- 链接:https://blog.fatekey.icu//article/AMSI3
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。