一个项目设备,记录下获取到固件的过程。
尝试1:串口提取(失败) 首先通过USB2TTL拿到了U-Boot shell。命令里没有md等命令,尝试通过设置bootargs环境变量方式来。
尝试2:mtd 挂载(失败) 还考虑使用 mtd 挂载的方式来读取,不过经过尝试,该方法仅适用于 ubi.img 没有错误的情况,否则会在 ubi attach 的时候报错。 对应的 flash 芯片为 GD5F2GQ5UExxG,对应文档链接:https://download.gigadevice.com/Datasheet/DS-00890-GD5F2GQ5UExxG-Rev1.6.pdf 可以看到比较重要的数据:
1 2 3 4 5 # 这四位是 read ID 返回的四个字节 Manufacturer ID: 0xC8 Device ID: 0x52 Byte2 Byte value: none Byte3 Byte value: none
提取到的 bin 文件差不多为 256MB ,共有 2k 个块 进行挂载不太行,首先是没有对应的 mtd simulated device,使用其他的类似的挂载上去也会发现有错误。
尝试3:ECC 修复(失败) 这里感觉是解决 bit flip 的最正规的方法,但奈何 提到的 flash 格式与 官方 datasheet 格式不一致,怀疑是厂商自定义了一套软件层的 ECC。以下仅作参考 感觉可以尝试利用起来 flash 的 ECC 了,如果真的是有 bit flip 存在的话。 直接在 linux 源码中找到了该设备对应的 布局源码:
1 2 3 4 5 6 7 8 9 10 SPINAND_INFO("GD5F2GQ5UExxG" , SPINAND_ID(SPINAND_READID_METHOD_OPCODE_DUMMY, 0x52 ), NAND_MEMORG(1 , 2048 , 128 , 64 , 2048 , 40 , 1 , 1 , 1 ), NAND_ECCREQ(4 , 512 ), SPINAND_INFO_OP_VARIANTS(&read_cache_variants_2gq5, &write_cache_variants, &update_cache_variants), SPINAND_HAS_QE_BIT, SPINAND_ECCINFO(&gd5fxgqx_variant2_ooblayout, gd5fxgq5xexxg_ecc_get_status)),
问了 gpt 可以知道基本信息:
page_size: 2048,每页2kb
oob_size: 128,每个 oob 区 128 字节
pages_per_block:每块 64 页
ECC 要求:每 512 字节 需要 4-bit ECC
按之前的理论,一个 page 一个 oob 区,其内存布局如下:
1 2 3 4 5 6 data1: 0x000 - 0x410 (size: 0x410) oob1/free: 0x410 - 0x43a (size: 0x2a) data2: 0x43a - 0x800 (size: 0x3c6) oob2/free: 0x800 - 0x802 (size: 0x2) data3: 0x802 - 0x82c (size: 0x2a) oob3/free: 0x82c - 0x880 (size: 0x54)
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 35 /mnt/SJTU/研究生/任务/Projects/码头/camera/haikang/firmware/most_common base ❯ hexdump -v -C -n $((2176*103)) ./most_common.BIN| tail -n 9 00036b00 ff ff 73 48 ef 6f 7c 68 e9 a1 6e 4a bd 8e 31 b6 |..sH.o|h..nJ..1.| 00036b10 75 d7 67 e3 f3 55 df ec fc cc 7a 17 41 41 ce 5c |u.g..U....z.AA.\| 00036b20 7c eb f6 4b 95 e5 da b0 72 86 55 c5 a3 6d 8d 81 ||..K....r.U..m..| 00036b30 7c 4a 8c 48 a6 b4 df 9f 31 f8 d3 76 2c 4c ec 6b ||J.H....1..v,L.k| 00036b40 c7 84 41 c9 88 e8 07 92 29 41 ab d3 3e 87 18 6a |..A.....)A..>..j| 00036b50 f6 ca 4e 9b 77 7c ff ff ff ff ff ff ff ff ff ff |..N.w|..........| 00036b60 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00036b70 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00036b80 /mnt/SJTU/研究生/任务/Projects/码头/camera/haikang/firmware/most_common base ❯ hexdump -v -C -n $((2176*13)) ./most_common.BIN| tail -n 9 00006e00 ff ff 5a e3 11 00 00 0a 40 20 c2 e3 01 50 a0 e3 |..Z.....@ ...P..| 00006e10 20 20 82 e3 00 20 83 e5 48 20 1b e5 01 3a a0 e3 | ... ..H ...:..| 00006e20 70 30 41 e3 00 10 a0 e3 08 20 83 e5 fb 77 2d 55 |p0A...... ...w-U| 00006e30 8b 33 62 35 c0 aa 38 84 74 70 11 80 5b 9e 7c d5 |.3b5..8.tp..[.|.| 00006e40 db b2 41 91 90 ef 8f 0f dd 5d f3 f0 cd 2a 0c 47 |..A......]...*.G| 00006e50 10 4f b7 0f 8f 68 ff ff ff ff ff ff ff ff ff ff |.O...h..........| 00006e60 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00006e70 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00006e80 /mnt/SJTU/研究生/任务/Projects/码头/camera/haikang/firmware/most_common base ❯ hexdump -v -C -n $((2176*3)) ./most_common.BIN| tail -n 9 00001900 ff ff db e5 00 40 8e e5 5c 00 00 0a 0b 00 00 3a |.....@..\......:| 00001910 04 00 50 e3 03 00 00 1a 2e 00 5b e5 70 00 ef e6 |..P.......[.p...| 00001920 1c d0 4b e2 f0 8b bd e8 00 30 e0 e3 53 23 5a 08 |..K......0..S#Z.| 00001930 6c 64 97 b8 68 c4 f8 6e d2 f8 a8 10 46 ff e4 14 |ld..h..n....F...| 00001940 ce d7 39 a9 7b 68 8b 56 cf 52 21 6c 92 17 e1 71 |..9.{h.V.R!l...q| 00001950 08 18 40 c6 2d bd ff ff ff ff ff ff ff ff ff ff |..@.-...........| 00001960 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00001970 ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff |................| 00001980
如上图所示,可见 oob2 区域固定为 0xffff 两个字节,最后 oob3 区域的最后 0x2a 字节固定为 0xff 编写脚本验证可知该条件成立:
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 import osBLOCK_SIZE = 0x880 violation_num = 0 blank_pattern1 = b"\xff" *0x2 blank_pattern2 = b"\xff" *0x2a const_pattern1 = b"\xff" *0x2 const_pattern2 = b"\xff" *0x2a with open ("./most_common.BIN" , "rb" ) as f: size = f.seek(0 , os.SEEK_END) print (f"target firmware size: {hex (size)} " ) block_num = size // BLOCK_SIZE for i in range (block_num): f.seek(BLOCK_SIZE*i+0x800 ) if i % 0x100 == 0 : print (f"read block ({i+1 } /{block_num} )" ) if f.read(2 ) != const_pattern1: print (f"block {i} violation pattern1!" ) violation_num += 1 f.seek(BLOCK_SIZE*i+0x856 ) if f.read(0x2a ) != const_pattern2: print (f"block {i} violates pattern2!" ) violation_num += 1 print (f"result: violations block num is {violation_num} , total block num is {block_num} ." )
也就是说有0x2c字节是固定值,剩下 0x80 -0x2c=84 字节即可能为进行ecc的部分,不过按照 linux 源码机制 应该是 有64字节为ecc,其他字节均为 free 字段,每 512 字节对应 4-bit ecc 这里放一下官方的 对应芯片 的 datasheet 吧:
据官方描述,应该是有64字节来用于 ECC 纠错 这里假设是要把原布局改为:data1 - data2 - data3 - oob2 - oob1 - oob3,然后可以得到正常的 2048 + 128 内存布局 有一个比较明显的例子: 在 0xa766990 地址,应该是直接将 数据data3 拼接到 data2 后面, 更新,这一步放弃了,看不懂 ecc 的格式,后面还是建议多次读,取众数吧
尝试2:flash dump固件(部分成功) 该方法也是我们最初采取的方法,但是当时提出来拿到 ubi 后发现各种方式都无法直接解析就暂时搁置了… 而且有一个很抽象的问题就是每次读取的 flash 内容都存在随机位置的 bit flip 。这个可能是使用热风枪吹 flash 时过热把 flash 烧坏了,不过后续还是部分绕过了该问题。
固件去除 ECC 段 查看从 flash 中 dump 到了固件,可以发现每隔0x800存在0x80的 spare padding 内容,需要进行patch,这一部分在后续继续查阅资料可以发现是 OOB(ECC)区域,并且该区域位置很奇怪(在0x410 有 0x2a 字节不知道是什么用处,在 0x802 后也有 0x2a 字节则是有效数据,需要保留)。但是发现每次dump到的flash不太一样,大概率是flash被吹坏了。
也有可能是 NAND 有一个 bit flip 的机制(不太懂,这个我觉得大概率就是flash质量不行了 XD
ubi 格式修复 使用 github 上的 ubidump repo 工具来解析。使用这个的目的是源码便于理解以及修改。 这里出现了另外一个问题是针对上面去除 ECC 后的 ubi.img 进行提取,只识别到app_pri, cfg_pri两个类型,以及只有一个文件。 通过调试发现可以通过修改 ubidump.py 的逻辑来改进,(应该是 vid_data,ubidump 没有集成多个 vtable 的能力,因此会默认选取最后一个 vtable[0]) 但是还是只能获取到部分,此时能提取到的文件从1个变为了1MB的文件系统,感觉还是损坏的问题(没错,就是 bit flip,导致有很多错误致使提取提前结束)。
brute force crc 这一操作非常的不优雅,但是却极其有效,因为基于了一个事实:发生 bit flip 的概率是比较小的,并且 ubi 对应的一个 inode data 的大小也不够大,因此在一个 node 中的 data 的 crc 校验失败大概率仅有一个 bit flip 导致,因此只要稍微 爆破 一下即可(实际上在爆破中确实有大 node 会导致卡比较久的时间) 首先在 listfiles 功能上进行实验,因为在 ubidump 提取文件时比如在 listfiles 功能中,解析的大多数 node 的 data 大小都比较小,可以尝试爆破的手段。 经过尝试非常可取,可以将所有的文件都列举出来(中间有一个 invalid node size 的报错,当然也是由于 bit filp 导致的 : ),这个会在最后再简单提及) 简单实现了一个假设只需要爆破1bit的情况的脚本。提取目标固件,可以成功提取到大小 38 MB 的文件系统。经过统计,共出现了422处需要爆破的位置,共有406处爆破成功crc。失败文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 test/app_pri/hik_ar9331_2.bin test/app_pri/heop/package/smart/dsp/model/ped_ied_detect_888/coef.bin test/app_pri/heop/package/smart/dsp/model/ped_ied_cls3d_888/coef.bin test/app_pri/heop/package/smart/dsp/libHMS_lib_V4.5_banding.a test/app_pri/heop/package/smart/fsa/lib/libobject_leave_event.so.1.1.0 test/app_pri/heop/package/smart/fsa/smart test/app_pri/app.tar.gz test/app_pri/h8_modules.tgz test/app_pri/webLib/doc/ui/images/pwd-reset.png test/app_pri/webLib/doc/page/panorama/image/refresh_click.png test/app_pri/webLib/doc/script/lib/jsPlugin/playctrl/Decoder.wasm test/app_pri/webLib/doc/script/lib/excanvas.js test/app_pri/webLib/doc/script/lib/angularjs/angularjs/angular.min.js test/app_pri/webLib/doc/script/playback/search.js test/app_pri/webLib/doc/script/config/faceAttendance/faceAttendanceAlgorithm.js test/app_pri/webLib/doc/script/config/system/newSettingVCAResource.js
然后发现还存在其他的报错类型,其实大概率也是因为仅靠 crc 来爆破并不是唯一解的原因,这种情况大概有68-16=52个,报错信息如下:
1 ERROR writing b'test/app_pri/curl', Compressed data violation -6
不过其实大差不差了,因为我们不需要模拟 : ),可以进行基本的二进制程序分析了。
接下来是另外一个问题:invalid inode size,也是 bit flip 的问题,直接修改掉即可 这里我们通过 listfiles 功能可以得到所有的文件数量为: 2135个(多了114个文件)
更新之前的结果,共有 72 个文件失败,19 个直接由于crc爆破失败导致。 似乎还有一些文件没有提出来,这个似乎是因为 vid_table 的选取问题,因为这一部分的修复为直接硬编码指定了对应 rootfs 的,其他的直接忽略掉了。