Debugging and De-bricking the Netgear R6200 via UART
Update: I forgot to credit my former colleague, Tim
(@bjt2n3904), 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.
In the previous
installment,
we filled out the ambit firmware header just enough to satisfy Netgear’s
broken UPnP server. We also patched out several ioctl() calls in
upnpd in order to test the SetFirmware exploit in emulation.
In the previous
part,
we switched gears back to the Netgear R6200 upnpd after spending some
time analyzing httpd. The HTTP daemon provided an understanding of how
the firmware header is supposed to be constructed. We found a header
parsing function in upnpd that was similar to its httpd counterpart.
So similar that it has the same memcpy() 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.
In the
previousfewposts,
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 /dev/mtd1 flash partition. Considering this a win, we’ll
now circle back to analyzing upnpd.
We’re about halfway through the Broken,
Abandoned
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.
Overview
This series, entitled Broken, Abandoned, and Forgotten Code, 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 kind of work. There are many artifacts of
incomplete implementation that stand in the way of straightforward
exploitation.
In the previous
post, I
finished discussing the abCheckBoardID() 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.
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.
Note: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
here
and
here.
In the previous
post,
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.
In
previousinstallments
I
shared
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 sa_parseRcvCmd() 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 part
four, I
showed how an undersized malloc() means a stock firmware crashes
upnpd. 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.
In the last
post, I
described how upnpd’s sa_parseRcvCmd() 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.
In the
previousposts,
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 sa_parseRcvCmd() function where hopefully an encapsulated firmware
image will be decoded.
In the part
1, I
showed how the Netgear R6200’s upnpd binary contains what appears to be
a hidden SOAP action related to the string “SetFirmware”. I also
showed how we can get into the upnp_receive_firmware_packets()
function if we play timing games and send our request in multiple
parts.
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.