11208elppA
Jonathan Levin, http://www.newosxbook.com/ (@Technologeeks) - 04/08/15
The 2nd Edition of MOXiI delves deep into a realm I totally ignored in the 1st Ed - that of Apple's private frameworks. Most of the "cool" functionality in both OS X and iOS is provided by private frameworks, and their number far outweighs the public ones (359 vs 126 in OS X). I'm hoping to provide a tour of the private frameworks in MOXiI2. While it's true that Apple won't allow any store apps to link with private frameworks (which they can easily verify via jtool -L
or jtool -S | grep dlopen
), it's still interesting - especially for an internals book, and may be useful for Cydia(iOS) or DMG (OSX) based apps. The 2nd Ed aims to provide a reference which - though far from complete - will provide an unprecedented level of detail on these frameworks.
One example of "cool" functionality is everything to do with WiFi. Apple's wifi stack is quite powerful, and provides lots of useful functionality, but most of it well hidden. Case in point - Relative Signal Strength Indicator (RSSI) values, which can enable you to get a better idea of where the force is strong with WiFi, and where it's not. You can get those by pressing alt (option) while clicking the WiFi status icon, or by using the airport
command, which is buried deep in Apple80211
pretty interesting. Or does it?
Enter: Apple80211
Surprisingly enough, airport
doesn't actually link with 80211 directly, but instead going through the public CoreWLan
which relies on the private CoreWifi
. This is also true for Apple80211
's days are limited in OS X (as they were in iOS). While the framework is still alive, however, it provides access to all things WiFi - communicating with the underlying driver, which itself is a subclass of the Apple80211Family.kext
. You can examine the framework's exports with nm
, or jtool -S -v
:
morpheus@Zephyr (~)$ ARCH=x86_64 jtool -S /System/Library/PrivateFrameworks/Apple80211.framework/Apple80211 | grep "T "
0000000000001638 T _ACInterfaceCreate
0000000000001721 T _ACInterfaceDeviceNameCopy
000000000000186d T _ACInterfaceGetPower
00000000000017d6 T _ACInterfaceSetPower
0000000000001986 T _ACNetworkAssociate
000000000000197e T _ACNetworkCopyBroadcastScanResults
0000000000001991 T _ACNetworkCopyDirectedScanResult
00000000000018f7 T _ACNetworkDisassociate
0000000000001999 T _ACNetworkGetBSSID
00000000000019a1 T _ACNetworkSet8021XProfile
00000000000019a9 T _ACNetworkSetRemember
00000000000019b1 T _ACNetworkSetRememberAtIndex
000000000000f8a1 T _Apple80211Associate
000000000000f8ad T _Apple80211Associate2
000000000000214d T _Apple80211BindToInterface
0000000000001aab T _Apple80211Close
0000000000007f1c T _Apple80211CopyValue
0000000000011740 T _Apple80211Disassociate
000000000001235c T _Apple80211ErrToStr
0000000000001af0 T _Apple80211EventMonitoringHalt
0000000000011edf T _Apple80211EventMonitoringInit
00000000000120db T _Apple80211EventMonitoringInit2
0000000000002289 T _Apple80211Get
0000000000001b55 T _Apple80211GetIfListCopy
00000000000088ba T _Apple80211GetInfoCopy
0000000000008860 T _Apple80211GetInterfaceNameCopy
0000000000008c9d T _Apple80211GetPower
0000000000001fa1 T _Apple80211GetVirtualIfListCopy
00000000000123ad T _Apple80211MaxLinkSpeed
00000000000019d4 T _Apple80211Open
000000000000d6c8 T _Apple80211Scan
000000000000ef99 T _Apple80211ScanAsync
000000000000f0d6 T _Apple80211ScanDynamic
0000000000008e54 T _Apple80211Set
0000000000008d4e T _Apple80211SetPower
00000000000122d6 T _Apple80211StartMonitoringEvent
0000000000012318 T _Apple80211StopMonitoringEvent
00000000000014e0 T _WirelessAirPortDeviceNameCopy
00000000000019b9 T _WirelessAttach
00000000000019c1 T _WirelessDetach
00000000000019c9 T _WirelessGetInfo2
Focusing on the Apple80211*
exports, an educated guess shows the sequence of calls should be something like: Apple80211Open() → Apple80211GetIfList() → Apple80211BindToInterface()
and then all the other calls such as Apple80211[Get/Set]
, and, of course - Apple80211Scan()
. To get the actual APIs, we need to reverse engineer the framework. So grab your copy of
Reversing Apple80211
To reverse Apple80211.framework
from OS X, you can use otool -tV
(jtool
, alas, does not support Intel disassembly yet, but I tried to provide the comments it does for ARM, manually). Starting with Apple80211Open
, you should see:
Apple80211Open/Close
; Let rdi = arg1. Note there's no rsi here, meaning only 1 argument. _Apple80211Open: 00000000000019d4 pushq %rbp 00000000000019d5 movq %rsp, %rbp 00000000000019d8 pushq %r15 00000000000019da pushq %r14 00000000000019dc pushq %r12 00000000000019de pushq %rbx 00000000000019df movq %rdi, %r14 ; r14 = arg1 00000000000019e2 movl $0xfffff0c4, %r12d ## imm = 0xFFFFF0C4 ; if (!arg1) goto 1a9f 00000000000019e8 testq %r14, %r14 00000000000019eb je 0x1a9f 00000000000019f1 movl $0x50, %edi ; rax = malloc(80) 00000000000019f6 callq 0x1e52c ## symbol stub for: _malloc 00000000000019fb movq %rax, %rbx ; rbx = malloc(80); 00000000000019fe movl $0xfffff0c3, %r12d ## imm = 0xFFFFF0C3 ; if (rax) goto 1a9f 0000000000001a04 testq %rbx, %rbx 0000000000001a07 je 0x1a9f ; memset (rbx, '\0', 0x50); 0000000000001a0d movq $0x0, 0x48(%rbx) 0000000000001a15 movq $0x0, 0x40(%rbx) 0000000000001a1d movq $0x0, 0x38(%rbx) 0000000000001a25 movq $0x0, 0x30(%rbx) 0000000000001a2d movq $0x0, 0x28(%rbx) 0000000000001a35 movq $0x0, 0x20(%rbx) 0000000000001a3d movq $0x0, 0x18(%rbx) 0000000000001a45 movq $0x0, 0x10(%rbx) 0000000000001a4d movq $0x0, 0x8(%rbx) 0000000000001a55 movq $0x0, (%rbx) ; r15 = 0 0000000000001a5c xorl %r15d, %r15d 0000000000001a5f movl $0x2, %edi 0000000000001a64 movl $0x2, %esi 0000000000001a69 xorl %edx, %edx ; rax = socket (2,2,0) = socket (AF_INET, SOCK_DGRAM, 0); 0000000000001a6b callq 0x1e562 ## symbol stub for: _socket 0000000000001a70 movl %eax, (%rbx) ; the socket stored at rbx 0000000000001a72 testl %eax, %eax ; if ( rax < 0) goto 1a85 0000000000001a74 js 0x1a85 0000000000001a76 movq %rbx, (%r14) ; rbx stored at r14, which was our arg1 0000000000001a79 movl %r15d, %eax 0000000000001a7c popq %rbx 0000000000001a7d popq %r12 0000000000001a7f popq %r14 0000000000001a81 popq %r15 0000000000001a83 popq %rbp 0000000000001a84 retq ; 0x1085 calls error.. 0000000000001a85 callq 0x1e4ae ## symbol stub for: ___error 0000000000001a8a movl (%rax), %r12d 0000000000001a8d testl %r12d, %r12d 0000000000001a90 je 0x1a79 0000000000001a92 testq %rbx, %rbx 0000000000001a95 je 0x1a9f 0000000000001a97 movq %rbx, %rdi 0000000000001a9a callq 0x1e50e ## symbol stub for: _free 0000000000001a9f movq $0x0, (%r14) 0000000000001aa6 movl %r12d, %r15d 0000000000001aa9 jmp 0x1a79
So we see it only takes one argument - which we can deduce is a pointer to a pointer to a struct (i.e. a pointer to a struct, as an out parameter). Looking at Apple80211Close()
(right after ..Open) reveals that it also takes one argument (the pointer struct), as one can expect, it undoes the Open by closing the socket descriptor and free()
ing the memory - but not before it also calls Apple80211EventMonitoringHalt()
, in case certain members of the structure are set:
_Apple80211Close:
0000000000001aab pushq %rbp
0000000000001aac movq %rsp, %rbp
0000000000001aaf pushq %rbx
0000000000001ab0 pushq %rax
0000000000001ab1 movq %rdi, %rbx ; rbx = arg1
0000000000001ab4 movl $0xfffff0c4, %eax ## imm = 0xFFFFF0C4
0000000000001ab9 testq %rbx, %rbx
; if (!arg1) goto 0x1ae9 (returns 0xfffff0c4)
0000000000001abc je 0x1ae9
0000000000001abe movl (%rbx), %edi
0000000000001ac0 testl %edi, %edi
0000000000001ac2 js 0x1ac9
; close (*arg1);
0000000000001ac4 callq 0x1e4d8 ## symbol stub for: _close
;
; if (*(arg1 + 0x20) || *(arg1 + 0x48)) { Apple80211EventMonitoringHalt(arg1) }
;
0000000000001ac9 cmpq $0x0, 0x20(%rbx)
0000000000001ace jne 0x1ad7
0000000000001ad0 cmpq $0x0, 0x48(%rbx)
0000000000001ad5 je 0x1adf
0000000000001ad7 movq %rbx, %rdi
0000000000001ada callq _Apple80211EventMonitoringHalt
; free (arg1);
0000000000001adf movq %rbx, %rdi
0000000000001ae2 callq 0x1e50e ## symbol stub for: _free
0000000000001ae7 xorl %eax, %eax
0000000000001ae9 addq $0x8, %rsp
0000000000001aed popq %rbx
0000000000001aee popq %rbp
So we have the first two prototypes, which look like:
struct Apple80211;
typedef struct Apple80211 *Apple80211Ref;
int Apple80211Open (Apple80211Ref *handle);
int Apple80211Close(Apple80211Ref handle);
Apple80211BindToInterface
This function is significantly longer than Open/Close, and apparently has two arguments (as noted by the saving of both rsi and rdi). But what are they?
_Apple80211BindToInterface: 000000000000214d pushq %rbp 000000000000214e movq %rsp, %rbp 0000000000002151 pushq %r15 0000000000002153 pushq %r14 0000000000002155 pushq %r13 0000000000002157 pushq %r12 0000000000002159 pushq %rbx 000000000000215a subq $0x58, %rsp ; set up stack of 88 bytes 000000000000215e movq %rsi, %r15 ; r15 = arg2 0000000000002161 movq %rdi, %rbx ; rbx = arg1 ; The following is the stack check 0000000000002164 movq 0x1feb5(%rip), %r13 ## literal pool symbol address: ___stack_chk_guard 000000000000216b movq (%r13), %rax 000000000000216f movq %rax, -0x30(%rbp) 0000000000002173 movl $0xfffff0c4, %r14d ## imm = 0xFFFFF0C4 ; if rbx (arg1) is null, jump to 2268, return error 0000000000002179 testq %rbx, %rbx 000000000000217c je 0x2268 ; if r15 (arg2) is null, jump to 2268, return error 0000000000002182 testq %r15, %r15 0000000000002185 je 0x2268 ; rax = *rbx 000000000000218b movl (%rbx), %eax 000000000000218d testl %eax, %eax ; if (*rbx < 0) jump to 2268 (i.e. if socket is not a valid descriptor) 000000000000218f js 0x2268 0000000000002195 leaq -0x78(%rbp), %rsi 0000000000002199 movq %rbx, %rdi 000000000000219c callq __getIfListCopy ; if (! __getIfListCopy(rbp - 0x78)) goto 0x21d6 00000000000021a1 testl %eax, %eax 00000000000021a3 jne 0x21d6 00000000000021a5 movq -0x78(%rbp), %r12 00000000000021a9 movq %r12, %rdi 00000000000021ac callq 0x1e39a ## symbol stub for: _CFArrayGetCount 00000000000021b1 xorl %esi, %esi 00000000000021b3 movq %r12, %rdi 00000000000021b6 movq %rax, %rdx 00000000000021b9 movq %r15, %rcx ; r12 = CFArrayContainsValue (r12, 0, CFArrayGetCout(), r15 = arg2); 00000000000021bc callq 0x1e38e ## symbol stub for: _CFArrayContainsValue 00000000000021c1 movb %al, %r12b 00000000000021c4 movq -0x78(%rbp), %rdi 00000000000021c8 callq 0x1e418 ## symbol stub for: _CFRelease ; if (r12 == 0) goto 0x2268) 00000000000021cd testb %r12b, %r12b 00000000000021d0 je 0x2268 ; exit with error 00000000000021d6 leaq -0x40(%rbp), %rsi 00000000000021da movl $0x10, %edx 00000000000021df movl $0x8000100, %ecx ## imm = 0x8000100 00000000000021e4 movq %r15, %rdi 00000000000021e7 callq 0x1e472 ## symbol stub for: _CFStringGetCString 00000000000021ec testb %al, %al 00000000000021ee je 0x2268 ; exit with error 00000000000021f0 cmpq $0x0, 0x20(%rbx) 00000000000021f5 je 0x21ff 00000000000021f7 movq %rbx, %rdi 00000000000021fa callq _Apple80211EventMonitoringHalt ..
Note that arg2 ends up in r15, which is then used in CFArrayContainsValue. This function is well documented:
morpheus@Zephyr (~) $ grep CFArrayContainsValue \
/Developer/SDKs/MacOSX10.10.sdk/System/Library/Frameworks/CoreFoundation.framework/Headers/CFArray.h
@function CFArrayContainsValue
Boolean CFArrayContainsValue(CFArrayRef theArray, CFRange range, const void *value);
The Array here is retrieved from the internal __getIfListCopy()
, and then arg15 is searched for in the array (the CFRange is 0..CFArrayGetCount()
). We can therefore deduce the value is also a CF*..
, and it makes sense it's a CFString. So we have:
int Apple80211BindToInterface(Apple80211Ref handle, CFStringRef interface);
This can quickly be corroborated by the following code:
int main (int argc, char **argv)
{
char *ifName = argv[1];
int rc = Apple80211Open(&handle);
CFStringRef ifName = CFStringCreateWithCString(kCFAllocatorDefault, ifName,
kCFStringEncodingISOLatin1);
if (rc) { fprintf(stderr, "Apple80211Open failed..\n"); }
rc = Apple80211BindToInterface(handle, ifName);
if (rc) { fprintf(stderr, "Apple80211BindToInterface failed..\n"); }
return (rc);
}
.. which works for "en0" as an argument, but not for bogus values.
Apple80211EventMonitoringInit
The Apple80211Close()
disassembly revealed a call to Apple80211EventMonitoringHalt
. This means that a client can register a callback for events on the interface - which is what you can see with Apple80211EventMonitoringInit()
:
_Apple80211EventMonitoringInit:
0000000000011edf pushq %rbp
0000000000011ee0 movq %rsp, %rbp
0000000000011ee3 pushq %r15
0000000000011ee5 pushq %r14
0000000000011ee7 pushq %r13
0000000000011ee9 pushq %r12
0000000000011eeb pushq %rbx
0000000000011eec subq $0x48, %rsp ; set up stack frame
0000000000011ef0 movq %rsi, %r15 = arg2
0000000000011ef3 movq %rdi, %rbx = arg1
0000000000011ef6 movl $0xfffff0c4, %r12d ## imm = 0xFFFFF0C4
; The usual argument validation - handle, socket, and arg2 not null
0000000000011efc testq %rbx, %rbx ; handle
0000000000011eff je error; // 0x12033
0000000000011f05 testq %r15, %r15 ; arg2
0000000000011f08 je error; // 0x12033
0000000000011f0e cmpl $0x0, (%rbx) ; *handle = i.e. the socket
0000000000011f11 js error; // 0x12033
0000000000011f17 movq %rcx, -0x70(%rbp) ; save arg3
0000000000011f1b movq %rdx, -0x68(%rbp) ; save arg4
0000000000011f1f cmpb $0x0, 0x4(%rbx) ; field 2 of structure
0000000000011f23 je 0x12033
; if offset 32 of structure is NOT set, we can jump over EventMonitoringHalt
0000000000011f29 cmpq $0x0, 0x20(%rbx) ; offset 32 of structure
0000000000011f2e je 0x11f38
0000000000011f30 movq %rbx, %rdi
0000000000011f33 callq _Apple80211EventMonitoringHalt
; after Halt : Setup socket
0000000000011f38 movl $0x20, %edi
0000000000011f3d movl $0x3, %esi
0000000000011f42 movl $0x1, %edx
; socket (0x20, 3, 1) = socket (PF_SYSTEM, 3, 1);
0000000000011f47 callq 0x1e562 ## symbol stub for: _socket
..
So this time we have four arguments. The first is a handle. The 2nd, 3rd and 4th are more challenging. One of them's a callback, the others..?
- Option 1: do more disassembly to figure out the arguments.
- Option 2: cheat with a debugger.
Calling the function with four arguments, passing 0xdead, 0xbeef and 0xdeadbeef for 2,3 and 4, respectively, we get a crash, and lldb reports:
(lldb) bt
* thread #1: tid = 0x52d48, 0x00007fff93113ce3 CoreFoundation`CFRunLoopAddSource + 67, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0xb)
* frame #0: 0x00007fff93113ce3 CoreFoundation`CFRunLoopAddSource + 67
frame #1: 0x00007fff8f3f500d Apple80211`Apple80211EventMonitoringInit + 302
frame #2: 0x0000000100000d37 80211`main(argc=1, argv=0x00007fff5fbffbc8) + 103 at 80211.c:50
(lldb) reg read rdi rsi rcx rdx
rdi = 0x00000000deadbeef
rsi = 0x0000000100200340
rcx = 0x0000000000000000
rdx = 0x00007fff7e5fef60 @"kCFRunLoopDefaultMode"
So arg4 is a runloop. We can use CFRunLoopGetCurrent()
for that. That's one down. The other two simply have to be a callback and a context. To get the callback arguments, we can use trial and error. Eventually, it boils down to :
typedef int (*Apple80211Callback_t) ( uint32_t err, // Usually 0, unless..
Apple80211Ref Handle, // 1st arg to Init
UInt32 Event, // from StartMonitoring (see below)
void *EventData, // Data ptr, or NULL
UInt32 EventDataLen, // count of eventData, if not NULL
void *Context); // 3rd arg to Init
int Apple80211EventMonitoringInit(Apple80211Ref handle, Apple80211Callback_t func,void *Context , CFRunLoopRef rlRef);
Apple80211StartMonitoringEvent
This one's easy. Two arguments, with the first being the handle (as usual), the second being the event to monitor.
_Apple80211StartMonitoringEvent: 00000000000122d6 pushq %rbp 00000000000122d7 movq %rsp, %rbp 00000000000122da movl $0xfffff0c4, %eax ## imm = 0xFFFFF0C4 00000000000122df testq %rdi, %rdi ; check first argument 00000000000122e2 je 0x12316 ; if null, error 00000000000122e4 cmpq $0x0, 0x20(%rdi) ; monitoring set 00000000000122e9 jne 0x122f2 00000000000122eb cmpq $0x0, 0x48(%rdi) ; offset 0x48 of the handle 00000000000122f0 je 0x12316 ; if 0, error 00000000000122f2 cmpl $0x45, %esi ; if arg2 > 0x45 00000000000122f5 ja 0x12316 00000000000122f7 decl %esi 00000000000122f9 movb %sil, %cl 00000000000122fc andb $0x7, %cl 00000000000122ff movl $0x1, %eax 0000000000012304 shll %cl, %eax 0000000000012306 shrl $0x3, %esi 0000000000012309 movzbl 0x14(%rdi,%rsi), %ecx 000000000001230e orl %eax, %ecx 0000000000012310 movb %cl, 0x14(%rdi,%rsi) 0000000000012314 xorl %eax, %eax 0000000000012316 popq %rbp 0000000000012317 retq
How do we find events to monitor? Well, we can just loop over all events..
.. Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef Callback: err 0, event 4, data 0x7fff58dbd538, len 8 bytes context 0xbeef Data: 00 00 00 00 08 00 00 00 ← Link state (00 = off) Callback: err 0, event 2, data 0x0, len 0 bytes context 0xbeef ← SSID, likely Callback: err 0, event 3, data 0x0, len 0 bytes context 0xbeef ← BSSID? Callback: err 0, event 1, data 0x0, len 0 bytes context 0xbeef ← Poweroff, definitely (power on) Callback: err 0, event 1, data 0x0, len 0 bytes context 0xbeef ← Power on. Note no data here. Shame Callback: err 0, event 54, data 0x7fff58dbd538, len 32 bytes context 0xbeef Data: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef Callback: err 0, event 11, data 0x0, len 0 bytes context 0xbeef Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef Callback: err 0, event 16, data 0x0, len 0 bytes context 0xbeef Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef Callback: err -3905, event 9, data 0x0, len 0 bytes context 0xbeef Callback: err 0, event 4, data 0x7fff58dbd538, len 8 bytes context 0xbeef Data: 01 00 00 00 00 00 00 00 Callback: err 0, event 2, data 0x0, len 0 bytes context 0xbeef ← SSID, likely Callback: err 0, event 3, data 0x0, len 0 bytes context 0xbeef ← BSSID? Callback: err 0, event 9, data 0x0, len 0 bytes context 0xbeef ← Association successful Callback: err 0, event 17, data 0x7fff58dbd538, len 8 bytes context 0xbeef Data: b1 ff ff ff 36 00 00 00
Apple80211GetPower/SetPower()
These are simple:
int Apple80211GetPower(Apple80211Ref handle, uint32_t *power);
int Apple80211SetPower(Apple80211Ref handle, uint32_t power);
Apple80211Scan
This function requires three arguments. The first is the handle. To figure out the other two, we start by passing the usual poison (0xdeadbeef) to see a crash reported:
Process 4954 stopped
* thread #1: tid = 0x6130b, 0x00007fff8f3f0ba6 Apple80211`Apple80211Scan + 1246, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0xdeadbeef)
frame #0: 0x00007fff8f3f0ba6 Apple80211`Apple80211Scan + 1246
Apple80211`Apple80211Scan + 1246:
-> 0x7fff8f3f0ba6: movq %rax, (%rcx)
0x7fff8f3f0ba9: xorl %r12d, %r12d
0x7fff8f3f0bac: callq 0x7fff8f401418 ; symbol stub for: CFRelease
0x7fff8f3f0bb1: movl -0x1778(%rbp), %edi
(lldb) reg read rcx
rcx = 0x00000000deadbeef
So the value of arg2 ends up being treated as a pointer (because rax is moved to the value it is pointed by). This means that this is an out value of some sort. Changing this to a void *, and passing it by reference works, and gets us some opaque object. Calling CFGetTypeID()
on it returns 19, which is a CFArray
, and is allocated by the framework for us. Individual entries (one per network found) are CFDictionary, which we can easily verify this by trying CFGetTypeID
on them - that's the Foundation's version of RTTI. When printed out to XML, an individual result looks something like this:
<?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>AGE</key>
<integer>0</integer>
<key>AP_MODE</key>
<integer>2</integer>
<key>BEACON_INT</key>
<integer>100</integer>
<key>BSSID</key>
<string>92:3:d8:7f:f0:62</string>
<key>CAPABILITIES</key>
<integer>1057</integer>
<key>CHANNEL</key>
<integer>1</integer>
<key>CHANNEL_FLAGS</key>
<integer>10</integer>
<key>HT_CAPS_IE</key>
<dict>
<key>AMPDU_PARAMS</key>
<integer>27</integer>
<key>ASEL_CAPS</key>
<integer>0</integer>
<key>CAPS</key>
<integer>429</integer>
<key>EXT_CAPS</key>
<integer>1024</integer>
<key>MCS_SET</key>
<data>
//8AAAAAAAAAAAAAgAAAAA==
</data>
<key>TXBF_CAPS</key>
<integer>212329990</integer>
</dict>
<key>HT_IE</key>
<dict>
<key>HT_BASIC_MCS_SET</key>
<data>
AAAAAAAAAAAAAAAAAAAAAA==
</data>
<key>HT_DUAL_BEACON</key>
<false/>
<key>HT_DUAL_CTS_PROT</key>
<false/>
<key>HT_LSIG_TXOP_PROT_FULL</key>
<false/>
<key>HT_NON_GF_STAS_PRESENT</key>
<true/>
<key>HT_OBSS_NON_HT_STAS_PRESENT</key>
<false/>
<key>HT_OP_MODE</key>
<integer>1</integer>
<key>HT_PCO_ACTIVE</key>
<false/>
<key>HT_PCO_PHASE</key>
<false/>
<key>HT_PRIMARY_CHAN</key>
<integer>1</integer>
<key>HT_PSMP_STAS_ONLY</key>
<false/>
<key>HT_RIFS_MODE</key>
<false/>
<key>HT_SECONDARY_BEACON</key>
<false/>
<key>HT_SECONDARY_CHAN_OFFSET</key>
<integer>0</integer>
<key>HT_SERVICE_INT</key>
<integer>0</integer>
<key>HT_STA_CHAN_WIDTH</key>
<false/>
<key>HT_TX_BURST_LIMIT</key>
<false/>
</dict>
<key>IE</key>
<data>
AAtvcHRpbXVtd2lmaQEIgoSLlowSmCQDAQEqAQAyBLBIYGwtGq0BG///AAAAAAAAAAAA
AIAAAAAABAbmpwwAPRYBAAUAAAAAAAAAAAAAAAAAAAAAAAAAfwgAAAAAAAAAQN0YAFDy
AgEBgAADpAAAJ6QAAEJDXgBiMi8A3QkAA38BAQAA/38=
</data>
<key>NOISE</key>
<integer>-92</integer>
<key>RATES</key>
<array>
<integer>1</integer>
<integer>2</integer>
<integer>5</integer>
<integer>6</integer>
<integer>9</integer>
<integer>11</integer>
<integer>12</integer>
<integer>18</integer>
<integer>24</integer>
<integer>36</integer>
<integer>48</integer>
<integer>54</integer>
</array>
<key>RSSI</key>
<integer>-54</integer>
<key>SSID</key>
<data>
b3B0aW11bXdpZmk= <!-- 'optimumwifi' in Base64 >-->
</data>
<key>SSID_STR</key>
<string>optimumwifi</string>
</dict>
</plist>
Obtaining these values programmatically is a simple enough matter with the CFDictionary
APIs. Note that RSSI value. This, and a little bit of curses, and you can make yourself a 20-line WiFi detector. Using the (private) frameworks for accelerometer and GPS, you could even make this into a useful app :-)
As for the third argument, trying the usual poison crashes us before the scan, and reveals:
* thread #1: tid = 0x616aa, 0x00007fff93de70dd libobjc.A.dylib`objc_msgSend + 29, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x18)
* frame #0: 0x00007fff93de70dd libobjc.A.dylib`objc_msgSend + 29
frame #1: 0x00007fff930be28f CoreFoundation`CFDictionaryGetValue + 159
frame #2: 0x00007fff8f3f0797 Apple80211`Apple80211Scan + 207
frame #3: 0x0000000100000c0e 80211`main(argc=1, argv=0x00007fff5fbffbc8) + 206 at 80211.c:107
frame #4: 0x00007fff90ede5c9 libdyld.dylib`start + 1
frame #5: 0x00007fff90ede5c9 libdyld.dylib`start + 1
(lldb) reg read rdx
rdx = 0x00007fff7dbbb880 @"SCAN_SSID_LIST"
Which means it's a Dictionary
, that is expected to have (at least) a "SCAN_SSID_LIST" key. In other words, this argument provides scan parameters. And thus we have:
int Apple80211Scan(Apple80211Ref handle,
CFArrayRef *scanResults,
CFDictionaryRef parameters);
The other variants (ScanAsync and ScanDynamic) are left as an excercise for the reader, but if you're impatient, just check out my reversed .h file.
Apple80211ErrToStr
Easy - give it a uint32_t, and return a char * error string. Examples: 16 - "Resource busy", 82 - "Device power is off".
Apple80211CopyValue
@TODO. For the impatient:int Apple80211CopyValue(Apple80211Ref handle, int field, CFDictionaryRef dictCanBeLeftNULL, CFDataRef outValue);
Next: (re)-porting Apple80211 to iOS
One of the cool things about iOS is that, deep down, it shares 80-90% of its code with OS X. Just compiled for ARM instead of Intel, and far more secure. Apple80211 existed as a private framework in iOS for a long time - first visible (in dlsym()
returns NULL).
There's a strong rationale for removing the framework: Apple is trying to make the system more secure, and adopt the client/server XPC model all throughout. Apple moves all the functionality into a daemon (in this case, /usr/sbin/wifid
), and any requests are made over XPC (a glorified term for Mach messages, really). Using XPC enables the use of entitlements, as the daemon can then check the "caller id" to see if the requesting process has the necessary declaratory permissions to perform the action. Said entitlements are embedded in the code signature, which only Apple can verifiably sign.
The actual functionality, however, is still very much there - and deep down the driver (Apple80211Family) is still the same driver. Thanks to the ingenious design of IOKit, the underlying chipset driver (Broadcom, or otherwise), would be hidden anyway by the family - which makes it possible to use the same framework in both OS X and iOS, and further means that we can reintroduce the user mode portion (i.e. Apple80211.framework) in one of two ways:
- Grabbing a copy of the 80211.framework from an older iOS version: (e.g. out of
IPConfiguration.bundle
)and copy the binary
or
- Decompile either OSX or iOS binary, and recompile it for ARM
Naturally, option #2 is the interesting one. But uncovering the APIs is only half the job - there's still the implementation to figure out, which (again, for the impatient) revolves around two ioctl()
codes - which work similarly in iOS - meaning 80211 shall rise again. And wifid
? It can sit in its sandbox, and wait for playmates to check entitlements. I'll discuss all this and more in Part II. Stay Tuned.. Read it here.
In the interim, for questions/comments, please use the Book's Forum. Especially if you have any requests for The 2nd Edition.
For more info, I discuss these reverse engineering techniques in depth at my company's Reverse Engineering OS X/iOS course. We're planning a public one in July - info@Technologeeks to inquire more or register early!
.Also, check out My other Book if you're into Android Internals!