对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

admin 2025年7月9日01:10:03评论0 views字数 6674阅读22分14秒阅读模式

Love for Microsoft Component Object Model, RPC and AMSI attack surface 

免责声明:本博客文章仅用于教育和研究目的。提供的所有技术和代码示例旨在帮助防御者理解攻击手法并提高安全态势。请勿使用此信息访问或干扰您不拥有或没有明确测试权限的系统。未经授权的使用可能违反法律和道德准则。作者对因应用所讨论概念而导致的任何误用或损害不承担任何责任。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

内容概要

  • 对不熟悉 COM 的读者:本文涉及大量 COM 技术,如果你不了解 COM,请先阅读 《Inside COM》 by Dale Rogerson,然后再回来!
  • 通过攻击 AMSI 的 COM 架构来绕过检测,包括破坏 vftable 和 IID/GUID
  • 针对 Defender 和 AMSI 之间的 RPC 通信通道进行攻击

AMSI 组件分析

如何识别一个组件?

实现 COM 接口的组件需要在 Windows 注册表中注册为 ComputerHKEY_CLASSES_ROOTCLSID{<GUID>}。这是一种便捷的定位 dll/exe 组件的方式,使得像 CoCreateinstance()这样的 API 可以通过库中的 GUID 值获取并实例化接口。

本节我们将讨论 AMSI 的 COM 特性。为了开始对其内部机制的研究,我们先检查注册表以确认 AMSI 确实是一个组件。手动检查 CLSID 键中的条目将是一项繁琐(且无意义)的工作,我编写了一个简单的 Python 代码来完成这个任务!

如下图所示,我们可以看到组件的 CLSID 和文件位置。这意味着 AMSI 是通过 COM 实现的。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

以下是我分享的 reg.py 代码

import winregimport itertoolsimport argparseparser = argparse.ArgumentParser()parser.add_argument("-m""--mod",nargs="+"type=str)args = parser.parse_args()ar = args.modaReg = winreg.ConnectRegistry(None, winreg.HKEY_CLASSES_ROOT)aKey = winreg.OpenKey(aReg, r'CLSID' )for i in itertools.count():    try:        aValue_name = winreg.EnumKey(aKey, i)        oKey = winreg.OpenKey(aKey, aValue_name )        infokey = winreg.QueryInfoKey(oKey)        for x in range(infokey[0]):            subkey = winreg.EnumKey(oKey, 0)            kn = "CLSID" +"\" +aValue_name+"\"+subkey            sKey = winreg.OpenKey(aReg, kn)            if subkey == "InprocServer32":                sValue = winreg.QueryValueEx(sKey, None)                #print(sValue[0])                if ar[0in sValue[0]:                    print(f"Module   :  {sValue[0]}")                    print(f"CLSID    :   {aValue_name}")    except Exception as e:        if "WinError 259" in str(e):            break        continue

下图展示了 amsi.dll 的导出函数。核心的 AMSI 功能通过以 Amsi* 开头的函数向客户端提供。高亮显示的函数用于 COM 操作。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

唯一以纯文本形式公开的是头文件 amsi.h。这足以让我们理解代码结构并识别接口和 IID/GUID 值。CAntimalware 类实现了 amsi.dll 导出的函数。该类具有唯一标识符 fdb00e52-a214-4aa1-8fba-4357bb0072ec,我们将在后续章节中经常看到这个 ID。这个 ID 与我们在 reg.py 输出中看到的 ID 相同。该类不是一个组件,它只是通过实例化组件调用 AMSI COM 方法的包装器,我们稍后会看到这一点。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

现在让我们检查所有 AMSI 接口,在众多接口中,下面展示的 IAntimalware2是最重要的一个。该接口继承自 IAntimalware。请注意该接口的接口 ID,我们将在后续章节详细讨论这个接口。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

下图展示了 IAntimalwa的接口声明,其中包含有趣的方法——Scan。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

针对 vfTable 的攻击

组件实现的方法保存在内存中的一个特殊表中,称为虚表 (virtual table) 或虚函数表 (virtual function table)。vftable 中的每个条目都是指向方法实现的指针。作为攻击者,这是一个有趣的目标,如果表中的指针条目被攻击者提供的代码覆盖会怎样?

编写 AMSI 扫描器

在深入探讨之前,让我们编写一个非常基础的 AMSI 客户端来进行试。客户端代码如下所示。在我们的测试中,我们将 Seatbelt.exe 提供给 AMSI。

