Uninformed: Informative Information for the Uninformed

Vol 8» 2007.Sep


Vulnerability Discovery

One of the major staples in a researcher's toolbox is binary analysis (where ``binary'' refers to compiled software code). Vulnerability research and discovery on OS X is no different in this regard. However, performing binary analysis on OS X requires some understanding of the underlying binary file format that is used. On OS X, Apple uses a universal binary file format called a Mach-O. In this context, a universal binary will execute on both Intel and PPC based machines. It accomplishes this by combining a compiled binary version of the program for each processor in an archive like format with a header that contains specific information relating to each processor type. The universal binary header is detected at runtime causing the correct compiled code for the platform to execute.

Although universal binaries provide an elegant solution for an operating system that supports multiple architectures, it leads to problems when performing binary analysis because not many tools support the file format at the time of this writing. Recently, IDA Pro added support for the binary format in 5.1. Prior to 5.1, reversing a universal binary required manual manipulation or scripting in an IDC.

Things I wish Google Told me: Disassembling OS X binaries

Apple provides tools that support the manipulation of universal binaries which are capable of creating a simplified binary suitable for hassle free loading into IDA Pro. One of these tools, ``lipo'', allows a researcher to extract the relevant chunk of compiled code from a universal binary. The following gives a quick example of using lipo on the Atheros driver from OS X 10.4.7. This will create a thin file called at.i386 that is suitable for loading into IDA Pro without the confusing archive headers and with the older PowerPC code.

lipo –thin i386 AirPortAtheros5424 –output at.i386

The vulnerability featured in this paper is a flaw in Apple's wireless device driver. This flaw was discovered through ``beacon'' and ``probe response'' fuzzing. Beacons are the packets that wireless access points broadcast several times a second to announce their presence to the world. They are also the packets that your notebook computer uses in order to build a list of nearby access-points. Probe-responses are similar packets that are used when a notebook computer probes for access points that are not otherwise broadcasting.

The bug described in this paper was found by the author while performing fuzzing experiments against other machines. During this time, one of the Macbooks in the vicinity running OS X 10.4.6 crashed unexpectedly. This crash produced a file called panic.log in /Library/Logs. A panic.log file contains information to help debug a kernel panic or crash on OS X. This includes the output of all the registers, a stack trace and the load address of the offending module and the address of its dependent modules. This information provides a great starting place to help track down a driver problem. However, in its default form, there are several shortcomings. The most apparent shortcoming is that the stack trace does not include symbol information. As such, one sees addresses rather than function names. In order to begin to track down a problem, one needs to do some basic math to manually discover the names of the functions. Luckily, the loading offsets did not change much on the test machine when reproducing this issue.

The following output shows an example panic.log:

panic(cpu 0 caller 0x0019CADF): 
Unresolved kernel trap (CPU 0, Type 14=pagefault), registers:
CR0: 0x8001003b, CR2: 0x62413863, CR3: 0x021d7000, CR4: 0x000006e0
EAX: 0x62413862, EBX: 0x00000003, ECX: 0x0c67bc8c, EDX: 0x00000003
ESP: 0x62413863, EBP: 0x0c67bad4, ESI: 0x03717804, EDI: 0x0371787c
EFL: 0x00010202, EIP: 0x008c923d, CS:  0x00000008, DS:  0x0c670010

Backtrace, Format - Frame : Return Address (4 potential args on stack)
0xc67b954 : 0x128b5e (0x3bc46c 0xc67b978 0x131bbc 0x0)
0xc67b994 : 0x19cadf (0x3c18e4 0x0 0xe 0x3c169c)
0xc67ba44 : 0x197c7d (0xc67ba58 0xc67bad4 0x8c923d 0x48) 
0xc67ba50 : 0x8c923d (0x48 0x10 0x1e200010 0xc670010)
0xc67bad4 : 0x8c7303 (0x371787c 0x1e202d0d 0x8 0x5)
0xc67bb24 : 0x8bccb9 (0x3699804 0xc67bc8c 0x1e202800 0x80)
0xc67bb84 : 0x8cd799 (0x369b46c 0xc67bc8c 0x1e202800 0x80)
0xc67bce4 : 0x8ddbd9 (0x369b46c 0x1e20cb00 0x36bbc04 0x80)
0xc67bd34 : 0x8ce9a5 (0x369b46c 0x1e20cb00 0x36bbc04 0x80)
0xc67be24 : 0x8de86a (0x369b46c 0x1e20cb00 0x36bbc04 0x46)
0xc67bf14 : 0x38dd6d (0x369b29c 0x354d080 0x1 0x36a7e58)
0xc67bf64 : 0x38cf19 (0x354d080 0x135d18 0x0 0x36a7e58)
0xc67bf94 : 0x38cc3d (0x3575140 0x3575140 0x0 0x450)
0xc67bfd4 : 0x197b19 (0x3575140 0x0 0x36a80d0 0x3) 
Backtrace terminated-invalid frame pointer 0x0
  Kernel loadable modules in backtrace (with dependencies):
    com.apple.driver.AirPortAtheros5424(104.1)@0x8bb000
    dependency: com.apple.iokit.IONetworkingFamily(1.5.0)@0x672000
    dependency: com.apple.iokit.IOPCIFamily(2.0)@0x563000
    dependency: com.apple.iokit.IO80211Family(112.1)@0x8a2000

