Reverse ASUS RT-AC5300

Downloading firmware

In this laboratory, we are going to use the model: RT-AC5300 firmware.

Extracting the firmware

At this moment, we need to unzip the zip file and use binwalk to analyze the .trx file. Using the option "-e" we can extract all the files from the .trx file.

unzip RT-AC5300_3.0.0.4_380_2356-g34e8f6b.trx.zip
binwalk RT-AC5300_3.0.0.4_380_2356-g34e8f6b.trx
binwalk -e RT-AC5300_3.0.0.4_380_2356-g34e8f6b.trx

We can see the firmware uses little-endian, and this is probably a sign that it is using a 3x ARM version or minor.

cd _RT-AC5300_3.0.0.4_380_2356-g34e8f6b.trx.extracted
ls -la
ls -la squashfs-root
ls squashfs-root/www 

After emulating it, seems like this:

User mode - Firmware emulation

Backing to the extracted folder from the .trx file (firmware itself), we have a file called 1C we need to analyze. We got some precious information, such as:

//CPIO file with the /dev and rootFS
139264        0x22000         ASCII cpio archive (SVR4 with no CRC), file name: "/dev", file name length: "0x00000005", file size: "0x00000000"
139380        0x22074         ASCII cpio archive (SVR4 with no CRC), file name: "/dev/console", file name length: "0x0000000D", file size: "0x00000000"
139504        0x220F0         ASCII cpio archive (SVR4 with no CRC), file name: "/root", file name length: "0x00000006", file size: "0x00000000"
139620        0x22164         ASCII cpio archive (SVR4 with no CRC), file name: "TRAILER!!!", file name length: "0x0000000B", file size: "0x00000000"

//Linux kernel version - it's important to full emulation
3163712       0x304640        Linux kernel version 2.6.36

//UNIX path - cool for searching related files/content on web
3735664       0x390070        Unix path: /home/yau/asuswrt/release/src-rt-7.14.114.x/src/linux/linux-2.6.36/arch/arm/include/asm/dma-mapping.h

We can confirm some details by using the Linux strings utility:

cat 1C | strings | grep "version"
Linux version 2.6.36.4brcmarm (yau@yau.chang) (gcc version 4.5.3 (Buildroot 2012.02) ) #1 SMP PREEMPT Fri Mar 4 20:12:49 CST 2016
2.6.36.4brcmarm SMP preempt mod_unload modversions ARMv7 
slabinfo - version: 2.1
squashfs: version 4.0 (2009/01/31) Phillip Lougher

cat 1C | strings | grep "/etc/" 
/etc/init

cat 1C | strings | grep "/root/"
/root/initrd

In sum, we can confirm the following details:

  • Kernel version: 2.6.36.4

  • gcc version 4.5.3

  • Buildroot 2012.02 (used to compile the rootFS)

  • ARMv7 processor - Dual Core 32-bit ARMv7 (Cortex-A9) @ 1.4GHz

In fact, we should check everytime the device configuration available on the vendor page to learn about it, and get the device datasheet in order to confirm some details.

Using qemu-arm approach

Per-process emulation is useful when only one binary needs to be emulated. To this that, next install full dependencies:

sudo apt-get install qemu
sudo apt-get install qemu-utils
sudo apt install qemu-kvm libvirt-clients libvirt-daemon-system bridge-utils
sudo adduser kali libvirt
sudo adduser kali kvm
sudo apt install qemu-system-arm
sudo apt install qemu-user

The user-mode emulation can be made by using the following command:

cd squashfs-root/usr/sbin
qemu-arm -L <prefix> ./httpd
or
qemu-arm-static ./httpd

The -L option is important for when the binary links to external dependencies such as uCLibc or encryption libraries. It tells the dynamic linker to look for dependencies with the provided prefix.

We can get the following error:

qemu-arm: Could not open '/lib/ld-uClibc.so.0': No such file or directory

By inspecting the /lib/ folder, we can see the following:

