1. 威客安全首页
  2. 安全资讯

针对某款摄像头设备的固件重打包及root shell获取过程

Aodzip@海特实验室 H4lo@海特实验室

概述

最近在研究一款 IPC 摄像头的固件,想要固件模拟进行动态调试然后发现由于固件给的 lib 库出了一些问题,导致无法模拟。手里正好有实体设备,于是想到使用拆设备找串口的方式来进行调试,找到串口调试发现需要登录密码,且弱口令无法进入系统,于是又想到了使用固件重打包的方式去掉登录密码,所以对固件进行结构的分析以及对固件修改后的重打包,同时记录下遇到的问题和相应的解决方法,最终获取 root shell 的过程。

串口调试信息

在一波物理攻击拆开设备外壳之后,找到 uart 串口后进行终端调试的常规操作。根据串口的输出,在启动内核时,可以看到这里的内存地址映射:

  1. [0.394000]0x000000000000-0x00000001d800:"factory_boot"

  2. [0.405000] mtd: partition "factory_boot" doesn't end on an erase block -- force read-only

  3. [ 0.422000] 0x00000001d800-0x000000020000 : "factory_info"

  4. [ 0.433000] mtd: partition "factory_info" doesn't start on an erase block boundary -- force read-oy

  5. [0.453000]0x000000020000-0x000000040000:"art"

  6. [0.463000]0x000000040000-0x000000050000:"config"

  7. [0.473000]0x000000050000-0x000000060000:"boot"

  8. [0.484000]0x000000060000-0x0000001c7c00:"kernel"

  9. [0.494000] mtd: partition "kernel" doesn't end on an erase block -- force read-only

  10. [ 0.510000] 0x0000001c7c00-0x0000007b0000 : "rootfs"

  11. [ 0.520000] mtd: partition "rootfs" doesn't start on an erase block boundary -- force read-only

  12. [0.538000]0x0000007b0000-0x000000800000:"rootfs_data"

  13. [0.549000] mtd: partition "rootfs_data" doesn't start on an erase block boundary -- force read-ony

  14. [ 0.568000] 0x000000060000-0x000000800000 : "firmware"

这里我们需要关注以下 kernel 以及 rootfs 的内存地址范围,kernel:0x000000060000-0x0000001c7c00,rootfs:0x0000001c7c00-0x0000007b0000。这里的内存地址范围对我们后续的重打包工作非常重要。

在启动内核、系统初始化之后,我们会发现这里有一个需要输入密码的 shell 登录命令行,使用 root 用户加上弱口令进行登录不成功。于是对固件重打包并重新刷上固件就是本文需要研究的内容。

针对某款摄像头设备的固件重打包及root shell获取过程

固件分析

在主板上找到了存储文件系统的 flash 芯片,发现此芯片的存储容量只有 8M,用热风枪一顿操作将其取下并使用编程器连接得到其中的固件内容。

按照惯例,直接使用 binwalk 来分析其固件结构:

针对某款摄像头设备的固件重打包及root shell获取过程

这里发现此固件文件由五个部分组成,前四个部分都是压缩数据,uboot 和 kernel 都位于此部分的压缩数据中。最后一部分是 Squashfs 文件系统,我们这边的目的就是在文件系统中留下后门方便进行后续调试。

文件系统修改

常见留后门的操作主要有:修改 passwd 文件、在文件系统的初始化启动脚本中加入 telnet 后门。比较方便的是改动 passwd 文件中的 root 用户的密码,这里可以选择修改也可以选择删除密码(删除之后就无需密码就可以登录)。

查看 passwd 文件,这里的密码破解不出来,于是这里就进行了删除处理:

  1. root:$1$aG9UJ4ev$gDdMidRwq4Rm6wrrfxcfT0:0:0:root:/root:/bin/ash

  2. nobody:*:65534:65534:nobody:/var:/bin/false

  3. admin:*:500:500:admin:/var:/bin/false

  4. guest:*:500:500:guest:/var:/bin/false

  5. ftp:*:55:55:ftp:/home/ftp:/bin/false

即将第一行改成:

  1. root::0:0:root:/root:/bin/ash

在修改完此文件之后,需要做的就是对根目录进行重打包。因为目标文件系统是 Squashfs 格式的,所以很自然就想到了用 mkquashfs 这个工具对根目录进行打包。

mkquashfs 打包文件系统

squashfs-tools 项目编译和安装

在这里可以找到 squashfs-tools 项目的源码:https://sourceforge.net/projects/squashfs/files/squashfs/
下载 4.4 版本的项目到本地之后解压,进入 /squashfs-tools 子文件夹下,修改 Makefile 文件,去掉 XZ_SUPPORT = 1 这个注释(为了让此工具支持 xz 压缩算法)。

