Porting exploits to a Netgear WNR2200

Porting exploits to a Netgear WNR2200

Software vulnerabilities and the accompanying exploits are still all too common. Fortunately the response to vulnerabilities has got significantly better in recent years, with quick patching of the most critical issues and efficient update channels.

By Jan Mitchell

Senior Researcher

21 September 2016

Despite this improved security posture embedded systems can often lag behind. As they often require a manual reflashing with a firmware image to update them they often feature outdated versions of Operating Systems and software. These devices, such as WiFi routers and ADSL modems, are generally packaged for consumers who typically will not manually patch embedded devices due to a lack of knowledge.

One saving grace of these embedded devices is that they tend to run on power-efficient RISC platforms, such as those designed by MIPS or ARM. Exploitation of non-mobile specific platforms still tends to focus on x86, leaving relatively few working proof-of-concepts or exploits for RISC systems.

This blog is an illustration of how to take a known exploit targeting the x86 architecture and port it to target a MIPS embedded system. In this case I have chosen a Netgear WNR2200.

A Note About Responsible Disclosure

The exploit which is eventually ported in this blog is both already known and already patched, having been initially assigned a CVE number in 2007. As such, there is no new bug to disclose. At the end of the blog we have a working remote exploit against a router, we have however, decided that the information in the blog is acceptable to release as the exploit can only target the LAN side of the router and additionally requires that a USB memory stick has been inserted into the device. With these contraints it is unlikely to be widely exploitable and a determined attacker would find it as easy to use as Netgear's telnet shell.

Firmware extraction

Before trying to find a suitable exploit to port to the router, it was necessary to discover the various software versions and system layout of the device.

A copy of the router firmware (version 1.0.1.96) was available for download from the Netgear site. When unzipped, the file was found to be a .img firmware image. To unpack and inspect this type of file the tool ‘binwalk’ was used to examine a binary file for known data headers and magic numbers and to report back information on how the file is structured.

The output from binwalk is shown here:

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
192           0xC0            Squashfs filesystem, big endian,
version 3.0, size: 6033818 bytes,{jump-to-offset:6033818}{file-size:6033818}
1379 inodes, blocksize: 65536 bytes, created: Thu Mar 26 03:33:52 2015
336044        0x520AC         LZMA compressed data, properties:
0x5D, dictionary size: 8388608 bytes, uncompressed size: 2740358 bytes

This output shows that binwalk was able to locate and read a SquashFS file system within the image. The next stage was to use the SquashFS file system tools to decompress and image. Part of the SquashFS toolset is a program called “unsquashfs” which is used exactly for this purpose. Running ‘unsquashfs –s’ on the image dumped the superblock information:

Reading a different endian SQUASHFS filesystem on C0.squashfs
Found a valid big endian SQUASHFS 3:0 superblock on C0.squashfs.
Creation or last append time Thu Mar 26 03:33:52 2015
Filesystem size 5892.40 Kbytes (5.75 Mbytes)
Block size 65536
Filesystem is not exportable via NFS
Inodes are compressed
Data is compressed
Fragments are compressed
Always_use_fragments option is not specified
Check data is not present in the filesystem
Duplicates are removed
Number of fragments 101
Number of inodes 1379
Number of uids 1
Number of gids 0

This confirmed that the image did contain a valid SquashFS file system. Following this verification an attempt was made to decompress the image. Unfortunately this attempt failed:

Reading a different endian SQUASHFS filesystem on C0.squashfs
Parallel unsquashfs: Using 8 processors
gzip uncompress failed with error code -3
read_block: failed to read block @0x5c0f6d
read_fragment_table: failed to read fragment table block
FATAL ERROR:failed to read fragment table

Investigations into this error message, combined with the LZMA data section in the firmware blob header, lead me to believe that the file system had been compressed using a non-standard build (for the date the firmware was created) and SquashFS had utilised LZMA for the compression.

The latest version of the SquashFS tools have LZMA support and so a new version of the toolset was downloaded and compiled, ensuring that appropriate compression suites were enabled. This newly compiled version of the unsquashfs tool was run over the firmware, unfortunately, with no further success. The suspicion was that there might be further custom modifications to the tool used to compress the image which were stopping a successful decompress.

