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.
(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).
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:
000000000001235c T _Apple80211ErrToStr
0000000000001b55 T _Apple80211GetIfListCopy
00000000000088ba T _Apple80211GetInfoCopy
0000000000008860 T _Apple80211GetInterfaceNameCopy
0000000000001fa1 T _Apple80211GetVirtualIfListCopy
00000000000123ad T _Apple80211MaxLinkSpeed
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:
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.
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.
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)
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):
(you can easily corroborate this by lldb:
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:
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:
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,
So we have Apple80211BindToInterface() figured out:
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):
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:
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:
The Power get/setters had a really simple interface, as shown in Part I. But what of their implementation? Look at Apple80211GetPower() first:
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!!!).
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
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:
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:
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:
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).
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:
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:
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:
That "used_for_monitoring2" which we still don't exactly know there can be figured out by looking at the disassembly of Apple80211EventMonitoringHalt:
So now we have:
Looking through the code of Apple80211EventMonitoringInit, something in particular sticks out:
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:
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
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.
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:
The dict dump produces the following output:
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:
If the above is confusing, here's a sample 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:
Playing around with the scan parameters produces the fields above very quickly, as does looking at the leaked headers @comex pointed out to:
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:
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.
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:
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:
with outResults being a Dictionary holding :
Placing a breakpoint on ioctl(2) inside Apple80211Associate2 shows us the following structure is sent to the driver:
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:
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:
A simple loop over these codes retrieves the possible errors:
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 :-).