Friday, October 20, 2017

Reversing AppleMobileFileIntegrity (AMFI) Part 2

Recap
Let's start with a quick recap of part 1 :
  • We introduced MACF(Mandatory Access Control Framework)
  • Learned a little about KEXTs and how they relate to MACF
  • Got introduced to Policies in the context of MACF
  • Learned to identify policies on both macOS and iOS
  • Saw that AppleMobileFileIntegrity.KEXT is a policy module
  • Delved a little into policy registration to extract and identify callback functions

Ad-hoc Binaries
As discussed in part 1, AAPL has full control of all system binaries and added the hashes of these binaries in the KEXT. These binaries as we saw are referred to as being ad-hoc signed and their validation involves a simple lookup of the hash in the KEXT's TrustCache. 


The TrustCache is located in the __TEXT.__const  section of the KEXT. So if we extract the hash of the amfid binary we should be able to find it in the TrustCache. This is demonstrated below where we first extract the signature:

 macho-reverser:iOS10.3 macho-reverser$ jtool --sig amfid | grep CDHash  
           CDHash:        758a25a4549569ac0d36d3b69a92c937180a18e2 (computed)  
 macho-reverser:iOS10.3 macho-reverser$   

And then locate it in the TrustCache:

 macho-reverser:AMFI macho-reverser$ jtool -d __TEXT.__const com.apple.driver.AppleMobileFileIntegrity.kext  

You will have to search through the output as the CDHash is buried deep into the Cache.
amfid CDHash located in __TEXT.__const section
Now there was a bug here - and we will discuss it next - as for a while you could trick hijack amfid and take over it's operations. AAPL fixed this in iOS 9 and now the KEXT verifies the hash before proceeding - as seen at offset 0xFFFFFFF00644F018.

Verifying amfid CDHash in KEXT 
As a side note to help with symbolication I use joker with the -j option to generate a companion file.

Inter-Process Communication(IPC) 101
*OS is built on the XNU kernel. And at the core of XNU is Mach. This microkernel handles among other things Interprocess communications and messaging. Mach's IPC services rely on the notion of "ports" which serve as communication endpoints. Servers and clients alike can allocate ports, however servers require either some type of locator service to allow clients to find them or otherwise need to be well-known. This is where the bootstrap server comes in. It's accessible to all processes on the system which may communicate with it via the bootstrap_port. Clients can request over this port, that the server look up a given service by its name and match them with its port. Here the name is a fully qualified name like "com.apple.MobileFileIntegrity" for example.

launchd
It used to be that mach_init took on the role of bootstrap_server, however launchd has since taken over this role and claims the port(bootstrap_port) during its startup. launchd is the first user-space program to be started by the kernel and therefore has a pid of 1. It's mission is simple - launch jobs(processes) with a specified criteria. Because all processes are it's spawns they inherit access to the bootstrap_port. And so if a service wishes to register - pre-launchd it would have been via the now deprecated api's bootstrap_create_server and bootstrap_create_service - with launchd it can do so in the server's plist and call bootstrap_check_in which will result in launchd handing over the port when it is ready to service requests:

 kern_return_t bootstrap_check_in(mach_port_t bp, // bootstrap_port   
                                  const name_t service_name, // name of service  
                                  mach_port_t *sp); // out: server port  

Note also that launchd pre-registers the port in the server plist. This server port is usually ephemeral but may also be well known if the key HostSpecialPort is added. Finally on launchd, it runs both systems services(daemons) and per-user services(agents).

The server(daemon's) plists can be found in /System/Library/LaunchDaemons:


 macho-reverser:LaunchDaemons macho-reverser$ ls -l  
 total 48  
 -rw-r--r-- 1 root wheel  678 Jun 15 17:37 bootps.plist  
 -rw-r--r-- 1 root wheel  909 Apr 4 2017 com.apple.AirPlayXPCHelper.plist  
 -rw-r--r-- 1 root wheel  811 Jul 31 21:21 com.apple.AppleFileServer.plist  
 -rw-r--r-- 1 root wheel  729 Jan 26 2017 com.apple.AssetCache.builtin.plist  
 -rw-r--r-- 1 root wheel  433 Mar 1 2017 com.apple.AssetCacheActivatorService.plist  
 -rw-r--r-- 1 root wheel  448 Mar 1 2017 com.apple.AssetCacheLocatorService.plist  
 -rw-r--r-- 1 root wheel  437 Mar 1 2017 com.apple.AssetCacheTetheratorService.plist  
 --  

Ok, that was a lot of theory just now and you may be wondering how it all ties in. Well, if you look at amfid's server plist i.e. com.apple.MobileFileIntegrity.plist you will notice that the service will be registered using  HOST_SPECIAL_PORT(18):


amfid plist w/ host_special_port 
So what this means is that when the KEXT makes the up call to amfid it does so over this special port with a call to _host_get_special_port at offset 0xFFFFFFF00644EEE8. You can see the port being set at offset 0xFFFFFFF00644EEE0:
AMFI.kext up call to amfid
Stealing SPECIAL_PORTS
As we alluded to earlier, prior to iOS 9 amfid's special port could be usurped. This ccould be accomplished with a call to the host_set_amfid_port macro in <mach/host_special_ports.h>.

 #define host_set_amfid_port(host, port)     \  
      (host_set_special_port((host), HOST_AMFID_PORT, (port)))  

Let's look at an example of how we might be able to achieve this. Before proceeding however, let's first look at amfid's(225.50.12.0.0) normal initialization routine. So going back to jtool, we first list the segments:

 macho-reverser:iOS10.3 macho-reverser$ jtool -l amfid   
 LC 00: LC_SEGMENT_64     Mem: 0x000000000-0x100000000     __PAGEZERO  
 LC 01: LC_SEGMENT_64     Mem: 0x100000000-0x100004000     __TEXT  
      Mem: 0x100002bd8-0x100003778          __TEXT.__text     (Normal)  
      Mem: 0x100003778-0x1000039c4          __TEXT.__stubs     (Symbol Stubs)  
      Mem: 0x1000039c4-0x100003c28          __TEXT.__stub_helper     (Normal)  
      Mem: 0x100003c28-0x100003d48          __TEXT.__const       
      Mem: 0x100003d48-0x100003da6          __TEXT.__oslogstring     (C-String Literals)  
      Mem: 0x100003da6-0x100003fb8          __TEXT.__cstring     (C-String Literals)  
      Mem: 0x100003fb8-0x100004000          __TEXT.__unwind_info       
 LC 02: LC_SEGMENT_64     Mem: 0x100004000-0x100008000     __DATA  
      Mem: 0x100004000-0x100004090          __DATA.__got     (Non-Lazy Symbol Ptrs)  
      Mem: 0x100004090-0x100004218          __DATA.__la_symbol_ptr     (Lazy Symbol Ptrs)  
      Mem: 0x100004218-0x100004328          __DATA.__const       
      Mem: 0x100004328-0x100004348          __DATA.__cfstring       
      Mem: 0x100004348-0x100004350          __DATA.__data       
 LC 03: LC_SEGMENT_64     Mem: 0x100008000-0x100008000     __RESTRICT  
      Mem: 0x100008000-0x100008000          __RESTRICT.__restrict       
 LC 04: LC_SEGMENT_64     Mem: 0x100008000-0x10000c000     __LINKEDIT  
 LC 05: LC_DYLD_INFO       
 LC 06: LC_SYMTAB         
      Symbol table is at offset 0x8780 (34688), 69 entries  
      String table is at offset 0x8da0 (36256), 1464 bytes  
 LC 07: LC_DYSYMTAB        
        1 local symbols at index   0  
        1 external symbols at index 1  
        67 undefined symbols at index 2  
        No TOC  
        No modtab  
       116 Indirect symbols at offset 0x8bd0  
 LC 08: LC_LOAD_DYLINKER        /usr/lib/dyld  
 LC 09: LC_UUID             UUID: A3B3B122-E61E-3D39-B082-BEBE3FAA86CC  
 LC 10: LC_VERSION_MIN_IPHONEOS     Minimum iOS version:  10.3.0  
 LC 11: LC_SOURCE_VERSION        Source Version:     225.50.12.0.0  
 LC 12: LC_MAIN             Entry Point:       0x3134 (Mem: 0x100003134)  
 LC 13: LC_LOAD_DYLIB          /usr/lib/libmis.dylib  
 LC 14: LC_LOAD_DYLIB          /usr/lib/libMobileGestalt.dylib  
 LC 15: LC_LOAD_DYLIB          /System/Library/PrivateFrameworks/MobileKeyBag.framework/MobileKeyBag  
 LC 16: LC_LOAD_DYLIB          /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation  
 LC 17: LC_LOAD_DYLIB          /System/Library/Frameworks/IOKit.framework/Versions/A/IOKit  
 LC 18: LC_LOAD_DYLIB          /usr/lib/libSystem.B.dylib  
 LC 19: LC_FUNCTION_STARTS       Offset: 34664, Size: 24 (0x8768-0x8780)   
 LC 20: LC_DATA_IN_CODE         Offset: 34688, Size: 0 (0x8780-0x8780)   
 LC 21: LC_CODE_SIGNATURE        Offset: 37728, Size: 384 (0x9360-0x94e0)   

And then we disassemble main (LC_MAIN) filtering on function calls. This will give us an idea of the general flow:

 macho-reverser:iOS10.3 macho-reverser$ jtool -d 0x100003134 amfid | grep BL  
 Disassembling from file offset 0x3134, Address 0x100003134 to next function, mmapped 0x11a561000  
   10000316c     BL   libSystem.B.dylib::_getopt     ; 0x100003928  
   10000319c     BL   libSystem.B.dylib::_os_log_create     ; 0x100003964  
   1000031dc     BL   libSystem.B.dylib::_openlog     ; 0x100003958  
   1000031e4     BL   libSystem.B.dylib::_setlogmask     ; 0x10000397c  
   1000031f4     BL   libSystem.B.dylib::_syslog     ; 0x1000039a0  
   100003210     BL   libSystem.B.dylib::_bootstrap_check_in     ; 0x1000038bc  
   100003218     BL   libSystem.B.dylib::___error     ; 0x100003898  
   100003220     BL   libSystem.B.dylib::_strerror     ; 0x100003988  
   100003234     BL   libSystem.B.dylib::_syslog     ; 0x1000039a0  
   100003270     BL   libSystem.B.dylib::_fprintf     ; 0x10000391c  
   100003278     BL   libSystem.B.dylib::_exit     ; 0x100003910  
   100003290     BL   libSystem.B.dylib::_dispatch_source_create     ; 0x1000038f8  
   1000032a8     BL   libSystem.B.dylib::_syslog     ; 0x1000039a0  
   1000032b0     BL   libSystem.B.dylib::_exit     ; 0x100003910  
   1000032b8     BL   libSystem.B.dylib::_dispatch_set_context     ; 0x1000038ec  
   1000032c8     BL   libSystem.B.dylib::_dispatch_source_set_event_handler_f     ; 0x100003904  
   1000032d0     BL   libSystem.B.dylib::_dispatch_resume     ; 0x1000038e0  
   1000032d4     BL   libSystem.B.dylib::_dispatch_main     ; 0x1000038c8  
   100003304     BL   libSystem.B.dylib::_syslog     ; 0x1000039a0  
   100003308     BL   libSystem.B.dylib::_xpc_transaction_begin     ; 0x1000039ac  
   10000331c     BL   libSystem.B.dylib::_dispatch_mig_server     ; 0x1000038d4  
   100003324     BL   libSystem.B.dylib::_xpc_transaction_end     ; 0x1000039b8  
   10000333c     BL   libSystem.B.dylib::_syslog     ; 0x1000039a0  
   100003350     BL   libSystem.B.dylib::_syslog     ; 0x1000039a0  
   100003418     BL   libSystem.B.dylib::_memchr     ; 0x100003934  
   1000034f8     BL   0x100002bd8  
   100003520     BL   libSystem.B.dylib::_strlen     ; 0x100003994  
   10000356c     BL   libSystem.B.dylib::___stack_chk_fail     ; 0x1000038a4  
   100003600     BL   0x100002f5c  
   1000036c0     BL   0x1000030ec  
   100003748     BLR   X8                      ; 0x100000cfeedfacf  

Note the call to _bootstrap_check_in that we discussed earlier. ARM'ed with this information, if we remove the calls we don't particularly care about like the logging routines etc,  we end up with the following PoC:

// Code inspired by Jonathan Levin 
#include <mach/mach.h>  
 #include <mach/mach_port.h>  
 #include <mach/mach_host.h>  
 #include <mach/host_priv.h>  
 #include <mach/host_special_ports.h>  
 #include <dispatch/dispatch.h>  
 #include <stdio.h>  
 #include <stdlib.h>  
 
#define CHECK_MACH_ERROR(a) do {kern_return_t rr = (a); if ((rr) != KERN_SUCCESS) \  
 { printf("Mach error %x (%s) on line %d of file %s\n", (rr), mach_error_string((rr)), __LINE__, __FILE__); abort(); } } while (0)  
void handler_f (void *arg)  
 {  
   // Just a skeleton....  
   printf("Handle yo biznizz!! \n");  
 }  
 int main(int argc, char **argv)  
 {  
      char *service_name = "com.apple.MobileFileIntegrity";  
      mach_port_t myhost = mach_host_self();  
      mach_port_t jackedAmfi = MACH_PORT_NULL;  
      host_priv_t host_priv;  
      kern_return_t err;  

      err = host_get_host_priv_port(myhost, &host_priv);  
      CHECK_MACH_ERROR(err);  

      err = mach_port_allocate(mach_task_self(), // task acquiring the port right,  
                               MACH_PORT_RIGHT_RECEIVE, // type of right,  
                               &jackedAmfi); // task's name for the port right;  
      
      // insert a send right: we will now have combined receive/send rights  
      err = mach_port_insert_right(mach_task_self(), jackedAmfi, jackedAmfi, MACH_MSG_TYPE_MAKE_SEND);  
      CHECK_MACH_ERROR(err); 
 
      // Bait and switch  
      err = host_set_amfid_port(host_priv, jackedAmfi);  
      CHECK_MACH_ERROR(err); 
 
      // GCD housekeeping  
      dispatch_source_t ms = dispatch_source_create(&_dispatch_source_type_mach_recv,   
         jackedAmfi,   
         0,          
         &_dispatch_main_q);  
      if (!ms) { printf("Error creating mig source"); exit(1); }  
      dispatch_set_context(ms, &ms);   
      dispatch_source_set_event_handler_f (ms, handler_f);  
      dispatch_resume (ms);  
      dispatch_main();  
 }  

It should be noted that the above PoC is incomplete as the handler logic needs to be added. But fear not, we get into this handler later on in the series.

Wrap Up
We have come to the end of part 2 in the series. To recap:
  • We got a HIGH level introduction to Mach IPC
  • Learned a bit about about launchd
  • Introduced the concept of special_ports
  • Determined how the KEXT communicated with it's daemon
  • Saw how special_ports can be usurped
That's all for now. Hope you enjoyed it! And as always if you notice anything that is not correct please don't hesitate to add a comment below...

References:
1. Mac OS X and iOS Internals: To the Apple's Core - For IPC & launchd section
2. MacOS and iOS Internals, Volume III: Security & Insecurity
3. Mac OS X Internals: A Systems Approach
4. *OS Internals - The Forum

1 comment:

  1. Continuing the exploration of Apple Mobile File Integrity (AMFI), part 2 delves deeper into the inner workings of this security mechanism. Godaddy Coupon Reverse engineering AMFI allows researchers.

    ReplyDelete