# Emulating the Tenda AC15 Wi-Fi Router with EMUX by Saumil Shah [@therealsaumil][saumil] [saumil]: https://twitter.com/therealsaumil June 2020 (revised October 2021) ## TL;DR: The [Tenda AC15 Wi-Fi Router](https://www.tendacn.com/en/product/Ac15.html) is a popular dual-band Gigabit WiFi router. For all its hardware prowess, the underlying software is rather [atrocious](https://www.cvedetails.com/vulnerability-list/vendor_id-13620/Tenda.html) It is [rife with vulnerabilities](https://www.cvedetails.com/vulnerability-list/vendor_id-16526/Tendacn.html) and notorious for harvesting private data and sending it "somewhere in the cloud". I emulated the AC15 router to study the software's inner workings. Emulating the AC15 router was a challenging exercise. The firmware couldn't be obtained via the [standard UART extraction](https://www.slideshare.net/saumilshah/hacklu-2018-arm-iot-firmware-emulation-workshop/19) technique that I usually use for most IoT devices. The AC15 doesn't store all its configuration data in the `nvram`, some of it is in a separate MTD partition. Launching the AC15's start up programs caused them to crash and the system to reboot, because certain custom library calls didn't return the values that were expected, due to missing emulated hardware. It took quite a bit of reverse engineering to figure out which functions we need to shim using `LD_PRELOAD`. Eventually, the labour bore fruit. ![EMUX Main Menu](img/emux-tenda-launcher.png) ![Tenda AC15 Emulation](img/armx-tenda-emulation.png) The emulation process was an great learning experience for me, which I present here. Read on! ## g00diez where? The emulated Tenda AC15 is included in the [EMUX Docker Image][docker]. You're welcome. ## Introduction The COVID-19 lockdown saw me working hard at [EMUX][emux], fixing bugs and adding new features for my upcoming [ARM IoT Firmware Laboratory][R0ARM] training. I wanted to show off EMUX's `nvram` handling capabilities and also to emulate a "popular" ARM IoT device that readers can easily get hardware access to. The Tenda AC15 is complex enough when it comes to emulation. This case study is written more as a tutorial for emulating your own hardware device. The approach described here does not require you to have a physical Tenda router on hand. All you need is a copy of the Tenda firmware from its website. I subsequently intend to publish two more articles - [Extracting the Tenda AC15's firmware directly from its SPI Flash chip][fw-extraction], and - Exploiting the vast array of vulnerabilities present in the AC15. ## Contents - [Section 1](#section-1---obtaining-the-firmware) - Obtaining the Firmware. - [1.1](#11-download-the-ac15-firmware) Download the AC15 Firmware - [1.2](#12-binwalk-the-firmware) binwalk the firmware - [1.3](#13-tracing-our-way-through-the-init-process) Tracing our way through the init process - [Section 2](#section-2---emulation-attempt-1) - Emulation attempt #1 - [2.1](#21-creating-the-tenda-device-directory) Creating the Tenda device directory - [2.2](#22-choosing-a-kernel) Choosing a kernel - [2.3](#23-copy-the-root-file-system) Copy the root file system - [2.4](#24-nvramini) nvram.ini - [2.5](#25-edit-the-config-file) Edit the config file - [2.6](#26-create-a-record-in-emuxdevices) Create a record in /emux/devices - [2.7](#27-starting-the-tenda-ac15-attempt-1) Starting the Tenda AC15, attempt #1 - [Section 3](#section-3---reverse-engineering-with-ghidra) - Reverse Engineering with Ghidra - [3.1](#31-code-snippets-from-reversed-functions---part-1) Code snippets from reversed functions - part 1 - [3.2](#32-code-snippets-from-reversed-functions---part-2) Code snippets from reversed functions - part 2 - [3.3](#33-error-message-nvram-partition-is-destroy) Error message "nvram partition is destroy" - [3.4](#34-bcm_nvram_-functions) bcm_nvram_ functions - [Section 4](#section-4---emulation-attempt-2---hooked-functions) - Emulation attempt #2 - hooked functions - [4.1](#41-tenda_hooksc) tenda_hooks.c - [4.2](#42-using-tenda_hooksso) Using tenda_hooks.so - [4.3](#43-preparing-for-a-limited-emulation) Preparing for a limited emulation - [4.4](#44-starting-the-tenda-ac15-attempt-2) Starting the Tenda AC15, attempt #2 - [4.5](#45-creating-nvram_ac15ini) Creating nvram_AC15.ini - [Section 5](#section-5---the-emulated-tenda-ac15) - The Emulated Tenda AC15 - [5.1](#51-starting-the-tenda-ac15-attempt-3) Starting the Tenda AC15, attempt #3 - [5.2](#52-fixing-the-factory-defaults) Fixing the "factory defaults" - [5.3](#53-starting-the-tenda-ac15-final-attempt) Starting the Tenda AC15, final attempt - [5.4](#54-process-memory-layout) Process Memory Layout - [5.5](#55-interacting-with-the-tendas-web-interface) Interacting with the Tenda's web interface - [Concluding thoughts](#concluding-thoughts) ## Section 1 - Obtaining the Firmware There are three popular techniques for obtaining the firmware. My preference is to grab the firmware from the physical router. This way, we can also obtain a snapshot of the configuration stored in its `nvram`. ![Inside the Tenda AC15](img/inside_tenda_ac15.jpg) 1. Discover the UART, access the `/dev/console`, hopefully find a shell and dump the `/dev/mtdblock`s. This is my preferred technique. 2. Extract the firmware directly from the SPI Flash memory using an EEPROM Programmer. 3. `binwalk` the firmware update published on the vendor's website (or CDROM). This is my least preferred technique since it doesn't give us a copy of the `nvram` contents. I shall cover techniques 1 and 2 in a separate post. ### 1.1 Download the AC15 Firmware My router is running firmware version 15.03.05.18. Grab it from http://down.tendacn.com/uploadfile/AC15/US_AC15V1.0BR_V15.03.05.18_multi_TD01.zip ![Tenda Firmware Download](img/tenda_firmware_download.png) ### 1.2 binwalk the firmware Our goal is to "footprint" the firmware and eventually perform a baseline static analysis of the extracted file system. We are looking for: * Kernel version * Kernel boot arguments * The Root filesystem * ...and any low hanging fruit we can find ``` # binwalk -e US_AC15V1.0BR_V15.03.05.18_multi_TD01.bin DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 64 0x40 TRX firmware header, little endian, image size: 10559488 bytes, CRC32: 0xFA817870, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x1C9CD4, rootfs offset: 0x0 92 0x5C LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4585280 bytes 1875220 0x1C9D14 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 8680744 bytes, 926 inodes, blocksize: 131072 bytes, created: 2017-05-10 14:10:50 ``` Contents of `_US_AC15V1.0BR_V15.03.05.18_multi_TD01.bin.extracted`: ``` -rw-r--r-- 1 root krafty 8680744 Jun 2 12:11 1C9D14.squashfs -rw-r--r-- 1 root krafty 4585280 Jun 2 12:11 5C -rw-r--r-- 1 root krafty 10559460 Jun 2 12:11 5C.7z drwxr-xr-x 15 root root 4096 May 10 2017 squashfs-root ``` Contents of `squashfs-root`: ``` drwxr-xr-x 2 root root 4096 May 10 2017 bin drwxr-xr-x 2 root root 4096 May 10 2017 cfg drwxr-xr-x 2 root root 4096 May 10 2017 dev lrwxrwxrwx 1 root root 8 Jun 2 12:11 etc -> /var/etc drwxr-xr-x 8 root root 4096 May 10 2017 etc_ro lrwxrwxrwx 1 root root 9 Jun 2 12:11 home -> /var/home lrwxrwxrwx 1 root root 11 Jun 2 12:11 init -> bin/busybox drwxr-xr-x 3 root root 4096 May 10 2017 lib drwxr-xr-x 2 root root 4096 May 10 2017 mnt drwxr-xr-x 2 root root 4096 May 10 2017 proc lrwxrwxrwx 1 root root 9 Jun 2 12:11 root -> /var/root drwxr-xr-x 2 root root 4096 May 10 2017 sbin drwxr-xr-x 2 root root 4096 May 10 2017 sys drwxr-xr-x 2 root root 4096 May 10 2017 tmp drwxr-xr-x 6 root root 4096 May 10 2017 usr drwxr-xr-x 6 root root 4096 May 10 2017 var lrwxrwxrwx 1 root root 11 Jun 2 12:11 webroot -> var/webroot drwxr-xr-x 8 root root 4096 May 10 2017 webroot_ro ``` Strings dump of file `5C`: ``` : root=/dev/mtdblock2 console=ttyS0,115200 init=init earlyprintk debug : Linux version 2.6.36.4brcmarm (root@linux-bkb8) (gcc version 4.5.3 (Buildroot 20 12.02) ) #1 SMP PREEMPT Wed May 10 21:54:27 CST 2017 %s version %s (root@linux-bkb8) (gcc version 4.5.3 (Buildroot 2012.02) ) %s : 2.6.36.4brcmarm SMP preempt mod_unload modversions ARMv7 : ``` We now have the kernel version, boot arguments and contents of the root file system. ### 1.3 Tracing our way through the init process From the boot arguments, we see that the kernel invokes `/sbin/init` as the first userland process after starting up. `init` in turn shall inspect `/etc/inittab` and proceed with the userland portion of the startup accordingly. `/etc/inittab` doesn't exist at boot time, however we realise that the `/etc_ro` directory is copied over to `/etc/` at some point in the initialisation process. Contents of `/etc_ro/inittab`: ``` ::sysinit:/etc_ro/init.d/rcS ttyS0::respawn:/sbin/sulogin ::ctrlaltdel:/bin/umount -a -r ::shutdown:/usr/sbin/wl radio off ::shutdown:/usr/sbin/wl -i eth2 radio off ``` The startup script `/etc_ro/init.d/rcS` performs the following actions: ``` : cfmd & echo '' > /proc/sys/kernel/hotplug udevd & logserver & tendaupload & if [ -e /etc/nginx/conf/nginx_init.sh ]; then sh /etc/nginx/conf/nginx_init.sh fi moniter & telnetd & : ``` 1. Creates `ramfs` and mount points 2. Copies `/etc_ro` to `/etc` 3. Copies `/webroot_ro` to `/webroot` 4. Launches the startup daemons 5. The following daemons are started: - cfmd - udevd - logserver - tendaupload - moniter - telnetd The startup process is summarised in the diagram below: ![Tenda init scripts](img/tenda_init_scripts.png) ## Section 2 - Emulation attempt #1 To create a new EMUX device, follow these steps: ![Creating a device in EMUX](img/emux-newdevice.png) 1. Create a device directory in EMUX for the Tenda AC15. 2. Choose a kernel. 3. Copy the root file system into the device directory. 4. We shall leave `nvram.ini` untouched for now, since we don't have the nvram contents. 5. Edit the device `config` file. 6. Create the Tenda record in `/emux/devices` ### 2.1 Creating the Tenda device directory We will use the ID `TENDA` for Create a new device directory under `/emux` by copying the `/emux/template` directory as `/emux/TENDA`: ``` $ cd /emux/ $ cp -r template TENDA ``` ### 2.2 Choosing a kernel I will choose kernel 2.6.39.4 as the base kernel for emulating the Tenda AC15. This kernel is already included with the EMUX release. I will describe how to compile your own kernel (specifically version 2.6.36.4 to match the version running on the router) in a separate article. The kernel is present as `zImage-2.6.39.4-vexpress`. ### 2.3 Copy the root file system Move the `squashfs-root` directory extracted by `binwalk` ([Section 1.2](#12-binwalk-the-firmware)) into `/emux/TENDA/squashfs-root`. ### 2.4 nvram.ini Since we do not have the contents of nvram, we shall leave `nvram.ini` as-is. Later, we shall populate `nvram.ini` once we gain more insight into the Tenda startup process. ### 2.5 Edit the config file Contents of `/emux/TENDA/config`: ``` # Tenda AC15 EMUX configuration # id=TENDA nvram=nvram.ini rootfs=squashfs-root randomize_va_space=0 initcommands="/etc_ro/init.d/rcS;/bin/sh" ``` ### 2.6 Create a record in /emux/devices ``` TENDA,qemu-system-arm-6.0.0,vexpress-a9,,,256M,zImage-2.6.39.4-vexpress,VEXPRESS2,Tenda AC15 Wi-Fi Router ``` ### 2.7 Starting the Tenda AC15, attempt #1 The `/emux/TENDA` directory looks like: ``` /emux/TENDA | |--- config |--- kernel | | | `--- zImage-2.6.39.4-vexpress | |--- nvram.ini `--- squashfs-root ``` Start the EMUX Launcher with the Tenda AC15 kernel ![EMUX Launcher for Tenda AC15](img/emux-tenda-launcher.png) From the EMUX Docker shell, start the Tenda AC15 init scripts ![Start the Tenda AC15 init scripts](img/emux-tenda-start.png) Output: (few errors have been removed) ``` Starting Tenda AC15 Wi-Fi Router Loading nvram from nvram.ini key1='value1' nv_get_config_data: Can't get config data : : key10='value10' >>> Starting TENDA [+] chroot /emux/TENDA/squashfs-root /.emux/emuxinit mkdir: can't create directory '/var/run': File exists init_core_dump 1816: rlim_cur = 0, rlim_max = 0 init_core_dump 1825: open core dump success init_core_dump 1834: rlim_cur = 5242880, rlim_max = 5242880 func:InitCfm begin load_mib.... Could not open mtd device 2 [get_mtd_size] Can't open file:CFM [get_mtd_size] Can't open file:CFM_BACKUP malloc buf failed load mib failed. flash_to_file:open Policy failed. going to rewrite flash... open Policy failed. rewrite flash ok... ######## STARTING PROGRAM ######### spawn-fcgi: child spawned successfully: PID: 683 ~ # /dev/nvram: No such device ########################################################### ###nvram partition is destory, restore by default and reboot## ########################################################### Could not open mtd device 1 envram_init: read flash error /dev/nvram: No such device load nvram from /webroot/nvram_default.cfg... /dev/nvram: No such device /dev/nvram: No such device (Sometime after this the kernel reboots) ``` In summary, we have: ![First Attempt](img/tenda_first_run.png) ## Section 3 - Reverse Engineering with Ghidra I used [Ghidra](https://ghidra-sre.org) to reverse engineer the binaries invoked by `/etc_ro/init.d/rcS` along with various shared libraries found in `/lib` These are the functions I reversed in the first pass: ![Reversing First Pass](img/tenda_reversing_with_ghidra1.png) ### 3.1 Code snippets from reversed functions - part 1 `InitCfm` was revealed from the startup messages. ``` int InitCfm(void) { : printf("func:%s begin load_mib....\n","InitCfm"); load_mib(0); // next step : } ``` and so was `load_mib` ``` int load_mib(int flags) { : mib_open(); iVar1 = mib_Load("/webroot/default.cfg",flags); // important filename : } ``` Inspection of `/webroot_ro/default.cfg` reveals that it stores the factory default configuration of the AC15. ``` int mib_Load(char *filename,int flags) { : iVar1 = get_cfm_blk_size_from_cache(); : if ((flags != 0) || (flag_init = nvram_cfm_init(buf_00,iVar1 << 3), flag_init == 0)) { : : } ``` Next, reverse `get_cfm_blk_size_from_cache` and `nvram_cfm_init` ``` int get_cfm_blk_size_from_cache(void) { : iVar1 = get_flash_type(); if (iVar1 == 4) { cfm_size_from_cache = 0x20000; } else { : } return cfm_size_from_cache; } ``` Observation: `get_flash_type()`'s return value is checked to see if it is `4` ``` uint get_flash_type(void) { int iVar1; uint uVar2; byte local_34 [32]; int local_14; local_14 = mtd_open(&DAT_0002de2c,2); if (local_14 < 0) { fwrite("Could not open mtd device 2\n",1,0x1c,stderr); uVar2 = 0xffffffff; } else { iVar1 = ioctl(local_14,0x80204d01,local_34); if (iVar1 == 0) { close(local_14); uVar2 = (uint)local_34[0]; } else { fwrite("Could not get mtd device info\n",1,0x1e,stderr); close(local_14); uVar2 = 0xffffffff; } } return uVar2; } ``` Observation: In the absence of MTD emulation, it is imperative to fake `get_flash_type() = 4` ### 3.2 Code snippets from reversed functions - part 2 Continuing with `nvram_cfm_init` ``` int nvram_cfm_init(char *buf,int len) { : iVar1 = get_flash_type(); if (iVar1 == 4) { ret = cfm_file_init(buf,len); } else { ret = cfm_mtd_init(buf); } return ret; } ``` Observation: If `get_flash_type() == 4` then the code execution follows `cfm_file_init` and not `cfm_mtd_init`. The latter would prove to be difficult in the light of missing MTD hardware. ``` int cfm_file_init(char *buf,int len) { : __fd = mount("/dev/mtdblock7","/cfg","jffs2",0,(void *)0x0); if (__fd != 0) { puts("Erase nflash MTD partition and mount again"); system("/bin/flash_erase /dev/mtd7 0x0 0x0"); __fd = mount("/dev/mtdblock7","/cfg","jffs2",0,(void *)0x0); if (__fd != 0) { printf("Mount nflash MTD jffs2 partition %s to %s failed\n","/dev/mtdblock7",&DAT_0001e984); } } pcVar1 = cfm_get_new_file_name(); __file = pcVar1; if (pcVar1 == (char *)0x0) { __file = "/cfg/mib.cfg"; } : : // long function : } ``` Observation: `cfm_file_init` opens `/cfg/mib.cfg` for reading initial configuration values, after mounting `/dev/mtdblock7` to `/cfg`. ``` char * cfm_get_new_file_name(void) { : iVar1 = cfm_get_new_file_flag(&cfm_flag,"/cfg/mib.cfg"); if (iVar1 == 0) { cfm_flag = 0; } iVar1 = cfm_get_new_file_flag(&cfm_backup_flag,"/cfg/mib_backup.cfg"); if (iVar1 == 0) { cfm_backup_flag = 0; } : } ``` Observation: `cfm_get_new_file_name` looks for `/cfg/mib.cfg` and `/cfg/mib_backup.cfg`. Eventually we shall see that these files are created by this function with configuration data populated from `/webroot_ro/default.cfg`. ([3.1](#31-code-snippets-from-reversed-functions---part-1)) ### 3.3 Error message "nvram partition is destroy" From the startup messages, we see an error message `nvram partition is destroy` flash past and thereafter the system reboots. Using Ghidra to search for this error message leads us to `FUN_0000e640` in `cfmd`: ``` void FUN_0000e460(void) { __s1 = (void *)bcm_nvram_get("default_nvram"); if ((__s1 == (void *)0x0) || (iVar1 = memcmp(__s1,"default_nvram",0xd), iVar1 != 0)) { puts("\x1b[0;32;31m\n###########################################################"); puts("###nvram partition is destory, restore by default and reboot##"); printf("###########################################################\n\x1b[m"); RestoreNvram(); doSystemCmd("reboot"); } return; } ``` Observation: `FUN_0000e460` calls: - `bcm_nvram_get` seems to be the way to access `nvram` data. This function needs to be mapped to the equivalent nvram access functions in `libnvram-armx.so`. - `RestoreNvram` looks interesting - `doSystemCmd("reboot")` this is where the reboot is triggered ### 3.4 bcm_nvram_ functions ``` char * bcm_nvram_get(char *name) { : iVar2 = bcm_nvram_init((void *)0x0); : sVar3 = read(nvram_fd,off,__size); : return value; } ``` Observation: in the absence of physican nvram, we need to map this function to `nvram_get` in `libnvram-armx` ``` int bcm_nvram_init(void *unused) { : if (nvram_fd < 0) { nvram_fd = open("/dev/nvram",2); if (-1 < nvram_fd) { nvram_buf = (char *)mmap((void *)0x0,0x10000,1,1,nvram_fd,0); if (nvram_buf != (char *)0xffffffff) { fcntl(nvram_fd,2,1); return 0; } close(nvram_fd); nvram_fd = -1; } perror("/dev/nvram"); piVar1 = __errno_location(); iVar2 = *piVar1; } : } ``` Observation: All the `/dev/nvram` error messages are coming from here. ``` void RestoreNvram(void) { envram_to_nvram(); bcm_nvram_restore(); return; } ``` ``` int bcm_nvram_restore(void) { pcVar1 = bcm_nvram_get("image_boot"); : __stream = fopen("/webroot/nvram_default.cfg","r"); if (__stream != (FILE *)0x0) { printf("load nvram from %s...\n","/webroot/nvram_default.cfg"); while (__src = fgets(mib_val,0x100,__stream), __src != (char *)0x0) { : bcm_nvram_set(mib_val,__src); memset(mib_val,0,0x100); : } fclose(__stream); } if (pcVar1 != (char *)0x0) { bcm_nvram_set("image_boot",imboot_value); bcm_nvram_commit(); } bcm_nvram_set("default_nvram","default_nvram"); iVar3 = bcm_nvram_commit(); return iVar3; } ``` Observation: If the system detects an empty nvram, `RestoreNvram` and `bcm_nvram_restore` populates the nvram with values from `/webroot_ro/nvram_default.cfg`. ## Section 4 - Emulation attempt #2 - hooked functions To proceed with the emulation, it is necessary to: 1. map the following functions: - `bcm_nvram_set` to `nvram_get` - `bcm_nvram_get` to `nvram_set` - `bcm_nvram_unset` to `nvram_unset` 2. fake `get_flash_type` to always return `4` 3. populate the nvram with meaningful values ### 4.1 tenda_hooks.c I wrote `tenda_hooks.c` along the same lines as my earlier work [custom_nvram_r6250.c][nvram_r6250]. Source: https://github.com/therealsaumil/custom_nvram/blob/master/tenda_hooks.c Pre-compiled `tenda_hooks.so` and `tenda_hooks_verbose.so` along with `libnvram-armx.so` are available at https://github.com/therealsaumil/custom_nvram/ ### 4.2 Using tenda_hooks.so To use these hooks, we need to `LD_PRELOAD` them before invoking `/etc_ro/init.d/rcS` The contents of `/emux/TENDA/config` are now as follows: ``` # Tenda AC15 EMUX configuration # id=TENDA nvram= rootfs=squashfs-root randomize_va_space=0 initcommands="export LD_PRELOAD=/lib/libnvram-armx.so:/lib/tenda_hooks_verbose.so;/etc_ro/init.d/rcS;/bin/sh" ``` ### 4.3 Preparing for a limited emulation My goal was to let `cfmd` run `FUN_0000e460()` successfully, so that `RestoreNvram()` can complete its task in populating the nvram values. And I wanted to grab the freshly populated nvram values before the router rebooted from `doSystemCmd("reboot")`. as described in [3.3](#33-error-message-nvram-partition-is-destroy) First, we change `/sbin/reboot` as follows: ``` #!/bin/sh echo "*** REBOOT INVOKED ***" > /dev/console sleep 600 ``` Next, we edit `/etc_ro/init.d/rcS` to prevent startup daemons subsequent to `cfmd` from running: ``` : chmod +x /etc/mdev.conf cfmd & read -p "*** cfmd started *** " # Pause echo '' > /proc/sys/kernel/hotplug udevd & logserver & : ``` ### 4.4 Starting the Tenda AC15, attempt #2 1. Start the EMUX Launcher with the Tenda AC15 kernel 2. From the EMUX Docker shell, start the Tenda AC15 init scripts The startup output is as follows: (similar lines have been truncated) ``` Starting Tenda AC15 Wi-Fi Router Syntax: ./run-init >>> Starting TENDA [+] chroot /emux/TENDA/squashfs-root /.emux/emuxinit mkdir: can't create directory '/var/run': File exists *** cfmd started *** init_core_dump 1816: rlim_cur = 0, rlim_max = 0 init_core_dump 1825: open core dump success [0x400821bc] system('echo /tmp/core-%e-%p-%t-%s > /proc/sys/kernel/core_pattern') = 0 init_core_dump 1834: rlim_cur = 5242880, rlim_max = 5242880 func:InitCfm begin load_mib.... [0x40054598] get_flash_type() = 4 [0x400555b0] get_flash_type() = 4 Erase nflash MTD partition and mount again flash_erase: error!: /dev/mtd7 error 2 (No such file or directory) [0x400550dc] system('/bin/flash_erase /dev/mtd7 0x0 0x0') = 65280 Mount nflash MTD jffs2 partition /dev/mtdblock7 to /cfg failed Failed to open persistent data file: /cfg/mib.cfg open: No such file or directory Failed to open persistent data file: /cfg/mib.cfg open: No such file or directory Failed to open persistent data file: (null) open: No such file or directory loading default config from /webroot/default.cfg file... [0x400cb9b4] Can't open nv_get_config_data: Can't get config data bcm_nvram_get('et0macaddr') = '' [0x400cb9b4] bcm_nvram_get('et0macaddr') = '' [0x400cb9b4] bcm_nvram_get('et0macaddr') = '' [0x400cb9b4] bcm_nvram_get('et0macaddr') = '' [0x400cbbc4] bcm_nvram_get('1:macaddr') = '' [0x400cbbdc] bcm_nvram_get('1:macaddr') = '' get_wlan_mac 700: get macaddr from nvram error not exist!(mib.c restore_change_wifi_ssid) Could not open mtd device 1 envram_init: read flash error Could not open mtd device 1 envram_init: read flash error Could not open mtd device 1 envram_init: read flash error error power wl2g.power [rate:2] Could not open mtd device 1 envram_init: read flash error error power wl5g.power [rate:5] flash_to_file:open Policy failed. going to rewrite flash... open Policy failed. rewrite flash ok... [0x0000e48c] bcm_nvram_get('default_nvram') = '' ########################################################### ###nvram partition is destory, restore by default and reboot## ########################################################### Could not open mtd device 1 envram_init: read flash error [0x40057834] bcm_nvram_get('image_boot') = '' [0x40057854] bcm_nvram_get('image_boot') = '' load nvram from /webroot/nvram_default.cfg... [0x400579ec] [nvram 0] bcm_nvram_set('0:boardvendor', '0x14E4') [0x400579ec] [nvram 0] bcm_nvram_set('0:ledbh10', '7') [0x400579ec] [nvram 0] bcm_nvram_set('0:leddc', '0xffff') [0x400579ec] [nvram 0] bcm_nvram_set('1:ledbh2', '7') : : : : : : : : : [0x400579ec] [nvram 0] bcm_nvram_set('wl_txbf_imp', '0') [0x400579ec] [nvram 0] bcm_nvram_set('wl0_txbf_imp', '0') [0x400579ec] [nvram 0] bcm_nvram_set('wl1_txbf_imp', '0') [0x40057a70] [nvram 0] bcm_nvram_set('image_boot', '') /dev/nvram: No such device [0x40057a90] [nvram 0] bcm_nvram_set('default_nvram', 'default_nvram') /dev/nvram: No such device [0x400821bc] system('reboot') = 2 ``` There are around 1000+ `bcm_nvram_set()` calls, all triggered by `RestoreNvram`. The emulation is paused at this point. It seems our nvram will now contain meaningful values. ### 4.5 Creating nvram_AC15.ini To grab the nvram contents populated by `RestoreNvram`, open the EMUX HOSTFS Shell from the EMUX terminal, and run the following: ``` EMUX HOSTFS [TENDA]:~> nvram show > /emux/TENDA/nvram_AC15.ini ``` As an aside, the `/cfg` directory is still empty. At some point, it will get populated with `mib.cfg` as observed in [3.2](#32-code-snippets-from-reversed-functions---part-2) ## Section 5 - The Emulated Tenda AC15 Armed with working data in `nvram_AC15.ini`, we can attempt to run the full emulation. Before we proceed, we need to restore `/sbin/reboot` and `/etc_ro/init.d/rcS` to their original versions. ### 5.1 Starting the Tenda AC15, attempt #3 Set the `nvram=` parameter in `/emux/TENDA/config` as follows: ``` # Tenda AC15 EMUX configuration # id=TENDA nvram=nvram_AC15.ini rootfs=squashfs-root randomize_va_space=0 initcommands="export LD_PRELOAD=/lib/libnvram-armx.so:/lib/tenda_hooks_verbose.so;/etc_ro/init.d/rcS;/bin/sh" ``` Start the emulation once again as follows: 1. Start the EMUX Launcher with the Tenda AC15 kernel 2. From the EMUX Docker shell, start the Tenda AC15 init scripts The startup process continues past `cfmd`. The system does not reboot. A condensed version of the output (with only the relevant parts) is shown below: ``` Starting Tenda AC15 Wi-Fi Router Loading nvram from nvram_fresh.ini 0:boardvendor='0x14E4' Can't open nv_get_config_data: Can't get config data 0:ledbh10='7' 0:leddc='0xffff' 1:ledbh2='7' : : : : wl0_txbf_imp='0' wl1_txbf_imp='0' default_nvram='default_nvram' vlan2ports='0 5' vlan1ports='1 2 3 4 5*' >>> Starting TENDA [+] chroot /emux/TENDA/squashfs-root /.emux/emuxinit mkdir: can't create directory '/var/run': File exists init_core_dump 1816: rlim_cur = 0, rlim_max = 0 init_core_dump 1825: open core dump success init_core_dump 1834: rlim_cur = 5242880, rlim_max = 5242880 func:InitCfm begin load_mib.... [0x40054598] get_flash_type() = 4 [0x400555b0] get_flash_type() = 4 Erase nflash MTD partition and mount again ######## STARTING PROGRAM ######### flash_erase: error!: /dev/mtd7 error 2 (No such file or directory) [0x400550dc] system('/bin/flash_erase /dev/mtd7 0x0 0x0') = 65280 Mount nflash MTD jffs2 partition /dev/mtdblock7 to /cfg failed crc = b5fad1ed, newcrc = b5fad1ed spawn-fcgi: child spawned successfully: PID: 1492 ~ # [0x40054598] get_flash_type() = 4 [0x40054598] get_flash_type() = 4 flash_to_file:open Policy failed. going to rewrite flash... open Policy failed. rewrite flash ok... : : : : [0x400821bc] system('ifconfig br0 hw ether 00:01:02:03:04:05') = 256 [0x400821bc] system('ifconfig vlan1 up') = 0 [0x400821bc] system('brctl addbr br0') = 0 [0x400821bc] system('brctl setfd br0 0') = 0 [0x400821bc] system('brctl stp br0 on') = 0 [0x400821bc] system('brctl addif br0 vlan1') = 0 [0x400821bc] system('ifconfig br0 up') = 0 [0x400821bc] system('ifconfig br0 192.168.0.1 netmask 255.255.255.0 ') = 0 Sat Jan 1 00:00:00 UTC 2000 : : : : iptables: goahead: 4: main----51 goahead: 4: initPlatform----398 goahead: 4: websOpen----233 ``` The system starts up. Looking at some of the `system()` commands from the output above, we observe that a Bridge Interface `br0` has been created and assigned the IP address `192.168.0.1`. We can inspect the network interfaces and listening ports from the EMUX HOSTFS Shell: ``` EMUX HOSTFS [TENDA]:~> ifconfig -a : : br0 Link encap:Ethernet HWaddr 52:54:00:12:34:56 inet addr:192.168.0.1 Bcast:192.168.0.255 Mask:255.255.255.0 inet6 addr: fe80::1/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:17 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:3388 (3.3 KiB) br0:dname Link encap:Ethernet HWaddr 52:54:00:12:34:56 inet addr:172.17.245.103 Bcast:172.17.255.255 Mask:255.255.255.255 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 eth0 Link encap:Ethernet HWaddr 52:54:00:12:34:56 inet addr:192.168.100.2 Bcast:0.0.0.0 Mask:255.255.255.0 inet6 addr: fe80::5054:ff:fe12:3456/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:23660 errors:0 dropped:0 overruns:0 frame:0 TX packets:21755 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:19665957 (18.7 MiB) TX bytes:3404166 (3.2 MiB) : : ``` Listening ports: ``` EMUX HOSTFS [TENDA]:~> netstat -nat Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN tcp 0 0 172.17.245.103:80 0.0.0.0:* LISTEN tcp 0 0 192.168.0.1:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:10002 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:10003 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:10004 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:8180 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:5500 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:8188 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:10004 127.0.0.1:45174 ESTABLISHED tcp 0 0 127.0.0.1:56915 127.0.0.1:10003 ESTABLISHED tcp 0 0 127.0.0.1:10003 127.0.0.1:56915 ESTABLISHED tcp 0 0 192.168.100.2:838 192.168.100.1:2049 ESTABLISHED tcp 0 0 192.168.100.2:22 192.168.100.1:34216 ESTABLISHED tcp 0 0 127.0.0.1:45174 127.0.0.1:10004 ESTABLISHED tcp 0 0 192.168.100.2:22 192.168.100.1:34218 ESTABLISHED tcp 0 0 :::22 :::* LISTEN tcp 0 0 :::23 :::* LISTEN ``` Running processes: ``` EMUX HOSTFS [TENDA]:~> ps ax : : : 1880 root cfmd 1881 root cfmd 1882 root netctrl 1883 root time_check 1884 root httpd 1885 root multiWAN 1886 root ucloud_v2 -l 4 1887 root business_proc -l 4 1890 root time_check 1892 root time_check 1903 root business_proc -l 4 1904 root business_proc -l 4 1905 root ucloud_v2 -l 4 1922 root netctrl 1923 root netctrl : : : ``` The webserver `httpd` is indeed running, however it is inaccessible due to the IP address of the network interface it is bound to (`br0`). ### 5.2 Fixing the "factory defaults" As discussed in [3.1](#31-code-snippets-from-reversed-functions---part-1) the factory defaults are be loaded from `mib_Load("/webroot/default.cfg", flags)` Inspecting `/webroot_ro/default.cfg` reveals the `lan.ip` parameter which is set as follows: ``` lan.ip=192.168.0.1 ``` We also notice files `/cfg/mib.cfg` and `/cfg/mib_backup.cfg` are freshly created. To change the default IP address to `192.168.100.2` as hardcoded in EMUX, I edited the `/webroot_ro/default.cfg` file: ``` lan.ip=192.168.100.2 lan.mask=255.255.255.0 ``` I also erased `/cfg/mib.cfg` and `/cfg/mib_backup.cfg`. ### 5.3 Starting the Tenda AC15, final attempt Now that our observations are complete, we can switch from `tenda_hooks_verbose` to `tenda_hooks`, to avoid excessive output. All we want to verify is the IP address should be set to `192.168.100.2` and that the web server should be accessible from a browser. Edit `/emux/TENDA/config`: ``` # Tenda AC15 EMUX configuration # id=TENDA nvram=nvram_AC15.ini rootfs=squashfs-root randomize_va_space=0 initcommands="export LD_PRELOAD=/lib/libnvram-armx.so:/lib/tenda_hooks.so;/etc_ro/init.d/rcS;/bin/sh" ``` Start the emulation once again as follows: 1. Start the EMUX Launcher with the Tenda AC15 kernel 2. From the EMUX Docker shell, start the Tenda AC15 init scripts One by one all the services and daemons start up. Checking the IP address of `br0`: ``` EMUX HOSTFS [TENDA]:~> ifconfig br0 br0 Link encap:Ethernet HWaddr 52:54:00:12:34:56 inet addr:192.168.100.2 Bcast:192.168.100.255 Mask:255.255.255.0 inet6 addr: fe80::1/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:50 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes:0 (0.0 B) TX bytes:14282 (13.9 KiB) ``` Checking the listening ports: ``` EMUX HOSTFS [TENDA]:~> netstat -nat Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:9000 0.0.0.0:* LISTEN tcp 0 0 172.17.245.103:80 0.0.0.0:* LISTEN tcp 0 0 192.168.100.2:80 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:10002 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:10003 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:10004 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:8180 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:5500 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:8188 0.0.0.0:* LISTEN tcp 0 0 192.168.100.2:22 192.168.100.1:51948 ESTABLISHED tcp 0 0 127.0.0.1:10004 127.0.0.1:54956 ESTABLISHED tcp 0 0 127.0.0.1:52552 127.0.0.1:10003 ESTABLISHED tcp 0 0 192.168.100.2:22 192.168.100.1:51952 ESTABLISHED tcp 0 0 192.168.100.2:988 192.168.100.1:2049 ESTABLISHED tcp 0 0 127.0.0.1:10003 127.0.0.1:52552 ESTABLISHED tcp 0 0 127.0.0.1:54956 127.0.0.1:10004 ESTABLISHED tcp 0 0 :::22 :::* LISTEN tcp 0 0 :::23 :::* LISTEN ``` ### 5.4 Process Memory Layout One of the goals of EMUX is to create an emulated process memory layout identical to that on the actual hardware. This allows us to guarantee that memory corruption exploits created on the emulated environment work as-is when targeting the actual hardware. Presented below is a comparision of the memory layout of the `httpd` process: ``` attackbox:~$ emuxmaps httpd 00008000-000f7000 r-xp 00000000 00:0f 278680 /emux/TENDA/squashfs-root/bin/httpd 000ff000-00102000 rw-p 000ef000 00:0f 278680 /emux/TENDA/squashfs-root/bin/httpd 00102000-00124000 rw-p 00000000 00:00 0 [heap] 40000000-40005000 r-xp 00000000 00:0f 278830 /emux/TENDA/squashfs-root/lib/ld-uClibc.so.0 40005000-40006000 rw-p 00000000 00:00 0 40007000-40008000 rw-p 00000000 00:00 0 4000c000-4000d000 r--p 00004000 00:0f 278830 /emux/TENDA/squashfs-root/lib/ld-uClibc.so.0 4000d000-4000e000 rw-p 00005000 00:0f 278830 /emux/TENDA/squashfs-root/lib/ld-uClibc.so.0 4000e000-40015000 r-xp 00000000 00:0f 137945 /emux/TENDA/squashfs-root/lib/libnvram-armx.so 40015000-4001c000 ---p 00000000 00:00 0 4001c000-4001d000 rw-p 00006000 00:0f 137945 /emux/TENDA/squashfs-root/lib/libnvram-armx.so 4001d000-40025000 rw-p 00000000 00:00 0 40025000-40026000 r-xp 00000000 00:0f 137935 /emux/TENDA/squashfs-root/lib/tenda_hooks.so 40026000-4002d000 ---p 00000000 00:00 0 4002d000-4002e000 rw-p 00000000 00:0f 137935 /emux/TENDA/squashfs-root/lib/tenda_hooks.so 4002e000-4003d000 r-xp 00000000 00:0f 278831 /emux/TENDA/squashfs-root/lib/libCfm.so 4003d000-40045000 ---p 00000000 00:00 0 40045000-40046000 rw-p 0000f000 00:0f 278831 /emux/TENDA/squashfs-root/lib/libCfm.so 40046000-40061000 rw-p 00000000 00:00 0 40061000-40081000 r-xp 00000000 00:0f 278841 /emux/TENDA/squashfs-root/lib/libcommon.so 40081000-40088000 ---p 00000000 00:00 0 40088000-40089000 rw-p 0001f000 00:0f 278841 /emux/TENDA/squashfs-root/lib/libcommon.so 40089000-40091000 rw-p 00000000 00:00 0 40091000-40099000 r-xp 00000000 00:0f 278832 /emux/TENDA/squashfs-root/lib/libChipApi.so 40099000-400a0000 ---p 00000000 00:00 0 400a0000-400a1000 rw-p 00007000 00:0f 278832 /emux/TENDA/squashfs-root/lib/libChipApi.so 400a1000-400a3000 r-xp 00000000 00:0f 278868 /emux/TENDA/squashfs-root/lib/libvos_util.so 400a3000-400ab000 ---p 00000000 00:00 0 400ab000-400ac000 rw-p 00002000 00:0f 278868 /emux/TENDA/squashfs-root/lib/libvos_util.so 400ac000-400c0000 r-xp 00000000 00:0f 278869 /emux/TENDA/squashfs-root/lib/libz.so 400c0000-400c7000 ---p 00000000 00:00 0 400c7000-400c9000 rw-p 00013000 00:0f 278869 /emux/TENDA/squashfs-root/lib/libz.so 400c9000-400d4000 r-xp 00000000 00:0f 278858 /emux/TENDA/squashfs-root/lib/libpthread.so.0 400d4000-400db000 ---p 00000000 00:00 0 400db000-400dc000 r--p 0000a000 00:0f 278858 /emux/TENDA/squashfs-root/lib/libpthread.so.0 400dc000-400e1000 rw-p 0000b000 00:0f 278858 /emux/TENDA/squashfs-root/lib/libpthread.so.0 400e1000-400e3000 rw-p 00000000 00:00 0 400e3000-400e4000 r-xp 00000000 00:0f 278855 /emux/TENDA/squashfs-root/lib/libnvram.so 400e4000-400eb000 ---p 00000000 00:00 0 400eb000-400ec000 rw-p 00000000 00:0f 278855 /emux/TENDA/squashfs-root/lib/libnvram.so 400ec000-400f9000 r-xp 00000000 00:0f 278861 /emux/TENDA/squashfs-root/lib/libshared.so 400f9000-40101000 ---p 00000000 00:00 0 40101000-40102000 rw-p 0000d000 00:0f 278861 /emux/TENDA/squashfs-root/lib/libshared.so 40102000-40168000 r-xp 00000000 00:0f 278863 /emux/TENDA/squashfs-root/lib/libtpi.so 40168000-4016f000 ---p 00000000 00:00 0 4016f000-40170000 rw-p 00065000 00:0f 278863 /emux/TENDA/squashfs-root/lib/libtpi.so 40170000-401a2000 rw-p 00000000 00:00 0 401a2000-401b1000 r-xp 00000000 00:0f 278852 /emux/TENDA/squashfs-root/lib/libm.so.0 401b1000-401b9000 ---p 00000000 00:00 0 401b9000-401ba000 r--p 0000f000 00:0f 278852 /emux/TENDA/squashfs-root/lib/libm.so.0 401ba000-401bb000 rw-p 00010000 00:0f 278852 /emux/TENDA/squashfs-root/lib/libm.so.0 401bb000-401e4000 r-xp 00000000 00:0f 278864 /emux/TENDA/squashfs-root/lib/libucapi.so 401e4000-401ec000 ---p 00000000 00:00 0 401ec000-401ed000 rw-p 00029000 00:0f 278864 /emux/TENDA/squashfs-root/lib/libucapi.so 401ed000-401ef000 rw-p 00000000 00:00 0 401ef000-401f9000 r-xp 00000000 00:0f 278849 /emux/TENDA/squashfs-root/lib/libgcc_s.so.1 401f9000-40200000 ---p 00000000 00:00 0 40200000-40201000 rw-p 00009000 00:0f 278849 /emux/TENDA/squashfs-root/lib/libgcc_s.so.1 40201000-40266000 r-xp 00000000 00:0f 278839 /emux/TENDA/squashfs-root/lib/libc.so.0 40266000-4026e000 ---p 00000000 00:00 0 4026e000-4026f000 r--p 00065000 00:0f 278839 /emux/TENDA/squashfs-root/lib/libc.so.0 4026f000-40270000 rw-p 00066000 00:0f 278839 /emux/TENDA/squashfs-root/lib/libc.so.0 40270000-40275000 rw-p 00000000 00:00 0 40275000-40277000 r-xp 00000000 00:0f 278845 /emux/TENDA/squashfs-root/lib/libdl.so.0 40277000-4027e000 ---p 00000000 00:00 0 4027e000-4027f000 r--p 00001000 00:0f 278845 /emux/TENDA/squashfs-root/lib/libdl.so.0 4027f000-40280000 rw-p 00000000 00:00 0 40280000-40281000 r-xp 00000000 00:0f 278860 /emux/TENDA/squashfs-root/lib/librt.so.0 40281000-40288000 ---p 00000000 00:00 0 40288000-40289000 r--p 00000000 00:0f 278860 /emux/TENDA/squashfs-root/lib/librt.so.0 befdf000-bf000000 rw-p 00000000 00:00 0 [stack] ffff0000-ffff1000 r-xp 00000000 00:00 0 [vectors] ``` Comparing it with the process memory layout on the actual hardware (deliberately non-randomized) ``` ~ # cat /proc/4388/maps 00008000-000f7000 r-xp 00000000 1f:03 289 /bin/httpd 000ff000-00102000 rw-p 000ef000 1f:03 289 /bin/httpd 00102000-00124000 rw-p 00000000 00:00 0 [heap] 40000000-40005000 r-xp 00000000 1f:03 124 /lib/ld-uClibc.so.0 40005000-40006000 rw-p 00000000 00:00 0 40007000-40008000 rw-p 00000000 00:00 0 4000c000-4000d000 r--p 00004000 1f:03 124 /lib/ld-uClibc.so.0 4000d000-4000e000 rw-p 00005000 1f:03 124 /lib/ld-uClibc.so.0 4000e000-4001d000 r-xp 00000000 1f:03 192 /lib/libCfm.so 4001d000-40025000 ---p 00000000 00:00 0 40025000-40026000 rw-p 0000f000 1f:03 192 /lib/libCfm.so 40026000-40041000 rw-p 00000000 00:00 0 40041000-40061000 r-xp 00000000 1f:03 205 /lib/libcommon.so 40061000-40068000 ---p 00000000 00:00 0 40068000-40069000 rw-p 0001f000 1f:03 205 /lib/libcommon.so 40069000-40071000 rw-p 00000000 00:00 0 40071000-40079000 r-xp 00000000 1f:03 201 /lib/libChipApi.so 40079000-40080000 ---p 00000000 00:00 0 40080000-40081000 rw-p 00007000 1f:03 201 /lib/libChipApi.so 40081000-40083000 r-xp 00000000 1f:03 209 /lib/libvos_util.so 40083000-4008b000 ---p 00000000 00:00 0 4008b000-4008c000 rw-p 00002000 1f:03 209 /lib/libvos_util.so 4008c000-400a0000 r-xp 00000000 1f:03 211 /lib/libz.so 400a0000-400a7000 ---p 00000000 00:00 0 400a7000-400a9000 rw-p 00013000 1f:03 211 /lib/libz.so 400a9000-400b4000 r-xp 00000000 1f:03 191 /lib/libpthread.so.0 400b4000-400bb000 ---p 00000000 00:00 0 400bb000-400bc000 r--p 0000a000 1f:03 191 /lib/libpthread.so.0 400bc000-400c1000 rw-p 0000b000 1f:03 191 /lib/libpthread.so.0 400c1000-400c3000 rw-p 00000000 00:00 0 400c3000-400c4000 r-xp 00000000 1f:03 121 /lib/libnvram.so 400c4000-400cb000 ---p 00000000 00:00 0 400cb000-400cc000 rw-p 00000000 1f:03 121 /lib/libnvram.so 400cc000-400d9000 r-xp 00000000 1f:03 190 /lib/libshared.so 400d9000-400e1000 ---p 00000000 00:00 0 400e1000-400e2000 rw-p 0000d000 1f:03 190 /lib/libshared.so 400e2000-40148000 r-xp 00000000 1f:03 188 /lib/libtpi.so 40148000-4014f000 ---p 00000000 00:00 0 4014f000-40150000 rw-p 00065000 1f:03 188 /lib/libtpi.so 40150000-40182000 rw-p 00000000 00:00 0 40182000-40191000 r-xp 00000000 1f:03 118 /lib/libm.so.0 40191000-40199000 ---p 00000000 00:00 0 40199000-4019a000 r--p 0000f000 1f:03 118 /lib/libm.so.0 4019a000-4019b000 rw-p 00010000 1f:03 118 /lib/libm.so.0 4019b000-401c4000 r-xp 00000000 1f:03 119 /lib/libucapi.so 401c4000-401cc000 ---p 00000000 00:00 0 401cc000-401cd000 rw-p 00029000 1f:03 119 /lib/libucapi.so 401cd000-401cf000 rw-p 00000000 00:00 0 401cf000-401d9000 r-xp 00000000 1f:03 187 /lib/libgcc_s.so.1 401d9000-401e0000 ---p 00000000 00:00 0 401e0000-401e1000 rw-p 00009000 1f:03 187 /lib/libgcc_s.so.1 401e1000-40246000 r-xp 00000000 1f:03 203 /lib/libc.so.0 40246000-4024e000 ---p 00000000 00:00 0 4024e000-4024f000 r--p 00065000 1f:03 203 /lib/libc.so.0 4024f000-40250000 rw-p 00066000 1f:03 203 /lib/libc.so.0 40250000-40255000 rw-p 00000000 00:00 0 40255000-40256000 r-xp 00000000 1f:03 213 /lib/librt.so.0 40256000-4025d000 ---p 00000000 00:00 0 4025d000-4025e000 r--p 00000000 1f:03 213 /lib/librt.so.0 befdf000-bf000000 rw-p 00000000 00:00 0 [stack] ``` The addresses of libraries such as `libc.so.0` and others in EMUX are slightly different when compared to those on the actual hardware. The reason is because of `/lib/libnvram-armx.so` and `tenda_hooks.so` being injected via `LD_PRELOAD`. Apart from the preloaded libraries, everything else matches. ### 5.5 Interacting with the Tenda's web interface 1. Open the browser and navigate to `http://192.168.100.2`. We are greeted by the Tenda Quick Setup Wizard ![Tenda Setup Wizard](img/tenda_01_setup_wizard.png) 2. Skip connecting the WAN cable ![Tenda Connect WAN cable](img/tenda_02_connect_wan.png) 3. Set the login password. The password for the Tenda emulated instance on the [EMUX Docker image][docker] is `ringzer0` ![Tenda Set Password](img/tenda_03_password.png) 4. Login to the Web Administration Interface with password `ringzer0` ![Tenda Login](img/tenda_04_login.png) 5. Enjoy! ![Tenda Running](img/tenda_05_running.png) ## Concluding thoughts This article was intended to familiarise you with what it takes to emulate a new device from scratch. I urge you to download the [EMUX Docker image][docker] and play with the emulated Tenda AC15. For those of you who want to learn about IoT exploitation, take this as a challenge and discover vulnerabilities on the AC15. Try to reproduce some existing CVE's against the emulated AC15, or find 0-days on your own! ### Upcoming articles - [Extracting the Tenda AC15's firmware directly from its SPI Flash chip][fw-extraction], and - Exploiting the vast array of vulnerabilities present in the AC15. Follow me on Twitter *[@therealsaumil][saumil]* for updates on [EMUX][emux], new articles, talks and trainings! ### BX LR [R0ARM]: https://ringzer0.training/arm-iot-firmwarelab.html [nvram_r6250]: https://github.com/therealsaumil/custom_nvram/blob/master/custom_nvram_r6250.c [emux]: https://armx.exploitlab.net/ [saumil]: https://twitter.com/therealsaumil [docker]: https://github.com/therealsaumil/emux [fw-extraction]: extracting-tenda-ac15-firmware.html