Emulating ARM Router Firmware
There are various reasons you might want to emulate firmware. If you want to do security research on router firmware, for example, emulation can help you debug certain services and look for vulnerabilities. You could also debug IoT firmware without emulating it. In that case you would gain root on the device via hardware hacking and drop gdbserver on the device and debug services remotely. But what if you don’t have the device? You download the firmware and emulate it.
In this post, I will show you how to emulate Arm router firmware. First, you need an Arm environment. Don’t have a spare Arm processor? No problem, QEMU is your friend!
For those of you who want to save time and get straight into it, I have prepared a new Lab VM that contains:
- QEMU emulated Armv7 environment ready to start
- Two different Tenda router firmware versions (AC6 and AC15)
- All scripts necessary to start the firmware emulation
- Two small Arm exploitation challenges to learn the basics of bypassing XN (more details in the next blog post)
Let’s say you want to emulate the Tenda AC6. Many vendors let you download firmware versions from their website. Once you chose and downloaded your firmware, you need to unpack and extract the binary with binwalk.
user@Azeria-Lab-VM $ wget https://down.tendacn.com/uploadfile/AC6/US_AC6V1.0BR_V15.03.05.16_multi_TD01.rar user@Azeria-Lab-VM $ unrar e US_AC6V1.0BR_V15.03.05.16_multi_TD01.rar user@Azeria-Lab-VM $ binwalk -e US_AC6V1.0BR_V15.03.05.16_multi_TD01.bin DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 64 0x40 TRX firmware header, little endian, image size: 6778880 bytes, CRC32: 0x80AD82D6, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x1A488C, rootfs offset: 0x0 92 0x5C LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4177792 bytes 1722572 0x1A48CC Squashfs filesystem, little endian, version 4.0, compression:xz, size: 5052332 bytes, 848 inodes, blocksize: 131072 bytes, created: 2017-04-19 16:18:08 user@Azeria-Lab-VM $ cd _US_AC6V1.0BR_V15.03.05.16_multi_TD01.bin.extracted
What you want from this is the Squashfs filesystem.
user@Azeria-Lab-VM $ ls _US_AC6V1.0BR_V15.03.05.16_multi_TD01.bin.extracted/ | grep squashfs-root squashfs-root
Inside the Azeria Labs VM, boot up the ARMv7 environment by clicking on the blue ARM icon in the sidebar. Wait until you see the following screen.
This is the terminal you can start your firmware emulation in. The emulation will make constant noise by spitting errors saying it can’t access certain peripherals.
Let’s start with the firmware emulation. From the folder binwalk extracted, run the following command to transfer the squashfs-root to the Arm environment.
user@Azeria-Lab-VM $ rsync -av squashfs-root user@192.168.0.1:/home/user/Tenda-AC6
Minimize this terminal and open Terminator (red terminal icon in the side bar). Terminator is neat because it let’s you split screens more easily. You can SSH into the Arm environment with the shortcut “ssh arm”.
You can install Terminator with:
user@Azeria-Lab-VM $ sudo apt-get install terminator
In your Arm environment, you should now have a folder with the squashfs-root of the firmware you extracted. In this folder, you create a script that starts the emulation. Normally, and in most cases, this process is simple and the script looks like this:
# disable ASLR sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space" # Switch to legacy memory layout. Kernel will use the legacy (2.4) layout for all processes sudo sh -c "echo 1 > /proc/sys/vm/legacy_va_layout" # Mount special folders to the existing Debian ARM environment to provide the emulated environment awareness of the Linux surroundings sudo mount --bind /proc /home/user/Router/squashfs-root/proc sudo mount --bind /sys /home/user/Router/squashfs-root/sys sudo mount --bind /dev /home/user/Router/squashfs-root/dev # Trigger the startup of the firmware sudo chroot /home/user/Router/squashfs-root /etc/init.d/rcS
Router firmware emulation is not magic, it’s as simple as the above script. However, turns out there are always exceptions to the rule. The first time I tried emulating the Tenda AC6 firmware, this script didn’t work and the process kept crashing without booting up in the first place. I solved this problem in a rather messy way, by reverse engineering the firmware and tracing back which parameters it’s complaining about. I wrote a program (hooks.c on Github)to simply give it what it wants and voila, it worked. I thought this was a very custom way of making this specific version of the firmware work. To my surprise, emulating a different Tenda firmware (AC15) resulted in the same problem, and guess what? The hooks I coded for the AC6 firmware still worked. I haven’t tested it on other Tenda firmware versions, but if seeing it work on the two I randomly selected made me want to release it for people who might run into the same problem. And who know, maybe it works for more Tenda versions? You can find the code on the Azeria Labs GitHub and on the Lab VM.
But let’s quickly go through the process of cross-compiling it for your emulation:
user@Azeria-Lab-VM $ wget https://uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-armv5l.tar.bz2 user@Azeria-Lab-VM $ tar xjf cross-compiler-armv5l.tar.bz2 user@Azeria-Lab-VM $ wget https://raw.githubusercontent.com/azeria-labs/Arm-firmware-emulation/master/hooks.c user@Azeria-Lab-VM $ cross-compiler-armv5l/bin/armv5l-gcc hooks.c -o hooks.so -shared user@Azeria-Lab-VM $ scp hooks.so user@arm:/home/user/Tenda-AC6/squashfs-root/hooks.so
Inside the Arm environment, cd to the folder you transferred the squashfs-root to and download the emulate.sh script.
user@azeria-labs-arm:~/Tenda-AC6$ wget https://raw.githubusercontent.com/azeria-labs/Arm-firmware-emulation/master/emulate.sh
The emulation script looks similar to the one I mentioned earlier, with the difference that it runs with the hooks.so file.
# Script to emulate Arm-based router firmware. This example is based on a QEMU emulated Debian environment. # You can download the VM with the QEMU Armv7 emulation (link in README) # br0 interface existence is necessary for successful emulation sudo ip link add br0 type dummy # Disable ASLR for easier testing. Can be re-enabled with the same command by replacing 0 with 1 or 2. sudo sh -c "echo 0 > /proc/sys/kernel/randomize_va_space" # Switch to legacy memory layout. Kernel will use the legacy (2.4) layout for all processes to mimic an embedded environment which usually has old kernels sudo sh -c "echo 1 > /proc/sys/vm/legacy_va_layout" # Mount special linux folders to the existing Debian ARM environment to provide the emulated environment with the Linux context. # Replace /home/user/Tenda with the path to your extracted squashfs-root. sudo mount --bind /proc /home/user/Tenda/squashfs-root/proc sudo mount --bind /sys /home/user/Tenda/squashfs-root/sys sudo mount --bind /dev /home/user/Tenda/squashfs-root/dev # Set up an interactive shell in an encapsulated squashfs-root filesystem and trigger the startup of the firmware. # Replace /home/user/Tenda with the path to your extracted squashfs-root. sudo chroot /home/user/Tenda/squashfs-root /bin/sh -c "LD_PRELOAD=/hooks.so /etc_ro/init.d/rcS"
Switch to the violet terminal (the one that popped up when you started the Arm environment) and run the emulation script from there. The reason is that it will generate a lot of noise, spitting out errors about not being able to reach certain peripherals and so on.
user@azeria-labs-arm:~/Tenda$ sudo ./emulate.sh
Now, let’s check if the firmware emulation has been successful. The first thing you want to check is if new processes are running.
user@azeria-labs-arm:~$ sudo netstat -tlpn sudo: unable to resolve host Tenda: Resource temporarily unavailable Active Internet connections (only servers) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 236/sshd tcp 0 0 0.0.0.0:5500 0.0.0.0:* LISTEN 809/miniupnpd tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN 450/ucloud_v2 tcp 0 0 172.18.166.182:80 0.0.0.0:* LISTEN 585/dhttpd tcp 0 0 192.168.0.1:80 0.0.0.0:* LISTEN 448/httpd tcp 0 0 127.0.0.1:10002 0.0.0.0:* LISTEN 450/ucloud_v2 tcp 0 0 127.0.0.1:10003 0.0.0.0:* LISTEN 450/ucloud_v2 tcp 0 0 0.0.0.0:10004 0.0.0.0:* LISTEN 451/business_proc tcp6 0 0 :::22 :::* LISTEN 236/sshd
Looks good! Now let’s check out the router interface by browsing to 192.168.0.1.
Perfect!
Now you can start playing around with the interface, attaching to processes, and debug them. Just a quick hint on how to attach to an httpd process with GDB to get you started:
user@azeria-labs-arm:~$ ps aux | grep httpd root 448 0.3 0.2 3692 2136 ? Ss 02:00 0:03 httpd root 585 0.1 0.0 2628 716 ? S 02:00 0:01 dhttpd user 9073 0.0 0.0 6736 532 pts/0 S+ 02:16 0:00 grep httpd user@azeria-labs-arm:~$ sudo gdb -q -p 448
Happy hacking!




