Using simple hardware and software, this post will show you how to extract and analyze the firmware of a GL.iNet GL-B1300 router. Identifying UART pins and connecting a JTAGulator will allow us to transmit and receive through the serial connection, access the U-Boot bootloader, and get a root shell on the main filesystem, allowing us to extract the firmware from memory.

What you’ll need

Identifying UART pins on the board

Accessing the main board on the router was fairly easy. Pulling off the rubber feet from the bottom of the router case exposes four small phillips-head screws. Once the screws were out, I had direct access to one side of the board as shown in this image:

Router board access

Carefully pull the wifi antennas from the sides, unscrew the two screws that are securing the board to the other side of the casing, remove the shielding, and pull the board out of the case.

UART (universal asynchronous receiver-transmitter) is what we’ll use to transmit and receive data to and from the router. Since UART on its own (one without a data bus) only has RX, TX, and some sort of power (usually labeled GRND or not labeled at all), it’s typically pretty easy to find on a device like this - other devices might be more difficult.

UART Diagram

On the edge of the board three conveniently labeled pins are sticking up: GND, RX, and TX.

Finding UART

Connecting to UART

Any piece of hardware that can be used to connect to UART is going to work slightly different, though it’s a little pricey, the JTAGulator is my favorite choice for debugging on-chip interfaces. However, any device that supports TTL to USB (like the Attify Badge, BUS Pirate, etc.) should work just fine.

NOTE: If you’re using a JTAGulator, make sure that you install the latest firmware before moving forward

Plug the JTAGulator USB into your host machine and open a terminal to find what tty interface the JTAGulator is running on:

ls /dev/tty*usb*
/dev/tty.usbserial-AB0P6L9O

Then connect to it using:

screen /dev/tty.usbserial-AB0P6L9O 115200
  1. Press the reset button on the JTAGulator to see it boot up in the terminal. If you’re not seeing anything on the screen, try pressing CTRL+X.

Info → If at any time during this process you get frozen inside screen, open another terminal and type killall screen, then enter the screen connect command again, press CTRL+X.

The JTAGulator has 3 GRND’s and 24-channels - we’ll take 3 jumper cables and attach them to GND, CH0, and CH1 as shown here:

JTAG Channels

Then take the other end of the jumper cables and add them to the marked channels on the router. The full assembly should look something like this:

Full assembly

Because the UART pins and output voltage (3.3v displayed right under the UART pins) are clearly labeled, we can go straight into UART passthrough mode on JTAGulator which will let us interact with the router. This is what it looks like:

Info → If the UART pins are not clearly labeled, the JTAGulator has a mode called Identify UART pinout which cycles through various configurations and baud to find the most likely setup for the device.

Poking around the Linux file system

After letting the router completely boot up, we are given a root shell. Since this router runs on OpenWRT, it’s not going to be locked down or encrypted, though some routers (especially enterprise-ish ones) might have some security mechanisms in place that make it harder to get a root shell.

As shown above, some good places to look to find what capabilities we have are:

  • ls /bin
  • ls /sbin
  • ls /usr/bin
  • ls /etc

Some other useful commands might be searching the filesystem for any .pem files:

find . -type f -name "*.pem*"
./etc/lighttpd/server.pem
./etc/openvpn/cert/dh1024.pem
head ./etc/lighttpd/server.pem
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDSnEJL9ymbUrPj
iOwFAKIiJcobxQRPbr9qGCWJNP1wpep/dz6GOWWBAmSL/E2Dy0MNoyVfDD7sl+F8
jbK+frARzPwPavgDbA1LVwInV2sVTSd88JDrmI3gby2hnpQRRh3zVEmJUvTop60q
I977iid9yckoNFSdikI0adF/CEdrVQ9qZILaJFJ0XCvfF1N2YsxT/QHLZ6oM9QuV
EflIKQQb8YTr6XSwvX8uwsUNBu44+5PIoYvBlZYL38pYRXTY/d8vXdUNE9uG1Ewq
4IQxMiEQ7mPSpliaig3LVhMsBB74fjx73a4QCy1sQ0gA2CCl7HKIDzRfXljWUude
NL2AqOJbAgMBAAECggEAPYwMk8aXEh0JFOVek9erie8hMRxSNiRXK9oCniYuKk1S
Sg2+59q+HwVj/MSuomU0IzgaI7ygZuO7sXp3UdQUAB+3SYopEFbzS6ERsA2L7Z2u
fISQ1UivrXbQDvsYqjOjbQiktMzZZWQa5sW01C17fPcLIgSo9aEB1+9UmZsBxAt/