When an OS X driver is loaded into IDA, the offsets are all relative to 0. In order to find the address where a kernel driver crashed you subtract the last address associated with the module from the stack trace from the module load address. You then subtract 0x1000 from the result because kernel modules are loaded in a page aligned fashioned. Here is a typical panic.log from /Library/Logs created for this example.

panic(cpu 1 caller 0x0019CADF): 
Unresolved kernel trap (CPU 1, Type 14=pagefault), registers:
CR0: 0x80010033, CR2: 0x00000004, CR3: 0x02209000, CR4: 0x000006a0
EAX: 0x00000000, EBX: 0x00111111, ECX: 0x000005c3, EDX: 0x00000039
ESP: 0x00000004, EBP: 0x0c74b758, ESI: 0x00111111, EDI: 0x0345bbf0
EFL: 0x00010206, EIP: 0x0090df95, CS:  0x00000008, DS:  0x03a10010

Backtrace, Format - Frame : Return Address (4 potential args on stack)
0xc74b5d8 : 0x128b5e (0x3bc46c 0xc74b5fc 0x131bbc 0x0)
0xc74b618 : 0x19cadf (0x3c18e4 0x1 0xe 0x3c169c)
0xc74b6c8 : 0x197c7d (0xc74b6dc 0xc74b758 0x90df95 0x110048)
0xc74b6d4 : 0x90df95 (0x110048 0x2920010 0x10 0x3a10010)
0xc74b758 : 0x8f2083 (0x345a000 0x111111 0xc74b778 0x800016c3)
0xc74b7a8 : 0x9112b7 (0x36d5804 0x90df78 0x345a000 0x3a1f5a5)
0xc74b7c8 : 0x9115b9 (0x345a000 0x345a46c 0x345bdb8 0x196fc1)
0xc74b808 : 0x8dec91 (0x345a000 0x36d6800 0xc74b828 0x0)
0xc74ba08 : 0x8d600c (0x368a360 0x3a1f5a5 0x6 0x339c91)
0xc74bcb8 : 0x38e698 (0x345a000 0x8 0x3a1f5a5 0x0)
0xc74bcf8 : 0x8d5284 (0x35aa900 0x8d5c7c 0x8 0x3a1f5a5)
0xc74bd38 : 0x3a3d5c (0x345a000 0x8 0x3a1f5a5 0x0)
0xc74bd88 : 0x18a83d (0x36f8d00 0x0 0x3a1f5a4 0x22)
0xc74bdd8 : 0x12b389 (0x3a1f57c 0x39c756c 0x0 0x0)
0xc74be18 : 0x124902 (0x3a1f500 0x0 0x50 0xc74befc)
0xc74bf28 : 0x193034 (0xc74bf54 0x0 0x0 0x0) 	Backtrace continues...
  Kernel loadable modules in backtrace (with dependencies):
    com.apple.driver.AirPortAtheros5424(104.1)@0x8e7000
       dependency: com.apple.iokit.IONetworkingFamily(1.5.0)@0x873000
       dependency: com.apple.iokit.IOPCIFamily(2.0)@0x57e000
       dependency: com.apple.iokit.IO80211Family(112.1)@0x8ce000
    com.apple.iokit.IO80211Family(112.1)@0x8ce000
       dependency: com.apple.iokit.IONetworkingFamily(1.5.0)@0x873000
       dependency: com.apple.iokit.IOPCIFamily(2.0)@0x57e000

Kernel version:
Darwin Kernel Version 8.7.1: Wed Jun  7 16:19:56 PDT 2006; 
 root:xnu-792.9.72.obj~2/RELEASE_I386

The AirPort Atheros module has a load address of 0x8e7000 which rules out the first three entries in the stack trace as being found within this driver. The fourth entry, 0x90df95, is within the range of the driver. By performing a few quick calculations, it is possible to calculate the relative offset into the associated driver's binary:

	0x90df95
-	0x8e7000
-	0x1000 = 0x25f95

Opening the driver in IDA Pro and then jumping to offset 0x25f95 will yield the following code from ath_copy_scan_results:

__text:00025F87                 mov     esi, [ebp+arg_4]
__text:00025F8A                 mov     edi, eax
__text:00025F8C                 add     edi, 1BF0h
__text:00025F92                 mov     eax, [esi+60h]
__text:00025F95                 movzx   ecx, byte ptr [eax+4]
__text:00025F99                 mov     eax, ecx
__text:00025F9B                 shr     al, 3

Looking at this crash log, one of the first lines quickly gives insight into how to analyze this dump:

panic(cpu 1 caller 0x0019CADF): 
Unresolved kernel trap (CPU 1, Type 14=pagefault)

A page fault usually means that some code tried to access an invalid address. In a case such as this, the CR2 register (shown with the gdb with info registers) will contain the offending address2.1. In this case, the offending address is 0x00000004. Looking at the instruction that commits the page fault one can see a dereference of EAX: movzx ecx, byte ptr [eax+4]. The EAX register is zero so the value of CR2 came from the machine adding 4 to the address of in EAX. By looking at the binary values, one can determine that this panic log was caused by a NULL pointer dereference in the wireless device driver. Although it is a bit out of the scope for this document, the three addresses that precede the Atheros address in the stack trace are:

0x128b5e	panic
0x19cadf	panic_trap
0x197c7d	trap_from_kernel

When performing OS X kernel auditing and exploit development, these three address will become a very familiar site in a panic log, so get used to ignoring the first three and starting at the fourth address.