首页
社区
课程
招聘
[原创]小米某25年路由器固件分析
发表于: 1天前 228

[原创]小米某25年路由器固件分析

1天前
228

在小米官网上扒的固件,直接拿来用了


首先binwalk扫描一下,发现是ubi文件系统

手工提取ubi需要ubireader_extract_images,并且还有字节对齐要求,所以先用binwalk直接解包试试


成功提取了两个有效文件,一个是kernel,一个是rootfs





虽然文件尾缀是ubifs,但是hexdump一下可知开头的魔数是 68 73 71 73,也就是squashfs,同时留意到binwalk的报告和hexdump下面还有7zXZ,所以我尝试解压了一下


不过,直接用binwalk或者unsquashfs就能提取了,下面出现了一堆warnning,应该都是软链接丢失的告警,稍后需要修复,这里先把它们存成txt便于一会儿用sed和awk处理



又出现了一个0.squashfs.bin,扫描后发现跟之前的扫描结果差不多,应该是备份或者神秘报错


先不管它,核心文件夹是squashfs-root

随便切一个目录进去,找个文件检查一下架构,发现是aarch64


检查一下inittab,fstab等文件,确定启动方式,挂载文件等
是非常经典的rcS脚本读取rc.d目录下的服务文件并按照S*的顺序启动,不过我没找到rcS脚本,但这个影响不大,证明已经被procd替代了,但procd也会按照那个顺序读取并启动
读etc下的fstab,发现啥也没有?
inittab没有mount的硬编码,S*里也没有mount,一会儿模拟固件时只能尝试挂一下tmp,proc和sys


写一个仿真脚本和一个软链接修复脚本,然后启动
随便找一个之前坏的软链接检查一下,发现软链接已经被修复


现在开始检查rc.d里的启动服务

有不少眼熟的东西,从S*按编号找(也就是启动顺序),有firewall,nginx,cron,telnet,mosquitto,baidupan,mi_docker,除此之外还有一些小米的自研服务

先观察一下telnet的脚本,开头是#!/bin/sh /etc/rc.common,这意味着内核会去掉#!后执行:

[解释器] [可选参数] [脚本路径] [脚本参数]
也就是/bin/sh /etc/rc.common /etc/init.d/telnet boot(rcS自带的boot,procd也一样)


去检查一下rc.common,就是把调用/etc/init.d/telnet时后面跟的参数读出来存进$action,放进procd框架里执行,然后运行$action "$@"(其实这个$@啥也没有)
观察到rc.common里有一个start_service()函数,里面只有一个return 0,但是后面又有一个. "$initscript",这意味着它会被telnet脚本里的这个函数覆盖

因此,telnet启动的完整流程就是:
执行rc.common telnet start
$action=start,执行start函数,由于source $initscript的存在,start会调用telnet脚本里的start_service,也就是这东西:

看起来它是检查ssh是否有公钥并且存在于/etc/shadow里,如果不存在才能执行$PROG,也就是/usr/sbin/telnetd,但很遗憾的是,它被注释了

因此telnet必定启动,然后检查login.sh,也就是PROG指定的登录脚本,它是决定身份认证和是否给shell的关键


这是login.sh的脚本,判断root的密码的状态,如果是正常哈希就不使用login,直接拒绝;如果是空密码或者锁定状态,就会进一步判断ft_mode是否等于1,可能是factory_test工厂模式,如果ft_mode=1,那么就可以无需密码,直接用telnet登录,否则就需要密码

这里我尝试通过/etc/init.d/telnet启动它来模拟登录情况,但是由于是qemu-user模式所以没有ubus,所以失败了,后面可以用qemu-system模拟一下


总地来说,这部分没什么漏洞可言,如果想利用,前提必须是ft_mode=1并且root密码出问题,而如果想达成这样的条件,那必然需要任意文件写


接下来分析其他东西


之前注意到有一个nginx,START=21,这个东西也可以研究一下,一般来讲iot设备的nginx为了符合需求,都是自己的魔改版本,所以可能会有特殊漏洞


