Reverse Engineering The MacOS AirPlayUIAgent - Part 001

Overview

To be honest with you, I have no real purpose for this blog series. I plan on writing about my past time efforts to reverse engineer random things I find on my laptop. Maybe I learn something, maybe you learn something, maybe we don't learn anything, maybe we fuck off and join the circus.

I've dicked around with a lot AirPlay shit in the past, and found the iOS implementations pretty interesting, so I decided to checkout what was going on locally.

AirPlayUIAgent

Checking my own process listing, I could see that AirPlayUIAgent was kicked off by launchd.

ps -x | grep AirPlayUIAgent
  363 ??         0:01.52 /System/Library/CoreServices/AirPlayUIAgent.app/Contents/MacOS/AirPlayUIAgent --launchd

This could also be validated by using the launchctl command to list information about the available services.

launchctl list | grep AirPlay 363    0    com.apple.AirPlayUIAgent

I remebered that on the iOS side there was a ton of XPC shit going on, and I'm always pretty interested in anything that has potentially implemented their own XPC services, so that is where I decided to start diving into first.

XPC

If you're still following along, at this point I've got the AirPlayUIAgent executable loaded into Hopper. I attempted to write a Hopper script that will enumerate all of the executable's procedures, check for out going function calls, and determine if those function calls were to any of the c-based XPC functions. Some of the relevant functions can be found here https://developer.apple.com/documentation/xpc/xpc_services_connection.h

try:
    document = Document.getCurrentDocument()
    segment = document.getSegment(0)
    procedure_count = segment.getProcedureCount()
    procedures = list()
    for i in xrange(procedure_count):
        procedures.append(segment.getProcedureAtIndex(i))
    for p in procedures:
        callees = p.getAllCallees()
        for i in callees:
            if i:
                name = segment.getNameAtAddress(i.toAddress())
                if name and "xpc" in name:
                    print "[+] procedure entry point -> {0} : label -> {1}".format(hex(p.getEntryPoint()), name)
                else:
                    continue
            else:
                continue
except Exception as e:
    raise e

The script produced the following unsorted output.

[+] procedure entry point -> 0x1000022e9L : label -> imp___stubs__xpc_set_event_stream_handler
[+] procedure entry point -> 0x1000022e9L : label -> imp___stubs__xpc_set_event_stream_handler
[+] procedure entry point -> 0x1000022e9L : label -> imp___stubs__xpc_connection_create_mach_service
[+] procedure entry point -> 0x1000022e9L : label -> imp___stubs__xpc_connection_set_event_handler
[+] procedure entry point -> 0x1000022e9L : label -> imp___stubs__xpc_connection_resume
[+] procedure entry point -> 0x100002716L : label -> imp___stubs__xpc_connection_cancel
[+] procedure entry point -> 0x100002716L : label -> imp___stubs__xpc_release
[+] procedure entry point -> 0x100002973L : label -> imp___stubs__xpc_dictionary_get_uint64
[+] procedure entry point -> 0x1000032ffL : label -> imp___stubs__xpc_dictionary_get_uint64
[+] procedure entry point -> 0x1000034c0L : label -> imp___stubs__xpc_transaction_begin
[+] procedure entry point -> 0x1000034c0L : label -> imp___stubs__xpc_transaction_end
[+] procedure entry point -> 0x100004811L : label -> imp___stubs__xpc_dictionary_get_string
[+] procedure entry point -> 0x100006b49L : label -> imp___stubs__xpc_get_type
[+] procedure entry point -> 0x100006b49L : label -> imp___stubs__xpc_connection_has_entitlement
[+] procedure entry point -> 0x100006b49L : label -> imp___stubs__xpc_connection_get_pid
[+] procedure entry point -> 0x100006b49L : label -> imp___stubs__xpc_connection_set_event_handler
[+] procedure entry point -> 0x100006b49L : label -> imp___stubs__xpc_connection_resume
[+] procedure entry point -> 0x100006b49L : label -> imp___stubs__xpc_connection_get_pid
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_create_reply
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_get_type
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_string
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_set_int64
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_connection_send_message
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_release
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_set_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_bool
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_set_string
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_set_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_set_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_string
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_get_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_dictionary_set_data
[+] procedure entry point -> 0x100006c7dL : label -> imp___stubs__xpc_connection_cancel

I now can identify the subroutines that are calling c-based XPC functions. For the most part my assumption is now that the AirPlayUIAgent is implementing one or more XPC services.

Scripting Hopper

For those of you who are interested in writing scripts for Hopper and have not tried yet, there is dick for documentation on the interwebz. I would suggest just diving into their Python API implementation -> /Applications/Hopper Disassembler v4.app/Contents/Resources/hopper_api.py. This is was all I really needed to churn something out.

Alright back to it! Apple's documentation states that the xpc_connection_create_mach_service function is used to create a remote service in which clients may connect.

