This article describes my experiences while conducting some reverse engineering of the Netgear WG102 firmware. For one thing, you might learn something about the approaches and methods useful when you want to find out “how they did that” (they = the device’s manufacturer) in general, on the other hand there is also a lot of WG102 resp. Netgear-specific information in here, of course.
For a larger WLAN installation at a customer’s site I have been doing evaluations of different access point models. The requirements were:
- IEEE 802.11g (802.11n explicitly not required since we were talking about providing internet access, no internal traffic)
- IEEE 802.3af Power over Ethernet (PoE)-capable (since some locations were not equipped with a power outlet and the @§$ power supplies keep on dying anyway, being the most neglected part of an access point package and the one manufacturers save on in an effort to maximize the $$$)
- preferrably OpenWrt-compatible
- originally the requirement was also two antennas as to exploit antenna diversity. However recent measurements I conducted showed that there is no real gain in off-the-shelf products: the Asus WL-320gP (two antennas) for example did equal or slightly worse to the Asus WL-320gE (one antenna), the Netgear WG302 sucked totally and all of them were beaten miles ahead by a small Fonera 2100! Apparantly the real challenge is to make the most of an efficient PCB/antenna layout.
- preferrably some sort of RADIUS accouting facilities, though this requirement had not been precisely detailed yet.
- a maximum price of 200 Euros (say $230)
While the second and the fourth requirement narrowed available products down to three (3!) access points under the over a 100 models on the market, even the PoE-requirement alone reduces available options considerably. To cut the story, the model I opted for is the Netgear WG102, which currently retails for around 110 euros (say $140). The Netgear models page in the deprecated OpenWrt wiki listed this model as basically untested resp. similar to the Netgear WGT624, which seems to be a still undergoing effort. This post is about my research towards this direction, examining possible OpenWrt compatibility.
Unlike the well-known Asus models, the Netgear devices behave entirely different as regards providing a recovery mode, in which a firmware could possibly be flashed via TFTP PUT. At first playing around with the WG102’s “Reset” button didn’t have any visible effect but to restore the factory defaults, alas no sign of a recognizable recovery mode with blinking “PWR” or “Test” LED. It is said that holding the buttons for more than 15 seconds would cause it to enter such a recovery mode, but that didn’t work out for me. Actually I had to simulate a firmware upgrade and cut the power and voila, the device didn’t boot up successfully any more :) Now holding “Reset” while powering up the device did cause it to enter a recovery mode.
However I could try TFTP put requests as long as I wanted, there was simply no reply, no TFTP transfer at all. While the unit replied ping requests you can’t just nmap it to see whether some sort of TFTP server is running. This is also not a sign of a rejected firmware since in order to reject a firmware, it would have to be received first. No, the funny thing is, the Netgear devices seem to do it the other way around: they are the client and you need to provide a TFTP server. Even funnier: since it can’t possibly figure out what IP the TFTP server is located at, it simply uses a fixed IP, as I found out while watching the network traffic: 192.168.0.36 (as I found out later, this was already documented for a related model in the DD-WRT Wiki). So you need to configure that IP on your server’s network interface, but you also need to give the new firmware the right name: “wg102.img”.
Even then however the question is what firmware do you want to flash and in what format? My first goal was not to readily flash some generated OpenWrt firmware but to get a look beneath the surface: telnet access or some other method to execute arbitrary commands so as to take a look at the filesystem etc. So either get telnet working or examine the webpages stored in the firmware image to find some hidden page, like Asus used to have in some firmwares where you could just enter commands in a form and get the command output back as results.
Now this OpenWrt wiki page, a SeattleWireless.net-hosted utility and various security services such as this Bugtraq report pointed to the possibility of opening up telnet access in various Netgear models by means of a special network packet sent to the telnet port. The special packet would have to contain the amazing username/password combination “Gearguy”/”Geardog” which would represent a “hidden Netgear service account”. However, this wouldn’t work for the WG102, just like the other reported combinations with “super” resp. “superman”. While there is something listening on the Telnet port (23), it will just keep saying “Sorry, unable to access input device” and not even bother about the special packet. So no easy way to open Telnet, although the “telnet remote administration facility” is reported as a feature both in various reseller descriptions as well as this netgear.de datasheet. (Note that the “larger brother” WG302 does have not only telnet but also ssh and even a serial port out-of-the-box.)
Now there could have been yet the possibility that the Telnet method still works and they just changed the username/password combination, as indicated in the response to the security vulnerability. In that case, an older firmware version, released before the publishing of the reports, could have carried the old combination. However, my access point was shipped with firmware v5.0.0 which does not allow for a simple firmware downgrade, even when uploaded via recovery mode. The installed firmware prevented this at first, but I got around that later (see below), yet to no avail. “Gearguy”/”Geardog” simply doesn’t work with the WG102.
Yet two possibilities still remained open:
- if the Telnet facility still exists and the username/password combination was changed, it must be retrievable from the firmware file somehow.
- as mentioned some hidden Web pages might exist where one might execute commands, eg. to enable telnet or just look around.
For both possibilities there is no way around conducting a closer look at the supplied firmware images resp. their sources.
Analyzing the WG102’s firmware header
For my experiments, I analyzed the firmware versions for the WG102 available from the Netgear websites (eg. the US one). A first analysis with “hexdump -C” shows this at the beginning:
00000000 41 48 30 30 48 1a c2 65 00 12 42 b4 cd c3 4f 77 |AH00H..e..B...Ow| 00000010 30 64 ac 58 db f4 f9 96 15 02 46 43 00 01 00 74 |0d.X......FC...t| 00000020 30 30 30 31 30 30 30 30 30 35 31 30 34 34 30 30 |0001000005104400| 00000030 30 31 30 31 30 30 30 30 30 30 30 30 30 30 30 30 |0101000000000000| 00000040 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 30 |0000000000000000| * 00000060 30 30 30 30 30 30 30 30 30 30 30 30 30 36 30 38 |0000000000000608| 00000070 30 30 30 30 30 30 30 30 30 30 30 30 38 30 30 30 |0000000000008000| 00000080 30 30 30 30 35 30 30 30 30 30 30 30 30 30 31 30 |0000500000000010| 00000090 00 00 00 00 7f 45 4c 46 01 02 01 00 00 00 00 00 |.....ELF........|
Now that’s a treacherous signature at the end at file offset 0x94 — 0x7F ‘E’ ‘ L’ ‘F’ indicates the beginning of an executable or a shared object in the standard ELF format. Thus everything beforehand can be considered a header. Comparing the different firmware versions available with each other gives the following insight:
- The first four bytes (ASCII “AH00”) seem to be the “magic” bytes to identify the firmware format.
- The next four bytes differ between the four, but their meaning is yet unknown.
- The bytes at offset 0x08 to 0x0B differ too and read for the given example 0x00 0x12 0x42 0xb4. Is this a 32-bit value? If so, due to Endianness there are two ways of constructing such a value. If the value were stored in little-endian format, then the value must be read right-to-left: 0xb4421200, which translates into decimal 3024228864. A large value. If the value were stored in big-endian format, then the value can be intuitively read left-to-right: 0x001242b4, which translates into decimal 1196724. Now the fun is: the entire file is 1196872 bytes long. Now guess what :) Doing the math reveals a difference of 1196872-1196724=148 bytes. We just said the ELF signature starts at 0x94. 0x94 is decimal 148 — so the header’s total size is 148 bytes and 1196724 is the size of the following ELF binary. Bytes 0x08 to 0x0B contains the “image size” in big-endian format.
Note that x86 and MIPS processors are little-endian, so if you just fread() parts of the header in a program you have to convert the data. Also note that while I used the “hexdump” command here, a “real” hex editor such as GNOME’s “ghex2” makes inspection easier since it continously displays values for both endian styles.
- The bytes beginning at offset 0x0C to 0x1B differ also and do not make sense in ASCII representation. Interpreting as 4 single 32-bit values does not give much clues either. However the knowledge that firmwares often contain checksums of the embedded image code gives a hint here. So let’s extract the ELF image and try the “md5sum” utility over it (it could have been CRC32 or yet another algorithm, too, but md5sum was the first thing that came to my mind):
# dd if=WG102_V5.0.0WW.img of=WG102_V5.0.0WW.elfz skip=148 bs=1 # md5sum WG102_V5.0.0WW.elfz cdc34f773064ac58dbf4f99615024643 WG102_V5.0.0WW.elfz
Voila. Exactly what’s in the header :) So bytes 0x0C – 0x1B are simply the md5sum over the ELF image, again, stored in big-endian format.
- The next whole bunch of bytes up to offset 0x7B do not differ. Bytes 0x20 to 0x7B do look like the ASCII representation of RAM/flash start addresses or similar (consider “fis list” output with the RedBoot boot loader), but I don’t bother for now.
- Bytes 0x7C – 0x7F do differ, but only between firmware v5.0.0 and the earlier versions. They read 0x38 0x30 0x30 0x30 (ASCII “8000”) here while they read 0x30 0x30 0x30 0x33 (ASCII “0003”) for earlier firmwares. This suggests that this field controls either some kind of features or implements the restriction that no firmware downgrade is possible. As we will see later, this is true.
- Bytes 0x80 – 0x83 do not differ.
- Bytes 0x84 – 0x87 differ and contain in ASCII the embodied firmware version. ASCII “5000” stands for v5.0.00 whereas in earlier firmwares eg. ASCII “2022” stands for V2.0.22. So 0x84 = major version, 0x85 = minor version, 0x86 and 0x87 = revision, all in ASCII digit representation.
- The remaining bytes do not differ either.
So what did we obtain? We know how to obtain the ELF image of the actual firmware and we know where the header encodes the firmware version. So let’s try to downgrade. First I just took the v5.0.0 image and modified the encoded version number to v5.0.01 to see whether it would be accepted. Yep, no problem.
Next I took a V2.0.22 image and replaced the version number with V5.0.01, so as to trick out the downgrade restriction. This test image was rejected: “Downgrade is rejected because autocell features are not supported”. Aha. So the restriction comes from something else. Remember bytes 0x7C – 0x7F from above that did not differ eg. between V2.0.22 and V4.0.6 firmwares but between V5.0.0 and all earlier firmwares? The Netgear website’s changelog for the V5.0.0 firmware gives a hint:
Due to loss of support from the AutoCell technology developer, we have been forced to remove AutoCell functions including rogue AP/Station detection from the WG102.
That’s why they disabled downgrade. But what-the-heck, who’s the master over your own hardware? You yourself, you got that right. Let’s just additionally edit the test image’s 0x7C – 0x7F bytes so they look like in V5.0.0 firmware. Made the change, fired it up, and again: voila, accepted :) We do not know the exact meaning of this header field, but as you can see, we don’t even have to. Some wit and logical thinking as well as the will to experiment (and risk bricking hardware) are enough. A small victory, but alas, to no greater use: the Telnet enable utility does not work with V2.0.22 firmware either :(
Analyzing the ELF firmware image itself
So we’re back at the two possibilities outlined earlier: getting to know the “right” username/password combination from the firmware image or finding some secret in the webinterface. To get along further, we need to deal with the extracted “<firmwarever>.elfz” file itself.
If you don’t know the UNIX “file” utility, now would be a good moment to get to know it:
# file WG102_V5.0.0WW.elfz WG102_V5.0.0WW.elfz: ELF 32-bit MSB executable, MIPS, MIPS-II version 1 (SYSV), statically linked, stripped
Aha, now this gives some hints as to the access points architecture, however we already know this information from the OpenWrt Wiki (Atheros 2312 is MIPS-compatible). Let’s try another useful utility:
# objdump -x WG102_V5.0.0WW.elfz WG102_V5.0.0WW.elfz: file format elf32-tradbigmips WG102_V5.0.0WW.elfz architecture: mips:6000, flags 0x00000002: EXEC_P start address 0x80b846e0 Program Header: LOAD off 0x00000060 vaddr 0x80b80000 paddr 0x80b80000 align 2**4 filesz 0x00124130 memsz 0x0013ddb0 flags rwx private flags = 10000001: [no abi set] [mips2] [not 32bitmode] [noreorder] Sections: Idx Name Size VMA LMA File off Algn 0 .text 00004730 80b80000 80b80000 00000060 2**4 CONTENTS, ALLOC, LOAD, CODE 1 .rodata 00000240 80b84730 80b84730 00004790 2**4 CONTENTS, ALLOC, LOAD, READONLY, DATA 2 .data 0011f7c0 80b84970 80b84970 000049d0 2**4 CONTENTS, ALLOC, LOAD, DATA 3 .bss 00019c80 80ca4130 80ca4130 00124190 2**4 ALLOC SYMBOL TABLE: no symbols
What you can see from here (in case you didn’t know already) is that ELF files contain a logical structure consisting of various headers (an ELF header at the beginning, a Program header in case of a program etc.) and sections. The “.text” section is program code, “.rodata” is read-only data such as constants, “.data” is variables and “.bss” I’m too lazy to look up :) Try “man elf” on your Linux box if you want to know more.
Now you could be tempted to go and try to disassemble the code. But wait! I’ve got a better idea. Use another really useful UNIX utility: “strings”. This doesn’t count your girlfriends underwear but searches arbitrary files for human-readable strings. And if you pipe strings’ output through “less” your eyes should catch the following output:
oversubscribed dynamic bit lengths tree incomplete dynamic bit lengths tree oversubscribed literal/length tree incomplete literal/length tree invalid distance code invalid literal/length code invalid block type invalid stored block lengths too many length or distance symbols invalid bit length repeat =xunknown compression method invalid window size incorrect header check need dictionary incorrect data check
The word “compression” should hit your eye here :) Without even trying to understand the code block in the ELF image, the simple presence of output like this combined with the absence of any other usable strings tells you what? Exactly, the code block most likely contains nothing but code to uncompress the following compressed image. And if you simply google for one of the strings above, you will find out that these strings come from the ever-famous “zlib” compression library (eg. via the really good Wikibooks page on Reverse Engineering File Formats). You will have heard of the inflate/deflate compression algorhythms which are related to gzip/zip. So what we can do now is try to find the start of the compressed image and uncompress it with some utility yet to discover.
Playing games with zlib
Easy said, but not as easily done. While “strings” extracts human-readable strings for us, it does not say where in the file those were found. So you could of course fire up your hex editor and search for the mentioned ASCII strings, but that would just give a rough idea of where the code ends and the compressed data must begin.
Instead, one must again look for some zlib-specific signature that would indicate the beginning of the compressed data or, alternatively, some zlib-specific structure that contains the appropriate file offset or something like that. Through a Google search I stumbled first over the find-zlib utility. This was originally written to detected executables and libraries containing vulnerable zlib code and while it confirms the zlib assumption, it does not report on actual positions either. The same holds true for the supposedly advanced method of using ClamAV to detect zlib code. However the signature files themselves come handy because of the ready-to-use signatures that can be searched for with your hex editor:
This translates into: 0x0100 0000 (4 times repeated after each other), 0x0200 0000 (4x), 0x0300 0000 (4x), 0x0400 0000 (4x), 0x0500 0000 (4x), 0x0000 0000, 0xC000 0000, 0xC0. It is sufficient to search for the last parts of this pattern. For example, if I search the extracted “WG102_V5.0.0WW.elfz” file for 0xC0000000C0, I get a match at 0x4AC7, which is, not by accident, behind the strings section we already discovered. Now this is just the end of a typical zlib signature after which follows what find-zlib calls a “cplext table”. And take a closer look at the following data, it indeed looks artificial: you will see lots of zeros, 0xFFs and combinations such as 0x7FFF. Until where? Take a good look! At 0x4C71, the data substantially changes. There is no more some sort of recognizable pattern, the bytes look really variegated, and you’ll have a hard time finding a zero byte. You get the picture? Isn’t this exactly what you would expect as output from a compression algorhythm? Would you consider an output with lots of zeros a testimonial for efficient compression? Of course not. So this must be it. We cut out everything before this offset and dump the rest into a new file, say, “WG102_V5.0.0WW.z”. I removed the “elf” from the suffix, since there’s no ELF header any longer, maybe some ELF stuff behind the data itself, we’ll see. The “.z” suffix is to indicate that this file contains zlib-compressed data.
Now we need some tool to decompress the data. As the compression algorhythm was called “deflate”, decompressing is called “inflating”. Searching for “inflate” gives no ready-to-use UNIX utility for this purpose and a utility such as “gzip” is not what we want since it implements (and expects) a full-weight header, directory information etc., all of which is useful for your “.tar.gz” archives but not for something to be written to resp. read from flash memory. Here the zlib-Site again comes to rescue: download and compile zpipe.c (you must have zlib development files installed, eg. a package “zlib-devel” or similar). zpipe follows traditional UNIX conventions of small tools combinable by means of eg. pipes: it reads from stdin and writes to stdout. By default it deflates (compresses), give the command line argument “-d” and it inflates (decompresses). Well it tries to, let’s see what it thinks about the “.z” file we just created:
# ./zpipe -d <WG102_V5.0.0WW.z >WG102_V5.0.0WW.out
No error messages? Very good, then we caught indeed the right block of data as is confirmed by:
# ls -la WG102_V5.0.0WW.z WG102_V5.0.0WW.out -rw-r--r-- 1 pief users 3002556 17. Jul 16:41 WG102_V5.0.0WW.out -rw-r--r-- 1 pief users 1177155 16. Jul 23:29 WG102_V5.0.0WW.z
The file size is the important thing. Obviously, there was something to decompress.
By now, you should have gained an idea of what to do with the newly generated “.out” file. Running “file”, “strings” on it, stuff like that. “file” won’t recognize whatever we have here now, but “strings” finally shows us plain-text excerpts from the integrated filesystem. Basically, the mess we now have must be a combination of a kernel and a filesystem of some sort. I did not investigate further. Why? Here’s why:
A simple search for “.htm” gives a list of the HTML files that form the Web interface. Those files beginning with “h_” represent the help texts which are shown in the right frame when you access the Web interface. Among the other files listed there, the only file that was not recognizable form within the Web browser seems to be “/techsupp.htm” (at least, if I looked correctly). Also, this would be a nice intuitive filename for a webpage that opens Sesame, eh? Unfortunately here we are wrong. Just access the page in your Firefox and you’ll see that there is only a single WMM option that can be enabled or disabled. No telnet enabling, no Shell access. Too bad. You may continue looking, but the page we look for simply doesn’t exit in the default firmware. So no-go in this direction unless you somehow want to roll your own firmware. In which case you’d definitely need a tool to recalculate checksums and rewrite headers etc.
But before we even think about that let’s take care of the other route we were considering, the hidden superuser credentials. Well, searching the “strings” output gives actually a hit for “superuser”, but it’s just a single string, with not even a single bit of additional helpful information around. Probably it’s just reminiscent of some magic NVRAM variable or filesystem-internal switch or whatever else developers might come up with. Whatever it is, we don’t know how to access or even change it, right? Searching for “Gearguy” / “Geardog”? Nope. Of course not. Would have been TOO easy. Think Murphy and such.
At this point I was remembering to which models resp. firmwares the Telnet enable instructions were referring. The OpenWrt wiki page on the Telnet console contains such indications of models and versions that supposedly work with the magic packet. So I grabbed those binaries, eg. “WNR3500-V1.0.29_8.0.29NA.chk” and “wgt624_v4_2_6_1_0_1.chk”. Notice the file extensions. These firmwares use an entirely different layout! Fortunately, the .CHK header format has already been documented, so it is by now quite easy to extract the parts of those firmwares. There’s no ELF image with compressed data, instead the .chk file directly contains the kernel and the rootfs to be booted. The position of kernel and rootfs can be determined from the .chk header. In case of the WNR3500 firmware I thus got my “WNR3500.test” file of 4106798 bytes. Running “file” on it helps this time: it’s a compressed SquashFS filesystem, which can be extracted using unsquashfs from the SquashFS distribution. To keep the story short, here’s what I did with the extracted filesystem:
# grep -r Gearguy * grep: etc/resolv.conf: No such file or directory [...] Binary file lib/libnvram.so matches grep: var: No such file or directory
You can safely ignore the warnings, the interesting thing is the “lib/libnvram.so” line. This is from the WNR3500 firmware, remember. The line says that in this “vulnerable” firmware version the superuser credentials were stored in the shared library that is used to access the NVRAM settings partition on the flash chip. So it doesn’t live in some file on the file system, it’s somehow accessible using the “nvram” command, I guess. The existence of a program “/usr/sbin/telnetenabled”, which contains references to this “libnvram.so” seems to confirm this.
Now at this point this made me realize that this way won’t lead me anything further, too. Why? Quite simple: among the supposedly “vulnerable” firmwares I could only find “.chk” variants, right? And in those the superuser credentials were found in NVRAM-related stuff. Yeah, well, quite easy: those devices differ in their internal workings from my WG102. The WNR3500 uses a CFE bootloader and a Broadcom design, I guess, since that’s where I know the whole NVRAM stuff from (Linksys WRT54G, Asus WL-500g (Premium) etc., they were all Broadcom-based and used a NVRAM partition, remember?). My WG102 instead, well, yes, what does it use then, if it doesn’t use NVRAM?
Doh! It doesn’t even run Linux!
I suddenly got some sort of enlightenment: look at the “strings” output of WG102_V5.0.0WW.out once again. Try searching for “Linux”. No match? Right. Netgear doesn’t even use Linux on this device, it seems. Search for “vxworks”. Bingo! Eben better: search for “Wind River”. Nice ASCII art, eh?
So you might wonder what my point is. My point is: all the time I was just silently assuming that the device would run some sort of Embedded Linux, seeing that a lot of devices do that. I was so “fallen in love” with the price and the features of the device and got spoiled by my reverse engineering success, that I didn’t even consider that the device probably does not run Linux. But obviously it does not.
Your next question is “why does that matter?”. Quite simple: while db90h/Bitsum Technologies apparantly succeeded in converting VxWorks-based WRT54G5 devices into Linux routers, I dropped the idea of getting OpenWrt on the WG102 for now for the following reasons:
- The fact that the device runs VxWorks means that it is not so easy to get a Linux kernel to boot at all. Laurence A. Lee convinced the VxWorks boot loader to boot a “DeviceScape” Linux kernel, but for one thing I don’t know what that exactly is (DeviceScape is an embedded company, so far, so good). And for another it would make much more sense to replace the boot loader with RedBoot, U-Boot or whatever suits the Atheros platform.
- However, playing around with and flashing custom firmwares is one thing, but playing with the boot loader is something different: you can usually recover from bad firmware because the boot loader is still there and (hopefully) provides a recovery mode, so you just TFTP a working firmware over and your problem’s solved. If, however, something goes wrong while installing a new boot loader, there is really nothing left that allows for such easy recovery and the device is bricked, unless you begin with JTAG operations.
- Seriously, JTAG is way more heavy hardcore stuff.
- If something goes wrong, in the case of the WG102 I don’t even have access to the boot loader, whatever boot loader there shall be, since there is no Serial port. Well, there is a serial port on the PCB, but this guy Andrew over here says it’s not connected internally. He said he wanted to investigate that further, but, as most guys, eventually didn’t come around to do that any more.
- And even if there were a serial port and/or a different boot loader and I could get a Linux kernel running, I’d still be one of the first, encountering most probably a serious number of issues to get the device properly supported by OpenWrt. Sure, that would be cool, rewarding, a great feeling — if I got that far. Eventually I’d get stuck before and frustrated, especially since I’d seem to be the only one seriously following this endeavour.
And even taken those issues aside, I’d not do it for a simple reason: I don’t have infinite spare time and it’d be just for fun, a hobby project so to say, entirely unpaid. At some point, if you’re wise, you have to recognize when it simply doesn’t make sense to continue walking a cumbersome trail — at least for now. There are simple other things that also require attention. See, it’s just an access point and in my specific usage scenario running OpenWrt would be “nice” but is not a “must have”. After all, in the first place access points have to provide for a stable WLAN connection. If the original Netgear firmware is able to achieve this, the appeal of running an alternative firmware is not that big as otherwise, of course…
I hope that you didn’t come to the impression that I documented my “waste of time”. That’s not true, it wasn’t a waste, even if I didn’t get the “happy end”. Finding out where the limits are in this endeavour and the experiences and the fun encountered during reverse engineering are not to be neglected :) I hope that you could get an impression of the Sherlock Holmes activities conducted here and maybe got one or more useful hints, in the event that you’re hacking on an embedded device.