通过Hijack DLL绕过AMSI

  • A+

译文声明
本文是翻译文章,文章原作者 Philippe Vogler,文章来源:https://sensepost.com
原文地址:https://sensepost.com/blog/2020/resurrecting-an-old-amsi-bypass/

前言

RingZer0开设的Red Teaming课程中,研究了DoubleAgent攻击手法。攻击者可以利用DoubeleAgent将任意代码注入到任何一个他想注入的进程中。并且由于注入发生在进程启动的一开始,因此攻击可以完全控制进程,而进程无法进行自我保护。我所关注的是反恶意软件扫描接口(AMSI)旁路绕过,这篇文章是对Cn33liz记录的原始DLL劫持方法的拓展,我的目标是找到一种逃避AMSI检测的新方法,我们只需要一个低权限用户可以进行写入的目录,即可绕过AMSI。

什么是AMSI?

AMSI为终端安全供应商提供了丰富的接口以帮助他们更好地对目标组件进行内存缓冲区安全扫描,或选择需要扫描的内容。例如常见的Windows Defender,Sophos和McAfee就使用了这个接口。支持AMSI的产品列表可以在这里找到。

从.NET Framework 4.8开始,AMSI还集成到该框架中,实现扫描程序集的目的。https://devblogs.microsoft.com/dotnet/announcing-the-net-framework-4-8/。

下面做一个关于AMSI简单的测试。我们在PowShell中输入敏感字符amsiutils,如果您的端点防护产品支持AMSI,并检测到了该字符串,PowerShell将会报错,提示该字符串是恶意的。
::: hljs-center

1.png
AMSI恶意字符串检测的示例
:::

有关AMSI的更多信息,请访问https://docs.microsoft.com/zh-cn/windows/win32/amsi/antimalware-scan-interface-portal。时至今日,已经存在了许多绕过AMSI的方法,而且在GitHub中有详尽的记录https://github.com/S3cur3Th1sSh1t/Amsi-Bypass-Powershell

复现劫持手法

在进行技术探讨之前,我们先了解一下运行PowerShell脚本时都进行了哪些API调用,打开/创建了哪些注册表项、加载了DLL等等。通过系统监视软件ProcMon的显示,它会首先尝试在其当前路径中查找并加载amsi.dll。如果在当前路径中没有找到dll文件,就会从System32路径中加载dll。这种情况就给了我们的DLL劫持提供了一丝机会。
::: hljs-center

2.png
ProcMon检测到可能的DLL劫持。
:::

根据Cn33liz博客中描述的方法,我在ProcMon显示的路径下放置了一个伪造的amsi.dll,试图造成DLL劫持。准备工作完成后,我再次运行了PowerShell。
::: hljs-center

3.png
DLL劫持后导致PowerShell损坏。
:::

如上图所示,伪造的DLL致使PowerShell损坏。

下面关于PowerShell 5中存在的DLL劫持漏洞的TimeLine:

*于2016年3月28日报告给Microsoft MSRC
*从Microsoft的角度来看,AMSI/AntiVirus并不是传统的安全边界,并且由于此类DLL劫持漏洞不会导致远程代码执行或特权提升,因此他们无法发布正式公告。然而,他们肯定有兴趣进一步探索如何去改进他们的反恶意软件产品,所以我期望会在未来的版本中得到修复。
https://cn33liz.blogspot.com/2016/05/bypassing-amsi-using-powershell-5-dll.html
**

根据我观察的结果,Microsoft最终对DLL劫持进行了一些加固。

常见DLL劫持问题

其实,在实际攻击中,DLL劫持不能简单粗暴的采取DLL替代的方法。因为这样做不仅会破坏进程的连续性,而且可能会在没有任何提示的情况下悄然失败。我们还需要了解被劫持DLL文件的原始代码结构,并了解该DLL实现的功能。

幸运的是,Microsoft已经记录了AMSI导出的函数 ,这大大简化了重构AMSI DLL的工作量。根据MSDN的显示,该dll实现了7个功能:

::: hljs-center

11.png
MSDN中的AMSI功能列表。
:::

一个最简单易行的方法是将DLL所有的导出函数定义为Void类型,这样这些存根函数在执行后不会返回结果,解决了程序连续性的问题。如果调用进程只是存储一个包含DLL导出函数的列表以备将来使用,或者只是验证加载的DLL中是否存在预期函数,则此方法可能有效。遗憾的是,这些导出函数的返回值会影响进程上下文的执行,当AMSI DLL缺少函数实现时,PowerShell.exe似乎没有完全加载。此时,如果不能访问原始DLL,通常要通过大量的逆向和调试工作来构建适当的DLL。或者,DLL代理可能是一个不错的选择。

构建自定义AMSI DLL

当在VS中创建DLL项目时,会提供一个默认模板。当调用的进程运行时或调用LoadLibrary函数时,首先会执行一个名为DllMain的DLL的入口点。当系统使用DLL_process_ATTACH标志调用DllMain时,DLL将被加载到当前进程的虚拟地址空间中。这也是我们进行函数定义的地方。通过阅读有关AMSI的MSDN文档后,我重构了函数,使它们接受正确的参数:
::: hljs-center

5.png
Visual Studio指示几个未定义函数参数。
:::

在编译项目之前,Visual Studio会提示几个变量未定义,,即HAMSICONTEXT,HAMSISESSION,AMSI_RESULT和r。在MSDN上,我只找到了关于AMSI_RESULT的定义,它是一个枚举类型。我从另外一篇研究AMSI的文章中获知了HAMSICONTEXT存根结构。不幸的是,我找不到任何关于HAMSISESSION和r的任何信息,我为它们构造了如下两个存根结构:
::: hljs-center

6.png
结构体定义。
:::

这样构造空结构体虽然不会与数据进行任何有意义的交互,但是足以让调用成功。

至此,所有典型的导出函数及其参数均已定义,DLL代码在VS中也不会发出警告信息。以下是完整的POC:
```
// dllmain.cpp : Defines the entry point for the DLL application.

include "pch.h"

include "iostream"

BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
LPCWSTR appName = NULL;
typedef struct HAMSICONTEXT {
DWORD Signature; // "AMSI" or 0x49534D41
PWCHAR AppName; // set by AmsiInitialize
DWORD Antimalware; // set by AmsiInitialize
DWORD SessionCount; // increased by AmsiOpenSession
} HAMSICONTEXT;
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
} AMSI_RESULT;

    typedef struct HAMSISESSION {
        DWORD test;
    } HAMSISESSION;

    typedef struct r {
        DWORD r;
    };

    void AmsiInitialize(LPCWSTR appName, HAMSICONTEXT * amsiContext);
    void AmsiOpenSession(HAMSICONTEXT amsiContext, HAMSISESSION * amsiSession);
    void AmsiCloseSession(HAMSICONTEXT amsiContext, HAMSISESSION amsiSession);
    void AmsiResultIsMalware(r);
    void AmsiScanBuffer(HAMSICONTEXT amsiContext, PVOID buffer, ULONG length, LPCWSTR contentName, HAMSISESSION amsiSession, AMSI_RESULT * result);
    void AmsiScanString(HAMSICONTEXT amsiContext, LPCWSTR string, LPCWSTR contentName, HAMSISESSION amsiSession, AMSI_RESULT * result);
    void AmsiUninitialize(HAMSICONTEXT amsiContext);
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
    break;
}
return TRUE;

}
```

实施劫持

我们将构造好的DLL植入ProcMon所示的路径中,然后运行PowerShell。不幸的是,我们失败了。
::: hljs-center

7.png
AMSI旁路失败。

:::

虽然DLL已经成功加载,并且PowerShell也可以正常运行。但是AMSI仍然阻止了恶意命令的执行。看来微软确实对原始的DLL劫持进行了修复。

从ProcMon显示结果中,我发现了一个奇怪的现象。PowerShell在加载了
C:WindowsMicrosoft.NETassemblyGAC_MSILSystem.Management.Automationv4.0_3.0.0.0__31bf3856ad364e35
路径下的AMSI.DLL后,仍会试图加载C:WindowsSystem32WindowsPowerShellv1.0下的AMSI.DLL。但是由于此路径下缺少该DLL,它将继续遵循典型的Windows DLL加载顺序,并尝试从C:WindowsSystem32加载它。很好,这意味着另一个潜在的DLL劫持的存在。

最终,我们在C:WindowsMicrosoft.NETassemblyGAC_MSILSystem.Management.Automationv4.0_3.0.0.0__31bf3856ad364e35C:WindowsSystem32WindowsPowerShellv1.0路径中中都放置了自定义的AMSI DLL ,成功绕过了AMSI。
::: hljs-center

8.png
成功绕过AMSI

:::

在后期经过测试后,我发现仅劫持DLL C:WindowsSystem32WindowsPowerShellv1.0就足够了。
::: hljs-center

9.png
使用单个DLL劫持即可成功绕过AMSI。

:::

我们不难发现,基于DLL搜索顺序,PowerShell总是首先尝试从加载应用程序的目录中加载AMSI DLL。这就意味着作为低权限的用户,绕过AMSI只是将PowerShell可执行文件和AMSI DLL复制到用户可写文件夹中即可。
::: hljs-center

10.png
以低特权用户身份绕过PowerShell的AMSI。

:::

预防

在准备这篇博文时,Windows Defender似乎增加了一些功能,实现从低权限用户的角度来检测DLL劫持。它规定在尝试复制amsi.dll到其他文件夹,需要管理员权限。因此,更新Windows Defender应该足以防止POC。这种改动可能发生在2020年6月28日至2020年6月30日之间。
::: hljs-center

14.jpg
Windows Defender更新描述

:::

结论

在进行最新的Windows Defender以及其他终端安全防护产品时更新之前,无论当前主机上访问权限如何,用户都能轻松绕过AMSI。其他脚本引擎(如jscript或cscript)不受此DLL劫持的影响,因为它们会直接从System32文件夹加载AMSI。尽管存在许多其它手法绕过AMSI,但有时简单的hijack即可实现目的。

相关推荐: 一个CS马伪装下的loader样本分析

0x01 开源情报收集 样本下载链接:https://app.any.run/tasks/ffc1ecff-e461-4474-8352-551db7e7b06f/ 常用平台:VT,微步,哈勃 app.any.run, joesandbox ::: hljs-…