前言

  0x27 服务是 UDS(ISO 14229)标准中的安全访问服务,目的是防止未授权的诊断操作。采用 Seed-Key 挑战-应答机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Tester(上位机/诊断仪)                    ECU(板子)
│ │
│ ① 27 03 (Request Seed, Level 3) │
│─────────────────────────────────────→│
│ │ 生成 16 字节随机 Seed
│ ② 67 03 [Seed_16B] │ 保存 Seed 供后续比较
│←─────────────────────────────────────│
│ │
│ 用 Seed + Secret Key │
│ 计算 HMAC-SHA1 │
│ 取前 16 字节作为 Key │
│ │
│ ③ 27 04 [Key_16B] │
│─────────────────────────────────────→│
│ │ 用相同的 Seed + Secret Key
│ │ 计算 HMAC-SHA1
│ │ 比较结果是否一致
│ ④ 67 04 (Positive Response) │
│←─────────────────────────────────────│
│ │ 解锁对应安全等级

逆向

推测算法实现

  证据 1:头文件声明了完整的输入/输出规范
Crypto_30_SecAccessFord_Algo.h 是公开的,明确定义了:

函数签名(参数类型、个数、含义)
输入长度(key=12, seed=16, mac=16/18)
输出(0=匹配,1=不匹配)
返回值(E_OK / E_NOT_OK)
接口契约是完全确定的。

  证据 2:数据结构暴露了算法身份

密钥提取

  原理:原静态库(.a / .lib)不是加密文件,它就是一堆 .o 目标文件的打包,其中并没有任何加密。
  静态库的格式和 .exe/.elf 一样,都是标准的 ELF(或 COFF)格式,里面分段存储:
段|内容|加密?
—-|—-|—-
.text|机器码(函数体)|❌ 明文
.rodata|只读数据(常量、字符串、密钥)|❌ 明文
.data|可读写全局变量|❌ 明文
.symtab|符号表(函数名、变量名)|❌ 明文

第一步:看库里有什么符号

1
arm-none-eabi-nm -S libService27.a

nm = name list,列出所有符号。输出含义:

1
2
00000000 0000000c R level01SecretKey
↑地址 ↑大小12字节 ↑类型:R=只读数据(.rodata)

符号类型标记:

T = 代码段(函数),t = 局部函数
R = 只读数据,r = 局部只读数据
D = 可读写数据
U = 未定义(引用外部符号)

第二步:导出段内容(提取数据)

1
arm-none-eabi-objdump -s libService27.a

-s = 显示所有段的十六进制 dump。找到目标段:

1
2
3
4
Contents of section .rodata.level01SecretKey:
0000 11223344 55667788 99aabbcc 123123
↑ ↑ ↑ ↑
每组4字节,这就是密钥的原始字节

读法:11 22 33 44 55 66 77 88 99 aa bb cc = 12 字节密钥。右边是 ASCII 对照(不可打印字符显示为 .)。

代码实现

  各部分代码分析。

ECU端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
UdsSecurityInit()                     ← canstack_init() 启动时调用
└─ SecurityCntInit() ← 从 NV 读取失败计数

get_security_seed(0x03, true) ← 收到 27 03
└─ generate_random_seed(16B) ← srand(ticks) + rand() 填充
└─ 返回 seed 指针

compare_security_key(0x03, key, 16) ← 收到 27 04
└─ get_security_seed(0x03, false) ← 取回之前保存的 seed
└─ memcpy(data, seed, 16)
└─ memcpy(data+16, level03_salt, 4) ← 拼接 salt "zaCK"
└─ Algo_Level3()
└─ hmac_sha1_verify()
└─ mbedtls_md_hmac(SHA1, secret_key, data) ← 计算 HMAC
└─ MemCmp(hmac_out, mac, 16) ← 常量时间比较前 16 字节
└─ 返回 match/mismatch

上位机端

整体逻辑

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
上位机 (Tester/Python脚本)                          ECU (MSPM0G3519)
═══════════════════════ ═══════════════════
│ │
│ ① 10 03 (DiagnosticSessionControl) │
│ ──────────────────────────────────────────────> │
│ │ 切换到 Extended Session
│ 50 03 00 32 01 f4 │
│ <────────────────────────────────────────────── │
│ │
│ ② 27 03 (RequestSeed, Level 03) │
│ ──────────────────────────────────────────────> │
│ │ generate_random_seed()
│ │ 生成 16 字节随机 seed
│ │ 保存到 se_seed[]
│ 67 03 + seed[16B] │
│ <────────────────────────────────────────────── │
│ │
│ ③ 上位机本地计算 Key │
│ ┌─────────────────────────────┐ │
│ │ data = seed ∥ salt("zaCK") │ │
│ │ = 16 + 4 = 20 字节 │ │
│ │ │ │
│ │ key = HMAC-SHA1( │ │
│ │ secret_key[12B], │ │
│ │ data[20B] │ │
│ │ )[:16] │ │
│ │ 取 SHA1 输出前 16 字节 │ │
│ └─────────────────────────────┘ │
│ │
│ ④ 27 04 + key[16B] (SendKey) │
│ ──────────────────────────────────────────────> │
│ │ compare_security_key()
│ │ ┌─────────────────────────┐
│ │ │ 取出 se_seed[] │
│ │ │ data = seed ∥ salt │
│ │ │ │
│ │ │ Algo_Level3(): │
│ │ │ expected = HMAC-SHA1( │
│ │ │ secret_key, data │
│ │ │ )[:16] │
│ │ │ │
│ │ │ 常量时间比较: │
│ │ │ key == expected ? │
│ │ └─────────────────────────┘
│ 67 04 (正响应) │
│ <────────────────────────────────────────────── │ 解锁成功
│ │