Skip to content

Instantly share code, notes, and snippets.

@Brandon7CC
Created April 10, 2024 23:53
Show Gist options
  • Save Brandon7CC/f7399e9d3b31500d2839744816343131 to your computer and use it in GitHub Desktop.
Save Brandon7CC/f7399e9d3b31500d2839744816343131 to your computer and use it in GitHub Desktop.
Given an XPC service name return the program's path using the mach bootstrap port to talk to `launchd` over an XPC pipe.
//
// x2p.c
// MachXPC
//
// Created by Brandon Dalton on 04/09/2024.
//
#include <xpc/xpc.h>
#include <stdio.h>
#include <stdlib.h>
#include <mach/mach.h>
#include <libproc.h>
// An XPC pipe is just a void pointer.
typedef void* xpc_pipe_t;
// Forward declare the XPC functions we'll need...
extern xpc_pipe_t xpc_pipe_create_from_port(mach_port_t port, uint64_t flags);
extern int xpc_pipe_routine(xpc_pipe_t pipe, xpc_object_t message, xpc_object_t* reply);
extern const char* xpc_strerror(int error);
extern kern_return_t mach_ports_lookup(task_t task, mach_port_t **ports, mach_msg_type_number_t *portsCnt);
/**
Constructs an XPC message to list services.
References:
- https://github.com/mach-kernel/launchk/blob/3c2ccf3063b590d28ba6c46b01749c271dec9127/launchk/src/launchd/message.rs#L8
- https://github.com/Elcomsoft/launjctl/blob/4695b0d06a5356dca90f29569c75ccefc856534e/launjctl.c#L659
- https://newosxbook.com/articles/jlaunchctl.html
@return An XPC object representing the message.
*/
xpc_object_t construct_list_services_message() {
xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_uint64(message, "type", 1);
xpc_dictionary_set_uint64(message, "handle", 0); // can be 0 - for system
xpc_dictionary_set_uint64(message, "subsystem", 3); // subsystem (3) is for launchd
xpc_dictionary_set_uint64(message, "routine", 815); // 815 is the routine for listing services
return message;
}
/**
Sends a constructed XPC message (dictionary) to `launchd` and retrieves the response.
References:
- https://github.com/Elcomsoft/launjctl/blob/4695b0d06a5356dca90f29569c75ccefc856534e/launjctl.c#L677
- https://newosxbook.com/articles/jlaunchctl.html
@param message The XPC message to send.
@return The XPC object containing the response, or NULL if an error occurred.
*/
xpc_object_t send_xpc_message(xpc_object_t message) {
mach_port_t *ports = NULL;
mach_msg_type_number_t portCount = 0;
kern_return_t kr = mach_ports_lookup(mach_task_self(), &ports, &portCount);
if (kr != KERN_SUCCESS || portCount == 0) {
fprintf(stderr, "❌ Failed to lookup Mach ports or no ports found: %s\n", mach_error_string(kr));
return NULL;
}
// The bootstrap port is the first one. We'll use it to talk to launchd.
xpc_pipe_t pipe = xpc_pipe_create_from_port(ports[0], 0);
if (!pipe) {
fprintf(stderr, "❌ Failed to create XPC pipe.\n");
return NULL;
}
// 🚀 Ship it! Send across the message!
xpc_object_t reply = NULL;
int error = xpc_pipe_routine(pipe, message, &reply);
if (error != 0 || !reply) {
fprintf(stderr, "❌ Error sending XPC message: %s\n", xpc_strerror(error));
return NULL;
}
return reply;
}
/**
Retrieves the executable path for a given process identifier (PID).
References:
- https://astojanov.github.io/blog/2011/09/26/pid-to-absolute-path.html
- https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/libsyscall/wrappers/libproc/libproc.h#L102
- https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/libsyscall/wrappers/libproc/libproc.c#L264
@param pid The process identifier (PID) for which to retrieve the executable path.
@param path_buffer A character buffer where the path of the executable will be stored.
@param buffer_size The size of the path_buffer to ensure safe string operations.
@return Returns 0 on successful retrieval of the path, 1 if an error occurs.
This function uses the `proc_pidpath` system call to fetch the path for the specified PID.
On failure, it prints an error message indicating the failure to retrieve the path.
*/
int get_path_by_pid(pid_t pid, char *path_buffer, size_t buffer_size) {
if (proc_pidpath(pid, path_buffer, buffer_size) <= 0) {
fprintf(stderr, "🫤 Failed to get program path for PID %d\n", pid);
return 1;
}
return 0;
}
/**
Processes the XPC response to find the PID of a specified service.
@param response The XPC object containing the response from the XPC service.
@param service_name The name of the service for which to find the PID.
@param pid_out A pointer to a variable where the PID of the specified service will be stored.
@return Returns 0 if the PID was successfully found, 1 if the service was not found or an error occurred.
The function first inspects the XPC response to ensure it is a dictionary. It then looks for a
'services' dictionary within the response. Once found, it attempts to locate the specified service
within this dictionary. If the service is found, it extracts the PID from the service information.
On success, the PID is stored in pid_out, and the function returns 0. Otherwise, appropriate
error messages are printed, and the function returns 1.
*/
int process_response_and_find_pid(xpc_object_t response, const char* service_name, pid_t* pid_out) {
if (xpc_get_type(response) == XPC_TYPE_DICTIONARY) {
// printf("Inspecting XPC response dictionary...\n");
// Get the services dictionary
xpc_object_t services_dict = xpc_dictionary_get_value(response, "services");
if (!services_dict || xpc_get_type(services_dict) != XPC_TYPE_DICTIONARY) {
fprintf(stderr, "❌ 'services' dictionary NOT found!\n");
return 1;
}
// Attempt to find the service within the services dictionary
xpc_object_t service_info = xpc_dictionary_get_value(services_dict, service_name);
if (!service_info || xpc_get_type(service_info) != XPC_TYPE_DICTIONARY) {
fprintf(stderr, "❌ The requested service `%s` was not found or the type is not an `XPC_TYPE_DICTIONARY`.\n", service_name);
return 1;
}
// Pull out the PID!
xpc_object_t pid_object = xpc_dictionary_get_value(service_info, "pid");
if (pid_object && xpc_get_type(pid_object) == XPC_TYPE_INT64) {
*pid_out = (pid_t)xpc_int64_get_value(pid_object);
return 0;
} else {
fprintf(stderr, "❌ PID not found or invalid for service: %s\n", service_name);
return 1;
}
} else {
fprintf(stderr, "❌ Response is not a dictionary.\n");
return 1;
}
}
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <service-name>\n", argv[0]);
return 1;
}
const char* service_name = argv[1];
pid_t service_pid = 0;
xpc_object_t message = construct_list_services_message();
xpc_object_t reply = send_xpc_message(message);
if (!reply) {
return 1;
}
if (process_response_and_find_pid(reply, service_name, &service_pid) != 0) {
fprintf(stderr, "❌ The requested: `%s` was not found\n", service_name);
return 1;
}
// Reference: https://astojanov.github.io/blog/2011/09/26/pid-to-absolute-path.html
char path_buffer[PROC_PIDPATHINFO_MAXSIZE];
if (get_path_by_pid(service_pid, path_buffer, sizeof(path_buffer)) != 0) {
return 1;
}
printf("(%s, w/PID %d) => %s\n", service_name, service_pid, path_buffer);
return 0;
}
@brandondalton7cc
Copy link

To compile: clang x2p.c -o x2p -framework Foundation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment