Apple 80211 - 28 Days Later (a.k.a 11201ellpA, Part II)

Jonathan Levin, http://www.newosxbook.com/ (@Technologeeks) - 05/06/15

Where were we?

If you read Part I, you'll recall I ended it with the proposition of forward engineering (i.e. reconstructing) the Apple80211.framework, so that it can be re-introduced to iOS, and resist Apple's attempts at eradicating it from OS X. The underlying functionality of 80211 in iOS has been moved into /usr/sbin/wifid (which likely just compiles it in statically), with new wrappers in the (also private) MobileWiFi.framework. OS X's /usr/libexec/wifid uses the private CoreWiFi.framework - no surprise here, since "CoreXXX" frameworks often get "MobileXXX" counterparts, and vice versa.

Requiescat in Pace, Apple80211

Moving things to wifid, whatever its location, is not just a nice practice of centralizing functionality: It is imperative in order to enforce entitlements. Simply put, entitlements are simple plists in Apple's deplorable XML grammar - very similar to Android Manifest permissions. What makes them so strong, however, is that the entitlements are embedded in the code signature, which only Apple can generate. This means that a process has no access to its own entitlements - which are stored deep in the kernel caches if and when the signature gets validated. Server processes can then check their caller's entitlements by using the csops properietary syscall (#169), though the Security.framework makes it easy to just get certain entitlements, post CFDictionary serialization and all that stuff. Using jtool's disassembler (ARM64 preferred) you can see this easily, since SecTaskCopyValueForEntitlement is one of the useful functions I had the tool decompile.

root@phontifex (~)# jtool -d __TEXT.__text /usr/sbin/wifid  | grep SecTaskCopy
   10002514c    BL     _SecTaskCopyValueForEntitlement  ; 0x1000b4208
;  R0 =  _SecTaskCopyValueForEntitlement(???,@"com.apple.wifi.manager-access");
# And, our favorite client:
root@phontifex (~)# jtool --ent /System/Library/CoreServices/SpringBoard.app/SpringBoard | grep wifi
        com.apple.wifi.manager-access
(that's a very high level view of a fascinating subject (and there are dozens of entitlements but nonetheless captures the gist of it. MOXiI-2 will have loads more on this, in a well needed dedicated chapter on security. The jtool -d | grep trick is super useful for enumerating entitlements by servers, and --ent is like the codesign(1) option).

Ex Favilla

No matter where the functionality for Wifi is packaged, under what name or whatever - the true functionality rests in the kernel. Apple80211.framework is merely a user mode front end for the Apple80211Family.kext, which your driver (for whatever chipset, usually BRCM) is a subclass of. If one can deduce the kernel level calls, it's a simple matter to recreate the framework, or drop it altogether in favor of a direct interface. And that's what this part is all about.

The previous part caught the attention of @Comex, who correctly commented that the 80211Family headers (including some of the message formats for ioctl(2) I'll discuss here) are already available, from the old 10.5 SDKs or his own (might I add, stellar) work. While it might defeat the purpose of reversing/rebuilding something when (partial) code is available, what I'm showing here does not rely on said headers or anything but the assembly, really - and basically tears apart and re-builds Apple80211.framework, for which no official headers exist. This also has the upside of being free from any APSL,GPL,LPL or any other software license.

Figuring out Apple80211 (Reversing, round II)

Reversing APIs (Part I) is the easy part of the job. Thanks to clear assignment of arguments to registers (and, > 4, the stack) on both ARM and Intel platforms, it's easy to determine the number of arguments a function expects. The poison method (0xdeadbeef and other nice hex values to known arguments) coupled with Apple's wonderful CFGetTypeID()) helps detemine their types with lldb, as was demonstrated.

Recreating the implementation, however, is far more difficult, and requires more detailed knowledge of assembly, as well as overall system-programming level knowledge (for the underlying system calls which are often involved). Apple80211 is fairly easy, however, as Apple chose to be more BSD-ish and less Mach-ish in their implementation. Let's do this, function by function. The reversing is pretty detailed, so here's a quick TOC:

Not handled here are:

000000000001235c T _Apple80211ErrToStr
0000000000001b55 T _Apple80211GetIfListCopy
00000000000088ba T _Apple80211GetInfoCopy
0000000000008860 T _Apple80211GetInterfaceNameCopy
0000000000001fa1 T _Apple80211GetVirtualIfListCopy
00000000000123ad T _Apple80211MaxLinkSpeed

Apple80211Open/Close

I've discussed the disassembly of both these functions in Part I, so it's easy to construct the following pseudo-code for them, like so:

/* We know the size if 0x50, since this gets malloc()ed.. */
struct Apple80211 {  
 /* Fear not, all unknowns will be revealed */
 /* 0x00 */	uint32_t	socket;   // Used for ioctl() 
 /* 0x04 */     uint32_t	unknown1;  
 /* 0x08 */     uint32_t	unknown2;  
 /* 0x0C */     uint32_t	unknown3;  
 /* 0x10 */     uint32_t	unknown4;  
 /* 0x14 */     uint32_t	unknown5;  
 /* 0x18 */     uint32_t	unknown6;  
 /* 0x1C */     uint32_t	unknown7;  
 /* 0x20 */     uint64_t	used_for_monitoring1;   // 1ac9: cmpq    $0x0, 0x20(%rbx)
 /* 0x28 */     uint32_t	unknown8;  
 /* 0x2C */     uint32_t	unknown9;  
 /* 0x30 */     uint32_t	unknown10;  
 /* 0x34 */     uint32_t	unknown11;  
 /* 0x38 */     uint32_t	unknown12;  
 /* 0x3C */     uint32_t	unknown13; 
 /* 0x40 */     uint32_t	unknown14;  
 /* 0x44 */     uint32_t	unknown15;  
 /* 0x48 */     uint64_t	used_for_monitoring2;  // 1ad0: cmpq    $0x0, 0x48(%rbx)

};
typedef struct Apple80211 *Apple80211Ref;


int Apple80211Open (Apple80211Ref *Handle)
{
        if (!Handle) return (0xFFFFF0C4);   // = error in args

        Apple80211Ref returned = malloc(80);
        if (!returned) return (0xfffff0c3); // =  internal error
        memset (returned, '\0', 80);
        returned->socket = socket (AF_INET, SOCK_DGRAM, 0);
        if (returned->socket < 0) return (0xfffff0c3); // = internal error
        *Handle = returned;
        return (0);
} //Apple80211Open

int Apple80211Close(Apple80211Ref  Handle)
{
        if (!Handle) return (0xFFFFF0C4);   // = error in args
        close (Handle->socket);
        if (Handle->used_for_monitoring1 || Handle->used_for_monitoring2)
                Apple80211EventMonitoringHalt(Handle);
        free(Handle);

        return (0);
} // Apple80211Close

So, we have Open and close, to the letter. We may still not know the difference between the two monitoring fields, but we're not done yet.

Apple80211BindToInterface

This function is a bit longer. We have the prototype calling for the handle and a dictionary. From the partial reversing we did, we have the listing below.

int Apple80211BindToInterface(Apple80211Ref handle, CFStringRef interface)
{
   somevar; // at -0x78(%rbp) - at this point we don't know its type
   anothervar; // at -0x40(%rbp) - at this point we don't know its type

 /* 2179: */ if (!handle) return (0xFFFFF0C4);
 /* 2182: */ if (!interface) return (0xFFFFF0C4);
 /* 218d: */ if (!handle->socket) return (0xFFFFF0C4);

 /* 2195: */ if(_getIfListCopy(handle, &somevar)
	{
 /* 21a5: */
 

We still don't know what _getIfListCopy does, but we see it gets two arguments. handle, and the address of some variable. We also see it returns a 0/non-zero return value. Looking deeper around 21a5, however, we can deduce that argument is a CFArray, since _CFArrayGetCount and CFArrayContainsValue() are both called on it. This means that a better name for it would be, say, ifaceList - toggle the above to see.

Looking at 21d6 (after the if), we see that a call to CFStringGetCString retrieves the C-String representation of the interface name. This is followed by a call to strcpy(). While generally a bad practice (all it takes is an 'n'), this is safe this time, since the GetString was constrainted by 16 bytes. 16 bytes is also the max for IFNAMSIZ, which means that our 'unknown1' through 'unknown4' are the interface name, in ASCII. That's four unknowns down.

Continuing with the disassembly we have:

Listing ..: Disassembly of Apple80211BindToInterface (cont)
; Use XMM register for faster memset (an optimization)
000000000000220f        xorps   %xmm0, %xmm0
0000000000002212        movaps  %xmm0, -0x60(%rbp)
0000000000002216        movaps  %xmm0, -0x70(%rbp)
000000000000221a        movq    $0x0, -0x50(%rbp)
0000000000002222        leaq    0x38(%rbx), %r12      ; Get address of handle + 0x38 to r12
0000000000002226        movq    $0x0, 0x38(%rbx)      ; and set it to 0
000000000000222e        leaq    -0x70(%rbp), %r15     ; r15 = rbp-70
0000000000002232        movl    $0x28, %edx           ; rdx = arg3 = 0x28
0000000000002237        movq    %r15, %rdi            ; rdi = arg1 = bp-0x70
000000000000223a        movq    %r14, %rsi            ; rsi = arg2 = r14 (handle->ifName)
000000000000223d        callq   0x1e4cc                 ## symbol stub for: ___strcpy_chk
; strcpy_chk (bp-0x70, handle->ifName, 0x28);
0000000000002242        movl    $0xc, -0x60(%rbp)     ; rbp-0x60 = 12
0000000000002249        movq    %r12, -0x50(%rbp)     ; rbp-0x50 = address of handle + 0x38
000000000000224d        movl    $0x8, -0x58(%rbp)     ; rbp-0x58 = 8
0000000000002254        movl    (%rbx), %edi          ; rdi = arg1 = *handle = handle->socket
0000000000002256        movl    $0xc02869c9, %esi     ## imm = 0xC02869C9 ; that's arg2
000000000000225b        xorl    %eax, %eax	      ; rax = 0 
000000000000225d        movq    %r15, %rdx 	      ; rdx = arg3 = r15 (=rbp-0x70)
0000000000002260        callq   0x1e526                ## symbol stub for: _ioctl
; rc = ioctl (handle->socket, $0xc02869c9,  rbp - 0x70
0000000000002265        movl    %eax, %r14d	      ; save rc of ioctl to r14d
; This is the boilerplate stack smash protection check
0000000000002268        movq    (%r13), %rax	      ; rax = *(r13) = ..
000000000000226c        cmpq    -0x30(%rbp), %rax     ; compare to rbp-0x30
0000000000002270        jne     0x2284		      ; if not, goto 0x2284
; and prolog
0000000000002272        movl    %r14d, %eax	      ; stack safe: return the ioctl rc
0000000000002275        addq    $0x58, %rsp	      ; clear stack
0000000000002279        popq    %rbx		      ; epilog
000000000000227a        popq    %r12
000000000000227c        popq    %r13
000000000000227e        popq    %r14
0000000000002280        popq    %r15
0000000000002282        popq    %rbp
0000000000002283        retq
0000000000002284        callq   0x1e4c0                 ## symbol stub for: ___stack_chk_fail
_Apple80211Get: ...

So, what do we have here? An ioctl(2) call, over the socket, with a code of 0xc02869c9, and a third argument, which is a buffer, which looks like this (remember the stack grows downwards, memory grows upwards):

bp-0x50  handle + 0x38  &(handle->unknown12)
bp-0x58  8
bp-0x5c  0
bp-0x60  12            
bp-0x70  handle->ifName; (16 bytes)
(you can easily corroborate this by lldb:
(lldb) bt
# Note that __getIfListCopy() uses ioctl, so the breakpoint will fire several times. A better 
# approach is to set a breakpoint *before* the ioctl inside Apple80211, but you'll have to account for ASLR (address
# shift). So either that, or 'c' several times, till you see this: 
* thread #1: tid = 0x7f0ae, 0x00007fff90d1c918 libsystem_kernel.dylib`ioctl, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00007fff90d1c918 libsystem_kernel.dylib`ioctl
    frame #1: 0x00007fff8f3e5265 Apple80211`Apple80211BindToInterface + 280
    frame #2: 0x0000000100000d46 80211`init + 134 at 80211.c:19
    frame #3: 0x0000000100000b4b 80211`main(argc=1, argv=0x00007fff5fbffb98) + 27 at 80211.c:86
    frame #4: 0x00007fff90ede5c9 libdyld.dylib`start + 1
(lldb) reg read rdi rsi rdx rbp
     rdi = 0x0000000000000003 # Our socket
     rsi = 0x00000000c02869c9 # The code
     rdx = 0x00007fff5fbffa70 # The buffer (bp - 0x70)
     rbp = 0x00007fff5fbffae0 # just to verify

# And, as can be expected: (pay no attention to that stack based address at the end)
(lldb) mem read $rdx
0x7fff5fbffa70: 65 6e 30 00 00 00 00 00 00 00 00 00 00 00 00 00  en0............. # ifName
0x7fff5fbffa80: 0c 00 00 00|00 00 00 00|08 00 00 00|00 00 00 00  ................
0x7fff5fbffa90: a8 17 11 00 01 00 00 00|90 f9 bf 5f ff 7f 00 00  ?........??_?...
		0x01001117a8 (on heap) | rbp-0x40 (ifArray)

In other words, we have some type of ioctl(2) code, and a request structure. This ioctl(2) ends up appearing all over the place, which means it's generic multiplexer, and one of the numbers (12 or 8) is a request code. That '8' seems to be a length, which would make 12 the more likely request type, and that pointer from our heap (which is part of the handle) is the data. This makes sense when we remember that the ioctl(2) could be coming from a 32-bit or 64-bit process. So:

struct apple80211_ioctl_str {
    char ifname[16];     /* 0x00 */
    uint32_t  type;      /* 0x10 */
    uint32_t  unknown;   /* 0x14 */
    uint32_t  length;    /* 0x18 */
    uint32_t  unknown;   /* 0x1b */ // likely for alignment of *data
    void     *data;      /* 0x20 */

As for that ugly 0xc02869c9 - well, the way codes are constructed is we have a set of macros, _IOW, or _IOWR - for a write or read-write operation, defined in <sys/ioccom.h> as follows:

#define     _IOW(g,n,t)     _IOC(IOC_IN,    (g), (n), sizeof(t))
#define     _IOWR(g,n,t)    _IOC(IOC_INOUT, (g), (n), sizeof(t))

This makes OS X ioctl(2) codes similar in some ways to Windows', and different from Linux: The code encodes in it the sizeof the argument, as well as a subsystem (g) and the request code (n). We know the sizeof(struct apple80211_ioctl_str) = 0x28. That means the 0x69 is 'i', and 'g' is 201 (0xc9). In other words,

#include <sys/ioccom.h>

typedef unsigned int uint32_t;
struct apple80211_ioctl_str {
    char ifname[16];
    uint32_t  type;
    uint32_t  unknown;
    uint32_t  length;
    void     *data;
};

#define APPLE80211_IOC_CODE_GET _IOWR('i', 201, struct apple80211_ioctl_str)
// we encounter this later:
#define APPLE80211_IOC_CODE_SET _IOW('i', 200, struct apple80211_ioctl_str) // 0x802869c8

So we have Apple80211BindToInterface() figured out:

_getIfListCopy(Apple80211Ref Handle, CFArrayRef *ListAsArray)
{
	// Still don't know how to implement that (hint: ioctl(2)...)
	// but we can actually just return 0 here since either way 
	// the binding will be checked later.

} // __getIfListCopy

int Apple80211BindToInterface(Apple80211Ref Handle, CFStringRef Interface)
{
  CFArrayRef ifaceList;

 /* 2179: */ if (!Handle) return (0xFFFFF0C4);
 /* 2182: */ if (!Interface) return (0xFFFFF0C4);
 /* 218d: */ if (!Handle->socket) return (0xFFFFF0C4);

 /* 2195: */ if(_getIfListCopy(Handle, &ifaceList))
        {
 /* 21a5: */  int ifaceCount = CFArrayGetCount(ifaceList);

              int ifaceFound = CFArrayContainsValue (ifaceList, CFRangeMake(0, ifaceCount), Interface );
              CFRelease(ifaceList);
              if (!ifaceFound)  goto exit;


        };

                int rc = 0xbadbad; // some error code. Who cares
 /* 21d6 */ char buffer[16]; // -0x40(%rbp)
            rc = CFStringGetCString (Interface, // CFStringRef theString,
                            buffer,        //  char *buffer,
                            16, // CFIndex bufferSize,
                            0x8000100);     // CFStringEncoding encoding );

  /* 21ec */ if (!rc) goto exit;

  /* 21f0 */ if (Handle->used_for_monitoring1) { Apple80211EventMonitoringHalt(Handle); }
  /* 21ff */ strcpy (Handle->interfaceName, buffer);

                struct apple80211_ioctl_str ioc;
                memset(&ioc, '\0', sizeof(ioc));
                strcpy(ioc.ifname, Handle->interfaceName);
                ioc.type   = 12;  // Bind
                ioc.length = 8;
                ioc.data   = &Handle->ioctl_binding; // formerly unknown12

             rc = ioctl (Handle->socket,
                             APPLE80211_IOC_CODE_GET, // i.e. 0xc02869c9
                             &ioc);

                             
 exit:
             return (rc);

}

Interlude: is this thing working?

At this point is that we can use a simple main() to verify we got things working (i.e. the ioctl(2) is good), by adding it to the library code (that is, the listings above):

// To compile -framework CoreFoundation. You won't need the *actual* Apple80211.
#include "80211.h" // or whatever you call the header
int main(int argc, char **argv)
{

    Apple80211Ref handle;
    int rc = Apple80211Open(&handle);
    if (rc) { fprintf(stderr, "Apple80211Open failed..\n"); exit(rc); }

    printf("Handle: %p\n", handle);

    CFStringRef ifName = CFStringCreateWithCString(kCFAllocatorDefault, "en0",
                                         kCFStringEncodingISOLatin1);
    rc = Apple80211BindToInterface(handle, ifName);
        
    if (rc) { fprintf(stderr, "Apple80211BindToInterface failed..\n"); }

    return (rc);

}

This will compile and run neatly in OS X, and will compile for iOS - but Apple80211BindToInterface will (in iOS 8 and later) fail , as a result of the ioctl(2) failing (with errno/perror(2) reporting - ENOTSUPP/"Operation not supported on socket"). This can be fixed by granting us the same entitlements that /usr/sbin/wifid itself possesses. Specifically, the following:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.wlan.authentication</key>
	<true/>
</dict>
</plist>

Because IO80211Family.kext now (kernels greater than 2423.3.12, which is last I checked that worked) checks for this entitlement in kernel mode, through that despicable AMFI* . Everything I'm showing in this article won't fly with the App Store anyway.. So you can use ldid -S or jtool's nifty new --sign self option (in v0.9) to self sign and bundle the entitlements.


Ok. So now, back to the functions:

Apple80211[Get/Set]Power()

The Power get/setters had a really simple interface, as shown in Part I. But what of their implementation? Look at Apple80211GetPower() first:

; standard prolog..
;
0000000000008caa        subq    $0x18, %rsp           ; Stack: 0x18 = 24 bytes.
0000000000008cae        movq    %rsi, %rbx            ; rbx saves arg2
0000000000008cb1        xorl    %r14d, %r14d          ; r14 is 0
0000000000008cb4        leaq    -0x30(%rbp), %rcx     ; rcx (arg3) is addr of some local, say "var"
0000000000008cb8        movl    $0x13, %esi           ; esi (arg2, 32-bit) is set to 0x13
0000000000008cbd        xorl    %edx, %edx            ; edx (arg4, 32-bit) is 0
0000000000008cbf        callq   _Apple80211CopyValue  ;
; int rc = Apple80211CopyValue (Handle, (uint32_t) 0x13, 0, &var);
0000000000008cc4        movl    %eax, %r15d           ; r15 = rc
0000000000008cc7        testl   %r15d, %r15d          ; if (!rc) goto exit (return 0)
0000000000008cca        jne     0x8d39
0000000000008ccc        movq    -0x30(%rbp), %rdi
0000000000008cd0        testq   %rdi, %rdi            ; if (var == 0) goto exit (return 0)
0000000000008cd3        je      0x8d39 
0000000000008cd5        movq    %rbx, -0x40(%rbp)     ; -0x40(rbp) = Handle;
0000000000008cd9        callq   0x1e39a                 ## symbol stub for: _CFArrayGetCount
; At this point we know our var @ -0x30(%rbp) is a CFArrayRef, 
;  CFIndex r12 = CFArrayGetCount (CFArrayRef var ); 
0000000000008cde        movq    %rax, %r12
0000000000008ce1        movq    -0x30(%rbp), %rdi      ; rdi holds the array
0000000000008ce5        testl   %r12d, %r12d           ; Check if CFArrayGetCount is negative
0000000000008ce8        jle     0x8d2d                 ; if it is, goto exit_but_release_first
0000000000008cea        xorl    %ebx, %ebx             ; ebx = 0
0000000000008cec        leaq    -0x34(%rbp), %r13      ; r13 = CFArray + 4
loop:
0000000000008cf0        movq    %rbx, %rsi             ; rsi = 0
0000000000008cf3        callq   0x1e3a0                 ## symbol stub for: _CFArrayGetValueAtIndex

; rax = CFArrayGetValueAtIndex (CFArrayRef var, 0);
0000000000008cf8        testq   %rax, %rax
0000000000008cfb        je      0x8d17                 ; if (rax == 0) goto no_value_at_index_0_so_return_r14d_0
0000000000008cfd        movl    $0x3, %esi
0000000000008d02        movq    %rax, %rdi
0000000000008d05        movq    %r13, %rdx
; CFNumberGetValue (    rax,         // CFNumberRef number,  
                        0x3,         //CFNumberType theType, 
	                -0x34(%rbp)) // void *valuePtr );
0000000000008d08        callq   0x1e412                 ## symbol stub for: _CFNumberGetValue
0000000000008d0d        cmpl    $0x0, -0x34(%rbp)
0000000000008d11        setne   %r14b			; sets r14 to 1 if ZF is clear, ie -0x34(%rbp) is not 0
0000000000008d15        jmp     0x8d1a 			;  past_r14d
no_value_at_index_0_so_return_r14d_0
0000000000008d17        xorl    %r14d, %r14d
_past_r14d:
0000000000008d1a        incq    %rbx
0000000000008d1d        movq    -0x30(%rbp), %rdi
0000000000008d21        cmpl    %r12d, %ebx    ; r12d is the CFArrayGetCount
0000000000008d24        jge     out            ; 0x8d30
0000000000008d26        testb   %r14b, %r14b   ; if (r14 == 0) goto loop
0000000000008d29        je      loop;
0000000000008d2b        jmp     out            ; 0x8d30
exit_but_release_first:
0000000000008d2d        xorl    %r14d, %r14d
out:
0000000000008d30        callq   0x1e418                 ## symbol stub for: _CFRelease
0000000000008d35        movq    -0x40(%rbp), %rbx
exit:
0000000000008d39        movb    %r14b, (%rbx)
0000000000008d3c        movl    %r15d, %eax
; Standard epilog 
; ..
0000000000008d4d        retq

So, a bit of a spaghetti loop here, but still simple enough: we call Apple80211CopyValue(), on value type 0x13 (apparently, power), and it returns a CFArray of CFNumbers (confound this @$#%$#$# NeXTStep programming model!!!).


int Apple80211GetPower(Apple80211Ref Handle,
                       uint32_t      *Power)
{

  CFArrayRef arrayOfValues;

  int rc = Apple80211CopyValue (Handle, (uint32_t) 0x13, (uint32_t) 0, &arrayOfValues);
  if (!arrayOfValues) { fprintf(stderr,"Apple80211CopyValue(..0x13..) failed\n"); exit(1);}

  // The real one loops to iterate over values, since there's more than one radio.. 
  // Keeping it simple - let's just take the first one

  CFIndex count = CFArrayGetCount (arrayOfValues);

  CFNumberRef num = CFArrayGetValueAtIndex (arrayOfValues, 0);
  CFNumberGetValue (    num,         // CFNumberRef number,
                        0x3,         //CFNumberType theType,
                        Power); // void *valuePtr );
  return (0);
} // end Apple80211GetPower


Problem: We don't have Apple80211CopyValue() figured out. I left that as a @TODO.. So, here it comes. But before that, a tip:

At any time, if you're piecing the functions yourself, you can call on the real Apple80211.framework functions, by simply defining their prototype, but not implementing them. If you do, compiling with -F /System/Library/PrivateFrameworks -f Apple80211 (on Mac) will automatically link the unimplemented ones with the framework, while those in a closer scope (i.e. those you've implemented and linked with) will override the framework implementations. This is super useful for validating that you're not making a mistake in any given function, which will cascade into a multitude of bugs and unexplained crashes later

Apple80211CopyValue()

The prototype for Apple80211CopyValue() is straightforward, given the above. The value is some int - and 0x13 is apparently power. otool has problems finding the function because of some bad opcodes a little bit before its address (07f1c) but the i386 disassembly works:

_Apple80211CopyValue:
; Standard prolog..
00009870        pushl   %ebp
00009871        movl    %esp, %ebp
00009873        pushl   %ebx
00009874        pushl   %edi
00009875        pushl   %esi
00009876        subl    $0x11c, %esp            ## imm = 0x11C
0000987c        calll   0x9881
00009881        popl    %esi
00009882        movl    0x8(%ebp), %edi        ; edi = arg3 (remember, 32-bit)
00009885        movl    0x1b797(%esi), %edx
0000988b        movl    (%edx), %eax      
0000988d        movl    %eax, -0x10(%ebp)
00009890        movl    $0xfffff0c4, %ecx       ## imm = 0xFFFFF0C4
; Sanity checks:
00009895        testl   %edi, %edi
00009897        je      error; ...0xa052
0000989d        cmpl    $0x0, (%edi)
000098a0        js      error; ... 0xa052
000098a6        cmpb    $0x0, 0x4(%edi)
000098aa        je      error; .. 0xa052
000098b0        cmpl    $0x0, 0x14(%ebp)
000098b4        je      error; ... 0xa052
;
; Wow. We're still here.. so:
000098ba        movl    %esi, -0xec(%ebp)
000098c0        movl    %edx, %edi
000098c2        movl    0xc(%ebp), %ebx
000098c5        movl    0x8(%ebp), %eax
000098c8        leal    0x4(%eax), %eax
; optimized memset of -0x38(%ebp) through -0x18(%ebp) - the ioctl struct
000098cb        xorps   %xmm0, %xmm0
000098ce        movaps  %xmm0, -0x28(%ebp)
000098d2        movaps  %xmm0, -0x38(%ebp)
000098d6        movl    $0x0, -0x18(%ebp)
000098dd        movl    %eax, 0x4(%esp)
000098e1        leal    -0x38(%ebp), %esi   ; get ioctl struct to esi
000098e4        movl    %esi, (%esp)        ; arg1 = ioctl struct
000098e7        movl    $0x24, 0x8(%esp)    ; 0x24 bytes
; strcpy_chk (ioc.ifName, handle->interfaceName, 0x24);
000098ef        calll   0x216da                 ## symbol stub for: ___strcpy_chk
;
000098f4        movl    %ebx, %eax
000098f6        movl    %eax, -0x28(%ebp)
000098f9        cmpl    $0xde, %eax
000098fe        jg      0x99a5
00009904        cmpl    $0xb6, %eax
00009909        movl    %edi, %edx
0000990b        jg      0x9a61
00009911        leal    0x1(%eax), %eax
00009914        cmpl    $0x62, %eax
00009917        ja      0xa04d
0000991d        movl    $0xfffff0c2, %ecx       ## imm = 0xFFFFF0C2
00009922        movl    -0xec(%ebp), %edi
00009928        movl    0x967(%edi,%eax,4), %eax
0000992f        addl    %edi, %eax
00009931        jmpl    *%eax
00009933        movl    %edx, %edi 

00009935        movl    0x8(%ebp), %eax        ; get the Handle into eax
00009938        movl    (%eax), %eax           ; get the socket into eax
0000993a        movl    %esi, 0x8(%esp)        
0000993e        movl    %eax, (%esp)
00009941        movl    $0xc02469c9, 0x4(%esp)  ## imm = 0xC02469C9
00009949        calll   0x21734                 ## symbol stub for: _ioctl
;ioctl (eax, 
        0xc02469c9,
	esi)
;... error handling and CF* crap

The function might look intimidating, until you realize that A) most of it is stuff we've seen before and B) the rest is just casting the results to a CFArray of CFDictionary or CFNumbers. Really, all we care about at this point is our ioctl(2) - same code (so that's good), and we just have to figure out the struct. Then there's a shortcut: lldb:

# break on GetPower first, so as not to get all the GetIfList ioctl(2)..
(lldb) b Apple80211GetPower
Breakpoint 1: 2 locations.
(lldb) r
Process 6199 launched: './80211test' (x86_64)
Handle: 0x1002009e0
..
Apple80211`Apple80211GetPower:
-> 0x7fff8f3ebc9d:  pushq  %rbp
(lldb) breakpoint set -n Apple80211CopyValue
Breakpoint 2: 3 locations.
..
Apple80211`Apple80211CopyValue:
-> 0x7fff8f3eaf1c:  pushq  %rbp
(lldb) reg read rdi rsi rdx rcx rbp
     rdi = 0x00000001002009e0   ; Handle
     rsi = 0x0000000000000013   ; The value to copy
     rdx = 0x0000000000000000   ; 0
     rcx = 0x00007fff5fbffaf0   ; The address to copy to
     rbp = 0x00007fff5fbffb20   ; just for ref..
(lldb) breakpoint set -n ioctl
Breakpoint 3: where = libsystem_kernel.dylib`ioctl, address = 0x00007fff90d1c918
(lldb) c
..
libsystem_kernel.dylib`ioctl:
-> 0x7fff90d1c918:  pushq  %rbp
(lldb) reg read rdi rsi rdx rbp
     rdi = 0x0000000000000003   ; the socket
     rsi = 0x00000000c02869c9   ; the code
     rdx = 0x00007fff5fbff900   ; the struct
     rbp = 0x00007fff5fbff960
(lldb) mem read $rdx
0x7fff5fbff900: 65 6e 30 00 00 00 00 00 00 00 00 00 00 00 00 00  en0............. # interfaceName
0x7fff5fbff910: 13 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00  ................ # type....len.....
0x7fff5fbff920: c8 4b bf 5f ff 7f 00 00 00 00 00 00 00 00 00 00  ?K?_?........... #0x7fff5fbf4bc8
# Let the ioctl(2) return..
(lldb) thread step-out
Apple80211`Apple80211Get + 1965:
-> 0x7fff8f3e5a36:  testl  %eax, %eax
(lldb) reg read rax
     rax = 0x0000000000000000  # Success...
(lldb) mem read 0x00007fff5fbff900  structure (unchanged)
0x7fff5fbff900: 65 6e 30 00 00 00 00 00 00 00 00 00 00 00 00 00  en0.............
0x7fff5fbff910: 13 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00  ................
0x7fff5fbff920: c8 4b bf 5f ff 7f 00 00 00 00 00 00 00 00 00 00  ?K?_?...........
(lldb) mem read 0x7fff5fbf4bc8 memory location
0x7fff5fbf4bc8: 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00  ................
0x7fff5fbf4bd8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
                version     | # radios |  power[0] |  power[1] |
If power is on:
0x7fff5fbf4bc8: 01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00  ................

Much easier, and makes more sense than pursuing decompilation. Doing the same on Apple80211SetPower reveals a call to Apple80211Set (the more generic version) , and this:

(lldb) bt
* thread #1: tid = 0x867e5, 0x00007fff90d1c918 libsystem_kernel.dylib`ioctl, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
  * frame #0: 0x00007fff90d1c918 libsystem_kernel.dylib`ioctl
    frame #1: 0x00007fff8f3ee128 Apple80211`Apple80211Set + 8916
    frame #2: 0x00007fff8f3ebe37 Apple80211`Apple80211SetPower + 233
    frame #3: 0x0000000100000ca4 80211test`main + 84
    frame #4: 0x00007fff90ede5c9 libdyld.dylib`start + 1
(lldb) reg read rdi rsi rcx rdx 
     rdi = 0x0000000000000003
     rsi = 0x00000000802869c8 # Note 8, not 9!
     rcx = 0x10007003ad634955
     rdx = 0x00007fff5fbffa50
(lldb) mem read $rdx
0x7fff5fbffa50: 65 6e 30 00 00 00 00 00 00 00 00 00 00 00 00 00  en0.............
0x7fff5fbffa60: 13 00 00 00 00 00 00 00 18 00 00 00 00 00 00 00  ................
0x7fff5fbffa70: 78 eb bf 5f ff 7f 00 00 d8 4f 62 7e ff 7f 00 00  x?_?...?Ob~?...
# Inspect the buffer
(lldb) mem read 0x7fff5fbfeb78
0x7fff5fbfeb78: 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00  ................
                version     | # radios |  power[0] |  power[1] |
# SetPower (1):
                01 00 00 00 02 00 00 00 01 00 00 00 01 00 00 00

Trying this same strategy by debugging /System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport -I, yields interesting values, which - thanks to airport's human readable output, are quick to deduce. But it turns out there's another (major) shortcut (which made me re-write half this article): Some of these are (somewhat) documented in the Apple80211_*.h headers, as mentioned by @comex, which was briefly available as part of the OS X 10.5 SDK (XCode 3.3, ah, nostalgia!) before Apple pulled it. It's obviously easier to cheat by looking at the headers, which provide both the codes and the arguments they expect (geez, @comex, why didn't you tell me this before I reversed this @#%$#%$#?!). So if you do grab the 10.5 SDK (or just the headers, which I've conveniently placed for you here), you'll see the following values (table partially filled out, yeah, 'cause it's a *ton* of work.. you can piece it together from the headers).

#APPLE80211_IOC_Expectssizeof()
1SSIDchar *0x20
2AUTH_TYPE..
3CYPHER_KEY
4CHANNELuint32_t version = 1,
uint32_t ch_version = 1,
uint32_t ch_channel,
bitmask_t ch_flags;
5POWERSAVE...
6PROTMODE
7TXPOWER 7 // req_type
8RATE 8 // req_type
9BSSIDuint32_t version = 1,
char etherAddr[6]
10SCAN_REQ
11SCAN_RESULT
12CARD_CAPABILITIES
13STATE
14PHY_MODE
15OP_MODE
16 RSSI
17NOISE
18 INT_MIT
19POWERuint32_t version = 1,
uint32_t num_radios
uint32_t power[4]
0x18
20ASSOCIATE
21ASSOCIATE_RESULT
22DISASSOCIATE
23STATUS_DEV_NAME
24IBSS_MODE
25HOST_AP_MODE
26 AP_MODEuint32_t version = 1,
enum 1 = adHoc, 2 = ap, 3 = ?
27SUPPORTED_CHANNELS
28LOCALE
29DEAUTH
30COUNTERMEASURES
FRAG_THRESHOLD
RATE_SET
SHORT_SLOT
MULTICAST_RATE
SHORT_RETRY_LIMIT
LONG_RETRY_LIMIT
TX_ANTENNA
RX_ANTENNA
ANTENNA_DIVERSITY
40ROM
DTIM_INT
STATION_LIST
DRIVER_VERSION
HARDWARE_VERSION
45 RAND
RSN_IE
BACKGROUND_SCAN
AP_IE_LIST
STATS
50ASSOCIATION_STATUS
COUNTRY_CODE
DEBUG_FLAGS
LAST_RX_PKT_DATAuint32_t version = 1,
uint32_t rate
int32_t rssi
uint32_t num_streams
char ether_addr[6]
RADIO_INFO
55 GUARD_INTERVAL
MIMO_POWERSAVE
MCS
RIFS
LDPC
60 MSDU
MPDU
BLOCK_ACK
PLS
PSMP
PHY_SUB_MODE
MCS_INDEX_SET
CACHE_THRESH_BCAST
CACHE_THRESH_DIRECT
WOW_PARAMETERS
70 WOW_ENABLED
7140MHZ_INTOLERANT

Apple80211EventMonitoring...

Ah. Finally something that's undocumented :-) We can go by disassembly here, or we can take a runtime-based approach. It's simple enough to write code to dump the handle before and after a call to Apple80211EventMonitoringInit(). We'll get something like this:

Handle, Before:
0x00: 03 00 00 00 65 6e 30 00 00 00 00 00 00 00 00 00 
0x10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x30: 00 00 00 00 00 00 00 00 eb 7e ff 2b ad 08 8d 00 
0x40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
EVENT MONITORING: setting callback 0x1041a3cd0, context 0xfeedfeed
Handle, After:
0x00: 03 00 00 00 65 6e 30 00 00 00 00 00 00 00 00 00 
0x10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
0x20: c0 20 41 50 de 7f 00 00 d0 3c 1a 04 01 00 00 00 
0x30: ed fe ed fe 00 00 00 00 eb 7e ff 2b ad 08 8d 00 
0x40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

As you can see, the callback and context are clearly visible in the handle, in offsets 0x28 and 0x30, respectively. But what's that at 0x20? For now, let's consider this a monitoring_blob. And let's fool around with dumping the handle during stages of monitoring:

When we start to monitor, by calling Apple80211StartMonitoringEvent(), we see:

EVENT MONITORING: setting callback 0x108ecfc00, context: 0xfeedfeed
Handle:
03 00 00 00 65 6e 30 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 
c0 01 50 e3 b7 7f 00 00 00 fc ec 08 01 00 00 00 
ed fe ed fe 00 00 00 00 eb 7e ff 2b ad 08 8d 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
STARTING TO MONITOR 0x01, 0x02,0x03,0x04, 0x40,0x42
Handle:
03 00 00 00 65 6e 30 00 00 00 00 00 00 00 00 00 
00 00 00 00 0f 00 00 00 00 00 00 80 02 00 00 00 
c0 01 50 e3 b7 7f 00 00 00 fc ec 08 01 00 00 00 
ed fe ed fe 00 00 00 00 eb 7e ff 2b ad 08 8d 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 

So our previous unknowns - 5,6,7 - are now a bitmap for events monitored (in practice, only up to 0x45 are actually monitored). used_for_monitoring1 (@0x20) is the blob, unknown8-9 (@0x28) is the user-defined callback, and unknown10-11 is the context. So we have:

/* We know the size if 0x50, since this gets malloc()ed.. */
#define IFNAMSIZ        16
struct Apple80211 {
 /* 0x00 */     uint32_t        socket;   // Used for ioctl()
 /* 0x04 */     char            ifName[IFNAMSIZ];
 /* 0x14 */     char            monitored_events_bitmap[0xc];
 /* 0x20 */     uint64_t        monitoring_blob;   // 1ac9: cmpq    $0x0, 0x20(%rbx)
 /* 0x28 */     uint64_t        monitoring_callback;
 /* 0x30 */     uint64_t        monitoring_context;
 /* 0x38 */     uint64_t        binding_handle;
 /* 0x40 */     uint32_t        unknown14;
 /* 0x44 */     uint32_t        unknown15;
 /* 0x48 */     uint64_t        used_for_monitoring2;  // 1ad0: cmpq    $0x0, 0x48(%rbx)

};

That "used_for_monitoring2" which we still don't exactly know there can be figured out by looking at the disassembly of Apple80211EventMonitoringHalt:

_Apple80211EventMonitoringHalt:
0000000000001af0        pushq   %rbp
0000000000001af1        movq    %rsp, %rbp
0000000000001af4        pushq   %rbx
0000000000001af5        pushq   %rax
0000000000001af6        movq    %rdi, %rbx              ; rbx = arg1 = handle
; if (! handle) return (0xfffff0c4);
0000000000001af9        movl    $0xfffff0c4, %eax       ## imm = 0xFFFFF0C4
0000000000001afe        testq   %rbx, %rbx              
0000000000001b01        je      0x1b4e
; if (! handle->monitoringblob) goto after;
0000000000001b03        movq    0x20(%rbx), %rdi
0000000000001b07        testq   %rdi, %rdi
0000000000001b0a        je      0x1b22
; So now we figure out that monitoring blob is a CFSocket!
; _CFSocketInvalidate(handle->monitoringblob);
0000000000001b0c        callq   0x1e43c                 ## symbol stub for: _CFSocketInvalidate
0000000000001b11        movq    0x20(%rbx), %rdi
; _CFRelease(handle->monitoringblob);
0000000000001b15        callq   0x1e418                 ## symbol stub for: _CFRelease
0000000000001b1a        movq    $0x0, 0x20(%rbx)
after:
; if (! handle->used_for_monitoring2) goto after1
0000000000001b22        movq    0x48(%rbx), %rdi
0000000000001b26        testq   %rdi, %rdi
0000000000001b29        je      0x1b38
; dispatch_source_cancel(handle->used_for_monitoring2) ; so it's a dispatch source
0000000000001b2b        callq   0x1e4ea                 ## symbol stub for: _dispatch_source_cancel
; after1
; memset to '\0':
0000000000001b30        movq    $0x0, 0x48(%rbx)  ; dispatch_source
0000000000001b38        movq    $0x0, 0x28(%rbx)  ; monitoring_callback
0000000000001b40        movb    $0x0, 0x1c(%rbx)  ; bitmap
0000000000001b44        movq    $0x0, 0x14(%rbx)  ; bitmap

So now we have:

/* We know the size if 0x50, since this gets malloc()ed.. */
#define IFNAMSIZ        16
struct Apple80211 {
 /* 0x00 */     uint32_t        socket;   // Used for ioctl()
 /* 0x04 */     char            ifName[IFNAMSIZ];
 /* 0x14 */     char            monitored_events_bitmap[0xc];
 /* 0x20 */     uint64_t        cfsocket_used_for_monitoring;   // 1ac9: cmpq    $0x0, 0x20(%rbx)
 /* 0x28 */     uint64_t        monitoring_callback;
 /* 0x30 */     uint64_t        monitoring_context;
 /* 0x38 */     uint64_t        binding_handle;
 /* 0x40 */     uint32_t        unknown14; /* probably also a uint64_t */
 /* 0x44 */     uint32_t        unknown15; /* as you'll see in a bit */
 /* 0x48 */     uint64_t        monitoring_dispatch_source;    // 1ad0: cmpq    $0x0, 0x48(%rbx)

};

Looking through the code of Apple80211EventMonitoringInit, something in particular sticks out:

0000000000011f38        movl    $0x20, %edi
0000000000011f3d        movl    $0x3, %esi
0000000000011f42        movl    $0x1, %edx
0000000000011f47        callq   0x1e562                 ## symbol stub for: _socket
; s = socket (PF_SYSTEM, SOCK_RAW, SYSPROTO_EVENT);
0000000000011f4c        movl    %eax, %r13d
0000000000011f4f        testl   %r13d, %r13d
0000000000011f52        js      0x12023
; if (s < 0) goto 0x12023, error...

0000000000011f58        movl    0xfe2a(%rip), %eax
0000000000011f5e        movl    %eax, -0x30(%rbp)
0000000000011f61        movq    0xfe18(%rip), %rax
0000000000011f68        movq    %rax, -0x38(%rbp)
0000000000011f6c        leaq    -0x38(%rbp), %rdx
0000000000011f70        movl    $0x800c6502, %esi       ## imm = 0x800C6502
0000000000011f75        xorl    %eax, %eax
0000000000011f77        movl    %r13d, %edi
0000000000011f7a        callq   0x1e526                 ## symbol stub for: _ioctl
; rc = ioctl(s,SIOCSKEVFILT, &kev_request);
0000000000011f7f        testl   %eax, %eax
; if (rc < 0) goto 0x12023, error...
0000000000011f81        jne     0x12023

The monitoring, therefore, takes place through a raw system event socket (book, chapter 17). This code is essentially the same as the internal __openEventSocket, which was probably inlined in this case. Seems like this is standard practice - several daemons in OS X and iOS listen on it:

root@Zephyr (~)# procexp all fds | grep 8021
locationd          219 FD 10u  socket system Event:   APPLE:IEEE80211:1
sharingd           199 FD 10u  socket system Event:   APPLE:IEEE80211:1
UserEventAgent     177 FD  5u  socket system Event:   APPLE:IEEE80211:1
blued               57 FD  5u  socket system Event:   APPLE:IEEE80211:1
airportd            29 FD 21u  socket system Event:   APPLE:IEEE80211:1
airportd            29 FD 25u  socket system Event:   APPLE:IEEE80211:1
airportd            29 FD 27u  socket system Event:   APPLE:IEEE80211:1
airportd            29 FD 28u  socket system Event:   APPLE:IEEE80211:1
airportd            29 FD 32u  socket system Event:   APPLE:IEEE80211:1
configd             24 FD 13u  socket system Event:   APPLE:IEEE80211:1
# And meanwhile, in iOS:
Phontifex:~ root# procexp all fds | grep 8021
wifid               80 FD  5u  socket system Event:   APPLE:IEEE80211:1
wifid               80 FD  9u  socket system Event:   APPLE:IEEE80211:1

The rest of the ..MonitoringInit function converts the socket to a CFSocket so it can be added as a dispatch source to the run loop. It also installs its own call back, which serves as a filter (with the bitmap) for the events added by Apple80211StartMonitoringEvent, which - if you sift through the code - you'll see is nothing more than in memory operations (no function calls). So we have monitoring figured out, too. And almost all of the handle, besides unknown14 and 15... So we're almost done, but how do we find unknown14 and 15, which are probably also some 64-bit quantity? Well, considering that 80211 has a penchant for using %rbx the handle, a search for 0x40(%rbx) reveals this snippet, in Apple80211ScanAsync

_Apple80211ScanAsync:
..
000000000000efad        movq    %rsi, %r15           ; r15 = saved arg2
..
000000000000efff        leaq    0x40(%rbx), %rcx     ; arg3 = handle + 0x40
000000000000f003        movw    $0x1, 0x40(%rbx)     ; handle[40] = 1
000000000000f009        leaq    0x41(%rbx), %r9      
000000000000f00d        leaq    -0x9b8(%rbp), %r12   ; r12 = local buffer
000000000000f014        xorl    %edx, %edx           ; arg4 = 0
000000000000f016        xorl    %r8d, %r8d           ; arg5  = 0
000000000000f019        movq    %r15, %rdi           ; arg1 = saved arg2
000000000000f01c        movq    %r12, %rsi           ; arg2 = local buffer
000000000000f01f        callq   __getScanData

Meaning offset 0x40 is used to hold asynchronous scan data - and is indeed a 64-bit pointer. But what exactly is it? Let's talk about scanning, next.

Apple80211Scan*

Apple80211 offers three forms of scanning: ..Scan, ..ScanAsync and ..ScanDynamic. All three are quite similar, calling on a helper function of __getScanData, and __WaitForScanResults.Apple80211Scan's code prolog (0x000d6c8) is actually incorrectly processed by otool. The function takes three arguments: The handle, a non NULL pointer (which is (CFDictionaryRef *) used for results), and another pointer (CFDictionaryRef)) which is used for scan parameters.

Figuring out the results is easy, using three lines of code to dump the CFDictionaryRef. This would look something like this code:

..
   Apple80211Ref handle;
   int rc = Apple80211Open(&handle);
   CFStringRef ifName = CFStringCreateWithCString(kCFAllocatorDefault, "en0",
                                         kCFStringEncodingISOLatin1);
   if (rc) { fprintf(stderr, "Apple80211Open failed..\n"); }
   rc = Apple80211BindToInterface(handle, ifName);
   if (rc) { fprintf(stderr, "Apple80211BindToInterface failed..\n"); }

   CFDictionaryRef returnedDict;
   rc = Apple80211Scan(handle, &returnedDict, NULL);

   if (rc) { fprintf(stderr, "Apple80211Scan returned %d\n", rc);}

   // This has been deprecated in 10.10. Meh
   CFDataRef xml = CFPropertyListCreateXMLData(kCFAllocatorDefault,
                                              (CFPropertyListRef)returnedDict);
  if (xml) {
        write(1, CFDataGetBytePtr(xml), CFDataGetLength(xml));
        CFRelease(xml);
        }


The dict dump produces the following output:

<?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">
<array>
...
        <dict>
                <key>80211D_IE</key>  Information elements for 80211.d, restricting channels/power as per local legislation
                <dict>
                        <key>IE_KEY_80211D_CHAN_INFO_ARRAY</key>
                        <array>
                                <dict>
                                        <key>IE_KEY_80211D_FIRST_CHANNEL</key> <integer>1</integer>
                                        <key>IE_KEY_80211D_MAX_POWER</key> <integer>36</integer>
                                        <key>IE_KEY_80211D_NUM_CHANNELS</key> <integer>13</integer>
                                </dict>
                        </array>
                        <key>IE_KEY_80211D_COUNTRY_CODE</key> <string>IN</string>
                </dict>
                <key>AGE</key>     > 0  - cached. 0 - freshly discovered by scan, not cached
                <integer>15000</integer>
                <key>AP_MODE</key> 2 - Base station. 1 - Ad hoc
                <integer>2</integer>
                <key>BEACON_INT</key> Sends beacon frames every 100 seconds
                <integer>100</integer>
                <key>BSSID</key> Access point MAC
                <string>5c:e:8b:f5:61:20</string>
                <key>CAPABILITIES</key>
                <integer>-31743</integer>
                <key>CHANNEL</key>  Channel (1,2,...11,.. etc)
                <integer>11</integer>
                <key>CHANNEL_FLAGS</key>
                <integer>10</integer>
                <key>HT_CAPS_IE</key>  capability information elements for 80211n
                <dict>
                        <key>AMPDU_PARAMS</key> <integer>23</integer>
                        <key>ASEL_CAPS</key> <integer>0</integer>
                        <key>CAPS</key> <integer>12</integer>
                        <key>EXT_CAPS</key> <integer>0</integer>
                        <key>MCS_SET</key> <data> //8AAAAAAAAAAAAAAAAAAA== </data>
                        <key>TXBF_CAPS</key> <integer>0</integer>
                </dict>
                <key>HT_IE</key>  various information elements for 80211n
                <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> <true/>
                        <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>11</integer>
                        <key>HT_PSMP_STAS_ONLY</key> <false/>
                        <key>HT_RIFS_MODE</key> <true/>
                        <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> <true/>
                </dict>
                <key>IE</key> Information elements (Base64-encoded)
                <data>
                ABZMZSBNZXJpZGllbiwgTmV3IERlbGhpAQiChIsMEpYYJAMBCwcGSU5JAQ0k
                KgEEMgQwSGBsCwUDAA8Sei0aDAAX//8AAAAAAAAAAAAAAAAAAAAAAAAAAAA9
                FgsIHQAAAAAAAAAAAAAAAAAAAAAAAABKDhQACgC0ABQAFAACAAoAfwYBAAAA
                AACtDwCg+AMABQAaAAAAEYtIVd0YAFDyAgEBgAADpAAAJ6QAAEJDXgBiMi8A
                3R4AoPgDAAEAAAAAAAAZbViCAGUAAVwOi/dilAABGAs=
                </data>
                <key>NOISE</key> Noise - lower values are better 
                <integer>-87</integer>
                <key>RATES</key> Transfer rates (corresponding to 80211[abdefgin] class)
                <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> Relative Signal Strength Indicator - higher values better
                <integer>-73</integer>
                <key>SSID</key>  The SSID, as a CFData blob, in Base64
                <data>
                TGUgTWVyaWRpZW4sIE5ldyBEZWxoaQ==
                </data>
                <key>SSID_STR</key> The SSID, as the string it should be
                <string>Le Meridien, New Delhi</string>
        </dict>
</array>
</plist>

So, to summarize, we have the results returned in an array of dictionaries, with each dictionary corresponding to an access point, and containing keys, some of whose values are dicts in themselves (eek!). Note that there may be multiple results for the same SSID (if it is provided by multiple BSSIDs). Though that can be toggled with parameters - notably SCAN_MERGE. A quick and somewhat dirty way of getting most of the parameters is thus:

Zephyr:~ morpheus$ otool -tV /System/Library/PrivateFrameworks/Apple80211.framework/Apple80211  |
grep \"SCAN_  | cut -d'@' -f2  | sort -u
# Note this will miss SSID_STR and BSSID, which you can see in the dump of __getScanData
"SCAN_BSSID_LIST"   # Limit scan to specific BSSIDs (MAC Addresses)
"SCAN_BSS_TYPE"     # CFNumber: 1 - Ad hoc, 2 - BSS. Can be |'ed
"SCAN_CHANNELS"     # CFArray of CFDicts of CHANNEL and CHANNEL_FLAGS (both CFNumbers) representing channels to scan
"SCAN_CLOSED_NETWORKS" # CFBoolean
"SCAN_CYCLE_REST_TIME" # Pause between scans
"SCAN_DIRECTED"     # Scan for specific SSIDs/BSSIDs
"SCAN_DWELL_TIME"   # Duration of each scan
"SCAN_MERGE"        # CFBoolean "true" if merge duplicate SSIDs on different BSSIDs
"SCAN_NUM_SCANS"    # # of times to scan
"SCAN_P2P"	    # Peer-to-Peer (awdl0) also
"SCAN_PHY_MODE"     # A, B, G, N, or AUTO(1) with optional submodes
"SCAN_REST_TIME"    # Pause between scans
"SCAN_SSID_LIST"    # CFArray of CFStrings for SSIDs to scan, if SCAN_DIRECTED
"SCAN_TYPE"	    # 1 - Active, 2 - Passive, 3 - Fast, 4 - Background (notify via kevent)

If the above is confusing, here's a sample dict:

<dict>
        <key>SCAN_BSS_TYPE</key> <integer>2</integer> Scanned for Infrastructure
        <key>SCAN_CHANNELS</key>
        <array>
                <dict>
                        <key>CHANNEL</key>       <integer>11</integer>
                        <key>CHANNEL_FLAGS</key> <integer>10</integer>   2.4 GHz (8) + 20 Mhz Wide (2)
                </dict>
        </array>
        <key>SCAN_MERGE</key> <true/>
        <key>SCAN_TYPE</key> <integer>1</integer> Active
    	<key>SSID_STR</key>
	<string>Le Meridien, New Delhi</string>
</dict>

And as you look at this, don't shoot the messenger. I didn't write this @#%$#%$#. Apple did. And this is just the gist of it. You can also specify SCAN_[B]SSID_LIST as a list of SSIDs or BSSIDs (base station MACs) to filter on. Crazy.

The driver, obviously, doesn't care for all that CF* encapsulation. Give it C-Strings and integers. tting a breakpoint on the scan shows us the following structure:

(lldb) mem read $rdx
0x7fff5fbff590: 65 6e 30 00 00 00 00 00 00 00 00 00 00 00 00 00  en0.............
0x7fff5fbff5a0: 0a 00 00 00 00 00 00 00 54 09 00 00 00 00 00 00  ........T.......
0x7fff5fbff5b0: 98 e7 bf 5f ff 7f 00 00 05 00 00 00 00 00 00 00  .?_?...........
# Note code (0xA, APPLE80211_IOC_SCAN_REQ) and struct size (0x54)
(lldb) mem read 0x7fff5fbfe798
0x7fff5fbfe798: 01 00 00 00 02 00 00 00 00 00 00 00 00 00 00 00  ................
                __version__ __bsstype__ _bssid (MAC)_____
0x7fff5fbfe7a8: 16 00 00 00 4c 65 20 4d 65 72 69 64 69 65 6e 2c  ....Le Meridien,
                __ssid_len_  ________SSID__ (up to 32 bytes)___
0x7fff5fbfe7b8: 20 4e 65 77 20 44 65 6c 68 69 00 00 00 00 00 00   New Delhi......
0x7fff5fbfe7c8: 00 00 00 00 01 00 00 00 01 00 00 00 00 00 00 00  ................
                ___________ _scan_type_ __phy_mode_  __dwell__
0x7fff5fbfe7d8: 00 00 00 00 01 00 00 00 01 00 00 00 0b 00 00 00  ................
                ___rest____ _num_chan__ _chan_vers_ _chan(11)__
0x7fff5fbfe7e8: 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
                __ch_flags_

Playing around with the scan parameters produces the fields above very quickly, as does looking at the leaked headers @comex pointed out to:

struct apple80211_scan_data
{
        u_int32_t                  version;                             // = 1
        u_int32_t                  bss_type;                            // apple80211_apmode
        struct ether_addr          bssid;                               // target BSSID
        u_int32_t                  ssid_len;                            // length of the SSID
        u_int8_t                   ssid[APPLE80211_MAX_SSID_LEN];
        u_int32_t                  scan_type;                           // apple80211_scan_type
        u_int32_t                  phy_mode;                            // apple80211_phymode vector
        u_int16_t                  dwell_time;                          // time to spend on each channel (ms)
        u_int32_t                  rest_time;                           // time between scanning each channel (ms)
        u_int32_t                  num_channels;                        // 0 if not passing in channels
        struct apple80211_channel  channels[APPLE80211_MAX_CHANNELS];   // channel list
};

So scanning is figured out. _IOW('i', 200, struct apple80211_ioctl_str) is used for the scan request - 0x802869c8, rather than 0xc02869c9, which is used for get.

Getting the scan results uses code 0xb. Continuing with the same breakpoint on ioctl, you'll see:

Process 10991 stopped
* thread #1: tid = 0x9026a, 0x00007fff86138918 libsystem_kernel.dylib`ioctl, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
    frame #0: 0x00007fff86138918 libsystem_kernel.dylib`ioctl
..
(lldb) mem read $rdx # Get arg3 of ioctl
0x7fff5fbff590: 65 6e 30 00 00 00 00 00 00 00 00 00 00 00 00 00  en0.............
0x7fff5fbff5a0: 0b 00 00 00 00 00 00 00 98 00 00 00 00 00 00 00  ................
0x7fff5fbff5b0: f0 f4 bf 5f ff 7f 00 00 05 00 00 00 00 00 00 00  ???_?...........
# Note code 0xb above, and size of 0x98
# Allow ioctl(2) to return, and examine contents
(lldb) thread step-out
(lldb) mem read 0x7fff5fbff4f0
0x7fff5fbff4f0: 01 00 00 00 01 00 00 00 0b 00 00 00 0a 00 00 00  ................
0x7fff5fbff500: 00 00 a9 ff 00 00 b7 ff 64 00 01 84 5c 0e 8b f5  ..??..??d...\..?
                 __noise___  __rssi____  |          ____bssid__
                                 beacon interval
0x7fff5fbff510: 61 20 0c 00 01 00 00 00 02 00 00 00 05 00 00 00  a ..............
                _____
0x7fff5fbff520: 06 00 00 00 09 00 00 00 0b 00 00 00 0c 00 00 00  ................
0x7fff5fbff530: 12 00 00 00 18 00 00 00 24 00 00 00 30 00 00 00  ........$...0...
0x7fff5fbff540: 36 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  6...............
0x7fff5fbff550: 16 4c 65 20 4d 65 72 69 64 69 65 6e 2c 20 4e 65  .Le Meridien, Ne
     ssid_len---+  |----------------------------------SSID-----
0x7fff5fbff560: 77 20 44 65 6c 68 69 00 00 00 00 00 00 00 00 00  w Delhi.........
                -(ssid_len bytes)--
0x7fff5fbff570: 00 00 00 00 98 3a 00 00 00 00 d4 00 00 00 00 00  .....:....?.....
                            ___age_____
0x7fff5fbff580: f0 f0 bf 5f ff 7f 00 00 ...........

And so you can figure out the scan results structure (which I did not find in the headers) rather easily, by printing the results dict (as I showed above), and correlating with this.

Apple80211[Dis]Associate

Let's start with Apple80211DisAssociate: That's easy. It's just Apple80211Set(arg, 0x16, 0 , 0, 0). Looking at the table back there you'll see 0x16 - decimal 22 - that's APPLE80211_IOC_DISASSOCIATE. Makes sense.

Apple80211Associate turns out to be a wrapper, internally calling Apple80211Associate2(arg1, arg2, arg3, 0);. The arg1 is our Handle. arg2 is a CFDictionary. See below:

_Apple80211Associate2:
000000000000f8ad     ... standard prototype, setting a stack of 0x3a8 bytes and stack_chk_guard ...
		     ... -0x3b0(%rbp) holds rcx (arg4) r14 holds rdx (arg3), rbx holds rsi, r13 holds rdi (arg1)...
000000000000f8df     leaq    -0x220(%rbp), %r12
000000000000f8e6     movl    $0x1d0, %esi            ## imm = 0x1D0
000000000000f8eb     movq    %r12, %rdi
000000000000f8ee     callq   0x1e4a8                 ## symbol stub for: ___bzero
; memset (bp-0x220, '\0', 0x1d0);
000000000000f8f3     movl    $0x1, -0x220(%rbp)      ; *(rbp-0x220) = 1
; r15 holds what will eventually be the return value. Code could be more optimized..
000000000000f8fd     xorl    %r15d, %r15d
; if (!handle) { goto 0x103ee; }
000000000000f900     testq   %r13, %r13 
000000000000f903     je      0x103ee
000000000000f909     xorl    %r15d, %r15d
; if (handle->socket <= 0) { goto 0x0103ee; }
000000000000f90c     cmpl    $0x0, (%r13)
000000000000f911     js      0x103ee
; if (!handle->ifName[0]) { goto 0x103ee; }
000000000000f917     xorl    %r15d, %r15d
000000000000f91a     cmpb    $0x0, 0x4(%r13)
000000000000f91f     je      0x103ee
; if (arg2 == 0) { goto 0x103ee; }
000000000000f925     testq   %rbx, %rbx
000000000000f928     je      0x103ee
000000000000f92e     movq    %r14, -0x3b8(%rbp)    ; save arg3 for future ref in rbp-0x3b8
; memset (rbp -0x230, '\0', 0x30);
000000000000f935     xorps   %xmm0, %xmm0
000000000000f938     movaps  %xmm0, -0x240(%rbp)
000000000000f93f     movaps  %xmm0, -0x250(%rbp)
000000000000f946     movq    $0x0, -0x230(%rbp)
; This shows us that arg2 is a dictionary, with the @"SSID" Key...
000000000000f951     leaq    0x12ec8(%rip), %rsi     ## Objc cfstring ref: @"SSID"
000000000000f958     movq    %rbx, %rdi
000000000000f95b     movq    %rbx, %r14              ; Save arg2 for future ref as r14
000000000000f95e     callq   0x1e3e8                 ## symbol stub for: _CFDictionaryGetValue
000000000000f963     movq    %rax, %rbx
; CFData ssidVal =  CFDictionaryGetValue ((CFDictionaryRef) arg2, (const void *) @"SSID");
; if (!rbx) { goto 0x103ee; } 
000000000000f966        xorl    %r15d, %r15d
000000000000f969        testq   %rbx, %rbx
000000000000f96c        je      0x103ee
;
; ssidLen = rbp(-0x214) = CFDataGetLength (ssidVal);
000000000000f972        movq    %rbx, %rdi
000000000000f975        callq   0x1e3d0                 ## symbol stub for: _CFDataGetLength
; if (ssidLen > 0x1f) { goto 0x103ee }
000000000000f97a        movl    %eax, -0x214(%rbp)
000000000000f980        leal    -0x1(%rax), %ecx
000000000000f983        xorl    %r15d, %r15d
000000000000f986        cmpl    $0x1f, %ecx
000000000000f989        ja      0x103ee
000000000000f98f        movl    $0x802869c8, %ecx       ## imm = 0x802869C8
000000000000f994        leaq    0x7fd79637(%rcx), %rcx
000000000000f99b        andq    %rcx, %rax

000000000000f99e        leaq    -0x210(%rbp), %rcx
000000000000f9a5        xorl    %esi, %esi
000000000000f9a7        movq    %rbx, %rdi
000000000000f9aa        movq    %rax, %rdx
000000000000f9ad        callq   0x1e3ca                 ## symbol stub for: _CFDataGetBytes
; CFDataGetBytes (cfdSSID,       // CFDataRef theData, 
					 0..ssidLen,    // CFRange range,
					 -0x210(%rbp)); // UInt8 *buffer 
; .. and the @"FORCE_BSSID" value..
000000000000f9b2        leaq    0x12f47(%rip), %rsi     ## Objc cfstring ref: @"FORCE_BSSID"
000000000000f9b9        movq    %r14, %rbx
000000000000f9bc        movq    %rbx, %rdi
000000000000f9bf        callq   0x1e3e8                 ## symbol stub for: _CFDictionaryGetValue
; CFDataRef cfdForceBSSID = CFDictionaryGetValue (arg2, @"FORCE_BSSID);
000000000000f9c4        movq    %rax, %rdi
000000000000f9c7        callq   _makeBoolRef
; brForceBSSID = makeBoolRef (cfdForceBSSID);
000000000000f9cc        testq   %rax, %rax
000000000000f9cf        je      0xfa4b
; if (!brForceBSSID) { goto fa4b; } // past all the BSSID stuff
000000000000f9d1        movq    %rax, %rdi
000000000000f9d4        callq   0x1e3ac                 ## symbol stub for: _CFBooleanGetValue
000000000000f9d9        movzbl  %al, %eax
000000000000f9dc        cmpl    $0x1, %eax
000000000000f9df        jne     0xfa4b
; if (CFBooleanGetValue (brForceBSSID) != 1) { goto fa4b;} 

000000000000f9e1        leaq    0x12e78(%rip), %rsi     ## Objc cfstring ref: @"BSSID"
000000000000f9e8        movq    %rbx, %rdi
000000000000f9eb        callq   0x1e3e8                 ## symbol stub for: _CFDictionaryGetValue
000000000000f9f0        xorl    %r15d, %r15d
000000000000f9f3        testq   %rax, %rax
000000000000f9f6        je      0x103ee
000000000000f9fc        leaq    -0x370(%rbp), %rsi
000000000000fa03        movl    $0x12, %edx
000000000000fa08        movl    $0x8000100, %ecx        ## imm = 0x8000100
000000000000fa0d        movq    %rax, %rdi
000000000000fa10        callq   0x1e472                 ## symbol stub for: _CFStringGetCString
000000000000fa15        xorl    %r15d, %r15d
000000000000fa18        testb   %al, %al
000000000000fa1a        je      0x103ee
000000000000fa20        leaq    -0x370(%rbp), %rdi
000000000000fa27        callq   0x1e502                 ## symbol stub for: _ether_aton
000000000000fa2c        xorl    %r15d, %r15d
000000000000fa2f        testq   %rax, %rax
000000000000fa32        je      0x103ee
000000000000fa38        movw    0x4(%rax), %cx
000000000000fa3c        movw    %cx, -0x1ec(%rbp)
000000000000fa43        movl    (%rax), %eax
000000000000fa45        movl    %eax, -0x1f0(%rbp)
000000000000fa4b        ;  past the BSSID stuff.. optimized memset (rbp -0x350, '\0', 0x100);
...
000000000000fabe        movq    -0x3b8(%rbp), %r14     ; remember -0x3b8(rbp) is arg3
000000000000fac5        testq   %r14, %r14
000000000000fac8        je      0xfb17
; if (!(arg3) { goto fb17 }
000000000000faca        movq    %r14, %rdi
000000000000facd        callq   0x1e478                 ## symbol stub for: _CFStringGetLength
; strLen = CFStringGetLength (arg3);   - telling as arg3 is a CFString!
000000000000fad2        movl    $0x8000100, %esi        ## imm = 0x8000100
; strLenInEnc = CFStringGetLength (ssidCFString)
000000000000fad7        movq    %rax, %rdi
000000000000fada        callq   0x1e47e                 ## symbol stub for: _CFStringGetMaximumSizeForEncoding
; CFStringGetMaximumSizeForEncoding (strLen, 0x8000100);
000000000000fadf        xorl    %r15d, %r15d
000000000000fae2        cmpq    $0xff, %rax
000000000000fae8        jg      0x103ee
; if (strLenInEnc > 255) { goto fb17 } ;
000000000000faee        testq   %rax, %rax
000000000000faf1        jle     0xfb17
; if (strlenInEnc < 0) { goto fb17 } ;
; Otherwise the string can fit in up to 255. -0x350(%rbp) will hold up to 256 characters.
000000000000faf3        leaq    -0x350(%rbp), %rsi
000000000000fafa        movl    $0x100, %edx            ## imm = 0x100
000000000000faff        movl    $0x8000100, %ecx        ## imm = 0x8000100
000000000000fb04        movq    %r14, %rdi
000000000000fb07        callq   0x1e472                 ## symbol stub for: _CFStringGetCString
; rc =  CFStringGetCString(arg3,  // CFStringRef theString,
				-0x350(%rbp)            //  char *buffer, 
				256,                    // CFIndex bufferSize,
				0x8000100);             //  CFStringEncoding encoding );
000000000000fb0c        xorl    %r15d, %r15d
000000000000fb0f        testb   %al, %al
000000000000fb11        je      0x103ee
; if (! rc) goto 0x103ee;
000000000000fb17        leaq    0x4(%r13), %rsi
000000000000fb1b        movl    $0x14, -0x240(%rbp)     # Note 0x14 = 20 = APPLE80211_IOC_ASSOCIATE           
000000000000fb25        movl    $0x1d0, -0x238(%rbp)    # 0x1D0 is the sizeof() the struct assoc_data
000000000000fb2f        movq    %r12, -0x230(%rbp)
000000000000fb36        leaq    -0x250(%rbp), %rdi
000000000000fb3d        movl    $0x28, %edx
000000000000fb42        callq   0x1e4cc                 ## symbol stub for: ___strcpy_chk
; strcpy_chk ( -0x250(%rbp), %rdi, 0x4(%r13), %rsi);
000000000000fb47	; get the value of @AP_MODE (a CFNumber)
000000000000fb83        ; get the value of @"WPA_IE"
000000000000fb95        ; get the value of @"RSN_IE"
...
; memset (-0x1e8(%rbp), '\0', 0x94);
000000000000fd7d ; setup ioctl arguments
000000000000fd97 ; rc = ioctl (Handle->socket,  0x802869C8, -0x250(%rbp));
; recall from all the way up there that -0x240(%rbp) holds "0x14" which is associate..
...
000000000000fda7        callq   __waitForJoinResult

So, Apple80211Associate basically checks its 2nd argument as a dict for "AssociateParameters", which can force association to not only an SSID, but also a specific BSSID (presumably where the signal is strong). Then the actual set is performed by an _IOW('i', 200, struct apple80211_ioctl_str) ioctl - 0x802869c8 , as with the association.

The prototype is therefore something like:

int Apple80211Associate(Apple80211Ref Handle, CFDictionaryRef AssocParams, CFStringRef ign);
int Apple80211Associate2(Apple80211Ref Handle, 
			 CFDictionaryRef AssocParams, 
			 CFStringRef ign, 
			 CFMutableDictionaryRef outResults);

with outResults being a Dictionary holding :

<pre> <dict> <key>ASSOC_RET_40_BIT_WEP <false/> <key>ASSOC_RET_OPEN_SYSTEM <false/> </dict>

Placing a breakpoint on ioctl(2) inside Apple80211Associate2 shows us the following structure is sent to the driver:

(lldb) mem read $rdx
0x7fff5fbff3e0: 65 6e 30 00 00 00 00 00 00 00 00 00 00 00 00 00  en0.............
0x7fff5fbff3f0: 14 00 00 00 00 00 00 00 d0 01 00 00 00 00 00 00  ........?.......
                ---type---- ----------- --length--- -----------
0x7fff5fbff400: 10 f4 bf 5f ff 7f 00 00 00 00 00 00 00 00 00 00  .??_?...........
		----data_ptr-----
0x7fff5fbff410: 01 00 00 00 02 00 01 00 00 00 00 00 16 00 00 00  ................
                ap_version   mode lower upper -pad- ap_ssid_len
0x7fff5fbff420: 4c 65 20 4d 65 72 69 64 69 65 6e 2c 20 4e 65 77   Le Meridien, New
0x7fff5fbff430: 20 44 65 6c 68 69 00 00 00 00 00 00 00 00 00 00   Delhi..........
		_ssid (up to 32 bytes)_________________________
0x7fff5fbff440: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
                    ap_bssid     | 
...
--

Note code 0x14 - (20) - which is ASSOCIATE. Structure size is 0x1d0. The SSID is up to 32 bytes, and is easy to see clearly. The rest of the fields could have been left as they are (mostly blank). Thanks to @comex's observation about the leaked headers, though, it becomes a simple matter to define the structure:

#define APPLE80211_MAX_SSID_LEN                 32
#define APPLE80211_MAX_RSN_IE_LEN               257

// Note structure isn't packed
        struct apple80211_assoc_data
        {
        u_int32_t                               version;
        u_int16_t                               ap_mode;                // apple80211_apmode
        u_int16_t                               ap_auth_lower;  // apple80211_authtype_lower
        u_int16_t                               ap_auth_upper;  // apple80211_authtype_upper
        u_int32_t                               ap_ssid_len;
        u_int8_t                                ap_ssid[ APPLE80211_MAX_SSID_LEN ];
        struct ether_addr               ap_bssid;               // prefer over ssid if not zeroed
        struct apple80211_key   ap_key; 
        // Robust security Network Information Elements
        u_int16_t                               ap_rsn_ie_len;
        u_int8_t                                ap_rsn_ie[ APPLE80211_MAX_RSN_IE_LEN ];
        u_int8_t                                ap_flags;               // apple80211_assoc_flags
        u_int8_t                                filler[0x50];
        } assoc_data;


Apple80211ErrToStr

A simple function, which provides a wrapper over strerror(3) (when the error code is less than sys_nerr(3), handles codes 0xfffff0c4 ("Parameter Error") through 0xffff0a5 (the very helpful "Error") internally, and returns "Unknown error" for everything else. This is shown in the disassembly below:

; edi is the error code we take as a single argument
000000000001235c        pushq   %rbp
000000000001235d        movq    %rsp, %rbp
0000000000012360        movq    0xfd11(%rip), %rax      ## literal pool symbol address: _sys_nerr
0000000000012367        cmpl    %edi, (%rax)
0000000000012369        leaq    0xcf41(%rip), %rax      ## literal pool for: "Unknown error"
0000000000012370        jle     0x123ab                 ; if ≤ sys_nerr
0000000000012372        cmpl    $0xfffff0a5, %edi       ## imm = 0xFFFFF0A5  (lower bound)
0000000000012378        jl      0x123ab	                __ exit            
000000000001237a        cmpl    $0xfffff0c4, %edi       ## imm = 0xFFFFF0C4  (upper bound)
0000000000012380        ja      0x123ab
0000000000012382        testl   %edi, %edi
0000000000012384        je      0x1238e			; if code is 0, then really no error.. ---+
+---000000012386        jle     0x12397                 ; if code is negative, it's ours          |
|   ; if we're still here, fall through to strerror:                                              |
|000000000012388        popq    %rbp                                                              |
|000000000012389        jmp     0x1e574                 ## symbol stub for: _strerror             |
|00000000001238e        leaq    0xcee9(%rip), %rax      ## literal pool for: "No Error"      ←---+
|000000000012395        jmp     __exit;                 ;; 0x123ab       
|  ; We get here from 012386 if code is in our error list
+--→000000012397        movl    $0xfffff0c4, %eax       ## imm = 0xFFFFF0C4
000000000001239c        subl    %edi, %eax
000000000001239e        cltq
00000000000123a0        leaq    __apple80211ErrList(%rip), %rcx 
00000000000123a7        movq    (%rcx,%rax,8), %rax
__exit:
00000000000123ab        popq    %rbp
00000000000123ac        retq

A simple loop over these codes retrieves the possible errors:

error -3931 is Error
error -3930 is Operation not permitted
error -3929 is IPC error
error -3928 is Ref not bound
error -3927 is Station does not support PCO transition time required by AP
error -3926 is Associating station does not support required 802.11n features
error -3925 is Supplicant timeout
error -3924 is Invalid PMK
error -3923 is Cipher suite rejected
error -3922 is Invalid RSN capabilities
error -3921 is Unsupported RSN version
error -3920 is Invalid AKMP
error -3919 is Invalid pairwise cipher
error -3918 is Invalid group cipher
error -3917 is Invalid information element
error -3916 is DSSS/OFDM Unsupported
error -3915 is Short slot unsupported
error -3914 is Unsupported rate set
error -3913 is AP full
error -3912 is Challenge failure
error -3911 is Invalid authentication sequence number
error -3910 is Authentication algorithm unsupported
error -3909 is Association denied
error -3908 is Reassociation denied
error -3907 is Unsupported capabilities
error -3906 is Unspecified failure
error -3905 is Timeout
error -3904 is Format error
error -3903 is Operation not supported
error -3902 is Unknown error
error -3901 is Unable to allocate memory
error -3900 is Parameter error

The IO80211Family.kext has some codes of its own, internally, which are accessible (in a somewhat convoluted manner) via the ioctl(2) interface. From what I've seen, though, that's more in iOS than OS X. A code frequently encountered is -528350142 - when a scan is in progress and you're trying to associate

Apple80211, Reborn (as jWiFi)

Given this, we have *all* the information we need to create Apple80211. In fact, we can do a better job, by getting rid of those #%#$%$# CF* APIs, and creating a pure, old-style C Interface. I aimed to provide a (hopefully fully) compatible API to Apple80211's main exported functions, as well as a new/old API, providing direct access to data (via the raw ioctl(2) interface, without Apple80211CopyValue() internally. It's possible to do the same to CoreWiFi and MobileWiFi as well, and implement them directly over the ioctl(2), instead of mach_msging the wifid (bearing in mind on iOS you'd need to entitle yourself with wlan.authentication). The attached therefore amalgamates the above into a fully open sourced library/framework, which you can download here. The code will compile cleanly for OS X or iOS (with the help of my gcc-iphone wrapper). For the guy commenting on reddit about "no obvious reasons to port it"? I say A) it's darn useful in a jailbroken and environment, B) Yes, We Can. :-)

So that's all for 80211. If you think I've left something unexplored or have any questions, the Book Forum is open for discussion. There'll be much more of my findings and observations about Apple's private frameworks (albeit without the tedious process of reversing, like this article), in MOXiI 2nd Edition. The code - like all code I'm releasing with the book, is (obviously) open source, and if you want to use it in whatever context you wish - be my guest (primum non nocere, and all that jazz you find in the SQLIte3 license). If you do use the code, I'd appreciate an acknowledgment, tweet, or positive review. You're also welcome to look into our training courses - the next one about OS X and iOS Internals coming to San Francisco 7/27/15 8/10/2015.


* - __ZN24AppleMobileFileIntegrity22AMFIEntitlementGetBoolEP4procPKcPb, if you're looking for the exact location - an excellent function to patch :-).