Patching, Emulating, and Debugging a Netgear Embedded Web Server

Posted on

Previously I posted about running and remotely debugging a Netgear UPnP daemon using QEMU and IDA Pro. This time we’ll take on the challenge of running the built-in web server from the Netgear R6200 in emulation.

The httpd daemon is responsible for so much more than the web interface. This daemon is responsible for a silly amount of system management, including configuring firewall rules, managing the samba and ftp file servers, managing attached USB storage, and many other things. And it does all of this management as part of its initialization, which means lots of opportunities to fail or crash when running in emulation as a standalone service.

Running this device’s web server in emulation involves substantially more work, but it is still doable. First we need to figure out how to invoke the httpd program. Below is a script I use to start up httpd in emulation.


# run with DEBUG=1 to attach gdbserver


if [ "x1" = "x$DEBUG" ];

rm ./tmp/shm_id
rm ./var/run/
ipcrm -S 0x0001e240
for ipc in $(ipcs -m | grep 0x | cut -d " " -f 2); do ipcrm -m $ipc; done

chroot $ROOTFS /bin/sh -c "LD_PRELOAD=/ $DEBUGGER /usr/sbin/httpd -S -E /usr/sbin/ca.pem /usr/sbin/httpsd.pem"

I partly based this on the command line arguments that httpd is invoked with on the actual device. The script chroots into the router’s filesystem and then runs httpd. Further, if you set DEBUG=1 on the command line, the script will use gdbserver to execute the daemon and wait for a debugger connection on port 1234.

As with the UPnP daemon, an early challenge is the fact that QEMU doesn’t provide NVRAM for configuration parameters, so calls to nvram_get() will fail. We can work around this with my project nvram-faker. Nvram-faker is loaded using LD_PRELOAD and hooks calls to nvram_get(). It reads a configuration from a text file and prints the results of nvram queries to standard error. Queries for unknown parameters are printed in red, helping to diagnose what parameters are needed that you haven’t yet provided. If you don’t have an instance of the hardware, this is an exercise in guesswork and trial and error. You need to intuit sane values for the queried parameters and iteratively fill in missing parameters as they are queried. If you do have the actual gear, and you can get a shell[1] on the device, you can extract the NVRAM configuration from flash and convert it into an INI file for nvram-faker. I’ll post the nvram configuration I ended up using at the end.

When attempting to run httpd, I found it kept crashing with SIGBUS. It turns out that on startup the daemon wants to open a shared memory segment for IPC. It looks for the file /tmp/shm_id, which may have been created by another process. If it finds that file it attempts to attach to the shared memory segment associated with the ID in the file. If the file doesn’t exist, then httpd opens a new shared memory segment and then writes the ID to that file. The problem is there is no check to see if shmat() failed, returning negative one (cast to a void *). In any case, the program attempts to deference the return value of shmat() at 0x00419308. In the case of failure, 0xffffffff is dereferenced, crashing the program. The crash is SIGBUS rather than SIGSEGV due to the misaligned memory access.

If the http server has previously run and not exited cleanly, then /tmp/shm_id will hang around. The solution is easy: have our script delete /tmp/shm_id before starting the server.

Another problem is that the http daemon…well…daemonizes. As far as I know, there’s no way to have IDA and gdbserver follow fork()s, so this is a problem. If we could get the daemon to run reliably, we could start it, let it daemonize, and then attach to the forked process. However, we’re going to need to do a fair amount of debugging just to get this program running, so letting it start and daemonize isn’t an option.

In the above screenshot we see the jump to daemon() at 0x004183fc. The easiest approach is to patch out the call to daemon().

Just a few thoughts about binary patching: It’s important to keep in mind that if you change the binary you’re analyzing, you’re no longer analyzing the same program. Instead you’re analyzing some other, slightly different program, with different behaviors. Hopefully nothing you change will make a material difference, but it’s hard to know. For example, it may be difficult to tell if a function that you patched out would have initialized some global structures that will now result in a crash or a slightly different code path. Just be aware of the ramifications, and patch only when necessary.

The return value of daemon() is checked and a value of 0 indicates success. A relatively nonintrusive way of replacing a call to daemon() and simulating success is to xor the $v0 register (which contains a function’s return value) with itself. The assembled bytes for the instruction:

xor $v0,$v0


00 42 10 26 

The target system is little endian, so these bytes must be swapped:

26 10 42 00 

I’ll leave the actual method[2] of assembling a single instruction and deriving its corresponding sequence of bytes as an exercise for the reader. If you have a cross compiler set up, one way is to create a source file with your instruction, assemble it with the gnu assembler, and then disassemble it with objdump. Another option is to use this lightweight python module, mips-assembler.

To patch the program using IDA, click on the instruction you want to patch, in this case the jalr to daemon() at 0x004183fc. Then switch to IDA’s hex view. The corresponding bytes will be highlighted. Right click and select “edit”.

The hex view changes to overwrite mode. Change the selected bytes to the ones corresponding to your patch. Right click again and choose “apply.” When you switch back to disassembly, you should see the patch.

Note that IDA hasn’t actually changed the original binary. In fact, one of IDA’s features is that once you disassemble a file, not only does it not touch the original file again, you don’t even need it. All you need from that point forward is the .idb file. However, IDA does have the ability to apply the patch to the original file. Select the Edit menu, “Patch Program,” then “Apply patches to input file.” IDA will prompt you to name the patch file and whether to make backup copy. This is a relatively new feature, so if you’re new to IDA, know that it wasn’t always this easy. Send Ilfak an email thanking him.

Now you should be able run the patched httpd without it daemonizing. Set a breakpoint somewhere past the original call to daemon() and verify that IDA stays attached.

With the daemon running, you can start the iterative process of building up an NVRAM configuration that will satisfy the many initialization steps. The goal is for execution to reach the select() at 0x00415564 in the http_d() function.

During this process, there was one initialization function that I wasn’t able to get past. The call to fwPtRulesInit() at 0x00419640 always hung in an endless loop. Since this has to do with firewall policy configuration, I decided it was worth the risk to patch it out with a nop instruction.

Here is the configuration I ended up with. I ended up using the complete configuration copied from the hardware’s NVRAM. I tweaked a few settings such as LAN IP address, and the HTTP admin’s password, but this is mostly a stock configuration[3]. (Apologies to mobile users. This is supposed to be a 500px iframe that shows about 20 lines and scrolls, but for some reason in my mobile browser, the entire 1500+ line configuration is rendered.)

Once I had the trouble spots patched out and had a working configuration, I was able to get the web server running and responding to requests from a web browser. Mostly.

As you can see, the web interface chrome appears to be working, but the text is mostly missing. It turns out there was a bug in libnvram-faker. As the library handles NVRAM queries, it prints to the console the parameters and their values. The problem is it was printing to standard output. The web server executes a number of shell commands using system(). Some of those commands redirect their standard output to a file, which the web server then uses. In particular, at 0x004B5C40, a string table gets generated by a shell command, then read in, and then immediately deleted. Since the file only exists for a moment, it’s not obvious this is happening.

Below, we see the function CreateHeader() getting called with two arguments. These are a string table in /www, and a compressed copy of the same file in /tmp.

Then in CreateHeader() we see a shell command being generated via sprintf() and then executed via system(). That shell command is bzip2 -c somefile > some_other_file. The resulting file is a redirection of bzip’s standard output.

Once I noticed this, I set a breakpoint just after the bzip command, and was able to make a copy of the file. When I decompressed it, I saw that it had all of libnvram-faker’s output mixed in with the strings. This had the result of breaking the templating system that generates the web interface’s HTML and javascript. Once I fixed that in libnvram-faker, I was able to get the web server working.

This should get you started emulating and debugging some more challenging binaries. With enough work you can get fairly complicated programs from an embedded device running in emulation. Sometimes this is convenient, so you don’t have to carry actual gear around when you’re doing research. Other times, it’s necessary; you may not be able to get interactive access to the hardware in order to debug its processes. In that case, emulation may be your only choice.

[1] Many devices have a UART connection which will let you connect via minicom or other serial terminal in order to get console access. Further, nearly every consumer Netgear device has a telnet backdoor listening on the local network.

[2] I use a tool that Craig Heffner wrote called “shellgasm.” It’s a nifty python program that calls gcc and objdump from your cross compiler toolchain. It will also turn asm code into a C-style or Python-style byte array that can be used for payloads in buffer overflows and such. Unfortunately it’s not available anywhere. Maybe if you pester Craig, he’ll post it on Github.

[3] This was actually more work than it sounds like. There was a bug in libnvram-faker. It didn’t allocate enough memory to accommodate all the lines of the INI file. This resulted in a crash with large configuration files. The crash was difficult to debug, so I mostly worked around it by iteratively uncommenting parts of the file until the web server worked. Just before finishing this post, I finally tracked down the bug, so now I can use the entire 1500+ lines of the default configuration.