-
Notifications
You must be signed in to change notification settings - Fork 4
/
bootloader.py
399 lines (310 loc) · 11 KB
/
bootloader.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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
#!/usr/bin/env python3
import hmac
import struct
from hashlib import sha1 as sha
import Crypto.Cipher.ARC4 as RC4
from lib.common import *
from lib.nand import NANDSection
class BootloaderHeader(NANDSection):
"""
See: https://free60.acabey.xyz/index.php/Bootloaders
struct BOOTLOADER_HEADER {
uint8_t abName[2];
uint16_t wBuild;
uint16_t wPairing;
uint16_t wFlags;
uint32_t dwEntry;
uint32_t dwLength;
uint8_t bSalt[16];
}
"""
HEADER_SIZE = 0x20
def __init__(self, header, currentoffset=0):
if len(header) < BootloaderHeader.HEADER_SIZE:
raise ValueError('Invalid size for bootloader header')
if all(b == 0 for b in header):
raise ValueError('Null data for bootloader header')
header = struct.unpack('>2s3H2I16s', header)
self.name = header[0]
self.build = header[1]
self.pairing = header[2]
self.flags = header[3]
self.entry = header[4]
self.length = header[5]
self.salt = header[6]
self.offset = currentoffset
def __repr__(self):
return 'Bootloader({})'.format(self.data)
def __str__(self):
ret = ''
ret += str(self.name)
ret += '\n'
ret += str(self.build)
ret += '\n'
ret += str(hex(self.pairing))
ret += '\n'
ret += str(hex(self.flags))
ret += '\n'
ret += str(hex(self.entry))
ret += '\n'
ret += str(hex(self.length))
ret += '\n'
ret += str(self.salt)
return ret
def enumerate(self):
ret = ''
ret += 'Name: '
ret += str(self.name)
ret += '\n'
ret += 'Build: '
ret += str(self.build)
ret += '\n'
ret += 'Pairing: '
ret += str(hex(self.pairing))
ret += '\n'
ret += 'Flags: '
ret += str(hex(self.flags))
ret += '\n'
ret += 'Entry: '
ret += str(hex(self.entry))
ret += '\n'
ret += 'Length: '
ret += str(hex(self.length))
ret += '\n'
ret += 'Salt: '
ret += '\n'
ret += str(self.salt)
return ret
def pack(self):
return struct.pack('>2s3H2I16s', self.name, self.build, self.pairing, self.flags, self.entry, self.length,
self.salt)
class CFHeader(BootloaderHeader):
"""
See: https://free60.acabey.xyz/index.php/Bootloaders#CF
struct BOOTLOADER_HEADER {
uint8_t abName[2];
uint16_t wBuild;
uint16_t wPairing;
uint16_t wFlags;
uint32_t dwEntry;
uint32_t dwLength;
uint8_t unknown[16];
uint8_t bSalt[16];
}
"""
HEADER_SIZE = 0x30
def __init__(self, header, currentoffset=0):
header = struct.unpack('>4H2I16s16s', header)
self.name = header[0].decode('ASCII')
self.build = header[1]
self.pairing = header[2]
self.flags = header[3]
self.entry = header[4]
self.length = header[5]
# TODO
self.unknown = header[6]
self.salt = header[7]
self.offset = currentoffset
class Bootloader(NANDSection):
"""
The generic container for bootloaders
Rather than try to keep track of when data is or is not encrypted, instead I opt to allocate memory for both encrypted and decrypted copies
While this comes at the expense of greater memory usage, it makes modifying data very easy
"""
def __init__(self, data_encrypted, header):
if all(b == 0 for b in data_encrypted):
raise ValueError('Null data for bootloader')
self.data_encrypted = data_encrypted
self.data_plaintext = None
self.header = header
self.key = None
def __str__(self):
return str(self.header)
def enumerate(self):
return self.header.enumerate()
def extract(self):
"""
Write bootloader contents into <Bootloader Name>.<Build>_dec.bin and <Bootloader Name>.<Build>_dec.bin
for plaintext and encrypted data, respectively
"""
if self.data_plaintext:
with open('output/' + self.header.name.decode('ASCII') + '.' + str(self.header.build) + '_dec' + '.bin',
'w+b') as plaintextout:
plaintextout.write(self.data_plaintext)
with open('output/' + self.header.name.decode('ASCII') + '.' + str(self.header.build) + '_enc' + '.bin',
'w+b') as encryptedout:
encryptedout.write(self.data_encrypted)
def replace(self, replacement, plaintext=False):
"""
Replace data with contents of provided file
Defaults to encrypted data, but named parameter can override
"""
if plaintext:
raise NotImplementedError('plaintext replacement not implemented')
with open(replacement, 'rb') as replacementdata:
replacementheader = type(self.header)(replacementdata.read(self.header.HEADER_SIZE), self.header.offset)
replacementdata.seek(0, 0)
self = type(self)(replacementdata.read(replacementheader.length), replacementheader)
def write(self, output):
"""
Write current (encrypted) contents to file at intended offset
"""
with open(output, 'r+b') as originaldata:
originaldata.seek(self.offset, 0)
originaldata.write(self.pack())
def updateKey(self, previouskey, salt=None):
"""
Derive the RC4 encryption key from the previous key and the salt stored in the header
Before decrypting the data, you must set the correct key by providing the previous key
When reencrypting the data, you may optionally change the salt so long as the header of the encrypted bootloader contains this salt
"""
if salt:
self.header.salt = salt
self.key = hmac.new(previouskey, self.header.salt, sha).digest()[0:0x10]
def encrypt(self):
"""
Encrypt the plaintext data
"""
self.data_encrypted = self.header.pack() + RC4.new(self.key).encrypt(self.data_plaintext[0x20:])
def decrypt(self):
"""
Decrypt the encrypted data
"""
self.data_plaintext = bytes(self.header.pack() + RC4.new(self.key).decrypt(self.data_encrypted[0x20:]))
def pack(self):
"""
Pack data into C style struct
"""
return bytes(self.header.pack() + self.data_encrypted)
def sign(self):
"""
Sign the payload with the 4BL RSA key
Only applies to SD, not any other bootloader
"""
raise NotImplementedError('can only sign SD bootloader')
class BL2(Bootloader):
def __init__(self, data_encrypted, header):
try:
Bootloader.__init__(self, data_encrypted, header)
except ValueError as e:
raise
except Exception as e:
raise
def updateKey(self, salt=None):
if salt:
self.header.salt = salt
self.key = hmac.new(Constants.SECRET_1BL, self.header.salt, sha).digest()[0:0x10]
def zeropair(self):
# Can only zeropair once decrypted
assert self.data_plaintext != None, 'Cannot zeropair encrypted bootloader ' + self.header.name
self.data_plaintext = self.data_plaintext[0:0x20] + "\0" * 0x20 + self.data_plaintext[0x40:]
class CB(BL2):
MAGIC_BYTES = b'CB'
def __init__(self, data_encrypted, header):
try:
BL2.__init__(self, data_encrypted, header)
except ValueError as e:
raise
except Exception as e:
raise
if self.header.name != CB.MAGIC_BYTES:
raise ValueError('Failed CB magic bytes check')
class CD(Bootloader):
MAGIC_BYTES = b'CD'
def __init__(self, data_encrypted, header):
try:
Bootloader.__init__(self, data_encrypted, header)
except ValueError as e:
raise
except Exception as e:
raise
if self.header.name != CD.MAGIC_BYTES:
raise ValueError('Failed CD magic bytes check')
class CE(Bootloader):
MAGIC_BYTES = b'CD'
def __init__(self, data_encrypted, header):
try:
Bootloader.__init__(self, data_encrypted, header)
except ValueError as e:
raise
except Exception as e:
raise
if self.header.name != CE.MAGIC_BYTES:
raise ValueError('Failed CE magic bytes check')
def patch(self):
pass
def compress(self):
pass
def decompress(self):
pass
class CF(Bootloader):
MAGIC_BYTES = b'CF'
def __init__(self, data_encrypted, header):
try:
Bootloader.__init__(self, data_encrypted, header)
except ValueError as e:
raise
except Exception as e:
raise
if self.header.name != CF.MAGIC_BYTES:
raise ValueError('Failed CF magic bytes check')
def zeropair(self):
# Can only zeropair once decrypted
assert self.data_plaintext != None, 'Cannot zeropair encrypted bootloader ' + self.header.name
self.data = self.data[0:0x21c] + "\0" * 4 + self.data[0x220:]
class SB(BL2):
MAGIC_BYTES = b'SB'
def __init__(self, data_encrypted, header):
try:
BL2.__init__(self, data_encrypted, header)
except ValueError as e:
raise
except Exception as e:
raise
if self.header.name != SB.MAGIC_BYTES:
raise ValueError('Failed SB magic bytes check')
class SC(Bootloader):
MAGIC_BYTES = b'SC'
def __init__(self, data_encrypted, header):
try:
Bootloader.__init__(self, data_encrypted, header)
except ValueError as e:
raise
except Exception as e:
raise
if self.header.name != SC.MAGIC_BYTES:
raise ValueError('Failed SC magic bytes check')
class SD(Bootloader):
MAGIC_BYTES = b'SD'
def __init__(self, data_encrypted, header):
try:
Bootloader.__init__(self, data_encrypted, header)
except ValueError as e:
raise
except Exception as e:
raise
if self.header.name != SD.MAGIC_BYTES:
raise ValueError('Failed SD magic bytes check')
def sign(self):
"""
Sign the payload with the 4BL RSA key
Only applies to SD, not any other bootloader
"""
raise NotImplementedError('bootloader signing is incomplete, disable this check if you know what you are doing')
class SE(Bootloader):
MAGIC_BYTES = b'SE'
def __init__(self, data_encrypted, header):
try:
Bootloader.__init__(self, data_encrypted, header)
except ValueError as e:
raise
except Exception as e:
raise
if self.header.name != SE.MAGIC_BYTES:
raise ValueError('Failed SE magic bytes check')
def patch(self):
pass
def compress(self):
pass
def decompress(self):
pass