macOS XPC

Support HackTricks

Basic Information

XPC, co oznacza XNU (jądro używane przez macOS) inter-Process Communication, to framework do komunikacji między procesami na macOS i iOS. XPC zapewnia mechanizm do bezpiecznych, asynchronicznych wywołań metod między różnymi procesami w systemie. Jest częścią paradygmatu bezpieczeństwa Apple, umożliwiając tworzenie aplikacji z oddzielonymi uprawnieniami, gdzie każdy komponent działa z tylko tymi uprawnieniami, które są mu potrzebne do wykonania swojej pracy, ograniczając w ten sposób potencjalne szkody wynikające z kompromitacji procesu.

XPC używa formy komunikacji międzyprocesowej (IPC), która jest zestawem metod dla różnych programów działających w tym samym systemie do przesyłania danych w obie strony.

Główne korzyści z XPC obejmują:

  1. Bezpieczeństwo: Oddzielając pracę na różne procesy, każdy proces może otrzymać tylko te uprawnienia, które są mu potrzebne. Oznacza to, że nawet jeśli proces zostanie skompromitowany, ma ograniczone możliwości wyrządzenia szkód.

  2. Stabilność: XPC pomaga izolować awarie do komponentu, w którym występują. Jeśli proces ulegnie awarii, może zostać uruchomiony ponownie bez wpływu na resztę systemu.

  3. Wydajność: XPC umożliwia łatwą współbieżność, ponieważ różne zadania mogą być uruchamiane jednocześnie w różnych procesach.

Jedynym minusem jest to, że oddzielanie aplikacji na kilka procesów i ich komunikacja za pomocą XPC jest mniej wydajne. Jednak w dzisiejszych systemach nie jest to prawie zauważalne, a korzyści są lepsze.

Application Specific XPC services

Komponenty XPC aplikacji są wewnątrz samej aplikacji. Na przykład, w Safari można je znaleźć w /Applications/Safari.app/Contents/XPCServices. Mają rozszerzenie .xpc (jak com.apple.Safari.SandboxBroker.xpc) i są również pakietami z głównym plikiem binarnym w środku: /Applications/Safari.app/Contents/XPCServices/com.apple.Safari.SandboxBroker.xpc/Contents/MacOS/com.apple.Safari.SandboxBroker oraz Info.plist: /Applications/Safari.app/Contents/XPCServices/com.apple.Safari.SandboxBroker.xpc/Contents/Info.plist

Jak możesz się domyślać, komponent XPC będzie miał różne uprawnienia i przywileje niż inne komponenty XPC lub główny plik binarny aplikacji. Z WYJĄTKIEM przypadku, gdy usługa XPC jest skonfigurowana z JoinExistingSession ustawionym na „True” w swoim pliku Info.plist. W takim przypadku usługa XPC będzie działać w tej samej sesji bezpieczeństwa, co aplikacja, która ją wywołała.

Usługi XPC są uruchamiane przez launchd w razie potrzeby i zatrzymywane po zakończeniu wszystkich zadań, aby zwolnić zasoby systemowe. Specyficzne dla aplikacji komponenty XPC mogą być wykorzystywane tylko przez aplikację, co zmniejsza ryzyko związane z potencjalnymi lukami.

System Wide XPC services

Usługi XPC dostępne w systemie są dostępne dla wszystkich użytkowników. Te usługi, czy to launchd, czy typu Mach, muszą być zdefiniowane w plikach plist znajdujących się w określonych katalogach, takich jak /System/Library/LaunchDaemons, /Library/LaunchDaemons, /System/Library/LaunchAgents, lub /Library/LaunchAgents.

Te pliki plist będą miały klucz o nazwie MachServices z nazwą usługi oraz klucz o nazwie Program z ścieżką do pliku binarnego:

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>

The ones in LaunchDameons są uruchamiane przez root. Więc jeśli proces bez uprawnień może komunikować się z jednym z nich, może być w stanie eskalować uprawnienia.

XPC Obiekty

  • xpc_object_t

Każda wiadomość XPC jest obiektem słownika, który upraszcza serializację i deserializację. Ponadto, libxpc.dylib deklaruje większość typów danych, więc możliwe jest upewnienie się, że otrzymane dane są oczekiwanego typu. W API C każdy obiekt jest xpc_object_t (a jego typ można sprawdzić za pomocą xpc_get_type(object)). Ponadto, funkcja xpc_copy_description(object) może być używana do uzyskania reprezentacji tekstowej obiektu, co może być przydatne do celów debugowania. Te obiekty mają również pewne metody do wywołania, takie jak xpc_<object>_copy, xpc_<object>_equal, xpc_<object>_hash, xpc_<object>_serialize, xpc_<object>_deserialize...

xpc_object_t są tworzone przez wywołanie funkcji xpc_<objetType>_create, która wewnętrznie wywołuje _xpc_base_create(Class, Size), gdzie wskazany jest typ klasy obiektu (jeden z XPC_TYPE_*) oraz jego rozmiar (do rozmiaru zostanie dodane dodatkowe 40B na metadane). Co oznacza, że dane obiektu będą zaczynały się od offsetu 40B. Dlatego xpc_<objectType>_t jest rodzajem podklasy xpc_object_t, która byłaby podklasą os_object_t*.

Należy zauważyć, że to deweloper powinien używać xpc_dictionary_[get/set]_<objectType>, aby uzyskać lub ustawić typ i rzeczywistą wartość klucza.

  • xpc_pipe

xpc_pipe to rura FIFO, którą procesy mogą używać do komunikacji (komunikacja wykorzystuje wiadomości Mach). Możliwe jest utworzenie serwera XPC, wywołując xpc_pipe_create() lub xpc_pipe_create_from_port(), aby utworzyć go za pomocą konkretnego portu Mach. Następnie, aby odbierać wiadomości, można wywołać xpc_pipe_receive i xpc_pipe_try_receive.