之前已经分析了rc.common的模式,所以这里它的流程跟telnet区别不大,我们可以直接来分析start_service函数:


这里可以看到它open_instance后执行/usr/bin/spawn-fcgi -a 127.0.0.1 -p 8920 -U nobody -F 1 -- /usr/bin/fcgi-cgi

随后有两个[ -d xxxxx] || mkdir,这是在判断,如果不存在/var/log/nginx和/var/lib/nginx目录,就自动创建这两个目录


然后调用init_config,实现如下:



这是将/etc/config/default-locations.conf里的client_max_body_size做一个替换,来源是local _body_size=$(uci -q get nginx.main.client_max_body_size),可以直接执行uci -q get nginx.main.client_max_body_size查看,结果是50M

再依葫芦画瓢执行一下uci -q get nginx.main.force_https,发现输出是0,那第二个if-fi就不用看了


第三个if是把“include /etc/nginx/default-locations.conf”放进$HTTP_LOCATIONS里,但此变量的值暂时未知

最后执行/usr/sbin/nginx -c /etc/nginx/nginx.conf -g 'daemon off;'

感觉这个过程可以被模拟一下,我们先手动替换/etc/nginx/default-locations.conf里的max_body_size,顺便创建那两个目录,也写进启动脚本里避免麻烦

随后,可以把/usr/bin/spawn-fcgi,/usr/bin/fcgi-cgi和/usr/sbin/nginx提取出来,一会儿用IDA研究一下


先读一下/usr/bin/spawn-fcgi的help

所以/usr/bin/spawn-fcgi -a 127.0.0.1 -p 8920 -U nobody -F 1 -- /usr/bin/fcgi-cgi
是绑定本机8920端口,将程序的所创建的Unix domain socket的属主改为“nobody”的id,并创建一个子进程运行fcgi-cgi


这里用qemu自带的strace观察一下spawn的系统调用,但是跟踪到clone以后就停了,不会追踪子进程,查阅文档发现qemu的strace并不提供-f这种追踪子进程选项,所以需要逆向一下,找到它传给子进程的参数


先检查基本信息:

其中ldd是需要对应架构,这是输出结果:
/ # ldd ./usr/bin/spawn-fcgi
    /lib/ld-musl-aarch64.so.1 (0x780e21c90000)
    libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x400000827000)
    libc.so => /lib/ld-musl-aarch64.so.1 (0x780e21c9000)



打开IDA,定位主函数位置是VA:0x12A0位置
节头表被删了,导致got.plt现在只能显示LOAD


尝试用gdb和qemu自带的gdbserver动态分析,但是gdb直接崩了,可能还是/proc的问题,失去了动态分析手段,只能根据strace的系统调用尝试静态分析

有一个clone系统调用,IDA里没有clone,但是有一个fork创建子进程,直接跳转



这是我还原的子进程执行的代码,i64会放在帖子末尾留待下载

这部分做的事就是根据-F参数后面的值来决定循环创建几个子进程,然后根据某变量的正负决定是否把它写进环境变量(关于v30部分,我猜测是用0号文件描述符来替代v30这个fd),接着,如果fork是被允许的,就把自身守护进程化,最后执行路径程序


这是一个完完全全的父进程,fork后生命周期基本就结束了,不属于daemon,因此我没有尝试找内存破坏漏洞。


晚上可能还会继续研究fcgi-cgi


文件有点大,压缩也没法传,这是网盘链接:
 d01K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5h3&6Q4x3X3g2T1j5h3W2V1N6g2)9J5k6h3y4G2L8g2)9J5c8Y4y4Q4x3V1j5I4h3V1N6c8L8p5k6x3c8q4y4d9e0h3M7&6M7U0S2i4x3%4f1^5y4p5#2b7f1g2)9K6c8Y4m8%4k6q4)9K6c8o6j5$3y4U0j5`. 提取码: 6666 
--来自百度网盘超级会员v3的分享


传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!

最后于 1小时前 被Pgldxx编辑 ,原因:
上传的附件:
收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回