The file "ld-uClibc.so.0" is present, so we need to just add the current dir to the qemu-arm execution. Otherwise, if the UClibc.so file has another name, we could resolve that by doing a symbolic link, e.g.: squashfs-root/lib/ld-uClibc-0.9.29.so to squashfs-root/lib/ld-uClic.so.

So, the final qemu command is the following:

qemu-arm -L  . usr/sbin/httpd

/dev/nvram: Permission denied
/dev/nvram: Permission denied
/dev/nvram: Permission denied
/dev/nvram: Permission denied
/dev/nvram: Permission denied
/dev/nvram: Permission denied
/dev/nvram: Permission denied
/dev/nvram: Permission denied
/dev/nvram: Permission denied
Generating SSL certificate...
1068532656:error:02001002:system library:fopen:No such file or directory:bss_file.c:391:fopen('/etc/cert.pem','r')
1068532656:error:20074002:BIO routines:FILE_CTRL:system lib:bss_file.c:393:
1068532656:error:140DC002:SSL routines:SSL_CTX_use_certificate_chain_file:system lib:ssl_rsa.c:682:
/dev/nvram: Permission denied
/dev/nvram: Permission denied
/dev/nvram: Permission denied
/dev/nvram: Permission denied
/dev/nvram: Permission denied
Generating SSL certificate...
1068532656:error:02001002:system library:fopen:No such file or directory:bss_file.c:391:fopen('/etc/cert.pem','r')
1068532656:error:20074002:BIO routines:FILE_CTRL:system lib:bss_file.c:393:
1068532656:error:140DC002:SSL routines:SSL_CTX_use_certificate_chain_file:system lib:ssl_rsa.c:682:
/dev/nvram: Permission denied
/dev/nvram: Permission denied

Using qemu-user-static approach

Another way to emulate the process is to perform a cross-architectural chroot with QEMU.

kali@kali:~$ sudo apt-get install binfmt-support qemu qemu-user-static debootstrap

Next, we copy the qemu-arm-static binary to the rootFS directory of the firmware. We then chroot into the firmware root and obtain a working shell:

which qemu-arm-static                                                                                                                                                                                                              130 ⨯
/usr/bin/qemu-arm-static

cp /usr/bin/qemu-arm-static squashfs-root/
cd squashfs-root/
sudo chroot . ./qemu-arm-static /bin/sh

This is possible due to QEMU registering with the kernel during installation to handle binaries with certain magic bytes via the binfmt_misc mechanism.

This is the best method to test a single binary or feature in a short period of time without losing a lot of hours configuring and preparing the environment.

Next, we can start e.g., telnetd service inside our emulated environment ;)

---inside chroot FS----
/usr/sbin # telnetd 
called setup()
/usr/sbin # 

---Guest machine---
└─$ netstat -antp

Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp6       0      0 :::23                   :::*                    LISTEN      -     

Emulation NVRAM

In the previous user emulation scenarios, we found problems to emulate correctly the NVRAM. In this way, we can use hooks in a simple library to intercept calls to libnvram using LD_PRELOAD.

You can simply access this project on GitHub, compiling the C files with the target arch (ARM) or just download the arm version from releases.

wget https://github.com/firmadyne/libnvram/releases/download/v1.0/libnvram.so.armel
mkdir -p squashfs-root/firmadyne/
cp libnvram.so /firmadyne/libnvram.so
mkdir -p /firmadyne/libnvram/ mkdir -p /firmadyne/libnvram.override/

sudo chroot . ./qemu-arm-static /bin/sh
export LD_PRELOAD="/firmadyne/libnvram.so"
httpd

You can also compile the nvram file using a cross-compiler for ARM architectures.

wget https://uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-armv5l.tar.bz2 
tar xjf cross-compiler-armv5l.tar.bz2 

