hacktricks/macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/macos-ipc-inter-process-communication/macos-xpc/README.md

20 KiB

macOS XPC

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

Información básica

XPC, que significa Comunicación entre Procesos de XNU (el kernel utilizado por macOS), es un marco de trabajo para la comunicación entre procesos en macOS e iOS. XPC proporciona un mecanismo para realizar llamadas de método seguras y asíncronas entre diferentes procesos en el sistema. Es parte del paradigma de seguridad de Apple, que permite la creación de aplicaciones con privilegios separados donde cada componente se ejecuta con solo los permisos necesarios para realizar su trabajo, limitando así el daño potencial de un proceso comprometido.

XPC utiliza una forma de Comunicación entre Procesos (IPC), que es un conjunto de métodos para que los programas diferentes que se ejecutan en el mismo sistema envíen datos de ida y vuelta.

Los principales beneficios de XPC incluyen:

  1. Seguridad: Al separar el trabajo en diferentes procesos, cada proceso puede recibir solo los permisos que necesita. Esto significa que incluso si un proceso está comprometido, tiene una capacidad limitada para causar daño.
  2. Estabilidad: XPC ayuda a aislar los bloqueos en el componente donde ocurren. Si un proceso se bloquea, se puede reiniciar sin afectar al resto del sistema.
  3. Rendimiento: XPC permite una fácil concurrencia, ya que diferentes tareas pueden ejecutarse simultáneamente en diferentes procesos.

La única desventaja es que separar una aplicación en varios procesos que se comunican a través de XPC es menos eficiente. Pero en los sistemas actuales esto apenas se nota y los beneficios son mayores.

Servicios XPC específicos de la aplicación

Los componentes XPC de una aplicación están dentro de la propia aplicación. Por ejemplo, en Safari se pueden encontrar en /Applications/Safari.app/Contents/XPCServices. Tienen la extensión .xpc (como com.apple.Safari.SandboxBroker.xpc) y también son paquetes con el binario principal dentro de él: /Applications/Safari.app/Contents/XPCServices/com.apple.Safari.SandboxBroker.xpc/Contents/MacOS/com.apple.Safari.SandboxBroker y un Info.plist: /Applications/Safari.app/Contents/XPCServices/com.apple.Safari.SandboxBroker.xpc/Contents/Info.plist

Como podrás pensar, un componente XPC tendrá diferentes derechos y privilegios que los otros componentes XPC o el binario principal de la aplicación. EXCEPTO si un servicio XPC está configurado con JoinExistingSession establecido en "True" en su archivo Info.plist. En este caso, el servicio XPC se ejecutará en la misma sesión de seguridad que la aplicación que lo llamó.

Los servicios XPC se inician mediante launchd cuando es necesario y se cierran una vez que todas las tareas están completas para liberar recursos del sistema. Los componentes XPC específicos de la aplicación solo pueden ser utilizados por la aplicación, lo que reduce el riesgo asociado con posibles vulnerabilidades.

Servicios XPC de todo el sistema

Los servicios XPC de todo el sistema son accesibles para todos los usuarios. Estos servicios, ya sean de tipo launchd o Mach, deben estar definidos en archivos plist ubicados en directorios especificados como /System/Library/LaunchDaemons, /Library/LaunchDaemons, /System/Library/LaunchAgents o /Library/LaunchAgents.

Estos archivos plist tendrán una clave llamada MachServices con el nombre del servicio y una clave llamada Program con la ruta al binario:

cat /Library/LaunchDaemons/com.jamf.management.daemon.plist

<?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>Program</key>
<string>/Library/Application Support/JAMF/Jamf.app/Contents/MacOS/JamfDaemon.app/Contents/MacOS/JamfDaemon</string>
<key>AbandonProcessGroup</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>Label</key>
<string>com.jamf.management.daemon</string>
<key>MachServices</key>
<dict>
<key>com.jamf.management.daemon.aad</key>
<true/>
<key>com.jamf.management.daemon.agent</key>
<true/>
<key>com.jamf.management.daemon.binary</key>
<true/>
<key>com.jamf.management.daemon.selfservice</key>
<true/>
<key>com.jamf.management.daemon.service</key>
<true/>
</dict>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

