-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy path3ds_decrypt_v2.py
247 lines (208 loc) · 15.6 KB
/
3ds_decrypt_v2.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
# /usr/bin/env python2
from Crypto.Cipher import AES
from Crypto.Util import Counter
from sys import argv
import struct
rol = lambda val, r_bits, max_bits: \
(val << r_bits % max_bits) & (2 ** max_bits - 1) | \
((val & (2 ** max_bits - 1)) >> (max_bits - (r_bits % max_bits)))
def to_bytes(num):
numstr = ''
tmp = num
while len(numstr) < 16:
numstr += chr(tmp & 0xFF)
tmp >>= 8
return numstr[::-1]
# Setup Keys and IVs
plain_counter = struct.unpack('>Q', '\x01\x00\x00\x00\x00\x00\x00\x00')
exefs_counter = struct.unpack('>Q', '\x02\x00\x00\x00\x00\x00\x00\x00')
romfs_counter = struct.unpack('>Q', '\x03\x00\x00\x00\x00\x00\x00\x00')
Constant = struct.unpack('>QQ',
'\x1F\xF9\xE9\xAA\xC5\xFE\x04\x08\x02\x45\x91\xDC\x5D\x52\x76\x8A') # 3DS AES Hardware Constant
# Retail keys
KeyX0x18 = struct.unpack('>QQ',
'\x82\xE9\xC9\xBE\xBF\xB8\xBD\xB8\x75\xEC\xC0\xA0\x7D\x47\x43\x74') # KeyX 0x18 (New 3DS 9.3)
KeyX0x1B = struct.unpack('>QQ',
'\x45\xAD\x04\x95\x39\x92\xC7\xC8\x93\x72\x4A\x9A\x7B\xCE\x61\x82') # KeyX 0x1B (New 3DS 9.6)
KeyX0x25 = struct.unpack('>QQ', '\xCE\xE7\xD8\xAB\x30\xC0\x0D\xAE\x85\x0E\xF5\xE3\x82\xAC\x5A\xF3') # KeyX 0x25 (> 7.x)
KeyX0x2C = struct.unpack('>QQ', '\xB9\x8E\x95\xCE\xCA\x3E\x4D\x17\x1F\x76\xA9\x4D\xE9\x34\xC0\x53') # KeyX 0x2C (< 6.x)
# Dev Keys: (Uncomment these lines if your 3ds rom is encrypted with Dev Keys)
# KeyX0x18 = struct.unpack('>QQ', '\x30\x4B\xF1\x46\x83\x72\xEE\x64\x11\x5E\xBD\x40\x93\xD8\x42\x76') # Dev KeyX 0x18 (New 3DS 9.3)
# KeyX0x1B = struct.unpack('>QQ', '\x6C\x8B\x29\x44\xA0\x72\x60\x35\xF9\x41\xDF\xC0\x18\x52\x4F\xB6') # Dev KeyX 0x1B (New 3DS 9.6)
# KeyX0x25 = struct.unpack('>QQ', '\x81\x90\x7A\x4B\x6F\x1B\x47\x32\x3A\x67\x79\x74\xCE\x4A\xD7\x1B') # Dev KeyX 0x25 (> 7.x)
# KeyX0x2C = struct.unpack('>QQ', '\x51\x02\x07\x51\x55\x07\xCB\xB1\x8E\x24\x3D\xCB\x85\xE2\x3A\x1D') # Dev KeyX 0x2C (< 6.x)
with open(argv[1], 'rb') as f:
with open(argv[1], 'rb+') as g:
print(argv[1]) # Print the filename of the file being decrypted
f.seek(0x100) # Seek to start of NCSD header
magic = f.read(0x04)
if magic == "NCSD":
f.seek(0x188)
ncsd_flags = struct.unpack('<BBBBBBBB', f.read(0x8))
sectorsize = 0x200 * (2 ** ncsd_flags[6])
for p in range(8):
f.seek((0x120) + (p * 0x08)) # Seek to start of partition information, read offsets and lengths
part_off = struct.unpack('<L', f.read(0x04))
part_len = struct.unpack('<L', f.read(0x04))
f.seek(((part_off[0]) * sectorsize) + 0x188) # Get the partition flags to determine encryption type.
partition_flags = struct.unpack('<BBBBBBBB', f.read(0x8))
if (partition_flags[7] & 0x04): # check if the 'NoCrypto' bit (bit 3) is set
print("Partition %1d: Already Decrypted?..." % p)
else:
if (part_off[0] * sectorsize) > 0: # check if partition exists
f.seek(
((part_off[0]) * sectorsize) + 0x100) # Find partition start (+ 0x100 to skip NCCH header)
magic = f.read(0x04)
if magic == "NCCH": # check if partition is valid
f.seek(((part_off[0]) * sectorsize) + 0x0)
part_keyy = struct.unpack('>QQ', f.read(
0x10)) # KeyY is the first 16 bytes of partition RSA-2048 SHA-256 signature
f.seek(((part_off[0]) * sectorsize) + 0x108)
tid = struct.unpack('<Q',
f.read(0x8)) # TitleID is used as IV joined with the content type.
plain_iv = (tid[::] + plain_counter[
::]) # Get the IV for plain sector (TitleID + Plain Counter)
exefs_iv = (tid[::] + exefs_counter[::]) # Get the IV for ExeFS (TitleID + ExeFS Counter)
romfs_iv = (tid[::] + romfs_counter[::]) # Get the IV for RomFS (TitleID + RomFS Counter)
f.seek((part_off[0] * sectorsize) + 0x160) # get exheader hash
exhdr_sbhash = str("%016X%016X%016X%016X") % (struct.unpack('>QQQQ', f.read(0x20)))
f.seek((part_off[0] * sectorsize) + 0x180)
exhdr_len = struct.unpack('<L', f.read(0x04)) # get extended header length
f.seek((part_off[0] * sectorsize) + 0x190)
plain_off = struct.unpack('<L', f.read(0x04)) # get plain sector offset
plain_len = struct.unpack('<L', f.read(0x04)) # get plain sector length
f.seek((part_off[0] * sectorsize) + 0x198)
logo_off = struct.unpack('<L', f.read(0x04)) # get logo offset
logo_len = struct.unpack('<L', f.read(0x04)) # get logo length
f.seek((part_off[0] * sectorsize) + 0x1A0)
exefs_off = struct.unpack('<L', f.read(0x04)) # get exefs offset
exefs_len = struct.unpack('<L', f.read(0x04)) # get exefs length
f.seek((part_off[0] * sectorsize) + 0x1B0)
romfs_off = struct.unpack('<L', f.read(0x04)) # get romfs offset
romfs_len = struct.unpack('<L', f.read(0x04)) # get romfs length
f.seek((part_off[0] * sectorsize) + 0x1C0) # get exefs hash
exefs_sbhash = str("%016X%016X%016X%016X") % (struct.unpack('>QQQQ', f.read(0x20)))
f.seek((part_off[0] * sectorsize) + 0x1E0) # get romfs hash
romfs_sbhash = str("%016X%016X%016X%016X") % (struct.unpack('>QQQQ', f.read(0x20)))
plainIV = long(str("%016X%016X") % (plain_iv[::]), 16)
exefsIV = long(str("%016X%016X") % (exefs_iv[::]), 16)
romfsIV = long(str("%016X%016X") % (romfs_iv[::]), 16)
KeyY = long(str("%016X%016X") % (part_keyy[::]), 16)
Const = long(str("%016X%016X") % (Constant[::]), 16)
KeyX2C = long(str("%016X%016X") % (KeyX0x2C[::]), 16)
NormalKey2C = rol((rol(KeyX2C, 2, 128) ^ KeyY) + Const, 87, 128)
if (partition_flags[3] == 0x00): # Uses Original Key
KeyX = long(str("%016X%016X") % (KeyX0x2C[::]), 16)
elif (partition_flags[3] == 0x01): # Uses 7.x Key
KeyX = long(str("%016X%016X") % (KeyX0x25[::]), 16)
elif (partition_flags[3] == 0x0A): # Uses New3DS 9.3 Key
KeyX = long(str("%016X%016X") % (KeyX0x18[::]), 16)
elif (partition_flags[3] == 0x0B): # Uses New3DS 9.6 Key
KeyX = long(str("%016X%016X") % (KeyX0x1B[::]), 16)
NormalKey = rol((rol(KeyX, 2, 128) ^ KeyY) + Const, 87, 128)
if (partition_flags[7] & 0x01): # fixed crypto key (aka 0-key)
NormalKey = 0x00
NormalKey2C = 0x00
if (exhdr_len[0] > 0):
# decrypt exheader
f.seek((part_off[0] + 1) * sectorsize)
g.seek((part_off[0] + 1) * sectorsize)
exhdr_filelen = 0x800
exefsctr2C = Counter.new(128, initial_value=(plainIV))
exefsctrmode2C = AES.new(to_bytes(NormalKey2C), AES.MODE_CTR, counter=exefsctr2C)
print("Partition %1d ExeFS: Decrypting: ExHeader" % p)
g.write(exefsctrmode2C.decrypt(f.read(exhdr_filelen)))
if (exefs_len[0] > 0):
# decrypt exefs filename table
f.seek((part_off[0] + exefs_off[0]) * sectorsize)
g.seek((part_off[0] + exefs_off[0]) * sectorsize)
exefsctr2C = Counter.new(128, initial_value=(exefsIV))
exefsctrmode2C = AES.new(to_bytes(NormalKey2C), AES.MODE_CTR, counter=exefsctr2C)
g.write(exefsctrmode2C.decrypt(f.read(sectorsize)))
print("Partition %1d ExeFS: Decrypting: ExeFS Filename Table" % p)
if (partition_flags[3] == 0x01 or partition_flags[3] == 0x0A or partition_flags[
3] == 0x0B):
code_filelen = 0
for j in range(10): # 10 exefs filename slots
# get filename, offset and length
f.seek(((part_off[0] + exefs_off[0]) * sectorsize) + j * 0x10)
g.seek(((part_off[0] + exefs_off[0]) * sectorsize) + j * 0x10)
exefs_filename = struct.unpack('<8s', g.read(0x08))
if str(exefs_filename[0]) == str(".code\x00\x00\x00"):
code_fileoff = struct.unpack('<L', g.read(0x04))
code_filelen = struct.unpack('<L', g.read(0x04))
datalenM = ((code_filelen[0]) / (1024 * 1024))
datalenB = ((code_filelen[0]) % (1024 * 1024))
ctroffset = ((code_fileoff[0] + sectorsize) / 0x10)
exefsctr = Counter.new(128, initial_value=(exefsIV + ctroffset))
exefsctr2C = Counter.new(128, initial_value=(exefsIV + ctroffset))
exefsctrmode = AES.new(to_bytes(NormalKey), AES.MODE_CTR, counter=exefsctr)
exefsctrmode2C = AES.new(to_bytes(NormalKey2C), AES.MODE_CTR,
counter=exefsctr2C)
f.seek((((part_off[0] + exefs_off[0]) + 1) * sectorsize) + code_fileoff[0])
g.seek((((part_off[0] + exefs_off[0]) + 1) * sectorsize) + code_fileoff[0])
if (datalenM > 0):
for i in xrange(datalenM):
g.write(exefsctrmode2C.encrypt(
exefsctrmode.decrypt(f.read(1024 * 1024))))
print(
"\rPartition %1d ExeFS: Decrypting: %8s... %4d / %4d mb..." %
p, str(exefs_filename[0]), i, datalenM + 1),
if (datalenB > 0):
g.write(exefsctrmode2C.encrypt(exefsctrmode.decrypt(f.read(datalenB))))
print(
"\rPartition %1d ExeFS: Decrypting: %8s... %4d / %4d mb... Done!" %
p, str(exefs_filename[0]), datalenM + 1, datalenM + 1)
# decrypt exefs
exefsSizeM = ((exefs_len[0] - 1) * sectorsize) / (1024 * 1024)
exefsSizeB = ((exefs_len[0] - 1) * sectorsize) % (1024 * 1024)
ctroffset = (sectorsize / 0x10)
exefsctr2C = Counter.new(128, initial_value=(exefsIV + ctroffset))
exefsctrmode2C = AES.new(to_bytes(NormalKey2C), AES.MODE_CTR, counter=exefsctr2C)
f.seek((part_off[0] + exefs_off[0] + 1) * sectorsize)
g.seek((part_off[0] + exefs_off[0] + 1) * sectorsize)
if (exefsSizeM > 0):
for i in xrange(exefsSizeM):
g.write(exefsctrmode2C.decrypt(f.read(1024 * 1024)))
print("\rPartition %1d ExeFS: Decrypting: %4d / %4d mb" %
p, i, exefsSizeM + 1),
if (exefsSizeB > 0):
g.write(exefsctrmode2C.decrypt(f.read(exefsSizeB)))
print("\rPartition %1d ExeFS: Decrypting: %4d / %4d mb... Done" %
p, exefsSizeM + 1, exefsSizeM + 1)
else:
print("Partition %1d ExeFS: No Data... Skipping..." % p)
if (romfs_off[0] != 0):
romfsSizeM = (romfs_len[0] * sectorsize) / (1024 * 1024)
romfsSizeB = (romfs_len[0] * sectorsize) % (1024 * 1024)
romfsctr = Counter.new(128, initial_value=romfsIV)
romfsctrmode = AES.new(to_bytes(NormalKey), AES.MODE_CTR, counter=romfsctr)
f.seek((part_off[0] + romfs_off[0]) * sectorsize)
g.seek((part_off[0] + romfs_off[0]) * sectorsize)
if (romfsSizeM > 0):
for i in xrange(romfsSizeM):
g.write(romfsctrmode.decrypt(f.read(1024 * 1024)))
print("\rPartition %1d RomFS: Decrypting: %4d / %4d mb" %
p, i, romfsSizeM + 1),
if (romfsSizeB > 0):
g.write(romfsctrmode.decrypt(f.read(romfsSizeB)))
print("\rPartition %1d RomFS: Decrypting: %4d / %4d mb... Done" %
p, romfsSizeM + 1, romfsSizeM + 1)
else:
print("Partition %1d RomFS: No Data... Skipping..." % p)
g.seek((part_off[0] * sectorsize) + 0x18B)
g.write(struct.pack('<B', int(0x00))) # set crypto-method to 0x00
g.seek((part_off[0] * sectorsize) + 0x18F)
flag = int(partition_flags[7]) # read partition flag
flag = (flag & ((
0x01 | 0x20) ^ 0xFF)) # turn off 0x01 = FixedCryptoKey and 0x20 = CryptoUsingNewKeyY
flag = (flag | 0x04) # turn on 0x04 = NoCrypto
g.write(struct.pack('<B', int(flag))) # write flag
else:
print("Partition %1d Unable to read NCCH header" % p)
else:
print("Partition %1d Not found... Skipping..." % p)
print("Done...")
else:
print("Error: Not a 3DS Rom?")
input('Press Enter to Exit...')