针对某款摄像头设备的固件重打包及root shell获取过程

接着 make & make install 即可。

打包命令

根据原来固件的输出,我们可以得到一些信息:

  1. Squashfs filesystem,// Squashfs 格式文件系统

  2. little endian,// 小端架构

  3. version 4.0,// 文件系统打包版本为 4.0

  4. compression:xz,// 此文件系统中采用压缩算法为 xz

  5. size:6192298 bytes,// 此文件系统的大小

  6. 1179 inodes,// 节点数

  7. blocksize:262144 bytes,// 文件系统块大小页大小

  8. created:2020-03-1802:05:12// 创建时间

这里的信息对我们来说比较重要的两个地方是:

  • 文件系统压缩算法为 xz

  • 块大小为 256K(262144 bytes)

那么我们在使用 mkquashfs 命令如下:

  1. mksquashfs squashfs-root/ squashfs-root.fs -comp xz -b 256K-nopad

打包的情况:

针对某款摄像头设备的固件重打包及root shell获取过程

在得到打包好的文件系统之后,按照常理,头部数据不改动,将其拼接到新的固件上:

  1. dd if=firmware.bin of=header.bin bs=1 count=1866752// 提取固件头部

  2. cat squashfs-root.fs >> header.bin // 将文件系统拼接到头部

对比整个固件文件的大小和文件系统的大小我们会发现:打包好的整个固件的大小是小于原始固件的,但是 Squashfs 文件系统的大小却比原始的文件系统大。导致这里原因的问题目前还不太清楚,可能是由于原始固件的文件系统打包方式并由不是与运行此命令的方式相同。

  1. h4lo@ubuntu:xxx$ ls -al header.bin firmware.bin

  2. -rw-rw-r--1 h4lo h4lo 8388608Jul1611:45 firmware.bin

  3. -rw-rw-r--1 h4lo h4lo 8084480Jul1612:24 header.bin



  4. h4lo@ubuntu:xxx$ binwalk firmware.bin

  5. ...

  6. 18667520x1C7C00Squashfs filesystem, little endian, version 4.0, compression:xz, size:6192298 bytes,1179 inodes, blocksize:262144 bytes, created:2020-03-1802:05:12



  7. h4lo@ubuntu:xxx$ binwalk header.bin

  8. ...

  9. 18667520x1C7C00Squashfs filesystem, little endian, version 4.0, compression:xz, size:6214768 bytes,1179 inodes, blocksize:262144 bytes, created:2020-07-1604:21:25

这样会导致一些问题,我们不妨先使用编程器将固件刷回 flash,用烙铁重新焊上 flash 到板子上。

问题定位

重新启动设备,查看 uart 串口的信息如下:

  1. Autobootingin1 seconds

  2. copying flash to 0x81500000

  3. flash status is0,0,0

  4. SF:Detected XM25QH64A with page size 256Bytes, erase size 64KiB, total 8MiB

  5. SF:8388608 bytes @0x0Read: OK

  6. verifying uboot partition...

  7. ok

  8. verifying kernel and romfs partition...

  9. failed


  10. Firmware check failed!

  11. Enter recovery mode.

  12. In: serial

  13. Out: serial

  14. Err: serial

  15. Net:RealtekPCIe GBE FamilyController mcfg =0024

  16. no hw config header

  17. new_ethaddr =00:00:23:34:45:66

  18. r8168#0

  19. Usingdefault environment

这里发现 verifying kernel and romfs partition. 是 failed 的状态。出现这种情况的原因主要有两种:一种是由于打包文件系统之后,在固件的某个地方做了某些验证或者类似 crc 的校验;还有一种情况我们在处理文件系统的时候内存偏移没有计算好导致的。

此时窗口是处于 boot 模式,可以操作一些命令:

针对某款摄像头设备的固件重打包及root shell获取过程

查看一下关于 boot 的启动参数,这里的 bootcmd=jmpaddr 0xbfc50000 代表跳转 0xbfc50000 这个地址中,而这个地址即内核在内存中的绝对加载地址中:

  1. rlxboot# printenv

  2. addmisc=setenv bootargs ${bootargs}console=ttyS0,${baudrate}panic=1

  3. baudrate=57600

  4. bootaddr=(0xBC000000+0x120000)

  5. bootargs=console=ttyS1,57600 root=/dev/mtdblock6 rts-quadspi.channels=quad

  6. bootcmd=jmpaddr 0xbfc50000

  7. bootdelay=1

  8. bootfile=/vmlinux.img

  9. ethact=r8168#0

  10. ethaddr=74:05:a5:4c:e1:b0

  11. gatewayip=192.168.1.1

  12. ipaddr=192.168.1.60

  13. load=tftp 80500000 ${u-boot}

  14. loadaddr=0x81500000

  15. netmask=255.255.255.0


  16. Environment size:435/131068 bytes

