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..

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.

Other notes

This article, while detailing more than the book has on entitlements, is still only scraping the surface. I've so far counted well over 100 entitlements, from miscellaneous providers ranging from SpringBoard to the various servers in /usr/libexec. Apple's own apps (in /Applications) make heavy use of entitlements (you might want to try jtool --ent on some. For third party developers, the only entitlements Apple allows are those in XCode, which are limited to iCloud access and a few other paltry features. I'm working on a list of entitlements, providers and consumers. This will take more time, though

iOS also has a daemon - tccd (part of the TCC.framework) which relies on entitlements (and provides an example of a case where the entitlement value can be an array, as in com.apple.private.tcc.allow. TCC (also present in OS X) is responsible for the UIAlert which pops up every now and then for "application XXX would like to access your photos/contacts/etc". Time (and reader interest) permitting, I'll cover this in a future article.

Summary

  • Entitlements are nothing more than strings in a property list. Most entitlements are simple CFBooleans (with a value of <true> denoting the entitlement exists).
  • The Entitlements property list is in a CS_ENTITLEMENTS_BLOB, as part of the code signature
  • Embedding the entitlements in the code signature allows the kernel to store the blob after validating the signature
  • Embedding the entitlements in the code signature also allows Apple to have the final say in verifying 3rd party apps and (dis)allowing any entitlements they would claim
  • Entitlement providers verify the entitlements by calling on the Security.Framework's SecTask APIs
  • Exceptions: get-task-allow and a few other select entitlements are enforced by AppleMobileFileIntegrity.kext (AMFI) in the kernel
  • The SecTask APIs, in turn, call on csops (syscall #169) or csops_audittoken (#170)
  • The burden of verifying the entitlements rests solely on the entitlement provider.
  • There's a flaw in the verification mechanism, which is very similar to the iOS 7 evasi0n technique (brilliant work, guys!). If you've read through the evasi0n7 writeups, it's trivial to figure it out