Los que están en LaunchDameons son ejecutados por root. Por lo tanto, si un proceso sin privilegios puede comunicarse con uno de ellos, podría ser capaz de escalar privilegios.

Mensajes de eventos XPC

Las aplicaciones pueden suscribirse a diferentes mensajes de eventos, lo que les permite ser iniciadas a pedido cuando ocurren dichos eventos. La configuración de estos servicios se realiza en archivos plist de launchd, ubicados en los mismos directorios que los anteriores y que contienen una clave adicional LaunchEvent.

Verificación del proceso de conexión XPC

Cuando un proceso intenta llamar a un método a través de una conexión XPC, el servicio XPC debe verificar si ese proceso tiene permitido conectarse. Aquí se muestran las formas comunes de verificar eso y las trampas comunes:

{% content-ref url="macos-xpc-connecting-process-check.md" %} macos-xpc-connecting-process-check.md {% endcontent-ref %}

Autorización XPC

Apple también permite que las aplicaciones configuren algunos derechos y cómo obtenerlos, por lo que si el proceso que llama los tiene, se le permitiría llamar a un método del servicio XPC:

{% content-ref url="macos-xpc-authorization.md" %} macos-xpc-authorization.md {% endcontent-ref %}

Espía XPC

Para espiar los mensajes XPC, puedes usar xpcspy que utiliza Frida.

# Install
pip3 install xpcspy
pip3 install xpcspy --no-deps # To not make xpcspy install Frida 15 and downgrade your Frida installation

# Start sniffing
xpcspy -U -r -W <bundle-id>
## Using filters (i: for input, o: for output)
xpcspy -U <prog-name> -t 'i:com.apple.*' -t 'o:com.apple.*' -r

Ejemplo de código en C

{% tabs %} {% tab title="xpc_server.c" %}

// gcc xpc_server.c -o xpc_server

#include <xpc/xpc.h>

static void handle_event(xpc_object_t event) {
if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) {
// Print received message
const char* received_message = xpc_dictionary_get_string(event, "message");
printf("Received message: %s\n", received_message);

// Create a response dictionary
xpc_object_t response = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(response, "received", "received");

// Send response
xpc_connection_t remote = xpc_dictionary_get_remote_connection(event);
xpc_connection_send_message(remote, response);

// Clean up
xpc_release(response);
}
}

static void handle_connection(xpc_connection_t connection) {
xpc_connection_set_event_handler(connection, ^(xpc_object_t event) {
handle_event(event);
});
xpc_connection_resume(connection);
}

int main(int argc, const char *argv[]) {
xpc_connection_t service = xpc_connection_create_mach_service("xyz.hacktricks.service",
dispatch_get_main_queue(),
XPC_CONNECTION_MACH_SERVICE_LISTENER);
if (!service) {
fprintf(stderr, "Failed to create service.\n");
exit(EXIT_FAILURE);
}

xpc_connection_set_event_handler(service, ^(xpc_object_t event) {
xpc_type_t type = xpc_get_type(event);
if (type == XPC_TYPE_CONNECTION) {
handle_connection(event);
}
});

xpc_connection_resume(service);
dispatch_main();

return 0;
}

{% tab title="xpc_client.c" %}

// gcc xpc_client.c -o xpc_client

#include <xpc/xpc.h>

int main(int argc, const char *argv[]) {
xpc_connection_t connection = xpc_connection_create_mach_service("xyz.hacktricks.service", NULL, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);

xpc_connection_set_event_handler(connection, ^(xpc_object_t event) {
if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) {
// Print received message
const char* received_message = xpc_dictionary_get_string(event, "received");
printf("Received message: %s\n", received_message);
}
});

xpc_connection_resume(connection);

xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(message, "message", "Hello, Server!");

xpc_connection_send_message(connection, message);

dispatch_main();

return 0;
}

{% tab title="xyz.hacktricks.service.plist" %}

