
Paracosme 是一个零点击远程内存破坏漏洞利用程序,可攻陷 ICONICS Genesis64,该漏洞利用已在 Pwn2Own Miami 2022 竞赛期间于舞台上成功演示。
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 平台上运行:
impacket:pip3 install impacketsc config lanmanserver start=disabled 关闭机器上运行的任何 SMB 服务器,然后重新启动smbserver.py(impacket 示例的一部分)启动 smbserver:python src\smbserver.py -smb2support x binpython 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 由一个类型和一个值组成。该函数乍一看写得不错,并且尽力只解包某些类型。其代码如下:
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 中找到它,其内容如下:
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。
现在,让我们仔细看看下面的代码:
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);
}