Frida Engage Part Three | You Down With XPC?
Overview
In the final installment of the Frida Engage blog series, we will demonstrate how to use Frida for hooking and inspecting Apple’s NSXPC
API using the CleanMyMac 3 application as our guinea pig.
NSXPC
XPC is one flavor of the Inter-Process Communication technologies provided by Apple.
“The XPC Services API, part of libSystem, provides a lightweight mechanism for basic interprocess communication integrated with Grand Central Dispatch (GCD) and launchd. The XPC Services API allows you to create lightweight helper tools, called XPC services, that perform work on behalf of your application.” [1]
Apple provides developers two XPC API formats:
NSXPCConnection API
- Objective-C based API that is part of the Foundation framework
XPC Services API
- C-based XPC Services API
The NSXPCConnection API
contains the following components:
NSXPCConnection
- A class that represents the bidirectional communication channel between two processes [1]
NSXPCConnection
- A class that describes the expected programmatic behavior of the connection [1]
NSXPCListener
- A class that listens for incoming XPC connections [1]
NSXPCListenerEndpoint
- A class that listens for incoming XPC connections [1]
Our goal is to understand how the NSXPConnection API
is implemented and utilized. From the serpent’s mouth, the NSXPConnection API
is used to create lightweight “helper tools”, called XPC services [1]. Let’s check out the XPC services that come bundled up with the CleanMyMac 3 application.
CleanMyMac 3 XPC Services
The CleanMyMac 3 application contains the following XPC services:
╭─rotlogix@carcossa /Applications/CleanMyMac 3.app/Contents/XPCServices ╰─$ ls -la total 0 drwxr-xr-x 4 rotlogix staff 128 Feb 9 08:47 . drwxr-xr-x 12 rotlogix staff 384 Feb 9 08:47 .. drwxr-xr-x 3 rotlogix staff 96 Feb 9 08:47 com.macpaw.CleanMyMac.IconsService.xpc drwxr-xr-x 3 rotlogix staff 96 Feb 9 08:47 com.macpaw.CleanMyMac.LipoService.xpc
The LipoService
looks pretty interesting! After some investigation, I discovered that this service simply wraps the lipo
command itself.
“The lipo command creates or operates on “universal” (multi-architecture) files. It only ever produces one output file, and never alters the input file. The operations that lipo performs are: listing the architecture types in a universal file; creating a single universal file from one or more input files; thinning out a single universal file to one specified architecture type; and extracting, replacing, and/or removing architectures types from the input file to create a single new universal output file.”
If we give the application a spin, we can see that the service has been launched. Let’s dive into LipoService
and see what XPC stuff is going on under the hood.
ps -x | grep macpaw 11257 ?? 0:00.02 ... /Contents/MacOS/com.macpaw.CleanMyMac.LipoService
LipoService
XPC Service Creation
In order to create an XPC Service using the NSXPCConnection API
you will need to configure a listener object [1].
int main(int argc, const char *argv[]) { MyDelegateClass *myDelegate = ... NSXPCListener *listener = [NSXPCListener serviceListener]; listener.delegate = myDelegate; [listener resume]; // The resume method never returns. exit(EXIT_FAILURE);
If we check out LipoService's
imports, we can observe the existence of the NSXPCListener
class!
0000000100006AF0 _OBJC_CLASS_$_NSXPCInterface /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation 0000000100006AF8 _OBJC_CLASS_$_NSXPCListener /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
The class NSXPCListener
contains a single cross reference, originating from the start
function.
__objc_classrefs:00000001000064F0 ; id classRef_NSXPCListener __objc_classrefs:00000001000064F0 classRef_NSXPCListener dq offset _OBJC_CLASS_$_NSXPCListener __objc_classrefs:00000001000064F0 ; DATA XREF: start+13↑r
public start start proc near push rbp mov rbp, rsp push r15 push r14 push r12 push rbx call _objc_autoreleasePoolPush mov r14, rax mov rdi, cs:classRef_NSXPCListener ; id mov rsi, cs:selRef_serviceListener ; SEL mov r12, cs:_objc_msgSend_ptr
The next requirement for creating an XPC service is creating a connection delegate class that conforms to the protocol NSXPCListenerDelegate
[1]. This class will be responsible for delegating to the XPC listener the ability to accept or reject new connections. This connection delegate will be the class CMLipoServiceDelegate
.
mov rbx, rax mov rdi, cs:classRef_CMLipoServiceDelegate ; id mov rsi, cs:selRef_new ; SEL call r12 ; _objc_msgSend mov r15, rax mov rsi, cs:selRef_setDelegate_ ; SEL mov rdi, rbx ; id mov rdx, r15 call r12 ; _objc_msgSend
CMLipoServiceDelegate
The CMLipoServiceDelegate
class implements a very important method:
-[CMLipoServiceDelegate listener:shouldAcceptNewConnection:]
The method shouldAcceptNewConnection
is responsible for handling new inbound XPC connections, which takes the form of the class NSXPCConnection
. The method shouldAcceptNewConnection
is also responsible for setting up everything the XPC service needs for performing the operations sent to it from an XPC client.
Remote Procedure Calls
The Objective-C NSXPCConnection
API provides a high-level remote procedure call interface that allows you to call methods on objects in one process from another via the XPC service [1].
To use the NSXPCConnection
API, you must create the following:
- An interface, which mainly consists of a protocol that describes what methods should be callable from the remote process
- This is created via the
NSXPCInterface's
static initializer methodinterfaceWithProtocol
Within the CMLipoServiceDelegate
class’s shouldAcceptNewConnection
implementation, the CMLipo
protocol is used to initialize the NSXPCInterface
instance.
mov r15, cs:classRef_NSXPCInterface mov r12, cs:protocolRef_CMLipo mov rbx, cs:selRef_interfaceWithProtocol_ mov rdi, r14 ; id call cs:_objc_retain_ptr mov [rbp+var_30], rax mov r13, cs:_objc_msgSend_ptr mov rdi, r15 ; id mov rsi, rbx ; SEL mov rdx, r12 call r13 ; _objc_msgSend
A quick and dirty way of identifying all of the methods provided by the protocol CMLipo
in IDA is inspecting the protocol’s Objective-C method list.
__objc_const:0000000100005C68 _OBJC_INSTANCE_METHODS_CMLipo __objc2_meth_list <18h, 1> __objc_const:0000000100005C68 ; DATA XREF: __data:_OBJC_PROTOCOL_$_CMLipo↓o __objc_const:0000000100005C70 __objc2_meth <offset sel_obtainArchitecturesForBinary_withReply_, ; "obtainArchitecturesForBinary:withReply:" ... __objc_const:0000000100005C70 offset aV32081624, 0>
Each NSXPCConnection
object provides three key features [1]:
- An
exportedInterface
property that describes the methods that should be available to the opposite side of the connection - An
exportedObject
property that contains a local object to handle method calls coming in from the other side of the connection
The exportedInterface
will conform to the CMLipo
protocol it was created from. The exportedObject
will end up being the CMLipoTask
class, which is set via the setExportedObject
method.
mov rdi, cs:classRef_CMLipoTask ; id mov rsi, cs:selRef_new ; SEL call r13 ; _objc_msgSend mov rbx, rax mov rsi, cs:selRef_setExportedObject_ ; SEL mov rdi, r14 ; id mov rdx, rbx
The CMLipoTask
class only contains a single method:
-[CMLipoTask obtainArchitecturesForBinary:withReply:]
We’ve also observed the obtainArchitecturesForBinary:withReply:
method in the CMLipo
protocol’s Objective-C method list. So, it should be clear that the CMLipoTask
is responsible for implementing the methods within the CMLipo
protocol.
Instrumentation
We should have everything we need to create a simple Frida script to start instrumenting the XPC service. The goals of the script are straightforward:
- Hook the
-[CMLipoServiceDelegate listener:shouldAcceptNewConnection:]
to observe new incoming connections - Hook
interfaceWithProtocol
to return the name of the specified protocol - Hook the
-[CMLipoTask obtainArchitecturesForBinary:withReply:]
method, and print out the first argument, which should be the name of a binary on disk
var protocol_getName = Module.findExportByName("/usr/lib/libobjc.A.dylib", "protocol_getName"); // const char * protocol_getName(Protocol *proto); var my_protocol_getName = new NativeFunction(protocol_getName, 'pointer', ['pointer']); var shouldAcceptNewConnection = ObjC.classes.CMLipoServiceDelegate["- listener:shouldAcceptNewConnection:"]; var interfaceWithProtocol = ObjC.classes.NSXPCInterface["+ interfaceWithProtocol:"]; var obtainArchitecturesForBinaryWithReply = ObjC.classes.CMLipoTask["- obtainArchitecturesForBinary:withReply:"]; Interceptor.attach(shouldAcceptNewConnection.implementation, { onEnter: function(args) { console.log("[+] Hooked shouldAcceptNewConnection [!]"); console.log("[+] NSXPCConnection => " + args[2]); } }); Interceptor.attach(interfaceWithProtocol.implementation, { onEnter: function(args) { console.log("[+] Hooked interfaceWithProtocol [!]"); console.log("[+] Protocol => " + args[2]); // Returns a const char * var name = my_protocol_getName(args[2]); console.log("[+] Protocol Name => " + Memory.readUtf8String(name)); } }); Interceptor.attach(obtainArchitecturesForBinaryWithReply.implementation, { onEnter: function(args) { console.log("[+] Hooked obtainArchitecturesForBinaryWithReply [!]"); console.log("[+] Binary => " + ObjC.Object(args[2]).toString()); } });
If we kick off the CleanMyMac 3 application, we get the following output.
[+] Hooked obtainArchitecturesForBinaryWithReply [!] [+] Binary => /Applications/IDA Pro 7.1/ida.app/Contents/MacOS/Assistant [+] Hooked shouldAcceptNewConnection [!] [+] NSXPCConnection => 0x7fba464004f0 [+] Hooked interfaceWithProtocol [!] [+] Protocol => 0x10b6dc650 [+] Protocol Name => CMLipo
Conclusion
I really hoped you enjoyed this three-part series on Frida. Scripts for each part in the series are located here => https://github.com/VerSprite/engage
Thanks for reading!
References
https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html
https://developer.apple.com/documentation/foundation/nsxpclistenerdelegate/1410381-listener?language=objc
https://developer.apple.com/documentation/objectivec/1418826-protocol_getname?language=objc
https://developer.apple.com/documentation/foundation/nsxpclistenerdelegate?language=objc
Protect Your Assets from Various Threat Actors
VerSprite’s Research and Development division (a.k.a VS-Labs) is comprised of individuals who are passionate about diving into the internals of various technologies.
Our clients rely on VerSprite’s unique offerings of zero-day vulnerability research and exploit development to protect their assets from various threat actors.
From advanced technical security training to our research for hire B.O.S.S offering, we help organizations solve their most complex technical challenges. Learn more about Research as a Service →
View our security advisories detailing vulnerabilities found in major products for MacOs, Windows, Android, and iOS.
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /
- /