<?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>Label</key>
<string>xyz.hacktricks.service</string>
<key>MachServices</key>
<dict>
<key>xyz.hacktricks.service</key>
<true/>
</dict>
<key>Program</key>
<string>/tmp/xpc_server</string>
<key>ProgramArguments</key>
<array>
<string>/tmp/xpc_server</string>
</array>
</dict>
</plist>

{% endtab %} {% endtabs %}

# Compile the server & client
gcc xpc_server.c -o xpc_server
gcc xpc_client.c -o xpc_client

# Save server on it's location
cp xpc_server /tmp

# Load daemon
sudo cp xyz.hacktricks.service.plist /Library/LaunchDaemons
sudo launchctl load /Library/LaunchDaemons/xyz.hacktricks.service.plist

# Call client
./xpc_client

# Clean
sudo launchctl unload /Library/LaunchDaemons/xyz.hacktricks.service.plist
sudo rm /Library/LaunchDaemons/xyz.hacktricks.service.plist /tmp/xpc_server

Ejemplo de código Objective-C

{% tabs %} {% tab title="oc_xpc_server.m" %}

// gcc -framework Foundation oc_xpc_server.m -o oc_xpc_server
#include <Foundation/Foundation.h>

@protocol MyXPCProtocol
- (void)sayHello:(NSString *)some_string withReply:(void (^)(NSString *))reply;
@end

@interface MyXPCObject : NSObject <MyXPCProtocol>
@end


@implementation MyXPCObject
- (void)sayHello:(NSString *)some_string withReply:(void (^)(NSString *))reply {
NSLog(@"Received message: %@", some_string);
NSString *response = @"Received";
reply(response);
}
@end

@interface MyDelegate : NSObject <NSXPCListenerDelegate>
@end


@implementation MyDelegate

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection {
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyXPCProtocol)];

MyXPCObject *my_object = [MyXPCObject new];

newConnection.exportedObject = my_object;

[newConnection resume];
return YES;
}
@end

int main(void) {

NSXPCListener *listener = [[NSXPCListener alloc] initWithMachServiceName:@"xyz.hacktricks.svcoc"];

id <NSXPCListenerDelegate> delegate = [MyDelegate new];
listener.delegate = delegate;
[listener resume];

sleep(10); // Fake something is done and then it ends
}

{% tab title="oc_xpc_client.m" %}

// gcc -framework Foundation oc_xpc_client.m -o oc_xpc_client
#include <Foundation/Foundation.h>

@protocol MyXPCProtocol
- (void)sayHello:(NSString *)some_string withReply:(void (^)(NSString *))reply;
@end

int main(void) {
NSXPCConnection *connection = [[NSXPCConnection alloc] initWithMachServiceName:@"xyz.hacktricks.svcoc" options:NSXPCConnectionPrivileged];
connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyXPCProtocol)];
[connection resume];

[[connection remoteObjectProxy] sayHello:@"Hello, Server!" withReply:^(NSString *response) {
NSLog(@"Received response: %@", response);
}];

[[NSRunLoop currentRunLoop] run];

return 0;
}

{% tab title="xyz.hacktricks.svcoc.plist" %}

xyz.hacktricks.svcoc.plist

Este archivo de propiedad de la lista de servicios de macOS (plist) se utiliza para definir los servicios que se ejecutan en segundo plano en un sistema macOS. Los servicios se definen utilizando el formato XPC (Interfaz de Comunicación entre Procesos) y se pueden utilizar para la comunicación entre procesos en macOS.

El archivo plist contiene una serie de claves y valores que definen el servicio y su comportamiento. Algunas de las claves comunes incluyen:

  • Label: El nombre único del servicio.
  • ProgramArguments: Los argumentos del programa que se ejecutará como parte del servicio.
  • MachServices: Los servicios Mach que el servicio puede utilizar para la comunicación entre procesos.
  • Sockets: Los sockets que el servicio puede utilizar para la comunicación entre procesos.

