A few months ago, I need to conduct dynamic analysis of a Daemon in a jailbroken iOS device, where the daemon is make up of some XPC Activities. Those XPC Activities could be found in the daemon’s configuration file (located at /System/Library/LaunchDaemons
, with the .plist
file extension) under the LaunchEvents
field:
# example daemon: audioanalyticsd
$ plutil -p "/System/Library/LaunchDaemons/com.apple.audioanalyticsd.plist"
# ...
"Label" => "com.apple.audioanalyticsd"
"LaunchEvents" => {
# XPC activities
"com.apple.xpc.activity" => {
"com.apple.audioanalyticsd.assets.refresh" => {
"AllowBattery" => 0
"GracePeriod" => 1800
"Interval" => 43200 # interval 12h = 43200s/3600s
"Priority" => "Maintenance"
"Repeating" => 1
}
}
}
"ProgramArguments" => [
0 => "/usr/libexec/audioanalyticsd"
]
# ...
However, those XPC activities usually have pre-defined intervals for periodically running, 12 hours in the above example, and up to 24 hours in some cases; some even should wait for certain system conditions to be satisfied. Of this reason, it is hard to analyze them especially when we require to debug them on demand. So I was looking for a way to run these XPC activities manually.
I was inspired by this article by Bryce. By investigating the logs in the Console application, he discovered that a daemon called dasd
(DuetActivitySchedulerDaemon) deciding which activities to run and when to run them. He used a debug tool lldb to debug the dasd
: 1) get the instance of the _DASDaemon
class, 2) call its method - [_DASDaemon forceRunActivities:]
. And, it successfully triggered the corresponding XPC activity!
(lldb) e (void)[[_DASDaemon sharedInstance]
forceRunActivities:@[@"com.apple.CacheDelete.daily"]]
Next step, he built a command line tool which puts the rewritten forceRunActivities:completion:
method in a new protocol, then uses Theos to hook the XPC communication interface class NSXPCInterface
and, after _DASDaemonClient
(share the same selector with _DASDaemon
) is loaded, replacing the original method of the original protocol in dasd
with the new method of the new protocol through XPC communication, in order to invoke the specific XPC activity and provide feedback.
But the method is still too much complicated. An easier path is to use Frida to hook the dasd
by loading Javascript script.
Test in Frida CLI
Our idea is clear: access the instance of the
_DASDaemon
class, then call its-[_DASDaemon forceRunActivities:]
method.
For the very beginning, use Frida CLI to dynamically debug dasd
.
frida -U dasd
Try to get the instance of the _DASDaemon
class as an Objective-C object.
// Get instance of _DASDaemon class
-> sharedInstance = ObjC.classes._DASDaemon.sharedInstance();
// Convert the instance to an ObjC object
-> sharedInstance = new ObjC.Object(sharedInstance);
After obtaining the instance, try to call the forceRunActivities:
method. From Bryce’s blog, we learned that the argument of the method is an array of strings, storing the XPC activity’s name. So the trick is, we use the [NSString stringWithString:]
method to store the name of the targeted XPC Activity into a string of type NSString
, then put the string into an array of type NSArray
, which is passed as the parameter to -[_DASDaemon forceRunActivities:]
method. Note that NSString
and NSArray
need to be declared as Objective-C classes in advance, otherwise an error will be reported.
// declare ObjC classes
-> const { NSString, NSArray } = ObjC.classes;
// initiate an ObjC string with value
-> const activity_string = NSString['stringWithString:']('com.apple.audioanalyticsd.assets.refresh');
// initiate an ObjC array with value
-> const activity_array = NSArray.alloc().initWithObject_(activity_string);
// call method of the instance
-> sharedInstance.forceRunActivities_(activity_array);
In the Console application, we observed that the specific XPC activity is launched successfully! Our next step is to write them as a JS script to be loaded directly by Frida.
Load JS script by Frida
We built the following JS script and encapsulated the behavior of calling the -[_DASDaemon forceRunActivities:]
method into a function run_activity
:
// dasd_schedule_activity.js
console.log("reloaded script");
var sharedInstance = ObjC.classes._DASDaemon.sharedInstance();
sharedInstance = new ObjC.Object(sharedInstance);
console.log("Got DASDaemon instance: " + sharedInstance);
function run_activity(activity_name) {
const {NSString, NSArray} = ObjC.classes;
const activity_string = NSString['stringWithString:'](activity_name);
const activity_array = NSArray.alloc().initWithObject_(activity_string);
sharedInstance.forceRunActivities_(activity_array);
console.log("ran activity");
}
console.log("run_activity('xpc_activity_name')");
Load the JS script by frida, then run the run_activity
function to invoke any XPC activity:
frida -U dasd --load dasd_schedule_activity.js
-> run_activity('your_xpc_activity_name')
Done! Now you can invoke any XPC activities on demand.
(Special thanks to Jiska)