#include<iostream>#include<windows.h>#include<amsi.h>/*                  HRESULT AmsiInitialize(                  [in]  LPCWSTR      appName,                  [out] HAMSICONTEXT *amsiContext                  );                  HRESULT AmsiScanBuffer(                  [in]           HAMSICONTEXT amsiContext,                  [in]           PVOID        buffer,                  [in]           ULONG        length,                  [in]           LPCWSTR      contentName,                  [in, optional] HAMSISESSION amsiSession,                  [out]          AMSI_RESULT  *result                  );*/typedefHRESULT(WINAPI* AmsiInitializeT)(LPCWSTR, HAMSICONTEXT*);typedefHRESULT(WINAPI* AmsiScanBufferT)(HAMSICONTEXT, PVOID, ULONG, LPCWSTR, HAMSISESSION, AMSI_RESULT*);//<Seatbelt, Version = 1.0.0.0, Culture = neutral, PublickeyToken = null>const DWORD assembly_size = 610304;const BYTE assembly[] = { 0x4d0x5a0x900x000x030x000x000x000x040x000x000x000xff0xff0x000x00,    0xb80x000x000x000x000x000x000x000x400x000x000x000x000x000x000x00,    /* truncated*/    0x000x000x000x000x000x000x000x000x000x000x000x000x000x000x000x00,    0x000x000x000x000x000x000x000x000x000x000x000x000x000x000x000x00};voidmain(){ HAMSICONTEXT AmsiCtx;    AMSI_RESULT amsiResult; HMODULE hAmsi = LoadLibrary(L"amsi"); AmsiInitializeT AmsiInitialize = (AmsiInitializeT)GetProcAddress(hAmsi, "AmsiInitialize");    AmsiScanBufferT _AmsiScanBuffer = (AmsiScanBufferT)GetProcAddress(hAmsi, "AmsiScanBuffer"); HRESULT hr = AmsiInitialize(L"Test", &AmsiCtx); if(FAILED(hr)) {  std::cout << "Failed"; }    hr = _AmsiScanBuffer(AmsiCtx, (PVOID)assembly, assembly_size, L"MAL_BUFF"nullptr, &amsiResult);    if (FAILED(hr))    {        std::cout << "Failed";    }}

AMSI 初始化与 HAMSICONTEXT

AMSI 初始化是反恶意软件扫描接口的重要步骤,它将帮助创建 CAmsiAntimalware组件的实例。该组件实现了扫描功能。我们将在后续章节深入探讨。AmsiInitialize的函数原型如下所示。AMSI 客户端在调用 AmsiScanBuffer等其他 API 之前必须先调用此 API。这是因为 AMSI 函数需要客户端提供 HAMSICONTEXT

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

回到我们的 AMSI 客户端,在调用 AmsiInitialize()后中断执行,检查变量 AmsiCtx的指针。成功调用 AmsiInitialize()后,我们将收到一个指向 HAMSICONTEXT类型内存块的指针。有趣的是,这种类型没有官方文档。如下图所示,amsi context 位于 0x0200F0B73580

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

MSDN 文档中提到 HAMSICONTEXT只是一个句柄。但我们会发现事实并非如此。它不仅仅是一个简单的句柄,而是 CAmsiAntimalware组件的实例。为什么这个信息如此重要?让我们一探究竟! 🙂

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

如前所述,HAMSICONTEXT的内容如下所示。由于我们不知道 HAMSICONTEXT的结构[或大小],理解其内容有些困难。但当我们开始调用一些 AMSI 函数时,就会有所了解。让我们通过 amsi.dll!AmsiScanBuffer()来探索这个神秘的 HAMSICONTEXT类型!

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

AmsiScanBuffer

在我们的 AMSI 扫描客户端中,Seatbelt 程序集通过 AmsiScanBuffer()API 传递给 AMSI。AmsiInitialize()创建的 amsi context 作为第一个参数传递给该函数。AmsiScanBuffer的 IDA 反编译代码如下所示。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

CAmsiBufferStream是实现IAmsiStream接口的组件。调用CAmsiBufferStream::CAmsiBufferStream()会使用传递给AmsiScanBuffer()的数据实例化相应对象。尾调用看起来非常有趣,我们可以看到使用amsiContext引用的内存,这是由AmsiInitialize()创建的HAMSICONTEXT类型的上下文。尾调用的 IDA 反汇编如下所示。现在代码更清晰了,mov rcx,[rbx + 0x10]amsiContext偏移 0x10 处检索指针并存储在 rcx 中。这个指针再次被解引用以检索另一个指针,该指针再次使用偏移量 0x18 解引用并存储在 rax 中。最后通过调用 rax 来执行尾调用。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

