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
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 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
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.
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:
- Apple80211Open/Close
- Apple80211BindToInterface
- Apple80211[Get/Set]Power
- Apple80211CopyValue
- Apple80211[Start/Stop/Event]Monitoring, etc.
- Apple80211Scan[/Dynamic/Async]
- Apple80211[Dis]Associate[2]
- Apple80211ErrToStr
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)
{
CFArrayRef 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: */
/* 21a5: */ ifaceCount = CFArrayGetCount(somevar);
int ifaceFound = CFArrayContainsValue (somevar, 0, ifaceCount, InterfaceName );
CFRelease(somevar);
if (!ifaceFound) goto 0x2268; // exit with error
};
/* 21d6 */ char buffer[16]; // -0x40(%rbp)
bool rc = CFStringGetCString (InterfaceName, // CFStringRef theString,
buffer, // char *buffer,
16, // CFIndex bufferSize,
0x8000100); // CFStringEncoding encoding );
/* 21ec */ if (rc) goto 0x2268; // exit with error
/* 21f0 */ if (handle->used_for_monitoring1) { Apple80211EventMonitoringHalt(handle); }
/* 21ff */ strcpy (handle->unknown1, buffer)
}
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:
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)
(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 #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
<!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 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:
-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 laterApple80211CopyValue()
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 CFNumber
s. 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_ | Expects | sizeof() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | SSID | char * | 0x20 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
2 | AUTH_TYPE | . | . | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
3 | CYPHER_KEY | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
4 | CHANNEL | uint32_t version = 1, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
5 | POWERSAVE | ... | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
6 | PROTMODE | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
7 | TXPOWER 7 // req_type
|
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 :
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_msg
ing 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 :-).