git clone https://github.com/firmadyne/libnvram.git
cd libnvram
../cross-compiler-armv5l/bin/armv5l-gcc nvram.c -o nvram -shared  -ldl
cp nvram ../../_RT-AC5300_3.0.0.4_380_2356-g34e8f6b.trx.extracted/squashfs-root/firmadyne
mv nvram libnvram.so

sudo chroot . ./qemu-arm-static /bin/sh
export LD_PRELOAD="/firmadyne/libnvram.so"
httpd
sudo chroot . ./qemu-arm-static /bin/sh
export LD_PRELOAD="/firmadyne/libnvram.so"
httpd

Hook functions

If we need to hook any function, LD_PRELOAD is our best friend.

For example, the next hook will emulate the native open file call from gclib.

#define _GNU_SOURCE

#include <stdio.h>
#include <dlfcn.h>

typedef FILE *(*fopen_t)(const char *pathname, const char *mode);
fopen_t real_fopen;

FILE *fopen(const char *pathname, const char *mode) {
  fprintf(stderr, "called fopen(%s, %s)\n", pathname, mode);
  return real_fopen(pathname, mode);
}

__attribute__((constructor)) static void setup(void) {
  real_fopen = dlsym(RTLD_NEXT, "fopen"); 
  fprintf(stderr, "called setup()\n");
}

We can compile it onto ARM using a cross compiler:

wget https://uclibc.org/downloads/binaries/0.9.30.1/cross-compiler-armv5l.tar.bz2 
tar xjf cross-compiler-armv5l.tar.bz2 
cross-compiler-armv5l/bin/armv5l-gcc hook.c -o hooks.so -shared  -ldl 

sudo chroot . ./qemu-arm-static /bin/sh
export LD_PRELOAD="/firmadyne/libnvram.so /hooks.so"
httpd

Even though we don't have the /etc/rc.d or /etc/init.d to run the appropriate RC script to kick off the userland services, we were able to execute single binaries, which can give us some input about the device operation, or even finding vulnerabilities at this level.

Firmware full emulation

To do this, we can use Buildroot 2012.02 to create your rootFS and compile kernel version 2.6.36.

To compile it, you can use a normal Linux distribution (Kali Linux), or try to use an old version of ubuntu 16.4.

To start using buildroot, you need to compile the manual and learn about it.

For this ASUS router, the best configs are the following:

make menuconfig

board config name:

To choose the right kernel defconfig:

or accessing: output/build/linux-2.6.36.4/arch/arm/configs

