Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

フィードお問い合わせプライバシー© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
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
ツール/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.

リポジトリを見る
2152217日前Kitploit レビュー済み

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有
要求された言語のコンテンツは利用できません。英語版を表示しています。

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
ツールをダウンロード