Further investigation revealed that the no-longer-updated package of tools “Firmware Mod Kit” might be able to assist in unpacking the image. The Firmware Modkit was a selection of tools and scripts used to access and reverse engineer the firmware of embedded systems. Included within this package are multiple customised versions of the SquashFS file system, able to unpack filesystems that have unusual or obfuscated features. After running the “unsquashfs_all” script, which attempts to unpack the image using all of the various unsquashfs versions I was able to retrieve a valid image.

The reported version of squashfs was "./src/squashfs-3.0/mksquashfs-lzma".

Once the file system was unpacked it was found to be an OpenWRT Linux base system. This tied neatly with the banners which were retrieved from telnet. Older Netgear routers require a magic packet to be sent to them to open the telnet port. Information on the magic packet required to open the port can be found here. Enabling the telnet port and then connecting to the router shows the OpenWRT banner for the Kamikaze release:

BusyBox v1.4.2 (2013-12-23 15:48:24 CST) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

 _______                     ________        __ 
|       |.-----.-----.-----.|  |  |  |.----.|  |_ 
|   -   ||  _  |  -__|     ||  |  |  ||   _||   _| 
|_______||   __|_____|__|__||________||__|  |____|
         |__| W I R E L E S S   F R E E D O M
KAMIKAZE (7.09)
-----------------------------------
 * 10 oz Vodka       Shake well with ice and strain
 * 10 oz Triple sec  mixture into 10 shot glasses.
 * 10 oz lime juice  Salute!
---------------------------------------------------
[email protected]:/#

Information on the various system components was retrieved. This would help us to find a suitable exploit and give us useful information on possible mitigations (e.g. whether NX or ASLR had been enabled within the kernel).

Table 1: Software versions

This is fortunately a fairly old build of both Samba and the kernel. Samba 3.0.24 was released in February 2007 and Linux 2.6.15 was released in January 2006. The old versions should make finding a suitable vulnerability to exploit easier.

Running the Samba daemon

Before finding a vulnerability to port it is important to be able to emulate and debug Samba so that any existing exploit can be easily ported. At this point I had a fully unpacked version of the firmware; a file system containing a Linux distribution. As the kernel and software in this distribution is MIPS-based I needed to emulate the relevant parts. Rather than emulating the entire system I chose to emulate just the Samba binary.

To emulate the binary the QEMU toolset was installed, including the QEMU static binaries. The static binaries allow emulating in a chrooted environment, as they do not need access to libraries from the host system. The binary “qemu-mips-static” was moved into the root of the firmware file system. From here the emulation can be started with the following command:

“sudo chroot . ./qemu-mips-static -g 5656 ./usr/sbin/smbd -i -d 10 > fileout.txt”

This line achieves a few goals.

  • It chroots into the firmware directory so that the Samba binary will see the directory as the root file system. 
  • It instructs the QEMU binary to start emulating Samba. The “–g” switch ensures that QEMU will create a GDB debugger stub with which we debug Samba (here it is listening for connections on port 5656).
  • We pass some switches to the Samba binary, namely “-d 10” to set the debugging level to the maximum, this helps give invaluable debugging information we might need to get the system up and running as well as when we are debugging and potentially crashing the application.
  • We redirect output to a file so that we can store a copy of any debugging logs which have been output.

Starting the binary with this command line revealed a few problems. The Samba binary was unable to locate an smb.conf config file as well as other necessary files. Something I noted on the running firmware on the router was that a number of the files within “/etc” directory had been symbolically linked to files in the “/tmp” directory. However the “/tmp” directory in the image was empty. The router was dynamically constructing the tmp directory at system start up. Before I could get Samba running fully the tmp directory would need to be reconstructed to the same state as the running firmware.

The following startup script files were identified, all which are used to build up the appropriate runtime environment when the router starts (relative to the firmware file system root):

  • /etc/init.d/samba – This file creates some required directories, copies various configuration files from /usr/config to directories in /tmp and then calls the following two executable files:
  • /usr/sbin/update_user – A bash script which echos various user lines into the /tmp/passwd file, adds a new user on the system and assigns passwords to the newly created users
  • /usr/sbin/update_smb – A native binary which updates the smb.conf file in /tmp. Possibly performs other actions as well

