新增 `DistractionDetectionLevel` 参数以控制驾驶员分心检测的灵敏度等级,并在 `dmonitoringd.py` 和 `helpers.py` 中实现不同等级对应的时间阈值配置。同时更新了相关逻辑以支持动态调整该参数。 fix(toyota): 支持 Toyota Wildlander PHEV 车型接入与控制 增加对 Toyota Wildlander PHEV 的指纹识别、车辆规格定义及接口适配,确保其在 TSS2 平台下的正常运行,并修正部分雷达ACC判断条件。 feat(ui): 优化 Dragonpilot 设置界面选项显示语言一致性 将 Dragonpilot 设置页面中的多个下拉选项文本进行国际化处理,统一使用翻译函数包裹,提升多语言兼容性。 chore(config): 更新 launch 脚本 API 地址并切换 shell 解释器 修改 `launch_openpilot.sh` 使用 `/usr/bin/bash` 作为解释器,并设置自定义 API 与 Athena 服务地址。 refactor(key): 实现 ECU 秘钥提取脚本并写入参数存储 创建 `key.py` 脚本用于通过 UDS 协议从 ECU 提取 SecOC 密钥,并将其保存至系统参数中供后续使用。 docs(vscode): 移除不再使用的终端配置项 清理 `.vscode/settings.json` 文件中过时的 terminal 配置内容。 feat(fonts): 新增中文字体资源文件 添加 `china.ttf` 字体文件以增强 UI 在中文环境下的渲染效果。 build(payload): 添加二进制负载文件 引入新的二进制 payload 文件用于辅助密钥提取流程。
235 lines
7.8 KiB
Python
235 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
||
|
||
import struct
|
||
from subprocess import check_output, CalledProcessError
|
||
|
||
from Crypto.Cipher import AES
|
||
from tqdm import tqdm
|
||
|
||
from panda import Panda
|
||
from opendbc.car.uds import UdsClient, ACCESS_TYPE, SESSION_TYPE, DATA_IDENTIFIER_TYPE, SERVICE_TYPE, ROUTINE_CONTROL_TYPE, NegativeResponseError
|
||
from opendbc.car.structs import CarParams
|
||
from opendbc.car.isotp import isotp_send
|
||
|
||
ADDR = 0x7a1
|
||
DEBUG = False
|
||
BUS = 0
|
||
|
||
SEED_KEY_SECRET = b'\xf0\x5f\x36\xb7\xd7\x8c\x03\xe2\x4a\xb4\xfa\xef\x2a\x57\xd0\x44'
|
||
|
||
# These are the key and IV used to encrypt the payload in build_payload.py
|
||
DID_201_KEY = b'\x00' * 16
|
||
DID_202_IV = b'\x00' * 16
|
||
|
||
# 车型版本
|
||
APPLICATION_VERSIONS = {
|
||
b'\x018965B4209000\x00\x00\x00\x00': b'\x01!!!!!!!!!!!!!!!!', # 2021 RAV4 Prime
|
||
b'\x018965B4233100\x00\x00\x00\x00': b'\x01!!!!!!!!!!!!!!!!', # 2023 RAV4 Prime
|
||
b'\x018965B4509100\x00\x00\x00\x00': b'\x01!!!!!!!!!!!!!!!!', # 2021 Sienna
|
||
b'\x018965B4221000\x00\x00\x00\x00': b'\x01!!!!!!!!!!!!!!!!', # 2021 WILDLANDER PHEV
|
||
}
|
||
|
||
KEY_STRUCT_SIZE = 0x20
|
||
CHECKSUM_OFFSET = 0x1d
|
||
SECOC_KEY_SIZE = 0x10
|
||
SECOC_KEY_OFFSET = 0x0c
|
||
|
||
def get_key_struct(data, key_no):
|
||
return data[key_no * KEY_STRUCT_SIZE: (key_no + 1) * KEY_STRUCT_SIZE]
|
||
|
||
def verify_checksum(key_struct):
|
||
checksum = sum(key_struct[:CHECKSUM_OFFSET])
|
||
checksum = ~checksum & 0xff
|
||
return checksum == key_struct[CHECKSUM_OFFSET]
|
||
|
||
def get_secoc_key(key_struct):
|
||
return key_struct[SECOC_KEY_OFFSET:SECOC_KEY_OFFSET + SECOC_KEY_SIZE]
|
||
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
check_output(["pidof", "boardd"])
|
||
print("boardd 正在运行,请在运行此脚本前先关闭 openpilot!(已中止)")
|
||
exit(1)
|
||
except CalledProcessError as e:
|
||
if e.returncode != 1: # 1 == no process found (boardd not running)
|
||
raise e
|
||
except FileNotFoundError:
|
||
pass
|
||
|
||
panda = Panda()
|
||
panda.set_safety_mode(CarParams.SafetyModel.elm327)
|
||
|
||
uds_client = UdsClient(panda, ADDR, ADDR + 8, BUS, timeout=0.1, response_pending_timeout=0.1)
|
||
|
||
print("获取应用程序版本...")
|
||
|
||
try:
|
||
app_version = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)
|
||
print(" - 应用软件标识(application)", app_version)
|
||
except NegativeResponseError:
|
||
print("无法读取应用软件标识。请循环点火。")
|
||
exit(1)
|
||
|
||
if app_version not in APPLICATION_VERSIONS:
|
||
print("应用程序版本异常!", app_version)
|
||
exit(1)
|
||
|
||
# Mandatory flow of diagnostic sessions
|
||
uds_client.diagnostic_session_control(SESSION_TYPE.DEFAULT)
|
||
uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC)
|
||
uds_client.diagnostic_session_control(SESSION_TYPE.PROGRAMMING)
|
||
|
||
# Get bootloader version
|
||
uds_client.diagnostic_session_control(SESSION_TYPE.DEFAULT)
|
||
uds_client.diagnostic_session_control(SESSION_TYPE.EXTENDED_DIAGNOSTIC)
|
||
bl_version = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)
|
||
print(" - 应用软件识别(引导加载程序)", bl_version)
|
||
|
||
if bl_version != APPLICATION_VERSIONS[app_version]:
|
||
print("引导加载程序版本异常!", bl_version)
|
||
exit(1)
|
||
|
||
# Go back to programming session
|
||
uds_client.diagnostic_session_control(SESSION_TYPE.PROGRAMMING)
|
||
|
||
# Security Access - Request Seed
|
||
seed_payload = b"\x00" * 16
|
||
seed = uds_client.security_access(ACCESS_TYPE.REQUEST_SEED, data_record=seed_payload)
|
||
|
||
key = AES.new(SEED_KEY_SECRET, AES.MODE_ECB).decrypt(seed_payload)
|
||
key = AES.new(key, AES.MODE_ECB).encrypt(seed)
|
||
|
||
print("\n安全访问...")
|
||
|
||
print(" - SEED:", seed.hex())
|
||
print(" - KEY:", key.hex())
|
||
|
||
# Security Access - Send Key
|
||
uds_client.security_access(ACCESS_TYPE.SEND_KEY, key)
|
||
print(" - Key OK!")
|
||
|
||
print("\nPreparing to upload payload...")
|
||
|
||
# Write something to DID 203, not sure why but needed for state machine
|
||
uds_client.write_data_by_identifier(0x203, b"\x00" * 5)
|
||
|
||
# Write KEY and IV to DID 201/202, prerequisite for request download
|
||
print(" - Write data by identifier 0x201", DID_201_KEY.hex())
|
||
uds_client.write_data_by_identifier(0x201, DID_201_KEY)
|
||
|
||
print(" - Write data by identifier 0x202", DID_202_IV.hex())
|
||
uds_client.write_data_by_identifier(0x202, DID_202_IV)
|
||
|
||
# Request download to RAM
|
||
data = b"\x01" # [1] Format
|
||
data += b"\x46" # [2] 4 size bytes, 6 address bytes
|
||
data += b"\x01" # [3] memoryIdentifier
|
||
data += b"\x00" # [4]
|
||
data += struct.pack('!I', 0xfebf0000) # [5] Address
|
||
data += struct.pack('!I', 0x1000) # [9] Size
|
||
|
||
print("\nUpload payload...")
|
||
|
||
print(" - Request download")
|
||
resp = uds_client._uds_request(SERVICE_TYPE.REQUEST_DOWNLOAD, data=data)
|
||
|
||
# Upload payload
|
||
payload = open("payload.bin", "rb").read()
|
||
assert len(payload) == 0x1000
|
||
chunk_size = 0x400
|
||
for i in range(len(payload) // chunk_size):
|
||
print(f" - Transfer data {i}")
|
||
uds_client.transfer_data(i + 1, payload[i * chunk_size:(i + 1) * chunk_size])
|
||
|
||
uds_client.request_transfer_exit()
|
||
|
||
print("\nVerify payload...")
|
||
|
||
# Routine control 0x10f0
|
||
# [0] 0x31 (routine control)
|
||
# [1] 0x01 (start)
|
||
# [2] 0x10f0 (routine identifier)
|
||
# [4] 0x45 (format, 4 size bytes, 5 address bytes)
|
||
# [5] 0x0
|
||
# [6] mem addr
|
||
# [10] mem addr
|
||
data = b"\x45\x00"
|
||
data += struct.pack('!I', 0xfebf0000)
|
||
data += struct.pack('!I', 0x1000)
|
||
|
||
uds_client.routine_control(ROUTINE_CONTROL_TYPE.START, 0x10f0, data)
|
||
print(" - Routine control 0x10f0 OK!")
|
||
|
||
print("\nTrigger payload...")
|
||
|
||
# Now we trigger the payload by trying to erase
|
||
# [0] 0x31 (routine control)
|
||
# [1] 0x01 (start)
|
||
# [2] 0xff00 (routine identifier)
|
||
# [4] 0x45 (format, 4 size bytes, 5 address bytes)
|
||
# [5] 0x0
|
||
# [6] mem addr
|
||
# [10] mem addr
|
||
data = b"\x45\x00"
|
||
data += struct.pack('!I', 0xe0000)
|
||
data += struct.pack('!I', 0x8000)
|
||
|
||
# Manually send so we don't get stuck waiting for the response
|
||
# uds_client.routine_control(ROUTINE_CONTROL_TYPE.START, 0xff00, data)
|
||
erase = b"\x31\x01\xff\x00" + data
|
||
isotp_send(panda, erase, ADDR, bus=BUS)
|
||
|
||
print("\nDumping keys...")
|
||
start = 0xfebe6e34
|
||
end = 0xfebe6ff4
|
||
|
||
extracted = b""
|
||
|
||
with open(f'data_{start:08x}_{end:08x}.bin', 'wb') as f:
|
||
with tqdm(total=end-start) as pbar:
|
||
while start < end:
|
||
for addr, *_, data, bus in panda.can_recv():
|
||
if bus != BUS:
|
||
continue
|
||
|
||
if data == b"\x03\x7f\x31\x78\x00\x00\x00\x00": # Skip response pending
|
||
continue
|
||
|
||
if addr != ADDR + 8:
|
||
continue
|
||
|
||
if DEBUG:
|
||
print(f"{data.hex()}")
|
||
|
||
ptr = struct.unpack("<I", data[:4])[0]
|
||
assert (ptr >> 8) == start & 0xffffff # Check lower 24 bits of address
|
||
|
||
extracted += data[4:]
|
||
f.write(data[4:])
|
||
f.flush()
|
||
|
||
start += 4
|
||
pbar.update(4)
|
||
|
||
key_1_ok = verify_checksum(get_key_struct(extracted, 1))
|
||
key_4_ok = verify_checksum(get_key_struct(extracted, 4))
|
||
|
||
if not key_1_ok or not key_4_ok:
|
||
print("SecOC key checksum verification failed!")
|
||
exit(1)
|
||
|
||
key_1 = get_secoc_key(get_key_struct(extracted, 1))
|
||
key_4 = get_secoc_key(get_key_struct(extracted, 4))
|
||
|
||
print("\nECU_MASTER_KEY ", key_1.hex())
|
||
print("SecOC Key (KEY_4)", key_4.hex())
|
||
|
||
try:
|
||
from openpilot.common.params import Params
|
||
params = Params()
|
||
params.put("SecOCKey", key_4.hex())
|
||
print("\nSecOC key written to param successfully!")
|
||
except Exception:
|
||
print("\nFailed to write SecOCKey param")
|