AR502H的分析遇到了一些瓶颈,缺少大量本该存在的二进制程序和脚本文件,目前又卡在一个证书的校验上,所以最近挑了一个看起来简单一些的换换脑子,另一个继续慢慢做,有进展了就继续往下写。
miio_client二进制程序
从小米25年出厂的某个固件上提取并获得,该路由器基于Openwrt完成二次开发,与过去的init启动方式不同,采用了openwrt的procd体系,/sbin/init拉起pid = 1后并调用其他程序加载大量模块后就执行exec,将pid = 1交给procd,由procd跟着init.d目录下的软链接拉起daemon和网络服务。而小米自己加入了一些魔改的私有协议和服务,这些闭源的协议不像openwrt的开源实现那样经过大量审计,更容易遇到漏洞。
miio_client就是这样一个程序,它通过私有协议与小米云以及用户的小米app进行通信,对此我查阅了一些相关资料:


此外,github上似乎还有一些放出的私有协议的具体报文格式,以及社区维护的工具,但相关性并不是很强。
本文主要面向该程序进行漏洞挖掘和逆向工作,因此关于固件提取不做详细记录。
根据官方文档可知,miio_client在小米很多的诸多IoT设备上都有部署,而不局限于路由器。它的部署主要分为nomqtt和带mqtt两种,这里我获取的是nomqtt(无mosquitto集成)的版本。
它主要与miio_client_helper_nomqtt.sh脚本相互发送json文本进行通信,启动后就监听127.0.0.1的54322端口,等待该脚本的主动连接。
这里我使用qemu-user-static搭配chroot建立实验环境,阅读过init.d下的miio_client的相关启动脚本后做出尝试:

成功运行,但是出现了报错,打开IDA通过搜索相关字符串锁定报错位置,简单逆向和重命名后还原如下:

可以确定主要报错原因是缺少data的路径,于是根据启动脚本手动mkdir了一个文件夹,并重新尝试启动:

观察其warning日志不难确定,它正在等待helper脚本的连接。
找到启动脚本运行:

/proc/mtd缺失,这里modprobe手动加载mtd模块并再次尝试运行helper脚本:


可以看到,helper脚本这边已经不再报错,一切正常,而miio_client这边出现了新的错误:did not int.
继续使用IDA搜索字符串定位逻辑:

这次的报错所在的逻辑位置较为深入和具体,需要至少6-7次交叉引用才能定位到主函数,所以我选择先恢复IDA符号,搭建动态调试环境并尝试追踪这条调用链展开逆向。
使用IDApython编写脚本
这里写了一个IDApython脚本用于恢复符号。虽然它是一个stripped的程序,**但是由于并不是采用plt桩来完成got表寻址,而是在text中直接寻址,因此got表虽然会被IDA识别出符号,却因为没有plt的引用,不会直接对got表项进行重命名,而是用off_xxx的格式对其进行命名,恢复时需要对其进行手动修改,让IDA识别符号,比较繁琐,所以用它来自动恢复:
from ida_segment import get_segm_by_name,getseg
from idautils import XrefsTo,XrefsFrom
from ida_name import get_name,set_name,SN_FORCE
def extern_find():
extern_seg = get_segm_by_name("extern")
extern_start = extern_seg.start_ea
extern_end = extern_seg.end_ea
extern_addr = (extern_start,extern_end)
return extern_addr
def traverse(start,end):
total = (end - start) / 8 + 1
total = int(total)
for i in range(0,total):
ea = start + i * 8
name = get_name(ea)
if name is not None:
text_member = XrefsTo(ea,0)
for txref in text_member:
found = False
got_member = XrefsFrom(txref.frm,0)
for dxref in got_member:
seg = getseg(dxref.to)
if dxref.to < start and seg.perm == 6:
res = set_name(dxref.to,"_" + name,SN_FORCE)
print(f"rename {name} success.")
found = True
break
if found:
break
def main():
extern_start,extern_end = extern_find()
traverse(extern_start,extern_end)
if __name__=="__main__":
main()随后搭建动态分析环境,使用qemu-user自带的-g参数,可以拉起它内置的gdbserver,监听使用qemu-user启动的程序;同时启动gdb-multiarch远程连接:


但是段错误崩溃了。使用gdb查看coredump文件,发现段错误发生在gdb内部,同时还有残留的/lib/ldxxx字符串,看到这里我大概明白应该是根路径识别错误的问题,上网搜了解决方案,得知可以用dbg的set sysroot来设置根目录,从而让dbg正确地识别到所需的库和动态链接器:

又对json的解析做了恢复,下次一起放出来。