The lines from the scripts were run manually and then the update_smb binary was emulated using the qemu-mips-static package. Once complete the tmp directory was laid out in the correct way for the Samba daemon. A check was made in /etc/ to ensure that the symbolic links now correctly pointed to existing files. Emulating the Samba daemon again resulted in a correctly running service.

Finding a vulnerability

Now that the version of Samba had been identified and was correctly running we could examine past CVEs to see if they would be suitable to port to the MIPS architecture. To aid in assessing CVEs the source code for the version of Samba on the device was obtained from the samba release repository.

A number of CVEs discovered in 2012 were identified for the 3.0.24 release of Samba. The first set of these (CVE-2012-1182) related to the pidl RPC code generator in Samba. This particular CVE was registered as affecting version “3.x before 3.4.16” and looked like a promising candidate. Examination of the source code however showed that the published range of affected versions was incorrect. The affected function “ndr_pull_lsa_SidArray” was not present in the source code. Further investigation revealed that no pidl code resided in 3.0.24 of Samba. Whilst this discounted the CVE it was useful information as no pidl generation bugs would be relevant to the version of Samba on the device.

After discounting this initial batch of CVEs I concentrated on another series of CVEs released even earlier in 2007. These bugs also manifested themselves in RPC code, but in older hand written functions as opposed to generated functions. These bugs are present in the unmarshalling RPC calls made to Samba’s implementation of RPC over SMB. At their heart they are all heap overflow vulnerabilities; one item count is used to allocate a buffer in the heap, a second count is then used to unmarshall data into the buffer. If the initial count is small and the second count is large then a heap overflow occurs with (mostly) user controlled data.

The vulnerability

The vulnerability that was identified was CVE-2007-2446. This is a remote code execution cause by a heap overflow. It exploits the fact that Samba uses a custom heap allocator called talloc. This heap allocator allocates blocks of memory in a tree structure, and at any node in the tree you can free the node and any children nodes. To help with this, talloc includes customisable free routines for data structures. These are stored in the metadata of the heap and can be overwritten by an overflow. These function pointers are called when a chunk is being freed, so overwriting them gives you arbitrary code execution at chunk free.

metasploit module already exists to exploit this vulnerability on x86. This made it an ideal candidate as there was existing working exploit code and an existing metasploit module base on which to add the MIPS target.

Debugging the emulated target

At this point we have the Samba daemon (smbd) emulated and running. We started the emulation with a command line that ensured that a GDB debugger stub was listening for connections. After loading the smbd binary into IDA Pro the IDA debugging features were used to connect to the awaiting debugger.

In doing this we immediately hit upon a problem. IDA will show us a flat view of the memory space of the process, but it has been unable to correctly identify the location of any of our binaries. As a result no symbols are mapped to the correct locations within the view of memory, and IDA is not even able to fully recognise which areas are code and which are data, displaying most areas as a series of raw bytes. Debugging in this environment would be difficult as it’s not possible to easily discern which function is executing or interesting function which we could breakpoint. Figure 1 shows this layout of memory at the point at which IDA breaks into the suspended process.

Figure 1: Initial IDA view of process memory

The function shown in Figure 1 is the initial point at which the debugger breaks. My initial assumption was that this therefore was within the smbd binary. After comparing the code to both the smbd main() function and the binary entry point it became clear that the broken function represented neither. A selection of opcode bytes from the broken function was taken and a search over the whole smbd binary was performed to try to find which function the debugger had landed in. The search turned up nothing, confirming the growing suspicion that this function did not reside in smbd at all.

To locate what this function might actually represent I stepped backwards through memory in IDA to the nearest page boundary. This might give me some information about what this memory might represent.

Figure 2: Module page boundary at 0x76436000

Figure 2 shows this page boundary and we can quite clearly see that the ELF magic value is present. Scanning through the headers suggests that this binary is ld-uClibc.so.0. Using readelf on the smbd binary confirms that ld-uClibc.so.0 is the dynamic loader for the binary and somewhere within this library is where we are currently broken.

Examining the code for uClibc shows that this function is likely the code from uClibc/ldso/ldso/mips/dl-startup.h and therefore the final instruction in the function is a jump into the real binary’s entry point. Stepping the debugger to this line and then stepping to the jump target leads us to the following section of code:

Figure 3: Initial function entry point

This code section at address 0x76CB76C0 looks very much like a binary entry point. The calls to __uClibc_main further reinforce this supposition.

At this point I stopped debugging and went back to examine any cross references to __uClibc_main in IDA's disassembly. There were a handful of cross references, and one of them was inside a function identical to that one shown in Figure 3. This function was at an offset (from 0x00000000, the IDA default) of 0x0003B6C0. Taking this offset and subtracting it from the offset that the address was found in memory, 0x76CB76C0, gives us the binary base address in memory, 0x76C7C000. IDA was instructed to rebase its representation of the binary to this base address. Once this was completed and the debugger reattached we now had a correctly mapped view of memory, showing relevant symbols and function names in the correct places:

Figure 4: Process memory now correctly based

Now that we could match up the available symbols in memory we could start debugging the interesting parts of the code as we ran the exploit against the Samba daemon. The symbols showed the majority of function names, but not all. Any unnamed functions which we needed to give a label to we did by cross referencing the disassembly view against the Samba source code.

Modifying the x86 exploit

As this vulnerability currently has a working exploit for the x86 architecture a handful of modifications were made to allow it to be thrown against a MIPS target. I did not expect this to work first time but the modifications would at least allow me to throw data against the correct functions and begin to examine where data lay on the heap and any further changes that would need to be made to create a fully working exploit.

The first modification was to create a working nop generation module for metasploit. The Samba exploit uses a nop sled in memory to reduce the chance of failure should the memory layouts not be 100% predictable. A nop sled is a section of processor instructions which do not change program state, but allows us to put a large block of instructions in memory, so that execution landing anywhere in this nop section will give us execution. This gives us a margin for error when we modify the program counter.

In order to generate this nop sled for a particular architecture a suitable nop sled generation module must exist. At first I created a very basic nop sled generator. A basic nop in MIPS assembly language is the instruction 0x00000000 (which decodes to the MIPS instruction “sll $0, 0” or logical left shift the zero register by zero). The basic generator returns a run of these 4-byte instructions up to the size requested by the caller. After getting the basic nop generator working I then created a more randomised generator, substituting in ~170 MIPS instructions which act as nops with no side effects.

Figure 5: Example of a randomly generated nop sleds

With a suitably created MIPS nop generator I just needed to add some target information to the exploit file to allow it to target the MIPS architecture. A target specification was added to target big-endian MIPS on Linux as well as a recognisable marker for the program counter overwrite (0xAABBCCDD) to make it easy to tell if we successfully took control of the program counter. Metasploit would now have the relevant information to attempt to throw the exploit against the emulated smbd service.

Debugging the exploit

If the exploit succeeds then we should end up with an exception being caught by the debugger with the program counter pointing to our magic value (0xAABBCCDD). On the first throw however we don’t get this far. The debugger catches an exception within a function that some cross referencing reveals to be talloc_chunk_from_ptr():

Figure 6: Exception in the initial attempt

The function talloc_chunk_from_ptr() is responsible for taking a memory address and stepping backwards to return the chunk header and metadata which proceeds the allocation. Additionally it validates that the metadata magic number is valid and that the memory is not already marked as freed. The exception occurs at the highlighted line above, whilst trying to load a value at a certain offset from the $a0 register. The MIPS architecture calling convention specifies that the $a0 register is used for the first parameter to a function. If we take a look at the registers we can see what value was passed into the talloc_chunk_from_ptr() function:

Figure 7: Register state

Figure 7 shows the register state at the point of the crash. Register $a0 contains the value 0x41414141, a familiar value (this value is composed of multiple copies of the ASCII character ‘A’) . The exploit we are modifying overwrites an existing talloc chunk header on the heap and replaces several key fields (the magic value to make the header valid, the various tree and linked list pointers and the all-important function pointer). The initial assumption is that one of the metadata pointers (next, prev, parent or child) has been set to an incorrect value in the exploit and the Samba code has tried to parse through a structure eventually faulting on the bad value. Examining the exploit source code shows that the only two values set to 0x41414141 are the next and prev pointers. To further confirm that this is the root of the problem we can examine the call stack leading up to the crash:

Figure 8: Call stack

At some point before this crash talloc_total_size() was called. We can take a look at this function to validate that our assumptions about the next and prev pointers being the case are justified:

Figure 9: The function talloc_total_size()