Należy zauważyć, że obiekt xpc_pipe jest xpc_object_t z informacjami w swojej strukturze o dwóch używanych portach Mach oraz nazwie (jeśli istnieje). Nazwa, na przykład, demona secinitd w jego plist /System/Library/LaunchDaemons/com.apple.secinitd.plist konfiguruje rurę o nazwie com.apple.secinitd.

Przykładem xpc_pipe jest bootstrap pipe utworzona przez launchd, co umożliwia udostępnianie portów Mach.

  • NSXPC*

To są obiekty wysokiego poziomu Objective-C, które umożliwiają abstrakcję połączeń XPC. Ponadto łatwiej jest debugować te obiekty za pomocą DTrace niż poprzednie.

  • GCD Kolejki

XPC używa GCD do przesyłania wiadomości, ponadto generuje pewne kolejki dyspozycyjne, takie jak xpc.transactionq, xpc.io, xpc-events.add-listenerq, xpc.service-instance...

Usługi XPC

To są bundles z rozszerzeniem .xpc znajdujące się w folderze XPCServices innych projektów, a w Info.plist mają ustawiony CFBundlePackageType na XPC!. Ten plik ma inne klucze konfiguracyjne, takie jak ServiceType, które mogą być Application, User, System lub _SandboxProfile, które mogą definiować piaskownicę, lub _AllowedClients, które mogą wskazywać uprawnienia lub ID wymagane do kontaktu z serwisem. Te i inne opcje konfiguracyjne będą przydatne do skonfigurowania usługi podczas uruchamiania.

Uruchamianie Usługi

Aplikacja próbuje połączyć się z usługą XPC, używając xpc_connection_create_mach_service, następnie launchd lokalizuje demona i uruchamia xpcproxy. xpcproxy egzekwuje skonfigurowane ograniczenia i uruchamia usługę z dostarczonymi FD i portami Mach.

Aby poprawić szybkość wyszukiwania usługi XPC, używana jest pamięć podręczna.

Możliwe jest śledzenie działań xpcproxy za pomocą:

supraudit S -C -o /tmp/output /dev/auditpipe

The XPC library używa kdebug do logowania działań wywołując xpc_ktrace_pid0 i xpc_ktrace_pid1. Kody, których używa, są niedokumentowane, więc należy je dodać do /usr/share/misc/trace.codes. Mają prefiks 0x29, a na przykład jeden z nich to 0x29000004: XPC_serializer_pack. Narzędzie xpcproxy używa prefiksu 0x22, na przykład: 0x2200001c: xpcproxy:will_do_preexec.

XPC Event Messages

Aplikacje mogą subskrybować różne wiadomości zdarzeń, co umożliwia ich inicjowanie na żądanie, gdy takie zdarzenia występują. Konfiguracja tych usług odbywa się w plikach launchd plist, znajdujących się w tych samych katalogach co poprzednie i zawierających dodatkowy klucz LaunchEvent.

XPC Connecting Process Check

Gdy proces próbuje wywołać metodę za pośrednictwem połączenia XPC, usługa XPC powinna sprawdzić, czy ten proces ma prawo się połączyć. Oto powszechne sposoby sprawdzania tego oraz typowe pułapki:

macOS XPC Connecting Process Check

XPC Authorization

Apple również pozwala aplikacjom na konfigurowanie niektórych praw i sposobów ich uzyskania, więc jeśli wywołujący proces je ma, będzie mógł wywołać metodę z usługi XPC:

macOS XPC Authorization

XPC Sniffer

Aby podsłuchiwać wiadomości XPC, możesz użyć xpcspy, które wykorzystuje 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

Innym możliwym narzędziem do użycia jest XPoCe2.

Przykład kodu C komunikacji XPC

// 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;
}
# 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

XPC Communication Przykład kodu Objective-C

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

Klient wewnątrz kodu Dylb

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

Remote XPC

Ta funkcjonalność dostarczana przez RemoteXPC.framework (z libxpc) pozwala na komunikację za pomocą XPC między różnymi hostami. Usługi, które obsługują zdalne XPC, będą miały w swoim plist klucz UsesRemoteXPC, jak ma to miejsce w przypadku /System/Library/LaunchDaemons/com.apple.SubmitDiagInfo.plist. Jednakże, chociaż usługa będzie zarejestrowana w launchd, to UserEventAgent z wtyczkami com.apple.remoted.plugin i com.apple.remoteservicediscovery.events.plugin zapewnia tę funkcjonalność.

Co więcej, RemoteServiceDiscovery.framework pozwala na uzyskanie informacji z com.apple.remoted.plugin, udostępniając funkcje takie jak get_device, get_unique_device, connect...

Gdy connect zostanie użyty i gniazdo fd usługi zostanie zebrane, możliwe jest użycie klasy remote_xpc_connection_*.

Możliwe jest uzyskanie informacji o zdalnych usługach za pomocą narzędzia cli /usr/libexec/remotectl, używając parametrów takich jak:

/usr/libexec/remotectl list # Get bridge devices
/usr/libexec/remotectl show ...# Get device properties and services
/usr/libexec/remotectl dumpstate # Like dump withuot indicateing a servie
/usr/libexec/remotectl [netcat|relay] ... # Expose a service in a port
...

Komunikacja między BridgeOS a hostem odbywa się przez dedykowany interfejs IPv6. MultiverseSupport.framework umożliwia nawiązywanie gniazd, których fd będzie używane do komunikacji. Można znaleźć te komunikacje za pomocą netstat, nettop lub opcji open source, netbottom.

Wsparcie dla HackTricks

Last updated