A (long) evening with Mobile_Obliterator and a look into iOS entitlements

Jonathan Levin, http://www.newosxbook.com/ - 12/31/13

1. About Part of what I do is reverse engineering. Most of the iOS-related information in the book (at least, those I was allowed to publish) was gleaned by reverse engineering by disassembling binaries, and occasionally debugging them. This is also why I ended up writing JTool, which started as a simple Mach-O header dump utility, but took a life on its own and ended up with its own disassembler. When I noticed /usr/libexec/mobile_obliterator , I couldn't help but be intrigued. It's a small executable (60-70k) with a big name. It's the binary responsible for Apple's "remote device wipe" feature. Not an overly sophisticated one (as I'll demostrate here) but it makes for a great demonstration of a virtually undocumented feature of iOS (which I admit I had to only scratch the surface of in the book) - Entitlements. With nothing else to do on a 13 hour flight in economy, this article is long overdue. Oh, and - happy new year :-) Why should you care? (Target Audience) If you want to know how entitlements work at the low-level, this should prove to be a good read. If you want to see examples of using JTool (some are in the forum), there are some here. You can follow along if you have the iOS filesystem image. It's encrypted, but you don't have to read the book to get around that.. If you want to know how entitlements work at the low-level, this should prove to be a good read. If you want to see examples of using JTool (some are in the forum), there are some here. You can follow along if you have the iOS filesystem image. It's encrypted, but you don't have to read the book to get around that.. The mobile_obliterator has been rewritten in iOS7 to use XPC. We'll get to that, too.

Why Obliterate? iOS devices have a wipe feature. This is a feature of last resort if your i-Device falls into the wrong hands. In those cases, you don't want personal data, and possibly confidential data, exposed. "Wiping" involves nuking all the user's data in /var/mobile - which is intentionally a separate partition. The responsible entity for starting mobile_obliterator is SpringBoard - in many ways the core binary in iOS, responsible for the familiar GUI, and more. In practice, however, it is launchd which starts mobile_obliterator, the same way it starts all other processes in iOS. This can be seen in mobile_obliterator's property list file: morpheus@Zephyr (~)% cd /Volumes/InnsbruckTaos11B511.N90OS/System/Library/LaunchDaemons morpheus@Zephyr (~)% file com.apple.mobile.obliteration.plist com.apple.mobile.obliteration.plist: Apple binary property list # A binary property list can be converted to the (much more readable) XML notation using plutil. # # We use: "-convert xml1" to request conversion to XML, # "-o -" to designate output to stdout, # " - " to specify input is from stdin morpheus@Zephyr (../LaunchDaemons)% plutil -convert xml1 -o - - < com.apple.mobile.obliteration.plist <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.apple.mobile.obliteration</string> <key>MachServices</key> <dict> <key>com.apple.mobile.obliteration</key> <true/> </dict> <key>POSIXSpawnType</key> <string>Interactive</string> <key>ProgramArguments</key> <array> <string>/usr/libexec/mobile_obliterator</string> </array> </dict> </plist> From the plist we see that the binary ( /usr/libexec/mobile_obliterator ) is associated with a Mach service - com.apple.mobile.obliteration iOS has a private framework - MobileObliteration.framework. While this framework, like most others, is in the shared library cache, a symbolicated version of it exists in the iOS SDK. Disassembling it reveals a fairly simple library: morpheus@Zephyr (~) % jtool -arch armv7s -S /Developer/Platforms/iPhoneOS.platform/DeviceSupport/\ Latest/Symbols/System/Library/PrivateFrameworks/MobileObliteration.framework/MobileObliteration 00000b28 t _perform_command 00000cf4 t _create_dict 00000e04 s stub helpers 00000b18 T _Mobile_Obliterate 00000c98 T _Mobile_Obliterate_Reboot 00000c40 T _Mobile_Obliterate_Synchronous U _CFDictionaryCreateMutable << other dependencies omitted for brevity >> Thus, we have the function of perform_command, and three exported commands: Mobile_Obliterate, and its reboot and Synchronous variants. The library is easy to disassemble, and can be further decompiled. What follows is JTool's decompilation, with further annotation: Disassembling from file offset 0xb18, Address 0xb18 >_Mobile_Obliterate: int Mobile_Obliterate(CFDictionaryRef cmd) { if (!cmd) return -1; else perform_command(cmd); } -- b18 2800 CMP R0, #0 -- b1a bf18 IT NE -- b1c f000b804 B.W 0xb28 ; 0xb28 _perform_command -- b20 f04f30ff MOV.w R0, #-1 ; R0 = 0xffffffff -- b24 4770 BX LR ; return(-1); -- b26 bf00 NOP >_perform_command: int perform_command (CFDictionaryRef cmd) -- b28 b5f0 PUSH {r4,r5,r6,r7,lr} -- b2a af03 ADD R7, SP, #12 ; R7 += 805d0c = 805d0c -- b2c f84d8d04 STR.W R8 ... -- b30 4605 MOV R5, R0 ; R5 = cmd;; -- b32 f04f36ff MOV.w R6, #-1 ; R6 = 0xffffffff -- b36 2d00 CMP R5, #0 if (!cmd) goto 0xc36 -- b38 f000807d BEQ.W 0xc36 ; 0xc36 -- b3c f24030bc MOVW R0, 0x3bc ; R0 = 0x3bc -- b40 2100 MOVS R1, #0 ; R1 = 0x0 -- b42 f2c00000 MOVT R0, 0x0 ; R0 += 0 = 3bc -- b46 4478 ADD R0, PC ; R0 += b4a = f06 com.apple.mobile.obliteration -- b48 f000e8fc BLX 0x1f8 ; 0xd44 _MOXPCTransportOpen ; ; R0 = MOXPCTransportOpen("com.apple.mobile.obliteration", 0); ; -- b4c 4680 MOV R8, R0 ; R8 = R0 ; ; handle = MOXPCTransportOpen("com.apple.mobile.obliteration", 0); ; ; if (!handle) ; { ; syslog ("Could not create transport connection"); ; return ... ; } ; MOXPCTransportResume ( ...); ; if (!MOXPCTransportSendMessage (cmd, handle)) ; { ; syslog ("Could not send request through transport"); ; } ; ; if (!MOXPCTransportReceiveMessage(msg, handle)) ; { ; syslog ("Could not receive response from server"); ; } ; ; status = CFDictionaryGetValue("IPCStatus"); ; if (!status) ; { ; syslog ("Status missing from response"); ; } ; ; if (CFEqual(status, "Complete")) ; { ; } ; else if CFEqual(status,"Error")) ; { ; syslog("error from server"); ; } ; else { syslog("Unrecognized return status"); } ; ; CFRelease(status); ; MOXPCTransportClose(handle); The MobileObliteration.framework, therefore, is in effect a simple XPC client of mobile_obliterator, using MOXPC calls (from the MobileSystemServices private framework). Incidentally, XPC itself isn't much more than wrappers over the venerable Mach bootstrap server functionality (still maintained by launchd ). The various exported functions fall through to perform_command , with the only subtle difference being the setting of additional keys in the CFDictionary passed to perform_command (Specifically, Mobile_Obliterate_Reboot sets the ObliterationRebootType key to ObliterationRebootNow and Mobile_Obliterate_Synchronous sets the SyncType key to Synchronous . Meanwhile... On the other side of launchd, mobile_obliterator wakes up, and initializes the XPC support. After opening /dev/console, it logs a startup message, and calls MOXPCTransportOpen to claim the com.apple.mobile.obliteration channel (as is rightfully his, by the launchdaemon plist), and calls MOXPCTransportSetMessageHandler to install a callback function for incoming messages, before going into a dispatch_main message loop. When messages come in, mobile_obliterator inspects the CFDictionary for the above mentioned keys, to determine if a reboot or a synchronous obliteration was requested. Once it figures out what the client requested, it can proceed to obliterate the device But... not so fast! While the mobile obliterator aims to please, it must exercise some discretion. Specifically, it can't just allow any client to connect. It therefore requires clients to have a special entitlement to allow its services to be invoked. Entitlements form the crux of the iOS permission model. Very similar to Android's Dalvik-level permissions, they implement a declarative security model wherein a given application can specify to iOS it requires special capabilities. Servers can then request iOS to verify a client has the required entitlements before granting it access to the service. Entitlements A client specifies its entitlements in its binary - they are embedded in the code signature, as an entitlement blob ( kSecCodeMagicEntitlement = 0xfade7171 ). Doing so allows Apple to ensure they are not misused: Apple is the sole provider of code signatures in iOS, so apps can't simply request entitlements without Apple's App Review process flagging them. Once a binary is code signed by Apple, even its own developer can no longer modify it in any way. You can use jtool to view both the code signature and the entitlements - the latter is such common usage I added a separate --ent option for it). Using jtool on Springboard reveals plenty of entitlements: morpheus@zephyr (~)$ jtool --sig ~/Documents/RE/SpringBoard.7.03 Blob at offset: 4028320 (26416 bytes) is an embedded signature Code Directory (19850 bytes) Version: 20100 Flags: adhoc Identifier: com.apple.springboard # of Hashes: 984 code + 5 special Hash @170 size: 20 Type: SHA-1 Requirement Set (12 bytes) with 0 requirements: Entitlements (6480 bytes) (use --ent to view) Blob Wrapper (8 bytes) morpheus@zephyr (~)$ jtool --ent ~/Documents/RE/SpringBoard.7.03 | more Entitlements: <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>allow-obliterate-device</key> <true/> <key>application-identifier</key> <string>com.apple.springboard</string> <key>aps-connection-initiate</key> <true/> <key>checklessPersistentURLTranslation</key> <true/> <key>com.apple.BTServer.allowRestrictedServices</key> <true/> <key>com.apple.BTServer.supportsPairing</key> <true/> <key>com.apple.CommCenter.Preferences-delete</key> <true/> <key>com.apple.MobileInternetSharing.allow</key> <true/> <key>com.apple.QuartzCore.global-capture</key> <true/> ... and many more During the Mach-O object loading, the kernel loads the code signature (LC_CODE_SIGNATURE load command) using a dedicated function ( load_code_signature in bsd/kern/mach_loader.c ). The function loads the code signature and stores it in the unified buffer cache, as a blob. Because it is in kernel memory, the code signature (once verified) can be fully trusted. In other words, the binary does not have a way to access its own entitlements (at least not from user mode) Servers can validate that a client holds the entitlement by using the public Security.Framework . Turning once more to mobile_obliterator , we see (full disassembly given; also possible to skim through annotations): -- 2a9e f00aea08 BLX 0xa410 ; 0xceb0 _SecTaskCreateWithAuditToken ; ; SecTaskRef = SecTaskCreateWithAuditToken(CFAllocatorRef allocator, audit_token_t token); ; Check if SecTaskCreateWithAuditToken failed: -- 2aa2 4604 MOV R4, R0 ; R4 = _SecTaskRef -- 2aa4 2c00 CMP R4, #0 -- 2aa6 d018 BEQ 0x30 ; 0x2ada - __no_sec_task_from_audit_token ; ; Otherwise, so far, so good: Get the entitlement for allow-obliterate-device ; -- 2aa8 f64b1150 MOVW R1, 0xb950 ; R1 = 0xb950 -- 2aac aa16 ADD R2, SP, #88 ; R2 = SP + 88 -- 2aae f2c00100 MOVT R1, 0x0 ; R1 += 0 = b950 -- 2ab2 4620 MOV R0, R4 ; R0 = _SecTaskRef -- 2ab4 4479 ADD R1, PC ; R1 += 2ab8 = e408 @"allow-obliterate-device" -- 2ab6 f00ae9fa BLX 0xa3f4 ; 0xceac _SecTaskCopyValueForEntitlement ; CFTypeRef = SecTaskCopyValueForEntitlement(SecTaskRef, ; @"allow-obliterate-device", // CFStringRef entitlement, ; SP + 88); // CFErrorRef *error); ; -- 2aba 4606 MOV R6, R0 ; R6 = CFTypeRef ; ; if (CFTypeRef == NULL) goto __could_not_extract_value ; -- 2abc 2e00 >CMP R6, #0 -- 2abe d023 BEQ 0x46 ; 0x2b08 __could_not_extract_value ; ; if (!error) goto so_far_so_good ; -- 2ac0 9816 LDR R0, [SP, #88] ; R0 = *(SP +88) -- 2ac2 b380 CBZ R0, 0x2b26 ; _so_far_so_good ; ; else error ("verify_obliteration_client", "@there was an error retrieving the entitlement value"); ; -- 2ac4 f64820eb MOVW R0, 0x8aeb ; R0 = 0x8aeb -- 2ac8 f2c00000 MOVT R0, 0x0 ; R0 += 0 = 8aeb -- 2acc f64b114e MOVW R1, 0xb94e ; R1 = 0xb94e -- 2ad0 f2c00100 MOVT R1, 0x0 ; R1 += 0 = b94e -- 2ad4 4478 ADD R0, PC ; R0 += 2ad8 = b5c3 verify_obliteration_client -- 2ad6 4479 ADD R1, PC ; R1 += 2ada = e428 @"There was an error retrieving the entitlement value" -- 2ad8 e04e B.n 0x9c ; 0x2b78 _to_common_error __no_sec_task_from_audit_token: -- 2ada f64820d5 MOVW R0, 0x8ad5 ; R0 = 0x8ad5 -- 2ade f2c00000 MOVT R0, 0x0 ; R0 += 0 = 8ad5 -- 2ae2 f64b1108 MOVW R1, 0xb908 ; R1 = 0xb908 -- 2ae6 f2c00100 MOVT R1, 0x0 ; R1 += 0 = b908 -- 2aea 4478 ADD R0, PC ; R0 += 2aee = b5c3 verify_obliteration_client -- 2aec 4479 ADD R1, PC ; R1 += 2af0 = e3f8 @"Could not create the security task from the audit token" -- 2aee f7ffff0f BL 0xfffffe1e ; 0x2910 _common_error __could_not_verify_obliteration_client: -- 2af2 f6464008 MOVW R0, 0x6c08 ; R0 = 0x6c08 -- 2af6 f2c00000 MOVT R0, 0x0 ; R0 += 0 = 6c08 -- 2afa f64a7190 MOVW R1, 0xaf90 ; R1 = 0xaf90 -- 2afe f2c00100 MOVT R1, 0x0 ; R1 += 0 = af90 -- 2b02 4478 ADD R0, PC ; R0 += 2b06 = 970e handle_message -- 2b04 4479 ADD R1, PC ; R1 += 2b08 = da98 @"Could not verify the obliteration client" -- 2b06 e186 B.n 0x30c ; 0x2e16 __could_not_extract_value -- 2b08 f64820a7 MOVW R0, 0x8aa7 ; R0 = 0x8aa7 -- 2b0c f2c00000 MOVT R0, 0x0 ; R0 += 0 = 8aa7 -- 2b10 f64b01fa MOVW R1, 0xb8fa ; R1 = 0xb8fa -- 2b14 f2c00100 MOVT R1, 0x0 ; R1 += 0 = b8fa -- 2b18 4478 ADD R0, PC ; R0 += 2b1c = b5c3 verify_obliteration_client -- 2b1a 4479 ADD R1, PC ; R1 += 2b1e = e418 @"Could not extract the value for the entitlement" -- 2b1c f7fffef8 BL 0xfffffdf0 ; 0x2910 _common_error -- 2b20 f04f35ff MOV.w R5, #-1 ; R5 = 0xffffffffffffffff -- 2b24 e02f B.n 0x5e ; 0x2b86 ; towards_CFRelease __so_far_so_good: -- 2b26 f00ae926 BLX 0xa24c ; 0xcd74 _CFBooleanGetTypeID -- 2b2a 4605 MOV R5, R0 ; R5 = 0xb5c3 -- 2b2c 4630 MOV R0, R6 ; R0 = CFTypeRef -- 2b2e f00ae948 BLX 0xa290 ; 0xcdc0 _CFGetTypeID ; ; if (CFGetTypeId(CFTypeRef) != CFBooleanGetTypeID) ; goto __entitlement_value_not_a_boolean ; -- 2b32 4285 CMP R5, R0 -- 2b34 d116 BNE 0x2c ; 0x2b64 __entitlement_value_not_a_boolean: ; ; otherwise still here: ; -- 2b36 f64a000e MOVW R0, 0xa80e ; R0 = 0xa80e -- 2b3a f2c00000 MOVT R0, 0x0 ; R0 += 0 = a80e -- 2b3e 4478 ADD R0, PC ; R0 += 2b42 = d350 -- 2b40 6800 LDR R0, [ R0, #0 ] ; R0 = *(d350) _kCFBooleanTrue -- 2b42 6801 LDR R1, [ R0, #0 ] ; R1 = kCFBooleanTrue -- 2b44 4630 MOV R0, R6 ; R0 = CFTypeRef -- 2b46 f00ae93a BLX 0xa274 ; 0xcdbc _CFEqual ; ; if (CFEqual(CFTypeRef, _kCFBooleanTrue)) goto to_obliterate ; -- 2b4a 2500 MOVS R5, #0 ; R5 = 0x0 -- 2b4c b9c0 CBNZ R0, 0x2b80 ; _to_obliterate; ; ;else error ("verify_obliteration_client", @"The client does not have the obliteration entitlement"); ; -- 2b4e f6482061 MOVW R0, 0x8a61 ; R0 = 0x8a61 -- 2b52 f2c00000 MOVT R0, 0x0 ; R0 += 0 = 8a61 -- 2b56 f64b01e4 MOVW R1, 0xb8e4 ; R1 = 0xb8e4 -- 2b5a f2c00100 MOVT R1, 0x0 ; R1 += 0 = b8e4 -- 2b5e 4478 ADD R0, PC ; R0 += 2b62 = b5c3 verify_obliteration_client -- 2b60 4479 ADD R1, PC ; R1 += 2b64 = e448 @"The client does not have the obliteration entitlement" -- 2b62 e009 B.n 0x12 ; 0x2b78 _to_common_error __entitlement_value_not_a_boolean: -- 2b64 f648204b MOVW R0, 0x8a4b ; R0 = 0x8a4b -- 2b68 f2c00000 MOVT R0, 0x0 ; R0 += 0 = 8a4b -- 2b6c f64b01be MOVW R1, 0xb8be ; R1 = 0xb8be -- 2b70 f2c00100 MOVT R1, 0x0 ; R1 += 0 = b8be -- 2b74 4478 ADD R0, PC ; R0 += 2b78 = b5c3 verify_obliteration_client -- 2b76 4479 ADD R1, PC ; R1 += 2b7a = e438 @"The entitlement value is not a boolean" _to_common_error: -- 2b78 f7fffeca BL 0xfffffd94 ; 0x2910 _common_error to_obliterate: The obliteration process itself isn't terribly complicated (though it's possible to walk through it with jtool as well). On a side note, iOS 6's obliterator (pre-XPC) had an easter egg in outputting ""And you will know my name is the Lord when I lay my vengeance upon thee."

CSOPS

The Security.Framework SecTask APIs (visible in the OS X SDK Headers ( SecTask.h ), but not in those of iOS) obtain the entitlements by calling on Apple's proprietary system calls in XNU dealing with code signing - csops (#169) and csops_audittoken (#170). These system calls are undocumented, but their implementation in the XNU open sources (bsd/kern/kern_proc.c) is straightforward to read. The relevant code is shown below:

static int csops_internal(pid_t pid, int ops, user_addr_t uaddr, user_size_t usersize, user_addr_t uaudittoken) ... switch (ops) { ... case CS_OPS_ENTITLEMENTS_BLOB: { void *start; size_t length; proc_lock(pt); if ((pt->p_csflags & CS_VALID) == 0) { proc_unlock(pt); error = EINVAL; break; } error = cs_entitlements_blob_get(pt, &start, &length); proc_unlock(pt); if (error) break; error = csops_copy_token(start, length, usize, uaddr); break; }

The heart of the function is cs_entitlements_blob_get (in bsd/kern/ubc_subr.c ). This function locates the entitlements blob in the bigger code signing super blob , and returns it to the caller. The blob itself is just the embedded plist. More information on the code signing blob itself can be found in the open source portion of Securitya (specifically, ./libsecurity_codesigning/lib/cscdefs.c and .h , so I won't get into it here.