--- tags: Analysis --- # Xiaomi AX3600 免拆机搞事 ## 准备工作 - 拿到SSH权限 - 背景知识: - UBIFS vs UBI vs MTD vs NAND - NAND是最底层的存储设备 - MTD可以将一整块的NAND分成各个分区 - UBI在MTD基础上创建一层封装,处理坏块、纠错等 - UBIFS在UBI基础上,进一步支持更强的分区功能 - NAND读写 - NAND是不可靠的存储设备,所以在写入时需要特别小心 - mtdparts - mtdparts参数可以强制指定内核使用某一特定分区表来划分mtd - 语法:mtdparts=deviceid:size[@offset]\(name),... - U-Boot - 负责引导Linux内核,存储于0:APPSBL中,有哈希校验 ## 开始 ### I. 替换掉uboot - 风险:如果APPSBL没有被正确写入,设备将会直接变砖,仅可通过nand编程进行恢复 1. `cat /proc/mtd`,确认你的APPSBL分区是在/dev/mtd7 - 正常输出: ``` root@XiaoQiang:~# cat /proc/mtd dev: size erasesize name mtd0: 00100000 00020000 "0:SBL1" mtd1: 00100000 00020000 "0:MIBIB" mtd2: 00300000 00020000 "0:QSEE" mtd3: 00080000 00020000 "0:DEVCFG" mtd4: 00080000 00020000 "0:RPM" mtd5: 00080000 00020000 "0:CDT" mtd6: 00080000 00020000 "0:APPSBLENV" mtd7: 00100000 00020000 "0:APPSBL" mtd8: 00080000 00020000 "0:ART" mtd9: 00080000 00020000 "bdata" mtd10: 00080000 00020000 "crash" mtd11: 00080000 00020000 "crash_syslog" mtd12: 023c0000 00020000 "rootfs" mtd13: 023c0000 00020000 "rootfs_1" mtd14: 01ec0000 00020000 "overlay" mtd15: 00080000 00020000 "rsvd0" mtd16: 0041e000 0001f000 "kernel" mtd17: 0160a000 0001f000 "ubi_rootfs" mtd18: 01876000 0001f000 "data" ``` 3. 备份你的APPSBL: `nanddump -f /tmp/APPSBL /dev/mtd7`,并传回到电脑 - 正常输出: ``` root@XiaoQiang:~# nanddump -f /tmp/APPSBL /dev/mtd7 ECC failed: 0 ECC corrected: 0 Number of bad blocks: 0 Number of bbt blocks: 0 Block size 131072, page size 2048, OOB size 64 Dumping data starting at 0x00000000 and ending at 0x00100000... ``` 4. 在你电脑上确认APPSBL_signed的MD5是:41D91E1DC98E284086DFB17EBCB4B8EE 5. 文件传给路由器,在路由器上确认APPSBL_signed的md5相同 - 正常输出: ``` root@XiaoQiang:~# md5sum /tmp/APPSBL_signed 41d91e1dc98e284086dfb17ebcb4b8ee /tmp/APPSBL_signed ``` 6. 刷入我们的新的APPSBL: `mtd write /tmp/APPSBL_signed /dev/mtd7` - 正常输出: ``` root@XiaoQiang:~# mtd write /tmp/APPSBL_signed /dev/mtd7 Unlocking /dev/mtd7 ... Writing from /tmp/APPSBL_signed to /dev/mtd7 ... ``` 7. 重新读出确认md5:`nanddump -f /tmp/APPSBL_cur /dev/mtd7` 8. 比对md5: `md5sum /tmp/APPSBL_cur`,确保为0F0142B626067463E906B7F1D5903EF3(md5不同的原因是读出来后面会有0xFF的无用数据) 9. 重启 10. 若可以正常重启并在此进入系统,则恭喜,全程风险最高的第一步成功完成 ### II. 确认patch后的uboot是否正常工作 - 风险:如果extrabootargs设置错误,将会给内核传递错误的启动参数,导致内核panic - 抢救措施:nvram无法直接清除,但是我们开启了uboot的shell,如果不想售后或者被拒保,可以通过拆开机器,走ttl进行修复 1. `nvram set boot_wait=on`,开启boot_wait,这样即使出现最坏情况(损坏+被拒保)也可以手动ttl抢救一下 2. `nvram set extrabootargs=uart_en=1`,简单的uart_en参数 3. `nvram get extrabootargs`,确认自己写入的东西是uart_en=1 4. `nvram commit`后reboot, 5. 查看`/proc/cmdline`中是否出现了uart_en字样,如果出现了,那么恭喜第二步完成 - 正常输出: ``` root@XiaoQiang:~# cat /proc/cmdline ubi.mtd=rootfs root=mtd:ubi_rootfs rootfstype=squashfs uart_en=1 rootwait swiotlb=1 ``` ### III. 开始设置分区 - 风险:如果extrabootargs中的mtdparts设置错误,将会给内核传递错误的分区信息,导致内核无法挂载rootfs而panic - 抢救措施:同上 1. 添加test分区到extrabootargs中 ``` nvram set 'extrabootargs=mtdparts=qcom_nand.0:1m@1m(0:MIBIB),512k@6656k(0:APPSBLENV),512k@8m(0:ART),512k(bdata),36608k@10m(rootfs),36608k(rootfs_1),31488k(overlay),146688k@115456k(test)' ``` 2. ```nvram get extrabootargs```确认上述内容无误后,执行```nvram commit``` - 正常输出: ``` root@XiaoQiang:~# nvram get extrabootargs mtdparts=qcom_nand.0:1m@1m(0:MIBIB),512k@6656k(0:APPSBLENV),512k@8m(0:ART),512k(bdata),36608k@10m(rootfs),36608k(rootfs_1),31488k(overlay),146688k@115456k(test) ``` 4. reboot后,查看/proc/mtd,查看是否在mtd7上出现了test分区 - 正常输出: ``` root@XiaoQiang:~# cat /proc/mtd dev: size erasesize name mtd0: 00100000 00020000 "0:MIBIB" mtd1: 00080000 00020000 "0:APPSBLENV" mtd2: 00080000 00020000 "0:ART" mtd3: 00080000 00020000 "bdata" mtd4: 023c0000 00020000 "rootfs" mtd5: 023c0000 00020000 "rootfs_1" mtd6: 01ec0000 00020000 "overlay" mtd7: 08f40000 00020000 "test" mtd8: 0041e000 0001f000 "kernel" mtd9: 0160a000 0001f000 "ubi_rootfs" mtd10: 01876000 0001f000 "data" ``` 5. 若出现了,则说明分区成功 ### IV. 建立文件系统 - 风险:手残如果敲错了mtd或ubi的号码,则rootfs将直接gg - 抢救措施:可以通过uboot的救砖功能来还原 1. ```ubiformat /dev/mtd7```,格式化建立ubi layer - 正常输出: ``` root@XiaoQiang:~# ubiformat /dev/mtd7 ubiformat: mtd7 (nand), size 150208512 bytes (143.2 MiB), 1146 eraseblocks of 131072 bytes (128.0 KiB), min. I/O size 2048 bytes libscan: scanning eraseblock 1145 -- 100 % complete ubiformat: 1146 eraseblocks are supposedly empty ubiformat: formatting eraseblock 1145 -- 100 % complete ``` 3. ```ubiattach -p /dev/mtd7```,挂载ubi layer - 正常输出: ``` root@XiaoQiang:~# ubiattach -p /dev/mtd7 UBI device number 2, total 1146 LEBs (145514496 bytes, 138.7 MiB), available 1102 LEBs (139927552 bytes, 133.4 MiB), LEB size 126976 bytes (124.0 KiB) ``` 5. ```ubimkvol /dev/ubi2 -m -N rootfs_data```,将整个ubi layer分成一个rootfs_data分区 - 正常输出: ``` root@XiaoQiang:~# ubimkvol /dev/ubi2 -m -N rootfs_data Set volume size to 139927552 Volume ID 0, size 1102 LEBs (139927552 bytes, 133.4 MiB), LEB size 126976 bytes (124.0 KiB), dynamic, name "rootfs_data", alignment 1 ``` 6. ```PREINIT=1 mount_root```,重新挂载文件系统,用mount、df等命令查看是否出现/overlay,以及是否成功建立overlayfs - 正常输出: ``` root@XiaoQiang:~# mount mtd:ubi_rootfs on /rom type squashfs (ro,noatime) proc on /proc type proc (rw,nosuid,nodev,noexec,noatime) sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime) cgroup on /sys/fs/cgroup type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset,cpu,cpuacct,blkio,memory,devices,freezer,net_cls,pids) tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noatime) ubi1_0 on /rom/data type ubifs (rw,relatime) ubi1_0 on /rom/userdisk type ubifs (rw,relatime) mtd:ubi_rootfs on /rom/userdisk/data type squashfs (ro,noatime) ubi1_0 on /rom/etc type ubifs (rw,relatime) ubi1_0 on /rom/ini type ubifs (rw,relatime) tmpfs on /dev type tmpfs (rw,nosuid,relatime,size=512k,mode=755) devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,mode=600,ptmxmode=000) debugfs on /sys/kernel/debug type debugfs (rw,noatime) /dev/ubi2_0 on /overlay type ubifs (rw,noatime) overlayfs:/overlay on / type overlay (rw,noatime,lowerdir=/,upperdir=/overlay/upper,workdir=/overlay/work) ``` 7. 若出现,则代表创建成功 ### V. 自动挂载 - 风险:同III,此外,如果extrabootargs长度超过256-66=190字节,将会造成uboot缓冲区溢出而无法开机 - 抢救措施:同III 1. ```nvram set 'extrabootargs=ubi.mtd=overlay ubi.mtd=test mtdparts=qcom_nand.0:512k@6656k(0:APPSBLENV),512k@8m(0:ART),512k(bdata),36608k@10m(rootfs),36608k(rootfs_1),31488k(overlay),146688k@115456k(test)'```,重新设置extrabootargs 2. 使用nvram get确认无误后,执行nvram commit并重启 - 正常输出: ``` root@XiaoQiang:~# nvram set 'extrabootargs=ubi.mtd=overlay ubi.mtd=test mtdparts=qcom_nand.0:512k@6656k(0:APPSBLENV),512k@8m(0:ART),512k(bdata),36608k@10m(rootfs),36608k(rootfs_1),31488k(overlay),146688k@115456k(test)' root@XiaoQiang:~# nvram get extrabootargs ubi.mtd=overlay ubi.mtd=test mtdparts=qcom_nand.0:512k@6656k(0:APPSBLENV),512k@8m(0:ART),512k(bdata),36608k@10m(rootfs),36608k(rootfs_1),31488k(overlay),146688k@115456k(test) ``` 4. 若重启后mount中仍然有overlay,则恭喜你,操作成功 - 正常输出: ``` root@XiaoQiang:~# mount mtd:ubi_rootfs on /rom type squashfs (ro,relatime) proc on /proc type proc (rw,nosuid,nodev,noexec,noatime) sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,noatime) cgroup on /sys/fs/cgroup type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset,cpu,cpuacct,blkio,memory,devices,freezer,net_cls,pids) tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noatime) /dev/ubi2_0 on /overlay type ubifs (rw,noatime) overlayfs:/overlay on / type overlay (rw,noatime,lowerdir=/,upperdir=/overlay/upper,workdir=/overlay/work) ubi1_0 on /data type ubifs (rw,relatime) ubi1_0 on /userdisk type ubifs (rw,relatime) overlayfs:/overlay on /userdisk/data type overlay (rw,noatime,lowerdir=/,upperdir=/overlay/upper,workdir=/overlay/work) ubi1_0 on /etc type ubifs (rw,relatime) ubi1_0 on /ini type ubifs (rw,relatime) tmpfs on /dev type tmpfs (rw,nosuid,relatime,size=512k,mode=755) devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,mode=600,ptmxmode=000) debugfs on /sys/kernel/debug type debugfs (rw,noatime) ``` ## 后记 ### 起因 - 我分析了小米的固件(6.13)、lua混淆(6.14)后,开始捯饬这个破路由器。我们最开始的资源只有一个开启了ssh但是没有拆机的路由器,以及一个开启了ttl的拆机路由 - 小米直接基于18.06 snapshot的qsdk开发的,而openwrt目前完全不支持ipq807x,所以目前自己编译固件并不现实 - 我之前曾用过高通ipq4019的ea6350v3,问题和小米类似,我通过mtdparts强行设置了分区从而扩大了rootfs的空间 - 我首先分析的是小米的文件系统挂载,他通过一个自定义的preinit脚本挂载了自己配置的一个overlay分区,而不是采用openwrt正常overlayfs的方案 - ![](https://i.imgur.com/Y9J8Lf7.png) - 进一步分析发现,这个小米似乎只用了112M的NAND空间,而且其中因为双系统的原因,rootfs只有34M,但是实际上他有256M的巨大nand - 所以这次我们的目标是让他挂载后面剩下的134M空间 ### 6.15白天:分析uboot - 为了达成目的,那我们就需要传递bootargs。我分析了小米的uboot实现 - uboot构造了一种奇妙的elf结构,似乎将exidx数据直接盖在了bss段的上面,导致ida分析失败。此外我还注意到elf有两个诡秘的PT_NULL段 - 经过一大段时间的逆向,我还原出了他启动的整个流程 - 搜索字符串可以快速定位到设置mtdparts的地方,分析了一阵得知他并不会读取bootargs,并且没有任何可以修改bootargs的地方 - 向上回溯交叉引用得知他是通过init_sequence_r,执行main_loop,然后在mainloop中设置了bootcmd为bootmiwifi,然后bootmiwifi根据是否开启了secureboot,来判断使用do_boot_signedimg还是do_boot_unsignedimg(实际上就是qsdk的官方原版内容) - 接着,我转而向上追溯分区表到底是怎么来的 - 分区表在uboot里面显示是通过smem来读取的,恢复smem的符号后,可以发现他在读取9号,也就是SMEM_AARM_PARTITION_TABLE,读取出来的partition table - 相应的内存布局也是从smem里面读出来的,id是902 - 然后我去分析了SBL1,通过特征值902很容易就找出来了smem的相关函数,到网上搜了一波发现竟然有人有sbl1的源码,内存布局的结构体实际上是usable_ram_partition_table - 然而分区表的相关逻辑符号缺失太多,分析不出来 ### 6.15晚上:测试分区 - 然后,我联系到了开了ttl的大佬进行联调 - 第一个最大的问题就是:在uboot shell里面我也没法设置bootargs... - 回去逆向发现,他先设置了bootargs环境变量,然后再从环境变量里面读出来bootargs,写入到fdt里面,然后用bootm 0x44000000#config-name这种特殊语法启动的 - 接着逆向发现可以通过bootmiwifi debug来让他打印出来他要用的cmdline - 然后我从反编译的dts里面找到了configuration的相关信息,有config@ac02, config@hk07等 - 接着我想到,可以动态patch他的内存,从而改变他的逻辑 - mw 0x4a902f68 0xe3a00000 - mw 0x4a902f6c 0xe12fff1e - 直接改掉设置bootargs环境变量的函数让他直接返回,这样就会用我们的bootargs了 - 然后我发现这样改似乎并没有用。我盲猜是有cache的锅,毕竟之前搞苹果的时候也出过这种问题,找了一下发现果然有icache和dcache命令,直接icache off && dcache off,然后就能patch上了 - patch之后,我立刻开始透传mtdparts进去,发现传入`mtdparts=nand0:0x23c0000@0x2dc0000(rootfs_1),0x8f40000@0x70c0000(test)`会直接挂载不了rootfs,分析了一会bootlog过了一会我就意识到前面的nand0应该换linux kernel那边的名字qcom_nand.0 - 然后我发现系统似乎并不会将已有的分区表信息与mtdparts合并,而是直接采用mtdparts的分区表,这就导致了我们得补上整个分区表,所以我把所有的mtd分区都用mtdparts命令加了一波 ``` set mtdparts mtdparts add nand0 0x100000 0:SBL1 mtdparts add nand0 0x100000 0:MIBIB mtdparts add nand0 0x300000 0:QSEE mtdparts add nand0 0x80000 0:DEVCFG mtdparts add nand0 0x80000 0:RPM mtdparts add nand0 0x80000 0:CDT mtdparts add nand0 0x80000 0:APPSBLENV mtdparts add nand0 0x100000 0:APPSBL mtdparts add nand0 0x80000 0:ART mtdparts add nand0 0x80000 bdata mtdparts add nand0 0x80000 crash mtdparts add nand0 0x80000 crash_syslog mtdparts add nand0 0x23c0000 rootfs mtdparts add nand0 0x23c0000 rootfs_1 mtdparts add nand0 0x1ec0000 overlay mtdparts add nand0 0x80000 rsvd0 ``` - 然后发现这样生成的mtdparts太长了,缓冲区只有256那么长,前面的又会加一堆东西 - 所以我开始删分区,bootloader的sbl1、appsbl,分区信息mibib可以删,配置信息devcfg、cdt、rpm这些分区也都没用,最后发现不得不保留有APPSBLENV和ART,还有rootfs和overlay - 这样删掉之后,我组出来了这样的: ``` mtdparts=qcom_nand.0:512k@6656k(0:APPSBLENV),512k@8m(0:ART),512k(bdata),512k(crash),36608k@10m(rootfs),36608k(rootfs_1),31488k(overlay),146688k@115456k(test) ``` - 接着,我成功的格式化了后面的空间,并建立了分区,现在问题是怎么让openwrt原有的overlay能挂载上 - 搜索许久后,全盘爆搜/overlay发现他实际上是由mount_root这个程序控制的,mount_root程序执行的操作是挂载rootfs后,调用libfstools.so搜索名字叫rootfs_data的分区,然后把它挂成overlay。想要触发这个操作的前提是PREINIT环境变量需要被设置。我重新格式化了一下,建了个rootfs_data分区,然后mount_root果然正常工作了 - 接着我发现,为了在开机的时候让他自动挂载rootfs_data,我需要先把ubi挂起来,否则他根本找不到rootfs_data。常用linux的人都知道,linux的kernel的cmdline开关如果需要传多个值,往往是写好多个XXX=YYY进去,我猜测ubi.mtd也是如此。搜索一番后发现linux果然是这样实现的:https://github.com/bmork/openwrt/blob/master/target/linux/generic/pending-4.14/490-ubi-auto-attach-mtd-device-named-ubi-or-data-on-boot.patch ### 6.16凌晨:尝试patch uboot - 那为了让我们的操作不需要ttl也可以完成,我们必须要改uboot,我选择把uboot的格式化字符串作为patch位点,这样可以让没有ttl的人也方便的设置 - 我预计到写uboot是一件非常危险的事情,而摆在我面前的有nandwrite和mtd write两种方式 - 所以我开始在实验,我首先使用nandwrite往crash分区写入开启factorymode的特殊标志,发现可以 - 接着我将uboot要patch的那一页拿出来,然后写到crash里面,再将patch后的那一页再写进去,模拟我patch的流程 - 然后我就发现nandwrite有非常严重的bug,写了两次之后就会开始出现ecc fail,使用nanddump读取也会出错,那一部分的数据会直接丢失。而且不管指定不指定写入oobdata都会gg,需要mtd erase后才能好 - 所以我去看小米的刷机是怎么刷的。小米的刷机逻辑在libupgrade.sh里面,发现他其实是使用的mtd write来写的 - 我怀疑是不是用mtd写需要特殊的格式(trx),根据情报ax1800曾经更新过uboot,我看了一下啊,就是简单的我们原来的分区 - (mtd write必须整区写,所以我才非常害怕,一开始不想使用这个方式) - 确定是走nanddump读取+mtd write写是没有问题了之后,我开始了patch - 首先我读取出来了原来appsbl的内容,然后直接写回去,重新读出来比对md5、重启,一切正常 - 这说明我上面的方法是正确的 - 接着我用新的appsbl,重复上面的步骤,然后设置nvram、重启,结果直接gg - 闯祸了的我直接给朋友打了600,让他再买一个,这样京东白天下单晚上就能到货 - 由于机器直接死掉,我初步分析了可能原因 - 可能是我patch的有问题:patch的时候我已经把整个启动流程分析了一遍,出错的可能性极低 - 可能是我设置nvram的时候有问题:我只设置了uart_en=1,这个flag是合法的,而且就算不合法linux也不会崩溃,所以也不太可能 - 可能是写入失败:我写入后校验了md5,出错可能性非常小 - 可能是有校验:这应该是最大可能了 ### 6.16白天:分析事故原因 - efsg醒来发现自己的路由器gg了,好在我给钱很痛快他没多说啥hhh - uboot有自动的救砖功能,根据分析,只要长按reset键就可以进入tftp救砖功能 - efsg实验了一下发现不行,症状是灯亮一下立刻就灭了 - 救砖功能也完全无法进入 - 这说明uboot完全没有正常运行 - 但是以小米的能力,他大概率并不会在路由器上投入资金来让高通签名;同样,uboot后面也并没有cert,所以数字签名不可能存在 - 这说明一定有什么checksum在里面 - 有ttl的大佬醒来了,我继续测试了一下ubi.mtd,发现如果只填rootfs和test这两个分区,后面挂载小米自己的userdata时会失败,分析了kernel源码发现他会按照参数顺序挂载ubi,所以我改了一下让他按顺序挂载rootfs、overlay、test这三个 - 然后,趁着他在,我顺便用uboot备份了一份appsbl的内容,拖回来和朋友的比对了一下,然后发现竟然是不一样的 - 不一样的地方位于PT_NULL的不加载段,就是那个一开始我看到的PT_NULL段 - 里面有一些熵非常高的数据,感觉很像checksum - 但是我试了各种关键字搜索,比如qualcomm pt_null、qualcomm sbl - 顺便我还去github上面搜索了qualcomm pt_null,确实出现了一些高通相关的工具,但是当时缺少大体了解,明明找到了sectool,却被我忽略过去了 ### 6.16晚上:成功 - efsg拿到了新买的路由器,又给我开了ssh - 群内的有qsdk的大佬上线了,他给我发了一个他编译的uboot,我比对了一下,发现也有PT_NULL段,并且内容不同,这说明一定是某些可变的数据,群内头脑风暴得知这就是高通打包出来之后的mbn头 - 所以我clone下来了网上泄露的qsdk,开始分析 - 我误以为mbn是由uboot生成的,所以我在makefile里面到处找,但是似乎并没有找到 - 由于整个系统非常庞大,为了能快速定位到底是哪一个步骤加入的发我央求大佬给我发一份编译前、编译后、打包后各个阶段的uboot文件 - 同时我也找到了高通官方的文档,大概了解了一些术语https://www.qualcomm.com/media/documents/files/secure-boot-and-image-authentication-technical-overview-v2-0.pdf - 过了许久,我意识到应该搜索elf mbn,果然在github上面找到了一堆sectool的相关代码,我才意识到原来这东西归sectool管 - 然后我才发现,mbn实际上也是一种二进制格式,然后这种格式可以被elf包起来,但是加elf段这些操作在高通的sectool里面有另外的叫法,叫elf_has_ht - 这样穿起来全部的知识后便豁然开朗了,我们只需要分析sectools然后重建hash究竟可以 - 我以为需要分析出来他hash的算法,结果发现他代码结构有点复杂 - 所以我从他用法入手,发现他只需要在windows下执行python sectools.py secimage系列命令便可以执行签名、校验等操作 - 这个工具会读取芯片组的配置文件,根据配置文件中的文件名选择对应的操作模式,所以我们直接将要操作的文件重命名成对应名称即可 - 为了验证这个工具的正确性 - 我首先拿着原版uboot,用他来验证;然后我用改过的uboot去验证,发现也能通过。分析了一波的他的代码发现他原来直接没有实现validate功能 - 所以我用这个工具重新打包了原版的uboot,发现打包后和打包前只有地址上的一些小差别,这说明这个工具结果实际上是正确的 - 所以我将我原来的patch用这个工具重新计算了hash - 命令:python sectools.py secimage -p 807x -t -i D:\TempWorkspace\XiaomiRouter\openwrt-ipq40xx-u-boot-stripped.elf - 重新生成后,在用原来的地址信息写回去,这样就和原来的uboot一模一样了! - 最后我传入到了机器中,再次刷写,成功搞定~