
CVE-2026-43783 के लिए प्रूफ-ऑफ-कॉन्सेप्ट और तकनीकी राइटअप, जो DesktopServicesHelper XPC के माध्यम से मनमाने chown द्वारा रूट प्राप्त करने वाला macOS स्थानीय विशेषाधिकार उन्नयन है।
मूल रूप से PT SWARM या andrd3v.github.io पर प्रकाशित
Finder जिस डेमन का उपयोग iCloud फ़ाइलों पर अनुमतियों को "मरम्मत" करने के लिए करता है, वह किसी भी चीज़ पर अनुमतियों की मरम्मत करने के लिए तैयार निकला - सिस्टम निर्देशिकाओं सहित। इस लेख में हम देखेंगे कि DesktopServicesHelper अंदर से कैसे काम करता है और क्यों एक एकल XPC अनुरोध रूट प्राप्त करने के लिए पर्याप्त था।
मैं com.apple.private.tcc.allow वाले लक्ष्यों की तलाश कर रहा था जिनका मान kTCCServiceSystemPolicyAllFiles हो और DesktopServicesHelper ने मेरा ध्यान खींचा। DesktopServicesHelper /System/Library/PrivateFrameworks/DesktopServicesPriv.framework/Versions/A/Resources/DesktopServicesHelper पर स्थित है, और Finder इसका उपयोग उन फ़ाइल संचालनों के लिए करता है जिनके लिए उन्नत विशेषाधिकारों की आवश्यकता होती है: फ़ाइलों को स्थानांतरित करना, अनुमतियाँ सेट करना, Trash के साथ काम करना।

बाइनरी को IDA में लोड किया और start() पर गया। सामान्य टाइमर और क्यू सेटअप के बाद, यह एक XPC लिसनर बनाता है - डेमन का प्रवेश बिंदु। लिसनर एक विशिष्ट नाम के अंतर्गत एक mach सेवा पंजीकृत करता है और आने वाले कनेक्शनों को सुनना शुरू करता है। कोई भी प्रक्रिया जो यह नाम जानती है, कनेक्ट कर सकती है और अनुरोध भेज सकती है। यहाँ मुख्य भाग है:
// get the service name
v16 = (const char *)sub_1000709F4(a1: 0);
// register the listener
mach_service = xpc_connection_create_mach_service(name: v16, targetq: nullptr, flags: 1u);
// set the incoming event handler
xpc_connection_set_event_handler(connection: mach_service, handler: &stru_1000B4B48);
xpc_connection_resume(connection: mach_service);
// start the event loop
CFRunLoopRun();
फ़ंक्शन sub_1000709F4 mach सेवा नाम लौटाता है। start() में इसे तर्क 0 के साथ कॉल किया जाता है, जिसका अर्थ है कि डेमॉन com.apple.DesktopServicesHelper नाम के अंतर्गत पंजीकृत होता है।
const char *__fastcall sub_1000709F4(int a1)
{
if ( a1 != 0 )
return "com.apple.DesktopServicesScriptingHelper";
else
return "com.apple.DesktopServicesHelper";
}
stru_1000B4B48 एक handler block (callback) है जिसे daemon प्रत्येक आने वाले event के लिए invoke करता है। XPC message-आधारित है: client keys और values वाला एक dictionary बनाता है, उसे daemon को भेजता है, और daemon सामग्री को parse करके तय करता है कि क्या करना है। सभी आने वाले events इस handler में पहुँचते हैं। आइए अंदर देखें।

