
Paracosme는 Pwn2Own Miami 2022 대회 무대에서 성공적으로 시연된, 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 플랫폼에서도 작동해야 합니다:
impacket을 설치합니다: pip3 install impacketsc config lanmanserver start=disabled로 컴퓨터에서 실행 중인 SMB 서버를 끄고 재부팅합니다.smbserver.py(impacket 예제의 일부)를 사용하여 smbserver를 시작합니다: python src\smbserver.py -smb2support x binpython src\paracosme.py --target <ip>로 익스플로잇을 시작합니다.
자세한 내용은 Pwn2Own ICS 2022 Miami 참가: ICONICS Genesis64의 제로 클릭 원격 메모리 손상 악용을 참조하십시오.
Paracosme는 Windows 21H2 x64 시스템에서 원격 코드 실행을 달성하기 위해 GenBroker64 프로세스에서 발견된 use-after-free 문제를 악용합니다.
대략적으로, GenBroker64 프로세스는 TCP 포트 38080에서 수신 대기하며 클라이언트와 핸드셰이크를 완료한 후 다양한 패킷을 역직렬화할 수 있습니다. 제가 찾은 문제는 네트워크 소켓에서 VARIANT를 읽는 코드에 있습니다. 기본적으로 variant는 타입과 값입니다. 이 함수는 언뜻 보기에는 잘 작성된 것처럼 보이며 특정 타입만 언팩하려는 노력을 기울입니다. 이는 다음과 같습니다:
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에서 찾을 수 있습니다. 여기 그 코드가 있습니다:
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 객체에서 또 다른 버그를 찾아야 하기 때문에 보안 관점에서 그다지 흥미롭게 느껴지지 않았습니다.
이제 아래 코드를 자세히 살펴보겠습니다:
SCODE sc = CoCreateInstance(clsid, NULL,
CLSCTX_ALL | CLSCTX_REMOTE_SERVER,
pSrc->vt == VT_UNKNOWN ? IID_IUnknown : IID_IDispatch,
(void**)&pSrc->punkVal); <-------------- [[0]]