
Paracosme هو استغلال عن بُعد لفساد الذاكرة بدون أي نقرة (zero-click) يخترق نظام ICONICS Genesis64، وقد تم عرضه بنجاح على المسرح خلال مسابقة Pwn2Own Miami 2022.
Paracosme هو استغلال لفساد الذاكرة كتبته لاستهداف مجموعة Genesis64 الإصدار 10.97.1 من تطوير ICONICS لتحقيق تنفيذ التعليمات البرمجية عن بُعد.
تم عرض هذا الاستغلال خلال مسابقة Pwn2Own 2022 Miami التي أُقيمت في مؤتمر S4x22. يمكنك القراءة عنه في المشاركة في 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.
يمكنك العثور على كود الاستغلال في src/paracosme.py، وPoC لتفعيل الانهيار / التحقق مما إذا كنت متأثرًا في src/paracosme-poc.py، والحمولة التي يتم تنفيذها على الجهاز في src/payload.
أفضل طريقة لمعرفة ما إذا كنت متأثرًا هي تفعيل Page Heap لـ GenBroker64.exe، ثم إعادة تشغيل الخدمة، وإرفاق مصحح أخطاء بـ GenBroker64.exe، وتشغيل paracosme-poc.py ضد خادمك، وسترى انهيارات مثل تلك الموضحة أدناه:
تحتاج إلى إرفاق مصحح أخطاء بالعملية المستهدفة لملاحظة الانهيار، وإلا فإن التطبيق يتجاهله.
تم اختبار الاستغلال على Windows فقط، لكنه يجب أن يعمل أيضًا على منصات Linux:
impacket باستخدام: pip3 install impacketsc config lanmanserver start=disabled ثم أعد التشغيلsmbserver.py (جزء من أمثلة impacket) عن طريق: python src\smbserver.py -smb2support x binpython src\paracosme.py --target <ip>
لمزيد من التفاصيل، يرجى الرجوع إلى المشاركة في Pwn2Own ICS 2022 Miami: استغلال فساد ذاكرة عن بُعد بدون نقر في ICONICS Genesis64.
يستغل Paracosme مشكلة استخدام بعد التحرير (use-after-free) موجودة في عملية GenBroker64 لتحقيق تنفيذ التعليمات البرمجية عن بُعد على نظام Windows 21H2 x64.
بشكل عام، تستمع عملية 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;
}
الدالة مملة في معظمها لأنها تحتوي أيضًا على منطق لتفريغ الأنواع البسيطة، لكن ما لفت انتباهي هو VT_DISPATCH / VT_UNKNOWN.
ما هذا بحق الجحيم؟ يمكنك إرسال معرف فئة (class ID) لأي كائن COM يطبّق إما IPersistStream أو IPersistStreamInit وسيتم تحميله عن طريق استدعاء IPersistream::Load لتهيئة الكائن. على الرغم من أن هذا مفاجئ وميزة غريبة، إلا أنني لم أجد هذا مثيرًا للاهتمام من الناحية الأمنية لأنني سأحتاج إلى العثور على خطأ آخر في كائن COM متاح على Windows 10 الافتراضي.
الآن، دعونا ننظر عن كثب إلى الكود أدناه:
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();
}