嵌入式设备固件提取与漏洞挖掘

前言

一些基础, 有需要, 总结一下.
又因为 “大流行” , 所以拖了许久.

root的shell获取

有root权限就可以为所欲为所欲为所欲为了, 比如说gdbserver写进去直接调试

uboot命令行

其中如成功进入uboot命令行, 那既可以尝试修改kernel的cmdline, 使之成功进入命令行
使用uboot中的printenv来查看env的值, 使用setenv/saveenv来保存env, 使用env来临时设置env, 最后使用run来启动指定的内容

比如这里有提示cmdline如下
image.png

其中一般变量bootargs中存在此字符串 且此变量在其他env中存在. 那么修改此env为/bin/bash后进行一个saveenv后, 直接重启就可获得交互式shell了

固件重打包

大概率是squashfs的只读, 也有JFFS2的可读可写;

修改主要盯得是开机的文件, 即/etc/init.d中的文件与关联文件; 直接加一句/bin/bash去启动shell即可, 同时直接改passwd也是蛮不错的选择.

squashfs

对于squashfs, 他是一个只读的文件系统; 如果将其raw给dump下来之后, 可以直接使用squashfs-tools套件中的相关命令来打包解包即可

如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
root@debian1:~/work/rootfs# mksquashfs squashfs-root/ packed_root_xz.sqsh -comp xz
Parallel mksquashfs: Using 2 processors
Creating 4.0 filesystem on packed_root_xz.sqsh, block size 131072.
[============================================================================================================================================================================================================================-] 320/320 100%

Exportable Squashfs 4.0 filesystem, xz compressed, data block size 131072
compressed data, compressed metadata, compressed fragments,
compressed xattrs, compressed ids
duplicates are removed
Filesystem size 1176.40 Kbytes (1.15 Mbytes)
30.48% of uncompressed filesystem size (3859.90 Kbytes)
Inode table size 2232 bytes (2.18 Kbytes)
20.17% of uncompressed inode table size (11068 bytes)
Directory table size 2762 bytes (2.70 Kbytes)
54.09% of uncompressed directory table size (5106 bytes)
Number of duplicate files found 0
Number of inodes 337
Number of files 302
Number of fragments 11
Number of symbolic links 18
Number of device nodes 0
Number of fifo nodes 0
Number of socket nodes 0
Number of directories 17
Number of ids (unique uids + gids) 2
Number of uids 1
unknown (1046)
Number of gids 1
unknown (1050)
root@debian1:~/work/rootfs# file *
mtdblock4: Squashfs filesystem, little endian, version 4.0, xz compressed, 1204620 bytes, 337 inodes, blocksize: 131072 bytes, created: Tue Mar 7 09:28:01 2024
packed_root.sqsh: Squashfs filesystem, little endian, version 4.0, zlib compressed, 1528898 bytes, 337 inodes, blocksize: 131072 bytes, created: Thu Jun 29 02:27:57 2024
packed_root_xz.sqsh: Squashfs filesystem, little endian, version 4.0, xz compressed, 1204636 bytes, 337 inodes, blocksize: 131072 bytes, created: Thu Jun 29 02:29:21 2024
squashfs-root: directory

这里的comp为squashfs的压缩格式, 理论上与原来保持一致即可

JFFS2

对于JFFS2, 他是一个可读可写的文件系统; 如果将其raw给dump下来之后, 需要先模拟为mtd后, 再对mtd进行挂载

参考这个 Mounting a JFFS2 dd image in Linux | Integriography: A Journal of Broken Locks, Ethics, and Computer Forensics (wordpress.com)

简单来说就是划了一块内存 然后直接挂载

1
2
3
4
5
6
7
8
umount /dev/mtdblock0
modprobe -r mtdram
modprobe -r mtdblock

modprobe mtdram total_size=32768 erase_size=$esize
modprobe mtdblock
dd if="$1" of=/dev/mtdblock0
mount -t jffs2 /dev/mtdblock0 $2

这里需要对mtd进行一些设置, 比如说块大小之类的; 这是将文件直接dd到模拟出来的mtd中, 如果想要直接在文件中修改的话需要将文件变为lo的设备, 需要使用losetup命令

