Force open sourcing launchd and libxpc, one binary at a time
Jonathan Levin, @Technologeeks, http://newosxbook.com/ - 10/07/15
Changelog:
- 11/09/15 - Added commpage for ARM64, "dumpjpcategory" command and fixed "lookup" for iOS + 32 bit support for iOS, OS X
- 11/14/15 - Added interpose for libxpc, for anyone wanting to try BYOL
About
Apple woke up one day with OS X 10.10, and decided to take launchd
off the opensource lists. This is surprising, since launchd
was one of the first projects to go on the Mac OS Forge, since its advent in 10.4. The last known source version seen in the wild is 842-92.1 for OS X 10.9.something, but as of 10.10 launchd
's project affiliation is moved to (the closed source) libXPC, and its version number is renumbered:
# In 10.9: morpheus@Ergo (~)$ what /sbin/launchd /sbin/launchd PROGRAM:launchd PROJECT:launchd-842.92.1 # In 10.10.4: morpheus@Zephyr (~) what /sbin/launchd /sbin/launchd PROGRAM:launchd PROJECT:libxpc-559.20.9 VERSION:Darwin Bootstrapper Version 2.0.2: Mon Mar 2 23:27:57 PST 2015; root:libxpc_executables-559.20.9~1/launchd/RELEASE_X86_64 # In iOS 9 (and also 10.11) morpheus@Phontifex (~) what /sbin/launchd /sbin/launchd PROGRAM:launchd PROJECT:libxpc-756.1.1 VERSION:Darwin Bootstrapper Version 3.0.0: Wed Aug 5 23:03:24 PDT 2015; root:libxpc_executables-756.1.1~3/launchd/RELEASE_ARM64
Similarly, launchctl(1)
, the interface utility, has been rewritten from scratch, made non-interactive (which is kind of a shame) and augmented with many more useful commands - primarily those that finally provide information on Mach ports. But, alas, where's the source??
The source
(download link)I had to do a lot of extensive reversing for MOXiI 2, wherein I delve for the first time into Apple's private frameworks and user-mode daemons. launchd
obviously fits into that important category - it already commanded chapter 7 of the first edition ("Alpha and Omega") - but that was before the rewrite. Add to that a gaping hole in MOXiI's 1's coverage - which barely touched on XPC (as it was just out and my (former) publisher wouldn't let me write about deep internals). Top that off with the arrogant (but funny :) rascal actively challenging me on Twitter* and@bruienne reminding me of my commitment to expose it.
So what I decided to share with the world for now is not launchd
itself - that will wait for MOXiI 2's release - but launchctl
's source. launchctl
is used quite a bit in Jailbreaks to communicate with launchd(8)
and ensure Daemons continue normal system startup after the jailbreak occurs. Apple knows that, and has in fact relocated the utility to the RAM Disk as of 9.0 (which obviously still doesn't work, since Pangu9 uses it). With this, you won't need the real launchctl
, as this works exactly the same. Additionally, I'm releasing the source clearly with the intent people use it. Feel free to build/integrate in your own code.
As with the original, jlaunchctl
provides help.
Usage: jlaunchctl... | help [subcommand] kill Sends a signal to the service instance. blame Prints the reason a service is running. print Prints a description of a domain or service. print-cache Prints information about the service cache. print-disabled Prints a description of a domain or service. procinfo Prints port information about a process. hostinfo Prints port information about the host. limit Reads or modifies launchd's resource limits. runstats Prints performance statistics for a service. examine Runs the specified analysis tool against launchd in a non-reentrant manner. dumpstate Dumps launchd state to stdout (iOS 9 only) list Lists information about services. start Starts the specified service. stop Starts the specified service. asuser Execute a program in the bootstrap context of a given user. submit Submit a basic job from the command line. error Prints a description of an error. version Prints the launchd version. variant Prints the launchd variant. # On iOS, these two commands are visible dumpjpcategory Dumps the jetsam properties category for all services. lookup Lookup Mach/XPC endpoint by name (new command, for iOS). help Prints the usage for a given subcommand. This is the 64-bit version of jlaunchctl
That "64-bit" or "32-bit" is just for my own purposes, since I recently updated the code to work well with 32-bit as well. I've packaged a (really) fat binary, so you can use arch
to take your pick as to which architecture to run.
Compiling & Running
Normally I do a full writeup, wherein I explain the process of reversing. I'm skipping it this time in favor of just providing the source, for your compilation pleasure, and leaving the XPC internals, etc for the book. The source I've provided will compile neatly (-Wall
) on both OS X and iOS, and you can find a fat binary for all architectures in the source bundle as well (The iOS version is obviously more useful than the OS X one, for purposes of jailbreaking/reverse engineering).
Do note - It's NOT the full source of launchctl
, but only of the interesting commands - primarily the procinfo
, list
, and such. I was also quite lazy here, and didn't implement the launchctl
convention of a "full path" to a service (e.g. system/com.apple.kextd or user/501/something...). Instead, you can pass uid=.. as an environment variable. For example:
# Default: Print for system (user 0). This does the same as launchctl print-disabled system Zephyr:~ morpheus$ ~/Documents/shared/10.11/launchd/jlaunchctl print-disabled disabled services = { "com.apple.AEServer" => true "com.apple.ManagedClientAgent.enrollagent" => true "com.apple.usbmuxd" => false "com.apple.rpmuxd" => false "com.apple.ftpd" => true "com.apple.mrt" => false "com.apple.stackshot" => false "com.apple.dynamic_pager" => false "org.ntp.ntpd" => false "com.apple.pacemaker" => true } # This does the same as launchctl print-disabled user/501 Zephyr:~ morpheus$ uid=501 ~/Documents/shared/10.11/launchd/jlaunchctl print-disabled disabled services = { "com.apple.TMHelperAgent.SetupOffer" => false "com.apple.FileStatsAgent" => true } login item associations = { } # uid= is required when looking for a per-user service: Zephyr:~ morpheus$ jlaunchctl print com.apple.tccd Error: 113 - Could not find specified service Zephyr:~ morpheus$ uid=501 ./jlaunchctl print com.apple.tccd com.apple.tccd = { active count = 2 path = /System/Library/LaunchAgents/com.apple.tccd.plist ... ...
Likewise try uid=501 jlaunchctl print
and jlaunchctl print
(implicitly does the same as launchctl print system
), and remember uid=...
when printing a per-user service, as without it you will get error 113. While I was at it, I added a simple command - lookup
- which will enable you to see if a Mach port is reachable or not (by name). Super useful for iOS. If a port isn't reachable, expect error 3.
If you read the source..
You'll see that I have #if 0
blocks showing the Mach messages used in each of the launchctl
requests. You are encouraged to get to them yourself, and it's quite easy to reproduce:
- Start
launchctl
with no arguments underlldb
- Set a breakpoint on
xpc_pipe_routine
- Run with whatever request argument you want to test
- When the breakpoint hits, set another breakpoint on
mach_msg
c
ontinue on the first two hits ofmach_msg
- these are the setup messages of the XPC pipemem read $rdi
to see the content of the third message. You'll see something like:(lldb) mem read $rdi # Note the mach_msg header 0x100200118: 13 15 13 80 b8 00 00 00 1b 03 00 00 13 04 00 00 ....?........... 0x100200128: 07 0b 00 00 00 00 00 10 01 00 00 00 03 0a 00 00 ................ # XPC Magic 0x100200138: 00 00 00 00 00 00 11 00 21 43 50 58 05 00 00 00 ........!CPX.... 0x100200148: 00 f0 00 00 80 00 00 00 06 00 00 00 73 75 62 73 .?..........subs 0x100200158: 79 73 74 65 6d 00 00 00 00 40 00 00 02 00 00 00 ystem....@...... 0x100200168: 00 00 00 00 66 64 00 00 00 b0 00 00 68 61 6e 64 ....fd...?..hand 0x100200178: 6c 65 00 00 00 40 00 00 00 00 00 00 00 00 00 00 le...@.......... 0x100200188: 72 6f 75 74 69 6e 65 00 00 40 00 00 c4 02 00 00 routine..@..?... 0x100200198: 00 00 00 00 6e 61 6d 65 00 00 00 00 00 90 00 00 ....name........ 0x1002001a8: 10 00 00 00 63 6f 6d 2e 61 70 70 6c 65 2e 6b 65 ....com.apple.ke 0x1002001b8: 78 74 64 00 74 79 70 65 00 00 00 00 00 40 00 00 xtd.type.....@.. 0x1002001c8: 01 00 00 00 00 00 00 00 03 0a 00 00 00 00 00 00 ................
and the serialization is readily evident. And interesting if ,say, you wanted to fuzz it to find faults in
launchd
's handling.
Note that most methods actually require you to pass a file descriptor (via file ports) to launchd
. This is clever, but sucks, because it means there's no easy way (convoluted popen()
notwithstanding) to get the data in a machine-friendly format which can be digested programmatically.
Update: I added a small XPC snooping library in the DYLD_INSERT_LIBRARIES
trick, etc. But useful, since it uses xpc_copy_description()
to dump XPC messages aplenty. Fascinating stuff. Expect more - much more - when I get to the XPC explication in MOXiI 2.
Speaking of MOXiI 2..
Both launchd
and libxpc
would benefit from more transparency - you know, just in case there is a certain 0-day vulnerability (or two three) in the implementation of XPC. The 2nd edition of MOXiI will therefore have a lot more than this. launchd
now has a dedicated chapter (I'm kicking SpringBoard and WindowServer to their own chapter), and XPC is covered in depth likewise in a chapter.
Update: And I'm glad to say I have libxpc.dylib
fully reversed :-) Anyone interested in OpenXPC? ;-)
Yes, I know I promised MOXiI will see print by October/November. I'm sort of on track, but as with Parkinson's law, there's always more and more to detail and explain - especially with the many under-the-hood changes AAPL put into iOS 9/10.11. I'm also now in "Android Mode", with Marshamllow FINALLY out, and the second volume of Android Internals due very soon. And I have real life to try and squeeze in every now and then. But once AIvII is out of the picture, MOXiI will once again command my full undivided attention. Stay tuned, (still aiming by end of year) and I promise you the wait will be over soon enough - and it will have been worth it.
.. and Technologeeks' Training
If you're reading this and following my articles, check out Technologeeks' Training on OS X and iOS Internals. This reverse-engineering oriented course is delivered by yours truly and goes even deeper than MOXiI 2 does, with practical and hands-on exercises. Our 2015 dates are full, but they tell me we're planning more training right around January 18th, 2016! Updates through@Technologeeks, or emailing info@you.know.where
.
Comments/Feedback
Through the website forum please. Technologeeks' Twitter doesn't normally respond to Twitter conversations or questions, but you can follow them for more updates on my work, as well as training
Greets
windknown, qwupz, and - how can I forget - launchderp :)