In the scripts output, the subroutine starting at 0x1000022e9 is calling the xpc_connection_create_mach_service function.

[+] procedure entry point -> 0x1000022e9L : label -> imp___stubs__xpc_connection_create_mach_service

After I navigated to that address in Hopper, it brought me to following ObjC method -> -[AirPlayUIAgent applicationDidFinishLaunching:]

Right before the call to xpc_connection_create_mach_service, in -[AirPlayUIAgent applicationDidFinishLaunching:] something is loaded into rdi at 0x100008bc2, which will be the first argument to the function.

lea        rdi, qword [0x100008bc2] imp___stubs__xpc_connection_create_mach_service
mov        edx, 0x1     ; argument "flags" for method imp___stubs__xpc_connection_create_mach_service
mov        rsi, rax     ; argument "targetq" for method imp___stubs__xpc_connection_create_mach_service
call       imp___stubs__xpc_connection_create_mach_service

That something, is the string com.apple.AirPlayAgent.xpc, which is the name the remote service is registered under.

0000000100008bc2   db   "com.apple.AirPlayAgent.xpc", 0

Checking Entitlements

In the script's ouput there is also a reference to the xpc_connection_has_entitlement function. This function is used to check the calling client's connection for a specific entitlement.

lea        rsi, qword [0x100008bc2] ; "com.apple.AirPlayAgent.xpc"
mov        rdi, rbx
call       imp___stubs__xpc_connection_has_entitlement
mov        r14b, al
test       r14b, r14b
jne        loc_100006c29

So the AirPlayUIAgent is checking whether or not the client has the com.apple.AirPlayAgent.xpc entitlement in order to proceed with the xpc service connection. The subroutine where this check is performed was most likely setup or called through xpc_connection_set_event_handler. A great of example of this would be the following.

http://nshipster.com/inter-process-communication/

static void connection_handler(xpc_connection_t peer) {
    xpc_connection_set_event_handler(peer, ^(xpc_object_t event) {
        peer_event_handler(peer, event);
        // peer_event_handler could handle the entitlement checks
    });

    xpc_connection_resume(peer);
}

int main(int argc, const char *argv[]) {
   xpc_main(connection_handler);
   exit(EXIT_FAILURE);
}

The rsi register is gonna hold the handler to be called when a event is received.

lea        rsi, qword [0x10000b7c0] ; argument "handler" for method imp___stubs__xpc_connection_set_event_handler
mov        rdi, rax     ; argument "connection" for method imp___stubs__xpc_connection_set_event_handler
call       imp___stubs__xpc_connection_set_event_handler

If we checkout the handler's memory at 0x10000b7c0, the fourth item into the structure - 0x100006b49 - is the function that is used to check the caller's entitlements

000000010000b7c0         dq         __NSConcreteGlobalBlock                     ; DATA XREF=-[AirPlayUIAgent applicationDidFinishLaunching:]+540
000000010000b7c8         dd         0x50000000
000000010000b7cc         dd         0x00000000
000000010000b7d0         dq         0x0000000100006b49
000000010000b7d8         dq         0x000000010000ba10

This function appears to allow clients that do not have the special entitlement to connnect.

The last block that any given descision can make in an if else manner is the following.

loc_100006c29:
mov        rax, qword [__NSConcreteStackBlock_10000b040] ; CODE XREF=sub_100006b49+56, sub_100006b49+83, sub_100006b49+113, sub_100006b49+152, sub_100006b49+178
mov        qword [rbp+var_40], rax
mov        dword [rbp+var_38], 0xc2000000
mov        dword [rbp+var_34], 0x0
lea        rax, qword [sub_100006c7d]
mov        qword [rbp+var_30], rax
lea        rax, qword [0x10000ba30]
mov        qword [rbp+var_28], rax
mov        qword [rbp+var_20], rbx
mov        byte [rbp+var_18], r14b
lea        rsi, qword [rbp+var_40] ; argument "handler" for method imp___stubs__xpc_connection_set_event_handler
mov        rdi, rbx     ; argument "connection" for method imp___stubs__xpc_connection_set_event_handler
call       imp___stubs__xpc_connection_set_event_handler
mov        rdi, rbx     ; argument "connection" for method imp___stubs__xpc_connection_resume
call       imp___stubs__xpc_connection_resume

There is another call to xpc_connection_set_event_handler.

call       imp___stubs__xpc_connection_set_event_handler

The handler argument being passed to xpc_connection_set_event_handler is function sub_100006c7d.

lea        rax, qword [sub_100006c7d]
mov        qword [rbp+var_30], rax

In this function the binary starts dealing with the c-based XPC data it was sent.

Wrapping Up

So I've uncovered the following things so far about the AIRPlayUIAgent:

  • The AirPlayUIAgent creates an XPC service registered under the name com.apple.AirPlayAgent.xpc
  • Clients need the com.apple.AirPlayAgent.xpc entitlement in order to connect to the XPC service