1
2
3
4
5
6
modprobe loop
losetup /dev/loop1 "$1"
modprobe block2mtd /dev/loop1,128kiB
modprobe mtdblock
modprobe jffs2
mount -t jffs2 /dev/mtdblock0 "$2"

就是先losetup再block2mtd然后mtdblock再mount就行了

总之, 这个jffs2比较复杂, 且进了shell后这里面的文件也可以在线修改, 所以可以搞但是没必要离线改了

其他方法

比如说ttl上直接爆破弱口令, 直接进行adb连接, 进行命令注入反弹shell等等

提取固件

观察是否有网络/USB/TTL接口等可以交互的

通过命令行获取

如果能够获取到交互式命令行, 直接使用dd对mount的设备进行一个物理读取.

image.png

一般情况下直接进行一个设备到文件的复制即可

1
dd if=/dev/<dev> of=/mnt/<file>

如果没有usb设备可以使用nc通过网络读取, 如读取/dev/mtdblock4, 那么使用如下命令即可

1
2
dd if=/dev/<dev> | nc <host> <port>
nc -l -p <port> | dd of=<file>

结果如下

image.png

当然也可以用cat代替dd 用/dev/tcp代替nc等等

通过uboot获取

如果能够进入uboot的交互模式, 那么其中有一些命令非常有用, 可直接导出相关的内容
首先查看命令 发现如下命令很有用
image.png

那么其中part命令可以查看分区

image.png

查看完成之后, 可以根据信息, 直接通过extfatdownloadxxx命令刷入tf卡中的固件

通过存储

其中fatls可以查看相关的文件
其中extfatdownloadrootfs可以直接下载存储介质中的文件到flash中
image.png

同时现代的板子上大部分可以进行在线烧录, 在uboot中找到相关的命令即可. 如rkusb

通过网络获取

其中命令bootp看起来可以直接从网络启动
有的也有tftp命令可以直接下发上传

通过内存获取

通过printenv后, 可以发现有时候kernel加载方式如下

1
read_kernel=sf probe 0:0 ${sf_hz} 0; sf read ${loadaddr} ${kernel_offset} ${kernel_size}

即使用sf模块将存储中的值读取到指定内存中, 则可以直接使用如下命令获取指定存储的二进制raw文件

1
2
sf read ${loadaddr} <rawoffset> <rawsize>
md.b ${loadaddr} <rawsize>

如上命令的读取结果如下
image.png

物理方式

直接读取出物理存储.

当然这个物理存储层到逻辑存储层存在对应的转换; 这个就看具体的实现了, 有的会加密, 有的会有相应的转换表.
但是整体的思路中, 系统最终还是需要读取的, 所以这个玩意的读取方式肯定在uboot或linux的内核驱动中.

直接读取存储芯片

存储芯片吹下来, 上相关硬件去读取.
类似于直接读linux下的/etc/mtd1

其中nand通常由(Data+OOB)/Page/Block/Chip

其中Chip指代每一个nand存储芯片, 其由若干个Block构成; 其中Block由若干个Page构成; 而Page由主数据存储及辅助数据存储(Out-Of-Band, OOB)构成.

其中主数据存储即我们储存数据的地方; 而辅助数据存储一般储存辅助数据, 用于主数据存储的辅助, 如ECC纠错/元信息等.

比如该芯片的存储信息如下
image.png

按照文档 他有4352(=4096+256) bytes/pages * 64 pages/block * 2048 blocks = 544MB

那么直接读取后按照对应的文档对数据进行还原即可.

一般主数据存储即我们需要的数据存储

在线读取

直接接spi等协议所需要的线到对应的触点上, 然后读取即可. 不需要接VCC供电.

漏洞挖掘

简单来说, 即找输入点, 看能与什么交互

比如说能插存储卡, 能插USB, 能联网, 能连蓝牙; 找到这些入口点后, 根据固件内的程序进行分析.

查看网络通信, 进行中间人; 构造包测试远端服务是否有问题.