block का invoke function sub_100032044 है। नए connection पर, daemon सबसे पहले client का euid और egid प्राप्त करता है - effective user ID और group ID - अर्थात् उन user और group के identifiers जिनके अंतर्गत connecting process चल रहा है:
euid = xpc_connection_get_euid(connection: v8);
egid = xpc_connection_get_egid(connection: v8);
sub_100071268(a1: euid, a2: egid);
फिर यह कनेक्शन पर आने वाले संदेशों के लिए एक हैंडलर असाइन करता है:
v15[2] = sub_10003AB2C; // block's invoke function
xpc_connection_set_event_handler(connection: v14, handler: v15);
xpc_connection_resume(connection: v14);
sub_10003AB2C - यह वह जगह है जहाँ क्लाइंट से वास्तविक XPC अनुरोध आते हैं। अब हमें यह समझने की आवश्यकता है कि डेमन कैसे तय करता है कि किन अनुरोधों को निष्पादित करना है और किन्हें अस्वीकार करना है।
sub_10003AB2C मुख्य डिस्पैचर है। यह वह जगह है जहाँ डेमन तय करता है कि किसी अनुरोध के साथ क्या करना है। फ़ंक्शन बड़ा है, तो चलिए इसे तोड़ते हैं।
सबसे पहले, यह संदेश से "request" स्ट्रिंग निकालता है - वह कमांड नाम जिसे क्लाइंट निष्पादित करना चाहता है:
reply = xpc_dictionary_create_reply(original: v12);
string = xpc_dictionary_get_string(xdict: v12, key: "request");
// ...
// logging: "Got Request '%{public}@'"
फिर डेमन कनेक्ट हो रहे प्रोसेस का ऑडिट टोकन प्राप्त करता है और जाँचता है कि यह App Sandbox के अंदर चल रहा है या नहीं। App Sandbox एक macOS एप्लिकेशन आइसोलेशन तंत्र है जो फ़ाइल सिस्टम, नेटवर्क और अन्य संसाधनों तक पहुँच को प्रतिबंधित करता है। यहीं से शाखाएँ बनती हैं:
xpc_dictionary_get_audit_token(a1: v12, a2: buf);
qmemcpy(__p, buf, sizeof(__p));
if ( (unsigned int)sandbox_check_by_audit_token(a1: __p, a2: 0, a3: 0) != 0 )
{
// client is sandboxed - check against allowlist
if ( (sub_10003B25C(a1: &v32, a2: "Handshake") & 1) == 0
&& (sub_10003B25C(a1: &v32, a2: "exit") & 1) == 0
&& (sub_10003B25C(a1: &v32, a2: "SetDefaultPermissionOnHomeSubdirectories") & 1) == 0
&& (sub_10003B25C(a1: &v32, a2: "MoveAsideICloudToArchiveInHome") & 1) == 0
&& (sub_10003B25C(a1: &v32, a2: "RepairPermissionsForCloudItems") & 1) == 0
&& (sub_10003B25C(a1: &v32, a2: "CopyClientActive") & 1) == 0
&& (sub_10003B25C(a1: &v32, a2: "DeleteItem") & 1) == 0 )
{
sub_100034250(a1: v12, a2: &v32); // not in the list - kill the daemon
}
}
else
{
// client is not sandboxed - check entitlement
*(_QWORD *)buf = sub_1000343C0(a1: v12, a2: CFSTR("com.apple.private.tcc.allow"));
v19 = (const __CFArray *)sub_10003B1E0();
v20 = v19;
if ( v19 == nullptr
|| (v35.length = CFArrayGetCount(theArray: v19),
v35.location = 0,
CFArrayContainsValue(theArray: v20, range: v35, value: CFSTR("kTCCServiceSystemPolicyAllFiles")) == 0) )
{
sub_100034250(a1: v12, a2: &v32); // no entitlement - kill the daemon
}
}
यदि क्लाइंट सैंडबॉक्स्ड है - तो अनुरोध नाम को सात स्ट्रिंग्स की allowlist के विरुद्ध जाँचा जाता है। यदि यह मेल खाता है - तो जाँच पास हो जाती है और अनुरोध आगे बढ़ जाता है। यदि नहीं - तो sub_100034250 डेमन को मार देता है।
यदि क्लाइंट सैंडबॉक्स्ड नहीं है - तो डेमन com.apple.private.tcc.allow entitlement की जाँच करता है जिसका मान kTCCServiceSystemPolicyAllFiles है। entitlement न होना भी मृत्यु का कारण है।
जाँच पास करने के बाद, अनुरोध दो हैंडलर तालिकाओं से होकर गुज़रता है। पहले sub_10002F7C8, फिर sub_10002A194:
// first table (8 commands, two arguments)
v21 = sub_10002F7C8(a1: buf);
// ...
if ( v21 != 0 )
v22(a1: v12, a2: v11); // found - invoke
// second table (21 commands, three arguments)
v27 = sub_10002A194(a1: buf);
// ...
if ( v27 != nullptr )
v27(a1: v12, a2: reply, a3: v11); // found - invoke
अब आइए हैंडलर तालिकाओं को देखते हैं। पहली तालिका (sub_10002F7C8):
__int64 __fastcall sub_10002F7C8(__int64 a1)
{
__int64 result; // x0
void **v3; // x21
void **v4; // x8
_BYTE v5[32]; // [xsp+8h] [xbp-128h] BYREF
__int64 v6; // [xsp+28h] [xbp-108h] BYREF
__int64 v7; // [xsp+48h] [xbp-E8h] BYREF
__int64 v8; // [xsp+68h] [xbp-C8h] BYREF
__int64 v9; // [xsp+88h] [xbp-A8h] BYREF
__int64 v10; // [xsp+A8h] [xbp-88h] BYREF
__int64 v11; // [xsp+C8h] [xbp-68h] BYREF
__int64 v12; // [xsp+E8h] [xbp-48h] BYREF
__int64 v13; // [xsp+108h] [xbp-28h] BYREF