acs5k_defconfig           mv78xx0_defconfig
acs5k_tiny_defconfig      mx1_defconfig
afeb9260_defconfig        mx21_defconfig
am200epdkit_defconfig     mx27_defconfig
ams_delta_defconfig       mx31pdk_defconfig
ap4evb_defconfig          mx3_defconfig
assabet_defconfig         mx51_defconfig
at572d940hfek_defconfig   n770_defconfig
at91cap9adk_defconfig     n8x0_defconfig
at91rm9200dk_defconfig    neocore926_defconfig
at91rm9200ek_defconfig    neponset_defconfig
at91sam9260ek_defconfig   netwinder_defconfig
at91sam9261ek_defconfig   netx_defconfig
at91sam9263ek_defconfig   nhk8815_defconfig
at91sam9g20ek_defconfig   ns9xxx_defconfig
at91sam9rlek_defconfig    nuc910_defconfig
ateb9200_defconfig        nuc950_defconfig
badge4_defconfig          nuc960_defconfig
bcmring_defconfig         omap3_defconfig
cam60_defconfig           omap_4430sdp_defconfig
carmeva_defconfig         omap_generic_1510_defconfig
cerfcube_defconfig        omap_generic_1610_defconfig
cm_x2xx_defconfig         omap_generic_1710_defconfig
cm_x300_defconfig         omap_generic_2420_defconfig
cns3420vb_defconfig       omap_h2_1610_defconfig
colibri_pxa270_defconfig  omap_innovator_1510_defconfig
colibri_pxa300_defconfig  omap_innovator_1610_defconfig
collie_defconfig          omap_osk_5912_defconfig
corgi_defconfig           omap_perseus2_730_defconfig
cpu9260_defconfig         onearm_defconfig
cpu9g20_defconfig         orion5x_defconfig
cpuat91_defconfig         palmte_defconfig
csb337_defconfig          palmtt_defconfig
csb637_defconfig          palmz71_defconfig
da8xx_omapl_defconfig     palmz72_defconfig
davinci_all_defconfig     pcm027_defconfig
dove_defconfig            picotux200_defconfig
ebsa110_defconfig         pleb_defconfig
ecbat91_defconfig         pnx4008_defconfig
edb7211_defconfig         pxa168_defconfig
em_x270_defconfig         pxa255-idp_defconfig
ep93xx_defconfig          pxa3xx_defconfig
eseries_pxa_defconfig     pxa910_defconfig
ezx_defconfig             qil-a9260_defconfig
footbridge_defconfig      raumfeld_defconfig
fortunet_defconfig        realview_defconfig
g3evm_defconfig           realview-smp_defconfig
g4evm_defconfig           rpc_defconfig
h3600_defconfig           s3c2410_defconfig
h5000_defconfig           s3c6400_defconfig
h7201_defconfig           s5p6440_defconfig
h7202_defconfig           s5p6442_defconfig
hackkit_defconfig         s5pc100_defconfig
htcherald_defconfig       s5pv210_defconfig
imote2_defconfig          sam9_l9260_defconfig
integrator_defconfig      shannon_defconfig
iop13xx_defconfig         shark_defconfig
iop32x_defconfig          simpad_defconfig
iop33x_defconfig          spear300_defconfig
ixp2000_defconfig         spear310_defconfig
ixp23xx_defconfig         spear320_defconfig
ixp4xx_defconfig          spear600_defconfig
jornada720_defconfig      spitz_defconfig
kafa_defconfig            stamp9g20_defconfig
kb9202_defconfig          stmp378x_defconfig
kirkwood_defconfig        stmp37xx_defconfig
ks8695_defconfig          sx1_defconfig
lart_defconfig            tct_hammer_defconfig
loki_defconfig            trizeps4_defconfig
lpd270_defconfig          u300_defconfig
lpd7a400_defconfig        u8500_defconfig
lpd7a404_defconfig        usb-a9260_defconfig
lubbock_defconfig         usb-a9263_defconfig
magician_defconfig        versatile_defconfig
mainstone_defconfig       viper_defconfig
mini2440_defconfig        xcep_defconfig
mmp2_defconfig            yl9200_defconfig
msm_defconfig             zeus_defconfig

To start the compilation process: make all or using a specific GCC version: make HOSTCC=gcc-4

Fixing kernel crashes or dependencies

"gets" undeclared here

In file included from clean-temp.h:22:0,
                 from clean-temp.c:23:
./stdio.h:477:1: error: 'gets' undeclared here (not in a function)
 _GL_WARN_ON_USE (gets, "gets is a security hole - use fgets instead");
 ^

Fix

find . -name stdio.in.h
./output/build/host-m4-1.4.16/lib/stdio.in.h

sed -i -e '/gets is a/d' lib/stdio.in.h

textinfo incompatibilities with GCC documentation

This is frustrating me too, I cannot install texinfo, because texinfo is compatible with the tex documentation for this version of the compiler I have to use, which is what's breaking the documentation in the first place.

