password
status
date
icon
category
tags
slug
summary
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 的程序。
参考文章
- 作者:fatekey
- 链接:https://blog.fatekey.icu/article/AMSI3
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。