How do we find events to monitor? Well, we can just loop over all events..
..
Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef
Callback: err 0, event 4, data 0x7fff58dbd538, len 8 bytes context 0xbeef Data: 00 00 00 00 08 00 00 00 ← Link state (00 = off)
Callback: err 0, event 2, data 0x0, len 0 bytes context 0xbeef ← SSID, likely
Callback: err 0, event 3, data 0x0, len 0 bytes context 0xbeef ← BSSID?
Callback: err 0, event 1, data 0x0, len 0 bytes context 0xbeef ← Poweroff, definitely
(power on)
Callback: err 0, event 1, data 0x0, len 0 bytes context 0xbeef ← Power on. Note no data here. Shame
Callback: err 0, event 54, data 0x7fff58dbd538, len 32 bytes context 0xbeef Data: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00
Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef
Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef
Callback: err 0, event 11, data 0x0, len 0 bytes context 0xbeef
Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef
Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef
Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef
Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef
Callback: err 0, event 16, data 0x0, len 0 bytes context 0xbeef
Callback: err 0, event 15, data 0x0, len 0 bytes context 0xbeef
Callback: err -3905, event 9, data 0x0, len 0 bytes context 0xbeef
Callback: err 0, event 4, data 0x7fff58dbd538, len 8 bytes context 0xbeef Data: 01 00 00 00 00 00 00 00
Callback: err 0, event 2, data 0x0, len 0 bytes context 0xbeef ← SSID, likely
Callback: err 0, event 3, data 0x0, len 0 bytes context 0xbeef ← BSSID?
Callback: err 0, event 9, data 0x0, len 0 bytes context 0xbeef ← Association successful
Callback: err 0, event 17, data 0x7fff58dbd538, len 8 bytes context 0xbeef Data: b1 ff ff ff 36 00 00 00
edit: @Comex apparently reversed the heck out of the codes (and the rest - see note below for his GITHub). His header file shows the APPLE80211_M_* constants for the events, and gets them all. Nice to see my hunches were validApple80211GetPower/SetPower()
These are simple:
int Apple80211GetPower(Apple80211Ref handle, uint32_t *power);
int Apple80211SetPower(Apple80211Ref handle, uint32_t power);
And actually set the system icon, thanks to the generated notification.Apple80211Scan
This function requires three arguments. The first is the handle. To figure out the other two, we start by passing the usual poison (0xdeadbeef) to see a crash reported:
Process 4954 stopped
* thread #1: tid = 0x6130b, 0x00007fff8f3f0ba6 Apple80211`Apple80211Scan + 1246, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0xdeadbeef)
frame #0: 0x00007fff8f3f0ba6 Apple80211`Apple80211Scan + 1246
Apple80211`Apple80211Scan + 1246:
-> 0x7fff8f3f0ba6: movq %rax, (%rcx)
0x7fff8f3f0ba9: xorl %r12d, %r12d
0x7fff8f3f0bac: callq 0x7fff8f401418 ; symbol stub for: CFRelease
0x7fff8f3f0bb1: movl -0x1778(%rbp), %edi
(lldb) reg read rcx
rcx = 0x00000000deadbeef
So the value of arg2 ends up being treated as a pointer (because rax is moved to the value it is pointed by). This means that this is an out value of some sort. Changing this to a void *, and passing it by reference works, and gets us some opaque object. Calling CFGetTypeID()
on it returns 19, which is a CFArray
, and is allocated by the framework for us. Individual entries (one per network found) are CFDictionary, which we can easily verify this by trying CFGetTypeID
on them - that's the Foundation's version of RTTI. When printed out to XML, an individual result looks something like this:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>AGE</key>
<integer>0</integer>
<key>AP_MODE</key>
<integer>2</integer>
<key>BEACON_INT</key>
<integer>100</integer>
<key>BSSID</key>
<string>92:3:d8:7f:f0:62</string>
<key>CAPABILITIES</key>
<integer>1057</integer>
<key>CHANNEL</key>
<integer>1</integer>
<key>CHANNEL_FLAGS</key>
<integer>10</integer>
<key>HT_CAPS_IE</key>
<dict>
<key>AMPDU_PARAMS</key>
<integer>27</integer>
<key>ASEL_CAPS</key>
<integer>0</integer>
<key>CAPS</key>
<integer>429</integer>
<key>EXT_CAPS</key>
<integer>1024</integer>
<key>MCS_SET</key>
<data>
//8AAAAAAAAAAAAAgAAAAA==
</data>
<key>TXBF_CAPS</key>
<integer>212329990</integer>
</dict>
<key>HT_IE</key>
<dict>
<key>HT_BASIC_MCS_SET</key>
<data>
AAAAAAAAAAAAAAAAAAAAAA==
</data>
<key>HT_DUAL_BEACON</key>
<false/>
<key>HT_DUAL_CTS_PROT</key>
<false/>
<key>HT_LSIG_TXOP_PROT_FULL</key>
<false/>
<key>HT_NON_GF_STAS_PRESENT</key>
<true/>
<key>HT_OBSS_NON_HT_STAS_PRESENT</key>
<false/>
<key>HT_OP_MODE</key>
<integer>1</integer>
<key>HT_PCO_ACTIVE</key>
<false/>
<key>HT_PCO_PHASE</key>
<false/>
<key>HT_PRIMARY_CHAN</key>
<integer>1</integer>
<key>HT_PSMP_STAS_ONLY</key>
<false/>
<key>HT_RIFS_MODE</key>
<false/>
<key>HT_SECONDARY_BEACON</key>
<false/>
<key>HT_SECONDARY_CHAN_OFFSET</key>
<integer>0</integer>
<key>HT_SERVICE_INT</key>
<integer>0</integer>
<key>HT_STA_CHAN_WIDTH</key>
<false/>
<key>HT_TX_BURST_LIMIT</key>
<false/>
</dict>
<key>IE</key>
<data>
AAtvcHRpbXVtd2lmaQEIgoSLlowSmCQDAQEqAQAyBLBIYGwtGq0BG///AAAAAAAAAAAA
AIAAAAAABAbmpwwAPRYBAAUAAAAAAAAAAAAAAAAAAAAAAAAAfwgAAAAAAAAAQN0YAFDy
AgEBgAADpAAAJ6QAAEJDXgBiMi8A3QkAA38BAQAA/38=
</data>
<key>NOISE</key>
<integer>-92</integer>
<key>RATES</key>
<array>
<integer>1</integer>
<integer>2</integer>
<integer>5</integer>
<integer>6</integer>
<integer>9</integer>
<integer>11</integer>
<integer>12</integer>
<integer>18</integer>
<integer>24</integer>
<integer>36</integer>
<integer>48</integer>
<integer>54</integer>
</array>
<key>RSSI</key>
<integer>-54</integer>
<key>SSID</key>
<data>
b3B0aW11bXdpZmk= <!-- 'optimumwifi' in Base64 >-->
</data>
<key>SSID_STR</key>
<string>optimumwifi</string>
</dict>
</plist>
Obtaining these values programmatically is a simple enough matter with the CFDictionary
APIs. Note that RSSI value. This, and a little bit of curses, and you can make yourself a 20-line WiFi detector. Using the (private) frameworks for accelerometer and GPS, you could even make this into a useful app :-)
As for the third argument, trying the usual poison crashes us before the scan, and reveals:
* thread #1: tid = 0x616aa, 0x00007fff93de70dd libobjc.A.dylib`objc_msgSend + 29, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x18)
* frame #0: 0x00007fff93de70dd libobjc.A.dylib`objc_msgSend + 29
frame #1: 0x00007fff930be28f CoreFoundation`CFDictionaryGetValue + 159
frame #2: 0x00007fff8f3f0797 Apple80211`Apple80211Scan + 207
frame #3: 0x0000000100000c0e 80211`main(argc=1, argv=0x00007fff5fbffbc8) + 206 at 80211.c:107
frame #4: 0x00007fff90ede5c9 libdyld.dylib`start + 1
frame #5: 0x00007fff90ede5c9 libdyld.dylib`start + 1
(lldb) reg read rdx
rdx = 0x00007fff7dbbb880 @"SCAN_SSID_LIST"
Which means it's a Dictionary
, that is expected to have (at least) a "SCAN_SSID_LIST" key. In other words, this argument provides scan parameters. And thus we have:
int Apple80211Scan(Apple80211Ref handle,
CFArrayRef *scanResults,
CFDictionaryRef parameters);
The other variants (ScanAsync and ScanDynamic) are left as an excercise for the reader, but if you're impatient, just check out my reversed .h file.
Apple80211ErrToStr
Easy - give it a uint32_t, and return a char * error string. Examples: 16 - "Resource busy", 82 - "Device power is off".
Apple80211CopyValue
@TODO. For the impatient:int Apple80211CopyValue(Apple80211Ref handle,
int field,
CFDictionaryRef dictCanBeLeftNULL,
CFDataRef outValue);
That covers almost all of them. You can figure out the rest using the same methods.Next: (re)-porting Apple80211 to iOS
One of the cool things about iOS is that, deep down, it shares 80-90% of its code with OS X. Just compiled for ARM instead of Intel, and far more secure. Apple80211 existed as a private framework in iOS for a long time - first visible (in /System/Library/PrivateFrameworks), then hidden (as in /System/Library/SystemConfiguration/IPConfiguration.bundle/IPConfiguration), but as of iOS 8 is has been removed (which, incidentally, is why tricks like This StackOverflow Question don't work - dlsym()
returns NULL).
There's a strong rationale for removing the framework: Apple is trying to make the system more secure, and adopt the client/server XPC model all throughout. Apple moves all the functionality into a daemon (in this case, /usr/sbin/wifid
), and any requests are made over XPC (a glorified term for Mach messages, really). Using XPC enables the use of entitlements, as the daemon can then check the "caller id" to see if the requesting process has the necessary declaratory permissions to perform the action. Said entitlements are embedded in the code signature, which only Apple can verifiably sign.
The actual functionality, however, is still very much there - and deep down the driver (Apple80211Family) is still the same driver. Thanks to the ingenious design of IOKit, the underlying chipset driver (Broadcom, or otherwise), would be hidden anyway by the family - which makes it possible to use the same framework in both OS X and iOS, and further means that we can reintroduce the user mode portion (i.e. Apple80211.framework) in one of two ways:
- Grabbing a copy of the 80211.framework from an older iOS version: (e.g. out of
IPConfiguration.bundle
)and copy the binary or- Decompile either OSX or iOS binary, and recompile it for ARM
Naturally, option #2 is the interesting one. But uncovering the APIs is only half the job - there's still the implementation to figure out, which (again, for the impatient) revolves around two ioctl()
codes - which work similarly in iOS - meaning 80211 shall rise again. And wifid
? It can sit in its sandbox, and wait for playmates to check entitlements. I'll discuss all this and more in Part II. Stay Tuned.. Read it here.
In the interim, for questions/comments, please use the Book's Forum. Especially if you have any requests for The 2nd Edition.
For more info, I discuss these reverse engineering techniques in depth at my company's Reverse Engineering OS X/iOS course. We're planning a public one in July - info@Technologeeks to inquire more or register early!