Or maybe instead of looking for filenames, you want to search for text inside the files:

grep -rnw . -e 'PRIVATE'
Binary file ./usr/sbin/hostapd matches
Binary file ./usr/sbin/tor matches
Binary file ./usr/sbin/tcpdump matches
Binary file ./usr/lib/libmbedcrypto.so.2.12.0 matches
Binary file ./usr/lib/libcrypto.so.1.1 matches
Binary file ./usr/lib/gl/libovpnapi.so matches
./usr/share/misc/magic:11718:0  string  SSH\ PRIVATE\ KEY  OpenSSH RSA1 private key,
./usr/share/misc/magic:11720:0  string  -----BEGIN\ OPENSSH\ PRIVATE\ KEY-----     OpenSSH private key
./usr/share/misc/magic:11729:0  string  -----BEGIN\ RSA\ PRIVATE   PEM RSA private key
./usr/share/misc/magic:11730:0  string  -----BEGIN\ DSA\ PRIVATE   PEM DSA private key
./usr/share/misc/magic:11731:0  string  -----BEGIN\ EC\ PRIVATE    PEM EC private key
./etc/lighttpd/server.pem:1:-----BEGIN PRIVATE KEY-----
./etc/lighttpd/server.pem:28:-----END PRIVATE KEY-----
Binary file ./etc/tertf/mac_vendor.db matches
Binary file ./lib/modules/4.4.60/umac.ko matches

Warning → running search commands like the above grep command is going to be extremely slow on some devices, especially if you specify the filesystem / directory. You may want to wait until we extract the firmware and explore the filesystem on your native machine.

Copying the firmware to host system

I failed many times when attempting to copy the firmware to my host system, these failures are outlined below in the Other methods of firmware extraction and why they didn’t work for me section. So I started looking around the board for the memory chip.

Winbond flash chip

After looking up the datasheet for this winbond 255Q256JVFQ I realized that this chip is a “3v 256MB Serial Flash Memory…“. I researched how linux works with flash memory and found this article on coresecurity.com which describes getting a list of all MTD blocks (memory technology device, used for interacting with flash memory) that a system has:

cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00040000 00010000 "0:SBL1"
mtd1: 00020000 00010000 "0:MIBIB"
mtd2: 00060000 00010000 "0:QSEE"
mtd3: 00010000 00010000 "0:CDT"
mtd4: 00010000 00010000 "0:DDRPARAMS"
mtd5: 00010000 00010000 "0:APPSBLENV"
mtd6: 00080000 00010000 "0:APPSBL"
mtd7: 00010000 00010000 "0:ART"
mtd8: 00400000 00010000 "0:HLOS"
mtd9: 01a80000 00010000 "rootfs"
mtd10: 00950000 00010000 "rootfs_data

The largest block is rootfs at /dev/mtd9, this is most likely going to be the filesystem. We can extract just that one block, or we can extract all of the blocks listed to perform further analysis.

If you have a router that has a USB port that mounts to /mnt, the easiest thing to do is to run something like: cp -av /dev/mtd9 /mnt/sda/output.bin for just the filesystem or cp -r /dev/mtd* /mnt/sda/output.bin for all blocks to copy to USB.

Depending on how large the blocks are and how much memory the router has, this could take a few minutes.

