Featured image of post IoT安全入门之固件分析

IoT安全入门之固件分析

分析目标是华为HG532

前言

想学IoT安全,但前置知识过于庞杂,现有的教程往往默认读者有固件分析和逆向基础。我的基础并不好,几乎看不懂文章,不知道每一步在做什么,而系统补基础容易变成空中楼阁且劝退,因此陷入了两难的境地

这篇文章不分析漏洞,主要用于熟悉IoT安全的基本流程和逆向分析

环境搭建

固件获取

首先是通过漏洞编号确认路由器型号与固件版本,比如这篇文章的华为HG532 CVE-2017-17215 固件,得到一个二进制文件HG532eV100R001C01B020_upgrade_packet.bin,这就是整个设备的软件镜像

主要工具

固件分析用到的各种工具更适合Linux环境,因此我使用Ubuntu 24.04.4作为宿主机

  • binwalk:用于解包和分析固件,需要配套工具sasquatch
1
2
3
4
5
sudo apt install binwalk

git clone --quiet --depth 1 --branch "master" https://github.com/devttys0/sasquatch
cd sasquatch
wget https://github.com/devttys0/sasquatch/pull/47.patch && patch -p1 < 47.patch && sudo ./build.sh
  • ida:对 ELF 程序进行静态分析
  • qemu:路由器固件一般是一套嵌入式Linux系统,适配路由器主机,无法在ubuntu下直接运行。为了让程序真正跑起来,需要用到QEMU。它模拟了固件运行所需的真实设备环境,翻译不同架构的指令
1
sudo apt install qemu-user-static qemu-system

固件初步处理

固件解包

使用binwalk进行解包,也就是从固件的二进制文件中提取出文件系统。解包后的文件系统在squashfs-root/目录下

1
binwalk -Me HG532eV100R001C01B020_upgrade_packet.bin

意思是binwalk会扫描文件里的特征,找到文件系统后递归扫描里面的内容

  • -e :尝试提取
  • -M: 提取后继续递归扫描里面的内容

文件分类

进入 squashfs-root 后,可以看到类似Linux系统的目录结构。binsbin 中是可执行程序,etc 中是配置文件,lib中是动态链接库

注意到vscode只能打开etc中的文件,可执行文件打开会乱码。这是因为它只能打开文本类文件,可执行文件是编译后的机器指令

使用下面的命令批量判断这些目录里每个文件的类型