这里我们手动执行一下这个命令,会发现这里确实会跳转到内核启动的地方,但是很快又会发现这里 kernel 出现了 panic,出现问题的原因是文件系统的位置没加载对。

针对某款摄像头设备的固件重打包及root shell获取过程

  • 这里具体的原因是因为在使用 mkquashfs 命令进行文件系统打包之后的文件比原文件文件大,导致 kernel 在访问分区表时,找到了 squashfs 文件系统的绝对加载地址,以及 size 的具体数值,但是由于整个文件系统的 size 变大导致无法正常进行解压导致的错误提示。

mtd 分区表修复

由于这里的文件系统在分区表中的 size 数值增大,因此这里我们研究的重点就是关于此固件 kernel 部分 mtd 分区表的修复。

mtd 分区表分析

知道了整个固件文件的内存映射之后,我们在 010 editor 中打开原始固件,跳转到 kernel 部分:

针对某款摄像头设备的固件重打包及root shell获取过程

0x60000 地址开始的前 0x200 个字节就是 mtd 分区表,关注到 0x60028 到 0x6006f 部分的数据,发现这其实就是分区表项的各个偏移。具体的格式为:

  1. 地址开头+ size +地址开头+ size +地址开头+...

每个地址的开头对应了各个区段的起始位置,再加上一个 size 值之后对应了该区段的结束位置。这里总共是 8 个地址,正好就对应了上文 kernel 打印出来的分区地址信息。

地址  size     地址      size    地址     size     地址      size     地址     size     地址      size   地址      size      地址      size      地址0x0  0x1d800  0x1d800  0x2800  0x20000  0x20000  0x40000  0x10000  0x50000  0x10000  0x60000  0x200  0x60200  0x167a00  0x1c7c00  0x5e8400  0x7b0000
  • 多出的 0x60000 – 0x60200 这个区段为 mtd 分区表本身,在 kernel 的输出信息中没有体现。从 0x60200 地址开始的数据为内核的 lzma 压缩数据(使用 binwalk 可以快速定位)。

另外,这里 0x7b0000 这个地址就是文件系统的结尾地址,我们可以跳转过去看看,比较有意思的是这里有一个类似标志位的十六进制数,猜测 uboot 就是通过此标志来判断加载的文件系统开头地址和结束地址是否正确。

针对某款摄像头设备的固件重打包及root shell获取过程

分区修改

同样查看一下修改之后的固件,首先 mtd 分区表不变(没有进行修改),我们还是跳转到 0x7b0000 这个原来文件系统的结束位置:

针对某款摄像头设备的固件重打包及root shell获取过程

发现其实这里的文件系统的数据并没有结束,因为在上文也可以知道打包后的 squashfs 文件系统的 size 大了很多,所以在分区表对应的位置需要对文件系统的结束地址进行扩充。

针对某款摄像头设备的固件重打包及root shell获取过程

假设这里指定文件系统结束地址为 0x7b6000,那么 size 大小为:0x7b6000-0x1c7c00=0x5ee400。同时最后的 size 的值也要进行修改,这个大小为 rootfs_data 用户空间的数据段的长度,因为整体的 flash 空间大小是 0x000000800000,所以这个值需要变小,具体为:0x800000-0x7b6000=0x4a000。

因此这里在相应的位置进行修改,修改后的结果如下:

针对某款摄像头设备的固件重打包及root shell获取过程

修改完分区表项之后,需要在固件文件末尾以 0xff 进行填充到 0x7fffff 的地址位置。

  1. python -c "print('xff'*(0x7fffff-0x7b507c))">> modify.bin

之后在文件系统的结尾,也就是 0x7b6000 位置补上十六进制:0xDEADC0DE。

针对某款摄像头设备的固件重打包及root shell获取过程

固件重打包

将得到的固件重新刷入 flash 中就行了,开机查看 uart 串口信息,可以正常解压内核运行系统,同时使用 root 用户进行登录之后,就成功获取了摄像头的 root shell。最后,感谢 aodzip 师傅的帮助和指导。

针对某款摄像头设备的固件重打包及root shell获取过程

针对某款摄像头设备的固件重打包及root shell获取过程

注:本文由E安全编译报道,转载请注原文地址 

https://www.easyaq.com

推荐阅读:


▼点击“阅读原文” 查看更多精彩内容

针对某款摄像头设备的固件重打包及root shell获取过程

喜欢记得打赏小E哦!

原文始发于微信公众号(E安全):针对某款摄像头设备的固件重打包及root shell获取过程

本文转为转载文章,本文观点不代表威客安全立场。

发表评论

登录后才能评论