If your router doesn’t have a USB port, but you’re able to get on the same network as the router, you can open a netcat listener on your host machine: nc -l 1337 | dd of=./output.bin, then type the following command into the router UART terminal: dd if=/dev/mtd9 | nc <internal host IP> 1337 - this will pipe the response of the mtd9 file to our host computer listening on port 1337.

On the router serial communication output you should see:

dd if=/dev/mtd9 | nc 192.168.8.212 1337
54272+0 records in
54272+0 records out
27787264 bytes (26.5MB) copied, 20.743084 seconds, 1.3MB/s

And on the receiving machine you should see:

nc -l 1337 | dd of=./output.bin
45918+16182 records in
54272+0 records out
27787264 bytes transferred in 23.180080 secs (1198756 bytes/sec)

If you wanted all of the blocks, you can copy one block at a time through nc, then on the host, run something like cp -r ./mtd*.bin ./output.bin to combine them into one file.

Using binwalk to extract the firmware contents

Now that we have an output.bin file that has the raw firmware in it, we can use binwalk to analyze the firmware contents.

Info → There are some important dependencies that binwalk requires to be able to extract different filesystems - follow their Quick Start Guide for installation steps.

When using binwalk to extract firmware contents, especially for non-open-source routers, there’s a chance that the firmware might be encrypted or compressed (compression can be taken care of by binwalk, encryption can’t). We can check this by running binwalk -E output.bin, giving us an output that looks like this:

And an image that looks like this:

Firmware entropy output

An encrypted or compressed firmware would have most, if not all, entropy around the 1.0 mark. This firmware has a lot of rising and falling entropy lines, meaning it’s most likely not encrypted or compressed.

Now we can run a binwalk extract by typing binwalk -e output.bin and see that we have a directory with the extracted contents, and the router’s linux filesystem in a directory called squashfs-root:

Info → If you extracted all of the mtd blocks like I did, but don't want to use binwalk on the whole file, you can use the following command to carve out the part you want extract: dd if=output.bin of=test.bin bs=1 skip=15532032 count=17965988 (this can take a while). 15532032 is the starting point of the extraction, and 17965988 is the size of what we want to extract. Both of these values are retrieved through the binwalk output shown above.

Other methods of firmware extraction (and why they didn’t work for me)

U-Boot mode’s md command

While the router is booting up, wait for the text Hit "gl" key to stop, type gl to enter the U-Boot bootloader shell. Typing h gives a list of commands we can run.

Info → The list of available commands might differ from router to router, even though they both might use U-Boot.

Typing smeminfo gives us a list of all allocated partitions and their memory information:

smeminfo
flash_type:             0x6
flash_index:            0x0
flash_chip_select:      0x0
flash_block_size:       0x10000
flash_density:          0x2000000
partition table offset  0x0
No.: Name             Attributes            Start             Size
  0: 0:SBL1           0x0000ffff              0x0          0x40000
  1: 0:MIBIB          0x002040ff          0x40000          0x20000
  2: 0:QSEE           0x0000ffff          0x60000          0x60000
  3: 0:CDT            0x0000ffff          0xc0000          0x10000
  4: 0:DDRPARAMS      0x0000ffff          0xd0000          0x10000
  5: 0:APPSBLENV      0x0000ffff          0xe0000          0x10000
  6: 0:APPSBL         0x0000ffff          0xf0000          0x80000
  7: 0:ART            0x0000ffff         0x170000          0x10000
  8: 0:HLOS           0x0000ffff         0x180000         0x400000
  9: rootfs           0x0000ffff         0x580000        0x1a80000

To extract the firmware from memory, you may be lucky enough to run the md command to view the memory straight from U-Boot mode. We can start reading some bytes (10 bytes, then 40 bytes, then 50 bytes), starting at address 0x580000:

md 0x580000 10
00580000: ab957e0d 001f6b00 00000001 00000000    .~...k..........
00580010: 00000000 80000000 00000000 00000000    ................
00580020: 00000000 00000000 00000000 00000000    ................
00580030: 00000000 00000000 00000000 00000000    ................
md 0x580000 40
00580000: ab957e0d 001f6b00 00000001 00000000    .~...k..........
00580010: 00000000 80000000 00000000 00000000    ................
00580020: 00000000 00000000 00000000 00000000    ................
00580030: 00000000 00000000 00000000 00000000    ................
00580040: 00000000 00000000 00000000 00000000    ................
00580050: 00000000 00000000 00000000 00000000    ................
00580060: 00000000 00000000 00000000 00000000    ................
00580070: 00000000 00000000 00000000 00000000    ................
00580080: 7a931a07 001f6b00 00000001 00000000    ...z.k..........
00580090: 00000000 00000000 00000000 00000000    ................
005800a0: 00000000 00000000 00000000 00000000    ................
005800b0: 00000000 00000000 00000000 00000000    ................
005800c0: 00000000 00000000 00000000 00000000    ................
005800d0: 00000000 00000000 00000000 00000000    ................
005800e0: 00000000 00000000 00000000 00000000    ................
005800f0: 00000000 00000000 00000000 00000000    ................
md 0x580000 50
00580000: ab957e0d 001f6b00 00000001 00000000    .~...k..........
00580010: 00000000 80000000 00000000 00000000    ................
00580020: 00000000 00000000 00000000 00000000    ................
00580030: 00000000 00000000 00000000 00000000    ................
00580040: 00000000 00000000 00000000 00000000    ................
00580050: 00000000 00000000 00000000 00000000    ................
00580060: 00000000 00000000 00000000 00000000    ................
00580070: 00000000 00000000 00000000 00000000    ................
00580080: 7a931a07 001f6b00 00000001 00000000    ...z.k..........
00580090: 00000000 00000000 00000000 00000000    ................
005800a0: 00000000 00000000 00000000 00000000    ................
005800b0: 00000000 00000000 00000000 00000000    ................
005800c0: 00000000 00000000 00000000 00000000    ................
005800d0: 00000000 00000000 00000000 00000000    ................
005800e0: 00000000 00000000 00000000 00000000    ................
005800f0: 00000000 00000000 00000000 00000000    ................
00580100:data abort
pc : [<87343524>]          lr : [<87343508>]
sp : 8706fd38  ip : 00000000     fp : 00580100
r10: 00580100  r9 : 00000000     r8 : 8706ff4c
r7 : 8706fd54  r6 : 00000010     r5 : 00000004  r4 : 00000004
r3 : 00000204  r2 : 00000001     r1 : 00000000  r0 : 00000009
Flags: nZCv  IRQs off  FIQs off  Mode SVC_32
Resetting CPU ...

resetting ...

For some reason pulling blocks of memory like this after a certain point always triggers a CPU reset and causes the system to crash and reboot.

Writing the contents of /dev/mtd9 to stdout and writing stdout to a file using picocom

Another possible method of extracting firmware is to pipe the firmware out to stdout via cat and save the output to a file on our host. To do this, we can use picocom.

Kill the JTAGulator screen session by typing killall screen and connect to the JTAGulator using the following:

picocom --b 115200 --logfile output.bin /dev/tty.usbserial-AB0P6L9O

You’ll see the same JTAGulator prompt that we saw before, so you’ll go through and set everything up for connecting to UART like before. Then type BUT DON’T PRESS ENTER cat /dev/mtd9

Keeping the picocom window open, and the command typed in, open a finder window to the output.bin file being written to on your computer, you should see all the output from the serial connection, setting up JTAGUlator, etc. - clear out all of the contents so it becomes a blank file and save it. Go back to your picocom terminal and press ENTER. You’ll see the raw data from mtd9 streaming across the terminal, and you should see the output.bin file grow in size every second as more data is added to it. This is a slow process, taking ~15 minutes to copy ~30mb. This is what the whole process looks like:

Once the stdout pipe is done, you should have a file that contains the firmware. The reason this method didn’t work for me is this router is quite chatty on stdout, it constantly writes status updates out to the terminal, which ends up corrupting the datastream coming from cat.