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 /System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport. That makes 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 /usr/libexec/wifid, which implies that 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 /System/Library/PrivateFrameworks/Apple80211.framework/Apple80211 from OS X (before Apple removes it from there, too :-), and follow along.

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

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);
Now all we need is events to monitor.

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
edit: @Comex apparently reversed the heck out of the codes (and the rest - see note below for his GITHub). His header file shows the APPLE80211_M_* constants for the events, and gets them all. Nice to see my hunches were valid

Apple80211GetPower/SetPower()

These are simple:

int Apple80211GetPower(Apple80211Ref handle, uint32_t *power);
int Apple80211SetPower(Apple80211Ref handle, uint32_t power);
And actually set the system icon, thanks to the generated notification.

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);
That covers almost all of them. You can figure out the rest using the same methods.

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 /System/Library/PrivateFrameworks), then hidden (as in /System/Library/SystemConfiguration/IPConfiguration.bundle/IPConfiguration), but as of iOS 8 is has been removed (which, incidentally, is why tricks like This StackOverflow Question don't work - 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:

  1. Grabbing a copy of the 80211.framework from an older iOS version: (e.g. out of IPConfiguration.bundle)and copy the binary
  2. or
  3. 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!


Note to @comex: *Sigh* When I started this it didn't strike me that headers exist. That said, the point of the article was reversing, without headers, so I hope that people found it useful. But thanks for the mention. https://gist.github.com/comex/0c19c1b3fa569f549947 is a great reference, and I'll use it in Part II when I explain about creating the framework from scratch. We should talk sometime, you and I. I'm a big fan of your work. Call me, maybe? :-) J@...com