Para aprovecharse de un archivo plist de servicio, un atacante puede modificar el archivo para ejecutar su propio código malicioso en el contexto del servicio. Esto puede permitir al atacante obtener privilegios elevados en el sistema o realizar otras acciones maliciosas.

Es importante asegurarse de que los archivos plist de servicio estén correctamente configurados y protegidos para evitar posibles abusos y ataques de escalada de privilegios.

{% endtab %}

<?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>Label</key>
<string>xyz.hacktricks.svcoc</string>
<key>MachServices</key>
<dict>
<key>xyz.hacktricks.svcoc</key>
<true/>
</dict>
<key>Program</key>
<string>/tmp/oc_xpc_server</string>
<key>ProgramArguments</key>
<array>
<string>/tmp/oc_xpc_server</string>
</array>
</dict>
</plist>

{% endtab %} {% endtabs %}

# Compile the server & client
gcc -framework Foundation oc_xpc_server.m -o oc_xpc_server
gcc -framework Foundation oc_xpc_client.m -o oc_xpc_client

# Save server on it's location
cp oc_xpc_server /tmp

# Load daemon
sudo cp xyz.hacktricks.svcoc.plist /Library/LaunchDaemons
sudo launchctl load /Library/LaunchDaemons/xyz.hacktricks.svcoc.plist

# Call client
./oc_xpc_client

# Clean
sudo launchctl unload /Library/LaunchDaemons/xyz.hacktricks.svcoc.plist
sudo rm /Library/LaunchDaemons/xyz.hacktricks.svcoc.plist /tmp/oc_xpc_server

Cliente dentro de un código Dylb

The Dylb code allows the creation of a client that can communicate with a server using the XPC framework in macOS. This client code can be embedded within an application to establish inter-process communication (IPC) with the server.

To create a client inside a Dylb code, follow these steps:

  1. Import the necessary frameworks:
#import <Foundation/Foundation.h>
#import <xpc/xpc.h>
  1. Define the XPC connection and event handler:
static xpc_connection_t connection;
static dispatch_queue_t queue;

void eventHandler(xpc_object_t event) {
    // Handle incoming events from the server
    // ...
}
  1. Implement the client code:
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Create the XPC connection
        connection = xpc_connection_create_mach_service("com.example.server", queue, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);
        
        // Set the event handler
        xpc_connection_set_event_handler(connection, ^(xpc_object_t event) {
            eventHandler(event);
        });
        
        // Resume the connection
        xpc_connection_resume(connection);
        
        // Send messages to the server
        // ...
        
        // Run the main loop
        dispatch_main();
    }
    return 0;
}
  1. Customize the code as needed, such as adding message sending functionality or handling server responses.

Remember to replace "com.example.server" with the actual Mach service name of the server you want to communicate with.

By embedding this client code within your application, you can establish IPC with a server using the XPC framework in macOS. This allows for secure and efficient communication between processes.

// gcc -dynamiclib -framework Foundation oc_xpc_client.m -o oc_xpc_client.dylib
// gcc injection example:
// DYLD_INSERT_LIBRARIES=oc_xpc_client.dylib /path/to/vuln/bin

#import <Foundation/Foundation.h>

@protocol MyXPCProtocol
- (void)sayHello:(NSString *)some_string withReply:(void (^)(NSString *))reply;
@end

__attribute__((constructor))
static void customConstructor(int argc, const char **argv)
{
NSString*  _serviceName = @"xyz.hacktricks.svcoc";

NSXPCConnection* _agentConnection = [[NSXPCConnection alloc] initWithMachServiceName:_serviceName options:4096];

[_agentConnection setRemoteObjectInterface:[NSXPCInterface interfaceWithProtocol:@protocol(MyXPCProtocol)]];

[_agentConnection resume];

[[_agentConnection remoteObjectProxyWithErrorHandler:^(NSError* error) {
(void)error;
NSLog(@"Connection Failure");
}] sayHello:@"Hello, Server!" withReply:^(NSString *response) {
NSLog(@"Received response: %@", response);
}    ];
NSLog(@"Done!");

return;
}
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