TP-Link Archer MR200(EU)v7 固件解密
1、判断固件是否被加密
emba扫描
EMBA full-scan 自带熵分析(P02 模块)
P02_firmware_bin_file_check.sh
给出有关所提供的固件二进制文件的一些非常基本的信息。
主函数:
1 | P02_firmware_bin_file_check() { |
fw_bin_detector函数:
1 | fw_bin_detector() { |
binwalk
1 | binwalk -E --log e.json 固件.bin |
生成一个熵值图和一份 e.json 文件可以直接看到固件的熵值具体情况

我们可以看到"entropy"就是熵值的具体数值
0–1908 "entropy"= 6.948 混合区:0x200 明文 tag(0x0–0x200)+ RSA 签名 + 0xFF pad(0x200–0x330)+ 密文开头
1908 之后 "entropy"= 7.89–7.91 纯密文。
我们得出结论 固件整包加密
2、设备端升级链逆向 分析固件是如何被解密的
从 TP-Link GPL 下载 MR200V7_gpl_src.tar.gz ,解包后定位
1 | tar tzf MR200V7_gpl_src.tar.gz | grep -E 'libcmm|libgdpr|libtpcrypto' |


使用ida 打开 libcmm.so 进行分析 定位到函数 writeFlash,用于将固件写入到flash芯片中
1 | int writeFlash(uint addr, uchar *buf, uint len) { |
我们可以发现 writeFlash 中未有解密操作步骤
我们在ida中对函数 writeFlash 按x查看交叉引用情况

oal_sys_writeAppFlash 里的 util_findSystemProc("smbd"/"vsftpd")写 flash 前确认文件服务不在跑,然后 writeFlash(0x20000, buf, len)——0x20000 写死,印证固件在写入时候就已经进行了解密操作了
分析解密只能是 httpd,升级请求,上传固件是由 httpd 接收,但是httpd是被加密了我们无法得知加密的具体逻辑和解密密钥
3、解密固件
现在有两种方法:
1、手中有真实设备,直接读取真实设备的flash芯片,获取到 httpd 来分析加密逻辑
2、找到该设备最后未加密版本的固件,因为libcmm.so中并没有解密逻辑,那只能是在其中一个版本更新中在 httpd 中加入了解密逻辑,后续更新的固件都是加密的
我们选择第二种解决方案
历史版本固件下载
使用上述 binwalk 方法对各个版本固件进行分析
Archer_MR200(EU)v7_1.2.0_0.9.1
Archer_MR200(EU)v7_1.1.0_0.9.1
我们可以看到 Archer_MR200(EU)v7_1.1.0_0.9.1 版本时熵值是5.4左右,基本是压缩数据,而不是加密数据
提取httpd文件
1 | sudo pacman -S squashfs-tools |

使用ida打开httpd,shift+f12 从字符串中查找
1 | BgIAAACkAABSU0ExAAgAAAEAAQA1HoEmJgj8kEpP2t94/Uhr77Gq4YngLjb6yYKBQMIqnv+15hYCIp+4fulmQ99ek/KZ8kCsSgRFrR2Ona9ICjeB+xuOKkMIXjkl6s1NhvAYVeszWfgVmxxxYG2r6k07KkPgv1GbWD2hC/xZ+ZbeB9XetEJuFfdDAqydGumX70oPQJPinFenq2CDZlQDMVHpQP/yICSh48tGlbqApg96yNXDmlh/XPBssAIzyPdgoxEAVwgEDulcxl7sWhNQ3r+7TGu0+0LRh4DfgBYKoKeKQcq557FPcufauj1y9i1H5XybPM9iYJxVAW+nnMnmkGA7HPYYKnO+Ni+4E7gShMl4OFnE |
http_parser_illMultiObjForUpdate 函数是固件上传的处理函数
sub_408570 PSS验签 + AES密钥导出

固件解密操作

升级链

取出公钥 b64 字符串

base64 解码
解码表
1 | ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff,ff, |
其实这个就是正常的base64表 我们将上面的字符串进行转码

1 | 06 02 00 00 00 a4 00 00 52 53 41 31 00 08 00 00 01 00 01 00 35 1e 81 26 26 08 fc 90 4a 4f da df 78 fd 48 6b ef b1 aa e1 89 e0 2e 36 fa c9 82 81 40 c2 2a 9e ff b5 e6 16 02 22 9f b8 7e e9 66 43 df 5e 93 f2 99 f2 40 ac 4a 04 45 ad 1d 8e 9d af 48 0a 37 81 fb 1b 8e 2a 43 08 5e 39 25 ea cd 4d 86 f0 18 55 eb 33 59 f8 15 9b 1c 71 60 6d ab ea 4d 3b 2a 43 e0 bf 51 9b 58 3d a1 0b fc 59 f9 96 de 07 d5 de b4 42 6e 15 f7 43 02 ac 9d 1a e9 97 ef 4a 0f 40 93 e2 9c 57 a7 ab 60 83 66 54 03 31 51 e9 40 ff f2 20 24 a1 e3 cb 46 95 ba 80 a6 0f 7a c8 d5 c3 9a 58 7f 5c f0 6c b0 02 33 c8 f7 60 a3 11 00 57 08 04 0e e9 5c c6 5e ec 5a 13 50 de bf bb 4c 6b b4 fb 42 d1 87 80 df 80 16 0a a0 a7 8a 41 ca b9 e7 b1 4f 72 e7 da ba 3d 72 f6 2d 47 e5 7c 9b 3c cf 62 60 9c 55 01 6f a7 9c c9 e6 90 60 3b 1c f6 18 2a 73 be 36 2f b8 13 b8 12 84 c9 78 38 59 c4 |
我们可以让大d老师来分析一下这串内容
我们解出来的是 MS PUBLICKEYBLOB 格式的 RSA-2048 公钥
TLV 遍历
1 | 004085ac move $s1, $a0 ; $s1 = 固件buffer |

$s1 = 固件buffer + 0x200 =TLV 表起点

循环
1 | 00408930 lw $v0, ($s1) ; v0 = TLV.type |
签名反转 + modpow
1 | 00408948 addiu $a2, $sp, 0x118 ; temp 缓冲 |
PSS 解包
尾字节检查
**
1 | .text:00408A14 addu $v0, $s0, $s1 |
MGF1 + 异或
1 | 00408aa0 jal RSA_SHA256_MGF1(out=$sp+0x218, len=$s7=223, seed=$s0+$s7, 32) |
导出 key/IV
1 | 00408bfc memset(em, 0, 8) ; M' 的前 8 字节 0x00 |
AES-128-CBC
1 | from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes |
1 | import base64, hashlib |

4、验证解密结果
xxd -s 0x200 -l 32 decrypted.bin
xxd -s 0x160200 -l 16 decrypted.bin 
切出dd if=decrypted.bin of=rootfs.sqfs bs=512 skip=2817 count=18432
unsquashfs -d rootfs rootfs.sqfs
附录
1 | RSA-2048 公钥 (b64, MS PUBLICKEYBLOB, e=65537, 提取自 1.1.0 httpd): |