/home/ubuntu/buildroot-2012.02/output/toolchain/gcc-4.5.3/gcc/doc/service.texi:6: warning: node prev `Service' in menu `Trouble' and in sectioning `Bugs' differ
/home/ubuntu/buildroot-2012.02/output/toolchain/gcc-4.5.3/gcc/doc/service.texi:6: warning: node up `Service' in menu `Bugs' and in sectioning `Top' differ
Makefile:4052: recipe for target 'doc/gcc.info' failed
make[2]: *** [doc/gcc.info] Error 1
make[2]: Leaving directory '/home/ubuntu/buildroot-2012.02/output/toolchain/gcc-4.5.3-initial/gcc'
Makefile:5027: recipe for target 'all-gcc' failed
make[1]: *** [all-gcc] Error 2
make[1]: Leaving directory '/home/ubuntu/buildroot-2012.02/output/toolchain/gcc-4.5.3-initial'

Instead of editing files, when I run make I pass the argument MAKEINFO.

One minor point about this. I think it should be pointed out what this does. It basically overrides the MAKEINFO variable predefined by GNU make, which normally points to the program to call in order to get makeinfo invoked. So by setting it to true you are effectively replacing calls to makeinfo by calls to /bin/true (can be seen with make -npf /dev/null |grep ^MAKEINFO).

Fix

make MAKEINFO=true

Can't use 'defined(@array)'

Can't use 'defined(@array)' (Maybe you should just omit the defined()?) at kernel/timeconst.pl line 373.
/home/ubuntu/buildroot-2012.02/output/build/linux-3.2.6/kernel/Makefile:141: recipe for target 'kernel/timeconst.h' failed
make[2]: *** [kernel/timeconst.h] Error 255
Makefile:945: recipe for target 'kernel' failed
make[1]: *** [kernel] Error 2
make[1]: *** Waiting for unfinished jobs....
make[1]: Leaving directory '/home/ubuntu/buildroot-2012.02/output/build/linux-3.2.6'
package/Makefile.package.in:393: recipe for target '/home/ubuntu/buildroot-2012.02/output/build/linux-3.2.6/.stamp_built' failed
make: *** [/home/ubuntu/buildroot-2012.02/output/build/linux-3.2.6/.stamp_built] Error 2

Fix

Can’t use ‘defined(@array)’ (Maybe you should just omit the defined()?) at kernel/timeconst.pl line 373 has prompted where is the delicate error message, open timeconst.pl to see (under the kerner directory).

cd output/build/linux-3.2.6/kernel/
vim timeconst.pl 

/!defined(@val))

change this (line 373):

 372        @val = @{$canned_values{$hz}};                                        
 373         if (!defined(@val)) {                                                          
 374                 @val = compute_values($hz);                                   
 375         }                                                                     
 376         output($hz, @val);


by:

 372        @val = @{$canned_values{$hz}};                                        
 373         if (!@val) {                                                          
 374                 @val = compute_values($hz);                                   
 375         }                                                                     
 376         output($hz, @val);
make

Compiling everything is a long journey, you can also test more recent kernel versions and so on to avoid some errors when you are building your environment for emulation.

At the end, you will get something like this on your output/images folder:

-rw-r--r-- 1 kali kali 62914560 Jul 27 18:30 rootfs.ext2
-rw-r--r-- 1 kali kali  1437696 Jul 27 18:30 rootfs.squashfs
-rw-r--r-- 1 kali kali  4085760 Jul 27 18:30 rootfs.tar
-rwxr-xr-x 1 kali kali      446 Jul 27 18:30 start-qemu.sh
-rwxr-xr-x 1 kali kali     8880 Jul 27 18:27 versatile-pb.dtb
-rw-r--r-- 1 kali kali  2871872 Jul 27 18:27 zImage

Now, it's time to start your qemu emulator:

qemu-system-arm -M versatilepb -kernel zImage -dtb versatile-pb.dtb -drive file=rootfs.ext2,if=scsi,format=raw -append "rootwait root=/dev/sda console=ttyAMA0,115200"

Other options instead of using qemu are docker containers.

docker pull kelvinlawson/arm-cortex
sudo docker run --privileged=true --name=firmware -p 2221:22 -p 8888:80 -p 4443:443 -p 2223:23 -it 8d319a850335
shell$: cd /sbin
shell$: ./preninit

Resources

Last updated