Figure 9 contains the source code for talloc_total_size() a function for calculating the size of a particular branch of the allocations tree. The function is recursive and calls talloc_chunk_from_ptr() (our crashing function) at each iteration, passing in each pointer in the linked list. The highlighted “c=c->next” is how our erroneous value is eventually delivered to talloc_total_size() and then talloc_chunk_from_ptr().

The fix for this is relatively simple. If we set the next and prev pointers in our overwritten header to 0x00000000 (NULL) rather than 0x41414141 the loop in talloc_total_size() will terminate early and our pointers will not be passed to any further functions for processing.

Once the exploit code had been modified and re-run we had success and the program counter was correctly overwritten with our marker value.

Redirecting execution

The existing exploit works by creating a giant nop sled in memory by sending a specifically crafted SMB request. When this buffer is unmarshalled another buffer is allocated and overrun. The overrun talloc header has a function pointer overwritten to point into the original buffer (somewhere in the nop sled). As the heap layout is not an exact science a brute force is attempted, changing the controlled pointer location at each attempt to work through the possible heap locations. If the attempt fails then the process instance is killed. This is acceptable as a new forked instance is created for each incoming connection and a crash in the forked process is cleaned up. When a new connection comes in a fresh process will be forked.

An attempt was made to find a more controllable way of redirecting execution than brute forcing the heap addresses. MIPS has significantly more registers than x86 and so some of them may be left with predictable and useful values at the point execution is gained. This might open up some opportunities for some ROP gadgets which could control execution much more reliably.  Whilst some of the registers pointed reliably to controllable data on the heap ultimately they were unusable. The area of the heap they pointed to was the overwritten talloc header and attempts to write shell code here were unsuccessful due to the talloc header being modified in between it being written and the point of execution. An attempt was made to find ROP gadgets which could take a register, add an offset and then branch to the register thereby allowing us to skip over the unstable part of the header but no suitable gadgets were discovered.

As it wasn’t possible to utilise predictable registers and ROP to reliably redirect execution we needed to fall back onto the heap brute force method. Examining the memory layout of the smbd process on the Netgear router it quickly becomes apparent that all instances of the process have the same memory layout and that this is the same across system reboots. This shows that the kernel on the device is not using ASLR (Address Space Layout Randomization, a modern OS defence mechanism that attempts to randomise where assets are loaded in memory) meaning that the memory layout is incredibly predictable. An interesting point on the lack of ASLR is that the kernel reports that it is in fact using ASLR (as seen by catting /proc/sys/kernel/randomize_va_space displaying a value of 1, which should cause randomised libraries which is not the case).

To find suitable addresses for our heap brute force we need to discover the bounds of the process heap. We can do this by catting the “/proc//maps” file of a running smbd daemon on the netgear router:

Figure 10: smbd process memory layout

The values from the heap data above (the range 0x558be000 to 0x55965000) were used as the limits of the brute force. The exploit provided a 64kb nop sled and then threw the exploit multiple times using values within this range for the pointer overwrite (going up in 60kb increments). One of the attempts will eventually land in the nop sled and lead to execution.

Sadly execution within these bounds failed, with no call back from the metasploit payload. Further debugging on the emulated version showed that the exploit should work it was just a case of getting the correct heap range. Confused why the exploit was not working I decided to expand the memory range to try beyond the reported bounds of the heap. After expanding the range to 0x558b0000 -  0x559c0000 I finally received a callback from the delivered shell code:

Figure 11:  Interactive shell callback

This exploitation was successful with a heap location of 0x559b4000 which is stable across exploitation attempts and router reboots. Strangely this location sits outside of the reported range of the heap. I have not yet been able to discern why this is the case but my suspicion is that the heap has grown and the kernel is failing to properly report this.

After making the heap location changes and the other modifications to the exploit code we now have a working MIPS Samba exploit!

Contact and Follow-Up

Jan works in our Research team in our Cheltenham office. See the contact page for how to get in touch.

Prior to joining Context, Jan has been an IT tutor for a college and worked as a software developer for the Windows platform, specialising in security. His background is primarily in C development with a focus on debugging, reverse engineering and some assembly.

When he's not got his nose in a debugger he can usually be found in one of his other interests: running, reading or gaming.

About Jan Mitchell

Senior Researcher