-
Notifications
You must be signed in to change notification settings - Fork 29
/
extract_keys.py
executable file
·236 lines (180 loc) · 7.87 KB
/
extract_keys.py
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#!/usr/bin/env python3
import struct
import time
import binascii
from subprocess import check_output, CalledProcessError
from Crypto.Cipher import AES
from tqdm import tqdm
from panda import Panda
try:
# If openpilot is installed
from panda.python.uds import UdsClient, ACCESS_TYPE, SESSION_TYPE, DATA_IDENTIFIER_TYPE, SERVICE_TYPE, ROUTINE_CONTROL_TYPE, NegativeResponseError
except ImportError:
# If installed from pip
from panda.uds import UdsClient, ACCESS_TYPE, SESSION_TYPE, DATA_IDENTIFIER_TYPE, SERVICE_TYPE, ROUTINE_CONTROL_TYPE, NegativeResponseError
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
# Confirmed working on the following versions
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
}
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 is running, please kill openpilot before running this script! (aborted)")
exit(1)
except CalledProcessError as e:
if e.returncode != 1: # 1 == no process found (boardd not running)
raise e
panda = Panda()
panda.set_safety_mode(Panda.SAFETY_ELM327)
uds_client = UdsClient(panda, ADDR, ADDR + 8, BUS, timeout=0.1, response_pending_timeout=0.1, debug=DEBUG)
print("Getting application versions...")
try:
app_version = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION)
print(" - APPLICATION_SOFTWARE_IDENTIFICATION (application)", app_version)
except NegativeResponseError:
print("Can't read application software identification. Please cycle ignition.")
exit(1)
if app_version not in APPLICATION_VERSIONS:
print("Unexpected application version!", 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(" - APPLICATION_SOFTWARE_IDENTIFICATION (bootloader) ", bl_version)
if bl_version != APPLICATION_VERSIONS[app_version]:
print("Unexpected bootloader version!", 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("\nSecurity Access...")
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
panda.isotp_send(ADDR, erase, 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")