要查看使用 amsiContext的指针解引用和尾调用的实际操作,我们需要运行如下所示的代码。以下代码与上述 IDA 反汇编相同。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

虚函数表 (vftable)

当我们单步执行 mov rcx, [rbc + 0x10]时,存储在 rcx 中的值如下所示。借助符号,我们可以看到 amsiContext + 0x10指向 CAmsiAntiMalware的 vfTable。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

让我们重新审视 AmsiCtx 的内存布局,根据我们的分析,CAmsiAntiMalware实例位于偏移量 0x10 (16)处。这意味着我们可以使用 AmsiInitialize()创建的 HAMSICONTEXT"句柄" 来访问 CAmsiAntiMalware的 vfTable。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

借助符号,我们可以清楚地看到地址 0x200F0CA1720指向 CAmsiAntiMalware::vfTable

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

回到 AmsiScanBuffer的尾调用,代码 move rax, [rax + 0x18]将 CAmsiAntiMalware::Scan()的地址存储在 rax 中。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

CAmsiAntiMalware::vfTable如下所示。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

当单步进入尾调用时,我们可以看到 jmp rax,rax指向 CAmsiAntiMalware::Scan()。所以基本上 amsi.dll!AmsiScanBuffer调用了 COM 方法 CAmsiAntiMalware::Scan()

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

尾调用前存储在 rax 中的数据如下所示。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

vfTable 存储在只读部分。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

现在我们已经看到了 vftable,并且知道 CAmsiAntiMalware::Scan() 的位置和表中的指针条目,为什么不直接将表中的指针条目指向 amsi.dll 中的某个 ret gadget 呢? 🙂 调用 VirtualProtect来授予 amsi.dll 写访问权限有点冒险。

针对 AMSI 初始化的攻击

破坏 CLSID

AmsiInitialize()代码封装了对象创建和各种检查,用于识别 AMSI 组件的 CLSID 存储在对象映射 (ATL) 中。以下代码检索值为 fdb00e52-a214-4aa1-8fba-4357bb0072ec的 CLSID_AntiMalware。如下图所示,代码从第一个四字节 FD B0 0E 52开始验证 CLID。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

让我们仔细看看内存中的 ATL 对象映射 __pobjmap_CAmsiAntimalware__pobjmap_CAmsiAntimalware映射保存了指向映射条目的指针。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

映射条目如下所示,这是指向位于 0x7FFD16ED07A0的实际数据的另一个指针。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

CLSID 存储在 0x7FFD16ED07A0

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

映射条目指针位于 .data 段,该段具有读写保护。这意味着我们不需要调用 VirtualProtect来使内存页可写,就可以破坏指针。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

找到此映射条目指针的简单方法是在 .data 段中搜索 0x7FFD16ED07A0的逆序字节。搜索返回的内存地址就是映射条目指针。现在我们可以简单地用指向 clsid 起始地址后 4 个字节的地址覆盖此指针。

这将导致初始化失败,AmsiInitialize() 将返回如下所示的错误代码。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

破坏 IID

初始化的重要步骤是创建 IAntimalware2接口对象,如下所示。IAntimalware2的 guid 被传递给 r8 并调用 ATL::CComClassFactory::CreateInstance()

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

篡改 GUID 指针将导致以下错误。不幸的是,可写段中没有指针可以篡改此 guid,您将不得不依赖 VirtualProtect。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

Defender RPC 调用

虽然这与 AMSI 没有直接关系,但出于好奇,我想探究杀毒软件 (AV) 和 AMSI 之间的通信机制。AMSI 会加载额外的 DLL 文件,如 mpoav.dll 和 mpclient.dll,这些都是与 Defender 相关的文件,并会调用各种内部 API,如下图所示。在下面的堆栈跟踪中,有趣的是我们可以看到 mpclient.dll(Defender 客户端)中的一个函数正在向 Defender 服务发起 RPC 调用。

对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sec

这让我想到,如果我们干扰 RPC 会发生什么? Andrea Bocchetti 在这篇文章中已经回答了这个问题,他称这种技术为 Ghosting AMSI。

原文始发于微信公众号(securitainment):对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 – Sabotage Sec

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年7月9日01:10:03
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   对微软组件对象模型 (COM)、RPC 和 AMSI 攻击面的研究 - Sabotage Sechttps://cn-sec.com/archives/4233879.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息