Jonathan Levin, http://www.newosxbook.com/ (@Technologeeks) - 04/08/15
The 2nd Edition of MOXiI delves deep into a realm I totally ignored in the 1st Ed - that of Apple's private frameworks. Most of the "cool" functionality in both OS X and iOS is provided by private frameworks, and their number far outweighs the public ones (359 vs 126 in OS X). I'm hoping to provide a tour of the private frameworks in MOXiI2. While it's true that Apple won't allow any store apps to link with private frameworks (which they can easily verify via jtool -L or jtool -S | grep dlopen), it's still interesting - especially for an internals book, and may be useful for Cydia(iOS) or DMG (OSX) based apps. The 2nd Ed aims to provide a reference which - though far from complete - will provide an unprecedented level of detail on these frameworks.
One example of "cool" functionality is everything to do with WiFi. Apple's wifi stack is quite powerful, and provides lots of useful functionality, but most of it well hidden. Case in point - Relative Signal Strength Indicator (RSSI) values, which can enable you to get a better idea of where the force is strong with WiFi, and where it's not. You can get those by pressing alt (option) while clicking the WiFi status icon, or by using the airport command, which is buried deep in /System/Library/PrivateFrameworks/Apple80211.framework/Resources/airport. That makes Apple80211 pretty interesting. Or does it?
Enter: Apple80211
Surprisingly enough, airport doesn't actually link with 80211 directly, but instead going through the public CoreWLan which relies on the private CoreWifi. This is also true for /usr/libexec/wifid, which implies that Apple80211's days are limited in OS X (as they were in iOS). While the framework is still alive, however, it provides access to all things WiFi - communicating with the underlying driver, which itself is a subclass of the Apple80211Family.kext. You can examine the framework's exports with nm, or jtool -S -v:
Focusing on the Apple80211* exports, an educated guess shows the sequence of calls should be something like: Apple80211Open() → Apple80211GetIfList() → Apple80211BindToInterface() and then all the other calls such as Apple80211[Get/Set], and, of course - Apple80211Scan() . To get the actual APIs, we need to reverse engineer the framework. So grab your copy of /System/Library/PrivateFrameworks/Apple80211.framework/Apple80211 from OS X (before Apple removes it from there, too :-), and follow along.
Reversing Apple80211
To reverse Apple80211.framework from OS X, you can use otool -tV (jtool, alas, does not support Intel disassembly yet, but I tried to provide the comments it does for ARM, manually). Starting with Apple80211Open, you should see:
Apple80211Open/Close
So we see it only takes one argument - which we can deduce is a pointer to a pointer to a struct (i.e. a pointer to a struct, as an out parameter). Looking at Apple80211Close() (right after ..Open) reveals that it also takes one argument (the pointer struct), as one can expect, it undoes the Open by closing the socket descriptor and free()ing the memory - but not before it also calls Apple80211EventMonitoringHalt(), in case certain members of the structure are set:
So we have the first two prototypes, which look like:
Apple80211BindToInterface
This function is significantly longer than Open/Close, and apparently has two arguments (as noted by the saving of both rsi and rdi). But what are they?
Note that arg2 ends up in r15, which is then used in CFArrayContainsValue. This function is well documented:
The Array here is retrieved from the internal __getIfListCopy(), and then arg15 is searched for in the array (the CFRange is 0..CFArrayGetCount()). We can therefore deduce the value is also a CF*.. , and it makes sense it's a CFString. So we have:
This can quickly be corroborated by the following code:
.. which works for "en0" as an argument, but not for bogus values.
Apple80211EventMonitoringInit
The Apple80211Close() disassembly revealed a call to Apple80211EventMonitoringHalt. This means that a client can register a callback for events on the interface - which is what you can see with Apple80211EventMonitoringInit():
So this time we have four arguments. The first is a handle. The 2nd, 3rd and 4th are more challenging. One of them's a callback, the others..?
Option 1: do more disassembly to figure out the arguments.
Option 2: cheat with a debugger.
Calling the function with four arguments, passing 0xdead, 0xbeef and 0xdeadbeef for 2,3 and 4, respectively, we get a crash, and lldb reports:
So arg4 is a runloop. We can use CFRunLoopGetCurrent() for that. That's one down. The other two simply have to be a callback and a context. To get the callback arguments, we can use trial and error. Eventually, it boils down to :
Now all we need is events to monitor.
Apple80211StartMonitoringEvent
This one's easy. Two arguments, with the first being the handle (as usual), the second being the event to monitor.
How do we find events to monitor? Well, we can just loop over all events..
edit: @Comex apparently reversed the heck out of the codes (and the rest - see note below for his GITHub). His header file shows the APPLE80211_M_* constants for the events, and gets them all. Nice to see my hunches were valid
Apple80211GetPower/SetPower()
These are simple:
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:
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:
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:
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:
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:
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!
.
Also, check out My other Book if you're into Android Internals!
Note to @comex: *Sigh* When I started this it didn't strike me that headers exist. That said, the point of the article was reversing, without headers, so I hope that people found it useful. But thanks for the mention. https://gist.github.com/comex/0c19c1b3fa569f549947 is a great reference, and I'll use it in Part II when I explain about creating the framework from scratch. We should talk sometime, you and I. I'm a big fan of your work. Call me, maybe? :-) J@...com