Skip to content
KitploitKITPLOIT
HerramientasExploitsBlog
Log in
Enviar
HerramientasExploitsBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

FeedsContactoPrivacidad© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-43783 — Proof-of-concept and technical writeup for CVE-2026-43783, a macOS local privilege escalation via DesktopServicesHelper XPC arbitrary chown to gain root. | Kitploit
Herramientas/GitHubGitHub/andrd3v/cve-2026-43783
Privilege EscalationVulnerability AnalysisExploitationReverse EngineeringPenetration TestingBinary AnalysisPapers & Research
GitHubandrd3v/cve-2026-43783

CVE-2026-43783

Proof-of-concept and technical writeup for CVE-2026-43783, a macOS local privilege escalation via DesktopServicesHelper XPC arbitrary chown to gain root.

Ver Repositorio
21522hace 17 díasRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
Contenido no disponible en el idioma solicitado. Mostrando versión en inglés.

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

Originally published on PT SWARM or andrd3v.github.io

The daemon that Finder uses to "repair" permissions on iCloud files turned out to be willing to repair permissions on anything - including system directories. In this article we'll look at how DesktopServicesHelper works under the hood and why a single XPC request was enough to get root.

Inside DesktopServicesHelper

I was looking for targets with com.apple.private.tcc.allow with the value kTCCServiceSystemPolicyAllFiles and DesktopServicesHelper caught my eye. DesktopServicesHelper lives at /System/Library/PrivateFrameworks/DesktopServicesPriv.framework/Versions/A/Resources/DesktopServicesHelper, and Finder uses it for file operations that require elevated privileges: moving files, setting permissions, working with the Trash.

Entitlements DesktopServicesHelper

Loaded the binary into IDA and went to start(). Past the usual timer and queue setup, it creates an XPC listener - the daemon's entry point. The listener registers a mach service under a specific name and starts listening for incoming connections. Any process that knows this name can connect and send a request. Here's the key part:

// 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();

The function sub_1000709F4 returns the mach service name. In start() it's called with argument 0, meaning the daemon registers under the name com.apple.DesktopServicesHelper.

const char *__fastcall sub_1000709F4(int a1)
{
  if ( a1 != 0 )
    return "com.apple.DesktopServicesScriptingHelper";
  else
    return "com.apple.DesktopServicesHelper";
}

stru_1000B4B48 is a handler block (callback) that the daemon invokes for every incoming event. XPC is message-based: the client constructs a dictionary with keys and values, sends it to the daemon, and the daemon parses the contents and decides what to do. All incoming events end up in this handler. Let's look inside.

stru_1000B4B48 in IDA

The block's invoke function is sub_100032044. On a new connection, the daemon first retrieves the client's euid and egid - effective user ID and group ID - the identifiers of the user and group under which the connecting process runs:

euid = xpc_connection_get_euid(connection: v8);
egid = xpc_connection_get_egid(connection: v8);
sub_100071268(a1: euid, a2: egid);

Then it assigns a handler for incoming messages on the connection:

v15[2] = sub_10003AB2C;  // block's invoke function
xpc_connection_set_event_handler(connection: v14, handler: v15);
xpc_connection_resume(connection: v14);

sub_10003AB2C - this is where actual XPC requests from the client arrive. Now we need to understand how the daemon decides which requests to execute and which to reject.

Request dispatcher

sub_10003AB2C is the main dispatcher. This is where the daemon decides what to do with a request. The function is large, so let's break it down.

First, it pulls the "request" string from the message - the command name the client wants to execute:

reply = xpc_dictionary_create_reply(original: v12);
string = xpc_dictionary_get_string(xdict: v12, key: "request");
// ...
// logging: "Got Request '%{public}@'"

Then the daemon retrieves the connecting process's audit token and checks whether it's running inside App Sandbox. App Sandbox is a macOS application isolation mechanism that restricts access to the file system, network, and other resources. Here's where it branches:

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
  }
}

If the client is sandboxed - the request name is checked against an allowlist of seven strings. If it matches - the check passes and the request goes through. If not - sub_100034250 kills the daemon. If the client is not sandboxed - the daemon checks for the com.apple.private.tcc.allow entitlement with the value kTCCServiceSystemPolicyAllFiles. No entitlement also means death.

After passing the check, the request falls through two handler tables. First sub_10002F7C8, then 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

Now let's look at the handler tables. The first table (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
Descargar herramienta