为了让你彻底掌握 mebdlts 3.4.1 下的启动全貌,我将这次分析分为四个部分:
物理内存地图与地址详解(地基)。
结构与二进制深度剖析(砖块)。
正常启动全流程代码与打印(盖楼)。
验签失败案例与故障分析(倒塌排查)。
第一部分:DDR 物理地址与内存映射详解
我们假设以下硬件环境,所有地址分析均基于此:
DDR 基址: 0x4000_0000
内核加载基址: 0x4200_0000
📋 详细内存布局表
第二部分:结构体与二进制深度剖析
- 签名头 (meb_sign_header_t) @ 0x4200_0000
结构体定义:
#pragma pack(push, 1)
typedef struct {
uint32_t magic; /* +0x00: 魔数 */
uint32_t version; /* +0x04: 版本 */
uint32_t image_len; /* +0x08: 被保护数据总长 (含加密头+密文) */
uint32_t flags; /* +0x0C: 标志位 */
uint8_t sha256_hash32; /* +0x10: 原始数据的哈希 */
uint8_t rsa_signature256;/* +0x30: RSA 签名 */
uint8_t reserved220; /* 填充 */
} meb_sign_header_t;
#pragma pack(pop)
二进制打印与数据分析 (DDR 0x42000000):
Offset Hex Data ASCII / 分析
00 00 42 42 45 4D 'BBEM' -> Magic: 0x4D454242 (MEBB, 小端模式)
00 04 01 04 03 00 Ver: 0x00030401 (v3.4.1)
00 08 00 20 05 00 Len: 0x00052000 (5MB + headers)
00 0C 01 00 00 00 Flags: 0x1 (Encrypted)
00 10 A1 B2 C3 ... (Hash) SHA256 digest of encrypted payload
00 30 9F 2A ... (Sig) RSA-2048 Signature Blob
- 加密头 (meb_enc_header_t) @ 0x4200_0200
结构体定义:
typedef struct {
uint32_t magic; /* +0x00 */
uint16_t algo; /* +0x04 */
uint16_t key_index; /* +0x06: OTP 密钥槽位 */
uint32_t data_len; /* +0x08: 密文长度 */
uint32_t plain_len; /* +0x0C: 明文长度 */
uint8_t iv16; /* +0x10: 初始化向量 */
uint8_t reserved96;
} meb_enc_header_t;
二进制打印与数据分析 (DDR 0x42000200):
Offset Hex Data ASCII / 分析
00 00 4E 45 45 4D 'NEEM' -> Magic: 0x4D45454E (MEEN)
00 04 01 00 Algo: 0x0001 (AES-256-CBC)
00 06 05 00 KeyIdx: 0x0005 (从 OTP 第 5 槽读密钥)
00 08 00 10 05 00 DataLen: 0x00051000
00 10 3A 1B 88 99 AA BB ... IV (16 Bytes Random)
- 加密前后对比分析
加密前 (原始 uImage @ 0x42000280):
数据:56 19 05 27 (uImage 魔数) ...
加密后 (Flash 中 @ 0x42000280):
数据:8F 3A 1C 99 (AES 密文) ...
分析:如果不进行解密,直接读取 0x42000280,U-Boot 看到的只是一堆毫无意义的乱码,完全无法识别这是内核镜像。
第三部分:正常启动全流程 (代码与打印)
阶段 1:Flash 拷贝到 DDR
命令:sf read 0x42000000 0x100000 0x500000
串口打印:
MEB-U-Boot 3.4.1> sf read 0x42000000 0x100000 0x500000
SF: 5242880 bytes @ 0x100000 Read: OK
阶段 2:验签与解密 (详细参数打印)
伪代码逻辑:
int meb_boot_secure(ulong addr) {
meb_sign_header_t *s_hdr = (meb_sign_header_t *)addr;
meb_enc_header_t *e_hdr = (meb_enc_header_t *)(addr + 0x200);
// --- 详细打印验签参数 ---
printf("[SEC] Base Addr : 0x%08lx\n", addr);
printf("[SEC] Sign Magic : 0x%08x\n", s_hdr->magic);
printf("[SEC] Sign Version : 0x%08x\n", s_hdr->version);
printf("[SEC] Image Len : %d (0x%x)\n", s_hdr->image_len, s_hdr->image_len);
printf("[SEC] Flags : 0x%x\n", s_hdr->flags);
// 计算哈希
uint8_t calc_hash[32];
sha256((void*)(addr + 0x200), s_hdr->image_len, calc_hash);
printf("[SEC] Calculated Hash: ");
print_hash(calc_hash); // 打印 32 字节哈希
// 验签
if (rsa_verify(s_hdr->sha256_hash, s_hdr->rsa_signature) == 0) {
printf("[SEC] RSA Verify : SUCCESS\n");
// --- 详细打印解密参数 ---
printf("[SEC] Enc Magic : 0x%08x\n", e_hdr->magic);
printf("[SEC] Algo : 0x%x (AES-256-CBC)\n", e_hdr->algo);
printf("[SEC] Key Slot : %d\n", e_hdr->key_index);
printf("[SEC] IV : ");
print_hex(e_hdr->iv, 16);
printf("[SEC] Plain Len : %d\n", e_hdr->plain_len);
// 解密 (原地解密 0x42000280)
aes_decrypt(e_hdr->key_index, e_hdr->iv, addr + 0x280, e_hdr->data_len);
printf("[SEC] Decrypt Status : OK\n");
return 0;
}
return -1; // 失败
}
正常启动日志:
SEC Base Addr : 0x42000000
SEC Sign Magic : 0x4D454242
SEC Sign Version : 0x00030401
SEC Image Len : 0x00052000
SEC Flags : 0x1
SEC Calculated Hash: 7f83b1657ff1fc53... (Matches Header)
SEC RSA Verify : SUCCESS
SEC Enc Magic : 0x4D45454E
SEC Algo : 0x1 (AES-256-CBC)
SEC Key Slot : 5
SEC IV : 3a1b8899aabbccddeeff001122334455
SEC Plain Len : 0x0004C000
SEC Decrypt Status : OK
阶段 3:U-Boot 头打印 (解密后)
解密完成后,0x42000280 变成了标准 uImage。U-Boot bootm 命令解析它。
U-Boot 命令:bootm 0x42000000
串口打印:
Booting kernel from Legacy Image at 42000280 ...
Image Name: Linux-Kernel-5.10
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 4980736 Bytes = 4.7 MiB
Load Address: 0x42008000
Entry Point: 0x42008000
Verifying Checksum ... OK
XIP Kernel Image ... OK
DDR 头信息打印 (md.w 0x42000280):
42000280: 5619 0527 0000 0000 0000 0000 0080 0040 .'V.........@..
42000290: 0080 0040 A1B2 C3D4 0004 C000 0502 0000 @.@............
5619 0527: 魔数 (小端 0x27051956)
0080 0040: 加载地址 (小端 0x40008000)
阶段 4:bootm 跳转与内核启动
代码分析:
/* arch/arm/lib/bootm.c */
void boot_jump_linux(bootm_headers_t *images)
{
// 1. 获取解密后的入口点
ulong entry = images->ep; // 0x42008000
// 2. 最终环境清理
cleanup_before_linux();
// 3. 跳转
kernel_entry(0, machid, (ulong)images->ft_addr);
}
内核启动代码 (init/main.c):
start_kernel() {
setup_arch(&command_line); // 初始化内存,解析设备树
mm_init(); // 内存管理
trap_init(); // 中断向量
rest_init(); // 创建 init 进程
}
第四部分:验签失败案例与详细分析
这是最关键的部分。如果验签失败,意味着镜像被篡改或 Flash 损坏。
- 场景设定
故障原因:黑客在 Flash 偏移 0x42000290 处修改了一个字节(或者 Flash 老化导致 bit 翻转)。
结果:密文数据变了,但签名头的 rsa_signature 没变(没法变,因为没有私钥)。
- 失败时的详细打印
串口日志:
SEC Base Addr : 0x42000000
SEC Sign Magic : 0x4D454242
SEC Sign Version : 0x00030401
SEC Image Len : 0x00052000
SEC Calculated Hash: 9e41f2a88c33b1d1... (计算出的值变了!)
SEC Stored Hash : 7f83b1657ff1fc53... (头里的值没变)
SEC RSA Verify : **FAILED**
SEC Error: Signature mismatch!
SEC Security Alert: Halting system.
- 详细问题分析
A. 二进制数据分析 :Stored Hash (在签名头里): 7f 83 b1 65 ... (这是厂家原始镜像的哈希)。
Calculated Hash (U-Boot 算的): 9e 41 f2 a8 ... (这是当前 Flash 数据的哈希)。
差异原因: AES-CBC 特性是"雪崩效应"。哪怕密文只改了 1 bit,解密后的明文也会面目全非,从而算出完全不同的 Hash。
B. 镜像解析失败分析
不匹配: U-Boot 用公钥解密 Signature 得到原始 Hash H1。它计算当前数据得到 Hash H2。
比对: H1 != H2。
后果:U-Boot 认为这个镜像不可信。
解密流程被跳过:因为验签没过,U-Boot 根本不会去读取加密头,也不会调用 AES 解密引擎。
U-Boot 头无法识别: 因为没有解密,0x42000280 处依然是乱码 8F 3A ...,而不是 27 05 ...。
系统挂起: U-Boot 执行 hang() 或者重启,拒绝启动被篡改的内核。
如果你在开发板遇到这个错误:
检查 Flash: 使用 sf read 读出来做 cmp 对比,看是否写入错误。
检查 Key: 确认 U-Boot 里的公钥是否与签名工具用的私钥是一对。
检查 IV: 加密头的 IV 是否在传输过程中损坏(虽然 IV 损坏会导致解密失败,但验签不一定会失败,除非 IV 也包含在签名范围内)。