因为各种原因,手上有一个中国大陆版的 TL-IPC6128-EZ,tplink为了不让用戶將它拿去其他国家用,可谓是下了真功夫。
其中最坑的就是OSD的水印时间是写死的中国大陆时区CST-8。
除了手动设定时间、搭建一个做过手脚的ntp伺服器以外,我选择看看机器备份档config.bin是否包含时区信息,于是有了这篇文章。(剧透:有时区信息,但是改了也没用)
整个过程踩了不少坑,记录下来。
1. 整体结构
config.bin
├─ [0x00:0x10] MD5(ciphertext) ← 唯一的明文头
└─ [0x10:EOF ] DES-CBC( 外层明文 ) ← 单 DES,key 来自设备encrypt_key,IV=0
├─ 0x000 TP_HEAD(0x18) + vendor_id/zone_code + 长度字段
├─ 0x200 顶层 NVMP-CONFIG 容器:magic + product_id + datalen
config_file_head:
+0x00 "NVMP-CONFIG\0" // 12 字节 magic
+0x10 product_id u32 BE // product_id
+0x14 datalen u32 BE // 其后所有节点字节数
└─ 0x218 配置节点(node)
├─ +0x10 count(LE) // JSON 条目数
├─ +0x14 clen(LE) // zlib 压缩流长度
├─ +0x18 ulen(LE) // 解压后长度
└─ +0x1c body = DES-CBC( zlib( item0\0 item1\0 … ) ) // 内层再一层 DES + zlib
外层与内层各有一层 DES(同 key、同 IV=0),内层 DES 之内再套 zlib,zlib 之内是一串 NUL 分隔的 JSON 配置表。加密为单 DES,非 AES。
key不知道是不是统一的,我这个型号的key是:D30474E282B5A282
2. 加密:单 DES
/bin/main 内含 AES_cbc_Decrypt_no_padding、AES256-CBC 等符号,但与 config.bin 无关:AES_cbc_Decrypt_no_padding(0x2f339c)全程序仅一个调用者,其上下文皆为 password、socket、RTSP 字符串,猜测是tplink云平台相关的东西。
config.bin 的解密路径为 uc_post_handle → 0x32b548 → 0x32b26c:
0x32b26c:
读取 /tmp/base-files/etc/encrypt_key
这是一个16 个十六进制字符,比如我手上这台是D30474E282B5A282
hex_str_to_bytes(dst, key_str, 8) → 转换成 8 字节 DES 密钥
密钥核心 0x30137c / 0x3013dc:
bic r3, r3, #7 ; 长度向下取整到 8 的倍数
… 每轮步进 #8 … ; 8 字节一组分组
同一把 key、同一个 IV=0 同时用于外层文件与内层节点 body 两处 DES。加解密为同一函数 0x32b26c 带 mode 参数:0x32b540(mode=0) 为加密、0x32b548(mode=1) 为解密。
3. 外层明文布局
偏移相对明文起点(= 文件偏移 − 0x10):
+0x000 TP_HEAD (0x18)
+0x00 00 00 01 00
+0x04 20 字节签名 ← 和设备 /tp_header 比对
+0x020 vendor_id u16 LE
+0x022 zone_code u16 LE
+0x040 len_head u32 BE
+0x044 len_data u32 BE ← 校验:filelen ≥ len_head + len_data
+0x200 config_file_head:
+0x00 "NVMP-CONFIG\0" 12 字节 magic
+0x10 product_id u32 BE (= 设备 product_id)
+0x14 datalen u32 BE (其后所有节点字节数)
+0x218 第一个配置节点
除开头 16 字节的 MD5 外,所有字段(含 product_id、TP_HEAD 签名)皆位于 DES 解密后的明文内,文件表面不可见。实测本机 product_id = 61281 (0xEF61)、vendor_id = 0、zone_code = 0。
4. 内层:NVMP 配置节点
逆向自 parse_config (0x32c514),并以真机数据交叉验证。
4.1 节点头(28 字节,0x218 起)
+0x00 "NVMP-CONFIG\0" 12 字节
+0x0c flag 通常 00 00 00 01 (BE 1)
+0x10 count u32 LE JSON 条目数 (实测 165)
+0x14 clen u32 LE zlib 压缩流长度 (实测 9583,未补齐)
+0x18 ulen u32 LE 解压后字节数 (实测 120431)
+0x1c body
注意端序:节点头的 count/clen/ulen 为小端,而顶层容器的 product_id/datalen 为大端,同文件混用Orz。
4.2 body 的三层解码
body(磁盘上,补齐到 8 字节)
── DES-CBC 解密 // 以 78 DA 开头的 zlib 流
── zlib 解压 // 120431 字节明文
── 按 \0 切分 // 165 段紧凑 JSON
每段为一张配置表,例如:
{"system":{"system":{"sys":{"dev_alias":"%e9%81%93%e8%b7%afA","timezone":"CST-8"}}}}
{"image":{"para":{"common":{"luma":"50","contrast":"50"}}}}
特征:值几乎皆为字符串(含数字、布尔);中文等经 URL 百分号编码;顶层键可重复(多条 system、image),须以保序列表处理,不可合并为单一字典;count 等于条目数,切分时丢弃尾部空段。
这里出现了时区,但是实测修改这个值并没有任何卵用,OSD时间不遵循这个时区设定,tplink 煞笔。
4.3 写回时的长度字段重算
comp = zlib(blob, level=9)
clen = len(comp) # 未补齐
body = DES(comp 补齐到 8)
datalen = 28 + len(body) # 顶层容器,大端
ulen = len(blob)
count = len(items)
外层 = 头部[:0x218] + 节点;补齐 8 字节,重算 len_head/len_data,外层 DES + MD5
5. 服务端校验:uc_post_handle
HTTP 路由 /admin/system/upload_conf 对应处理函数 0x210420。逆向之后得到校验顺序:
context 非空
↓
MD5(file[16:]) == file[0:16]↓
DES-CBC 解密成功
↓
总长 ≥ BE32(@0x40) + BE32(@0x44)↓
明文长度 > 0x200(config_file_head 有效)
↓
BE32(@0x210) == 设备 product_id↓
TP_HEAD[0x04:0x18] 匹配 && vendor_id 匹配 && zone_code 匹配
↓
parse_config 成功
全数通过后进入成功分支(0x210634 → msg_send(0x5014))应用配置。
负责落盘的函数(~0x291f80)实际调用:
fopen/fwrite→ 写/tmp/base-files/etc/hardware.config(及/tmp/hardware.config)fopen/fwrite→ 写/tmp/app_config.binsystem()×2 → 执行(逐字命中固件内字符串):mkdir -p /tmp/base-files/etc/;tar -zxvf /tmp/app_config.bin -C /tmp/;rm -rf /tmp/app_config.bin;chmod -R 777 /tmp/base-files;chmod -R 777 /tmp/radio;// tplink非常风骚的操作,这里可以构造一个带路径穿越的tar.gz包,直接覆盖任意文件
用ai写的config.bin参数值修改、打包工具
使用说明
一、 基础准备
在运行脚本之前,你需要确保环境满足以下要求:
- 健康的大脑
- Python 3。
- 安装依赖库: 该脚本需要
pycryptodome库来进行 DES 解密。
不懂去问AI
二、大概流程
如果你想修改摄像头的某个功能参数(比如修改网络设置、账号等),最标准的流程是:导出 → 修改 → 校验 → 导入。
第一步:导出当前配置为 JSON
首先,你需要从设备上获取一份原始的 config.bin 文件。 使用 export 命令将其转换为人类可读的 JSON 文件:
bashpython build_config_bin.py export config.bin config.json
会得到一个 config.json 文件,里面包含了所有的配置项。
第二步:修改 JSON 文件
手动打开 config.json,找到你想修改的参数进行编辑。
第三步:将修改后的 JSON 导入并生成新文件
使用 import 命令。这里需要原始的 config.bin 作为模板,因为它包含了设备唯一的硬件标识(如 product_id, vendor_id 等)。
bashpython build_config_bin.py import config.bin config.json new_config.bin
新构造的文件在同目录下 new_config.bin。
第四步:校验文件
先本地检查一下可不可以通过固件检查:
bashpython build_config_bin.py verify new_config.bin
结果:如果看到 全部通过,说明文件格式本身合法,大概率可以上传成功(摄像头可以轻松变砖 笑)。
三、 其他功能
除了上述标准流程,脚本还提供了一些快捷工具:
1. 快速修改
如果你只需要改一个简单的字符串参数,可以使用 set 命令直接在二进制文件上操作:
bash# 假设你要把某个路径下的 key 改为 "new_value"
python build_config_bin.py set config.bin output.bin --kv "path.to.key=new_value"
2. 跨设备适配(重建配置)
如果你有一份 A 设备的配置,想把它的内容搬到 B 设备上,可以使用 rebuild。它会保留 A 设备的配置内容,但把 B 设备的硬件 ID 填入头部:
bashpython build_config_bin.py rebuild template.bin output.bin --product-id 61281 --vendor-id 0 --zone-code 0
3. 查看内部结构
可以使用 nodes 命令查看配置里有哪些节点:
bashpython build_config_bin.py nodes config.bin
这会列出所有的 NVMP 节点、类型(JSON 还是二进制数据)以及它们在文件中的位置。
4. 提取特定内容
如果你只需要提取某一部分(比如提取出里面的 app_config 压缩包):
bashpython build_config_bin.py extract config.bin 0 some_data.bin
注:0 是节点序号,可以在 nodes 命令中查看。
5. 基础解密/加密
如果你只是想单纯地把文件转换成明文或加密回二进制:
- 解密:
python build_config_bin.py decrypt config.bin plaintext.txt - 加密:
python build_config_bin.py encrypt plaintext.txt config.bin
重要提示
DES Key:硬编码了 DES 解密密钥(D30474E282B5A282),不确定其他型号可不可以用。
备份:在操作任何 config.bin 之前,请务必保留原始文件的备份。
硬件匹配:除非你非常清楚自己在做什么,否则不要在不同型号的摄像头之间随意混用 config.bin,这可能导致设备变砖。
不提供任何保证。