1
file bin/* sbin/* etc/* lib/* usr/bin/* 2>/dev/null

这里筛选几个有代表性的输出:

1
bin/busybox: ELF 32-bit MSB executable, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, no section header

这是一个 ELF 可执行程序

  • ELF:Linux 下常见的二进制文件格式
  • 32-bit:这是 32 位程序
  • MSB:大端序,也就是高位字节放在前面
  • MIPS:这个程序的架构是 MIPS
  • MIPS32 rel2:MIPS 指令集版本
  • dynamically linked:程序运行时需要依赖动态库
  • interpreter /lib/ld-uClibc.so.0:程序启动时需要通过这个动态链接器加载运行
  • no section header:文件中没有 section header,很多固件程序会被精简掉这部分信息
1
2
3
bin/cat: symbolic link to busybox
bin/ls:  symbolic link to busybox
bin/sh:  symbolic link to busybox

这种文件是指向BusyBox的符号链接。它们并不是实现命令的二进制程序,真正的程序是busybox。因此无需分析这类文件,分析 busybox即可

嵌入式设备为了节省存储空间,经常用一个BusyBox实现很多常用命令。比如系统调用 ls 时,实际运行的是 BusyBox,只是 BusyBox 会根据自己被调用时的名字执行对应功能

1
sbin/init: symbolic link to ../bin/busybox

init 和系统启动流程有关,也指向 BusyBox

1
bin/startbsp: POSIX shell script, ASCII text executable

这是 shell 脚本,是文本文件,可以直接打开阅读。ASCII text executable 表示它是文本且可执行。脚本能直接看出某些启动命令或配置逻辑,可以优先分析这类文件

1
bin/sys: symbolic link to /dev/null

指向 /dev/null,也就是 Linux 里的空设备。写入的东西会被丢弃,也读不出来正常内容。所以这是占位或被废弃的命令

1
2
3
etc/inetd.conf: ASCII text
etc/inittab:    ASCII text
etc/profile:    ASCII text

这些是普通文本文件,可以直接打开阅读。一般是重要的配置类文件

1
2
etc/servercert.pem: PEM certificate
etc/serverkey.pem:  PEM RSA private key

证书和私钥相关文件

  • PEM certificate:PEM 格式证书
  • PEM RSA private key:PEM 格式 RSA 私钥
1
lib/extra: directory

这是普通目录,需要继续进入目录内部扫描文件

1
2
lib/ld-uClibc-0.9.30.so: ELF 32-bit MSB shared object, MIPS, MIPS32 rel2 version 1 (SYSV), static-pie linked, no section header
lib/ld-uClibc.so.0:      symbolic link to ld-uClibc-0.9.30.so

这是动态链接器相关文件。前面很多 ELF 程序里都有 interpreter /lib/ld-uClibc.so.0,说明这些程序运行时会通过这个路径找到动态链接器

  • ld-uClibc.so.0:程序要找的动态链接器路径
  • ld-uClibc-0.9.30.so:实际存在的具体版本文件
  • symbolic link:把通用名字指向具体版本
1
lib/libhttpapi.so: ELF 32-bit MSB shared object, MIPS, MIPS32 rel2 version 1 (SYSV), dynamically linked, no section header

这是动态库,是供其他程序调用的库文件。固件中的程序运行时会依赖这些 .so 文件

  • shared object:共享库文件
  • MIPS:库文件的架构是 MIPS
  • dynamically linked:它本身也可能依赖其他库
  • no section header:文件中没有 section header

启动流程

这个固件里 /sbin/init 指向 BusyBox,BusyBox 的 init 会读取 /etc/inittab 作为启动配置。所以观察启动流程首先看 etc/inittab

1
2
3
4
5
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh

# tty2::askfirst:-/bin/sh
#::ctrlaltdel:/bin/umount -a -r
  • ::sysinit:/etc/init.d/rcSinit 启动后,会在初始化阶段执行 /etc/init.d/rcS

  • ::respawn:-/bin/sh:反复拉起一个 shell。 -表示让 shell 按 login shell 方式启动, login shell 会读取 /etc/profile~/.profile这类配置

  • # tty2::askfirst:-/bin/sh:控制台登录。这行被注释了,不会执行

  • #::ctrlaltdel:/bin/umount -a -r:响应 Ctrl+Alt+Del 这类重启动作, /bin/umount -a -r 是卸载文件系统。这行被注释了,不会执行

这里主要需要查看两个文件:etc/init.d/rcS/etc/profile

etc/init.d/rcS只打印信息和设置 PATH,没有继续启动其他服务。这条线断掉了

1
2
3
4
5
6
#!/bin/sh
/bin/echo "rcs"
PATH=/sbin:/bin:/usr/bin:/usr/sbin
export PATH

echo "RCS DONE"

/etc/profile才是真正的目标,摘取其中重要的命令如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//脚本调用
swapdev
startbsp
// 后面带有&的是后台启动,持续运行的程序
test -e /bin/atserver && atserver &
test -e /bin/usbdiagd && usbdiagd &
atmcmdd &
tcwdog -t 1 /dev/watchdog &
//最后的前台程序
mic

初步分析刚刚筛选出来和启动相关的程序:

bin/swapdev:环境准备的脚本,创建设备节点

bin/startbsp:环境初始化的脚本,挂载目录

bin/atserver:没有找到对应文件,可能是残留逻辑

bin/usbdiagd:没有找到对应文件,可能是残留逻辑

bin/atmcmdd:后台启动的 ELF 程序,常驻运行

bin/tcwdog:后台启动的 ELF 程序,和看门狗相关。这里真正被启动的是 tcwdog/dev/watchdog 是传给它的设备节点参数

bin/mic:最后执行的ELF程序,没有&说明 profile 执行到这里后,它可能会接管后续流程

逆向分析

比较有分析价值的是micatmcmdd,这里先分析mic

用ida打开,首先确认识别的文件类型与架构和之前file命令输出的一致,其他保持默认即可

打开Functions window窗口,它就是IDA 识别出来的函数目录

这里的函数名分为几类:

  • _init:前面带有下划线的函数,一般是ELF和运行库产生的,可以不重点分析
  • sscanf:比较熟悉常见的名称,是libc 或系统接口,无需单独分析
  • sub_40xxxx:真实命名未知的函数,以地址临时命名
  • ATP_xxx_yyy:这里有大量ATP开头的函数,根据命名猜测是这个固件的功能函数。其中xxx是功能分类,yyy是具体操作
    • ATP_UTIL_xxx:util分类是通用功能,比如进程、socket、定时器。如ATP_UTIL_ForkProcess是util下创建子进程的函数
    • ATP_MSG_xxx:msg分类是消息与通信的实现,如发送、队列等。如ATP_MSG_SendSimpleMsg就是msg分类下发送简单信息的函数
    • ATP_MIC_xxx:如ATP_MIC_SockSvrCreatemic分类下创建socket服务的函数。暂时看不出这个分类具体是什么功能,注意到有些函数调用了msg

双击一个函数进入,可能出现两种情况:

  • 只有短短一行,可以看到.extern,说明引用了这个函数,但它的实现代码不在这个文件里。也就是说这部分保存的是调用关系
  • 出现这样的流程图,说明真正进入了函数的实现逻辑。在这里可以按F5反编译,查看伪代码分析具体功能

注意:如果F5报错Sorry, the current file is not decompilable,检查是否用ida64打开了32位程序,分析32位程序时应该使用普通版本的ida

打开Imports窗口,这里记录了程序调用的外部库函数,也就是.extern

把光标放到函数上,然后按 X看交叉引用,这样就能看到程序里调用了这个外部函数的所有地方。这里主要分析调用时的上下文

注意到只有ATP_MIC_xxx在这个文件中实现,其他两个分类都是外部引用。下一步回到main函数,根据实际调用关系继续分析

Main函数分析

双击main函数进入它的实现逻辑界面,首先看一下程序的流程图

开头

这部分的意思是进入main后先检查启动参数。可以看到main(int argc, char **argv, ...)argc是参数个数,argv是参数内容。这里先判断参数个数是不是2,然后取出argv[1],调用strcmp把它和字符串"atpv"比较。如果返回0说明两个字符串相同,所以启动参数是atpv时,程序会进入左侧,否则进入正常启动流程

左侧这部分调用了GetVersion,并且在最后直接退出,判断是用于打印版本信息

初始化阶段

继续分析右侧。puts打印"Start mic now ..."说明这里是启动阶段。signal是注册信号处理函数。最后调用了init,是函数初始化部分

如果这里初始化失败,会输出错误信息,然后直接来到结束阶段

这里注意到报错输出直接说明了每一部分的作用

"Create inetd app failed: %x.\n"

创建一个inetd app,也就是一个服务入口表,每个app是其中一个服务项

"Append stdin to inetd app failed: %x.\n"

stdin加到socket server里,说明它能接收来自标准输入的数据

"Create msg svr failed: %x.\n"

创建msg server,说明 mic 有消息通信功能

"Load cfm failed: %x.\n"

初始化 CFM 相关数据,也就是和配置与管理相关的部分。成功后便进入inetd app的启动阶段

启动阶段

这里首先打印了CFM正常的信息,然后检查dns服务。如果已经启动,会打印Init app already start.并继续,否则调用ATP_MIC_InetdLaunchApp进行启动。这里还出现了Init kill pid = %d. ,说明会清理已有进程

"Create monitor svr failed: %x.\n"

这部分是创建监听模块,用于监听后续事件

"Mic start return: %x.\n"

启动logcms,让它们在后台运行,然后启动socket基础服务,让前面准备好的功能开始运行。调用结束后再检查返回值

结尾

最后进入 main 的结尾,清理前面创建的inetdsocket环境,然后返回0

到这里已经将main函数的主要流程分析了一遍。后续可以通过main函数调用的功能函数来深入分析

QEMU分析

前面的静态分析可以看到,micmain函数会检查启动参数。如果参数是atpv,程序会进入版本信息分支打印结果,然后退出

这里用 QEMU 进行验证。之前的file命令显示程序是大端 MIPS,所以用qemu-mips-static进行模拟,传入atpv参数

1
qemu-mips-static -L . ./bin/mic atpv

可以看到如之前分析所见,输出了版本信息

Like 0
本站已不稳定运行 小时 分钟
共发表文章 33 篇 ,总计 162.49 k 字
本站总访问量: