3ds_decrypt_v2.py (20962B)
1 # /usr/bin/env python3 2 from Crypto.Cipher import AES 3 from Crypto.Util import Counter 4 from sys import argv 5 import struct 6 7 rol = lambda val, r_bits, max_bits: (val << r_bits % max_bits) & (2**max_bits - 1) | ( 8 (val & (2**max_bits - 1)) >> (max_bits - (r_bits % max_bits)) 9 ) 10 11 12 def to_bytes(num): 13 numstr = bytearray(b"") 14 tmp = num 15 while len(numstr) < 16: 16 numstr.append(tmp & 0xFF) 17 tmp >>= 8 18 return bytes(numstr[::-1]) 19 20 21 # Setup Keys and IVs 22 plain_counter = struct.unpack(">Q", b"\x01\x00\x00\x00\x00\x00\x00\x00") 23 exefs_counter = struct.unpack(">Q", b"\x02\x00\x00\x00\x00\x00\x00\x00") 24 romfs_counter = struct.unpack(">Q", b"\x03\x00\x00\x00\x00\x00\x00\x00") 25 Constant = struct.unpack( 26 ">QQ", b"\x1F\xF9\xE9\xAA\xC5\xFE\x04\x08\x02\x45\x91\xDC\x5D\x52\x76\x8A" 27 ) # 3DS AES Hardware Constant 28 29 # Retail keys 30 KeyX0x18 = struct.unpack( 31 ">QQ", b"\x82\xE9\xC9\xBE\xBF\xB8\xBD\xB8\x75\xEC\xC0\xA0\x7D\x47\x43\x74" 32 ) # KeyX 0x18 (New 3DS 9.3) 33 KeyX0x1B = struct.unpack( 34 ">QQ", b"\x45\xAD\x04\x95\x39\x92\xC7\xC8\x93\x72\x4A\x9A\x7B\xCE\x61\x82" 35 ) # KeyX 0x1B (New 3DS 9.6) 36 KeyX0x25 = struct.unpack( 37 ">QQ", b"\xCE\xE7\xD8\xAB\x30\xC0\x0D\xAE\x85\x0E\xF5\xE3\x82\xAC\x5A\xF3" 38 ) # KeyX 0x25 (> 7.x) 39 KeyX0x2C = struct.unpack( 40 ">QQ", b"\xB9\x8E\x95\xCE\xCA\x3E\x4D\x17\x1F\x76\xA9\x4D\xE9\x34\xC0\x53" 41 ) # KeyX 0x2C (< 6.x) 42 43 # Dev Keys: (Uncomment these lines if your 3ds rom is encrypted with Dev Keys) 44 # 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) 45 # 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) 46 # 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) 47 # 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) 48 49 with open(argv[1], "rb") as f: 50 with open(argv[1], "rb+") as g: 51 print(argv[1]) # Print the filename of the file being decrypted 52 f.seek(0x100) # Seek to start of NCSD header 53 magic = f.read(0x04) 54 if magic == b"NCSD": 55 56 f.seek(0x188) 57 ncsd_flags = struct.unpack("<BBBBBBBB", f.read(0x8)) 58 sectorsize = 0x200 * (2 ** ncsd_flags[6]) 59 60 for p in range(8): 61 f.seek( 62 (0x120) + (p * 0x08) 63 ) # Seek to start of partition information, read offsets and lengths 64 part_off = struct.unpack("<L", f.read(0x04)) 65 part_len = struct.unpack("<L", f.read(0x04)) 66 67 f.seek( 68 ((part_off[0]) * sectorsize) + 0x188 69 ) # Get the partition flags to determine encryption type. 70 partition_flags = struct.unpack("<BBBBBBBB", f.read(0x8)) 71 72 if ( 73 partition_flags[7] & 0x04 74 ): # check if the 'NoCrypto' bit (bit 3) is set 75 print(f"Partition {p}: Already Decrypted?...") 76 else: 77 if (part_off[0] * sectorsize) > 0: # check if partition exists 78 79 f.seek( 80 ((part_off[0]) * sectorsize) + 0x100 81 ) # Find partition start (+ 0x100 to skip NCCH header) 82 magic = f.read(0x04) 83 84 if magic == b"NCCH": # check if partition is valid 85 f.seek(((part_off[0]) * sectorsize) + 0x0) 86 part_keyy = struct.unpack( 87 ">QQ", f.read(0x10) 88 ) # KeyY is the first 16 bytes of partition RSA-2048 SHA-256 signature 89 90 f.seek(((part_off[0]) * sectorsize) + 0x108) 91 tid = struct.unpack( 92 "<Q", f.read(0x8) 93 ) # TitleID is used as IV joined with the content type. 94 plain_iv = ( 95 tid[::] + plain_counter[::] 96 ) # Get the IV for plain sector (TitleID + Plain Counter) 97 exefs_iv = ( 98 tid[::] + exefs_counter[::] 99 ) # Get the IV for ExeFS (TitleID + ExeFS Counter) 100 romfs_iv = ( 101 tid[::] + romfs_counter[::] 102 ) # Get the IV for RomFS (TitleID + RomFS Counter) 103 104 f.seek( 105 (part_off[0] * sectorsize) + 0x160 106 ) # get exheader hash 107 exhdr_sbhash = str("%016X%016X%016X%016X") % ( 108 struct.unpack(">QQQQ", f.read(0x20)) 109 ) 110 111 f.seek((part_off[0] * sectorsize) + 0x180) 112 exhdr_len = struct.unpack( 113 "<L", f.read(0x04) 114 ) # get extended header length 115 116 f.seek((part_off[0] * sectorsize) + 0x190) 117 plain_off = struct.unpack( 118 "<L", f.read(0x04) 119 ) # get plain sector offset 120 plain_len = struct.unpack( 121 "<L", f.read(0x04) 122 ) # get plain sector length 123 124 f.seek((part_off[0] * sectorsize) + 0x198) 125 logo_off = struct.unpack( 126 "<L", f.read(0x04) 127 ) # get logo offset 128 logo_len = struct.unpack( 129 "<L", f.read(0x04) 130 ) # get logo length 131 132 f.seek((part_off[0] * sectorsize) + 0x1A0) 133 exefs_off = struct.unpack( 134 "<L", f.read(0x04) 135 ) # get exefs offset 136 exefs_len = struct.unpack( 137 "<L", f.read(0x04) 138 ) # get exefs length 139 140 f.seek((part_off[0] * sectorsize) + 0x1B0) 141 romfs_off = struct.unpack( 142 "<L", f.read(0x04) 143 ) # get romfs offset 144 romfs_len = struct.unpack( 145 "<L", f.read(0x04) 146 ) # get romfs length 147 148 f.seek((part_off[0] * sectorsize) + 0x1C0) # get exefs hash 149 exefs_sbhash = str("%016X%016X%016X%016X") % ( 150 struct.unpack(">QQQQ", f.read(0x20)) 151 ) 152 153 f.seek((part_off[0] * sectorsize) + 0x1E0) # get romfs hash 154 romfs_sbhash = str("%016X%016X%016X%016X") % ( 155 struct.unpack(">QQQQ", f.read(0x20)) 156 ) 157 158 plainIV = int(str("%016X%016X") % (plain_iv[::]), 16) 159 exefsIV = int(str("%016X%016X") % (exefs_iv[::]), 16) 160 romfsIV = int(str("%016X%016X") % (romfs_iv[::]), 16) 161 KeyY = int(str("%016X%016X") % (part_keyy[::]), 16) 162 Const = int(str("%016X%016X") % (Constant[::]), 16) 163 164 KeyX2C = int(str("%016X%016X") % (KeyX0x2C[::]), 16) 165 NormalKey2C = rol( 166 (rol(KeyX2C, 2, 128) ^ KeyY) + Const, 87, 128 167 ) 168 169 if partition_flags[3] == 0x00: # Uses Original Key 170 KeyX = int(str("%016X%016X") % (KeyX0x2C[::]), 16) 171 elif partition_flags[3] == 0x01: # Uses 7.x Key 172 KeyX = int(str("%016X%016X") % (KeyX0x25[::]), 16) 173 elif partition_flags[3] == 0x0A: # Uses New3DS 9.3 Key 174 KeyX = int(str("%016X%016X") % (KeyX0x18[::]), 16) 175 elif partition_flags[3] == 0x0B: # Uses New3DS 9.6 Key 176 KeyX = int(str("%016X%016X") % (KeyX0x1B[::]), 16) 177 NormalKey = rol((rol(KeyX, 2, 128) ^ KeyY) + Const, 87, 128) 178 179 if ( 180 partition_flags[7] & 0x01 181 ): # fixed crypto key (aka 0-key) 182 NormalKey = 0x00 183 NormalKey2C = 0x00 184 print("0-key detected") 185 186 if exhdr_len[0] > 0: 187 # decrypt exheader 188 f.seek((part_off[0] + 1) * sectorsize) 189 g.seek((part_off[0] + 1) * sectorsize) 190 exhdr_filelen = 0x800 191 exefsctr2C = Counter.new(128, initial_value=(plainIV)) 192 blen_s = to_bytes(NormalKey2C) 193 print( 194 f"NormalKey2C: {NormalKey2C} of len {len(blen_s)}" 195 ) 196 exefsctrmode2C = AES.new( 197 blen_s, AES.MODE_CTR, counter=exefsctr2C 198 ) 199 print(f"Partition {p} ExeFS: Decrypting: ExHeader") 200 g.write(exefsctrmode2C.decrypt(f.read(exhdr_filelen))) 201 202 if exefs_len[0] > 0: 203 # decrypt exefs filename table 204 f.seek((part_off[0] + exefs_off[0]) * sectorsize) 205 g.seek((part_off[0] + exefs_off[0]) * sectorsize) 206 exefsctr2C = Counter.new(128, initial_value=(exefsIV)) 207 exefsctrmode2C = AES.new( 208 to_bytes(NormalKey2C), 209 AES.MODE_CTR, 210 counter=exefsctr2C, 211 ) 212 g.write(exefsctrmode2C.decrypt(f.read(sectorsize))) 213 print( 214 f"Partition {p} ExeFS: Decrypting: ExeFS Filename Table" 215 ) 216 217 if ( 218 partition_flags[3] == 0x01 219 or partition_flags[3] == 0x0A 220 or partition_flags[3] == 0x0B 221 ): 222 code_filelen = 0 223 for j in range(10): # 10 exefs filename slots 224 # get filename, offset and length 225 f.seek( 226 ((part_off[0] + exefs_off[0]) * sectorsize) 227 + j * 0x10 228 ) 229 g.seek( 230 ((part_off[0] + exefs_off[0]) * sectorsize) 231 + j * 0x10 232 ) 233 exefs_filename = struct.unpack( 234 "<8s", g.read(0x08) 235 ) 236 if str(exefs_filename[0]) == str( 237 ".code\x00\x00\x00" 238 ): 239 code_fileoff = struct.unpack( 240 "<L", g.read(0x04) 241 ) 242 code_filelen = struct.unpack( 243 "<L", g.read(0x04) 244 ) 245 datalenM = (code_filelen[0]) / (1024 * 1024) 246 datalenB = (code_filelen[0]) % (1024 * 1024) 247 ctroffset = ( 248 code_fileoff[0] + sectorsize 249 ) / 0x10 250 exefsctr = Counter.new( 251 128, initial_value=(exefsIV + ctroffset) 252 ) 253 exefsctr2C = Counter.new( 254 128, initial_value=(exefsIV + ctroffset) 255 ) 256 exefsctrmode = AES.new( 257 to_bytes(NormalKey), 258 AES.MODE_CTR, 259 counter=exefsctr, 260 ) 261 exefsctrmode2C = AES.new( 262 to_bytes(NormalKey2C), 263 AES.MODE_CTR, 264 counter=exefsctr2C, 265 ) 266 f.seek( 267 ( 268 ((part_off[0] + exefs_off[0]) + 1) 269 * sectorsize 270 ) 271 + code_fileoff[0] 272 ) 273 g.seek( 274 ( 275 ((part_off[0] + exefs_off[0]) + 1) 276 * sectorsize 277 ) 278 + code_fileoff[0] 279 ) 280 if datalenM > 0: 281 for i in range(int(datalenM)): 282 g.write( 283 exefsctrmode2C.encrypt( 284 exefsctrmode.decrypt( 285 f.read(1024 * 1024) 286 ) 287 ) 288 ) 289 print( 290 f"\rPartition {p} ExeFS: Decrypting: {str(exefs_filename[0])}... {i} / {datalenM+1} mb..." 291 ) 292 if datalenB > 0: 293 g.write( 294 exefsctrmode2C.encrypt( 295 exefsctrmode.decrypt( 296 f.read(datalenB) 297 ) 298 ) 299 ) 300 print( 301 f"\rPartition {p} ExeFS: Decrypting: {str(exefs_filename[0])}... {datalenM + 1} / {datalenM + 1} mb... Done!" 302 ) 303 304 # decrypt exefs 305 exefsSizeM = ((exefs_len[0] - 1) * sectorsize) / ( 306 1024 * 1024 307 ) 308 exefsSizeB = ((exefs_len[0] - 1) * sectorsize) % ( 309 1024 * 1024 310 ) 311 ctroffset = sectorsize / 0x10 312 exefsctr2C = Counter.new( 313 128, initial_value=(exefsIV + ctroffset) 314 ) 315 exefsctrmode2C = AES.new( 316 to_bytes(NormalKey2C), 317 AES.MODE_CTR, 318 counter=exefsctr2C, 319 ) 320 f.seek((part_off[0] + exefs_off[0] + 1) * sectorsize) 321 g.seek((part_off[0] + exefs_off[0] + 1) * sectorsize) 322 if exefsSizeM > 0: 323 for i in range(int(exefsSizeM)): 324 g.write( 325 exefsctrmode2C.decrypt(f.read(1024 * 1024)) 326 ) 327 print( 328 f"\rPartition {p} ExeFS: Decrypting: {i} / {exefsSizeM + 1} mb" 329 ) 330 if exefsSizeB > 0: 331 g.write(exefsctrmode2C.decrypt(f.read(exefsSizeB))) 332 print( 333 f"\rPartition {p} ExeFS: Decrypting: {exefsSizeM + 1} / {exefsSizeM + 1} mb... Done" 334 ) 335 336 else: 337 print(f"Partition {p} ExeFS: No Data... Skipping...") 338 339 if romfs_off[0] != 0: 340 romfsSizeM = (romfs_len[0] * sectorsize) / (1024 * 1024) 341 romfsSizeB = (romfs_len[0] * sectorsize) % (1024 * 1024) 342 343 romfsctr = Counter.new(128, initial_value=romfsIV) 344 romfsctrmode = AES.new( 345 to_bytes(NormalKey), AES.MODE_CTR, counter=romfsctr 346 ) 347 348 f.seek((part_off[0] + romfs_off[0]) * sectorsize) 349 g.seek((part_off[0] + romfs_off[0]) * sectorsize) 350 if romfsSizeM > 0: 351 for i in range(int(romfsSizeM)): 352 g.write( 353 romfsctrmode.decrypt(f.read(1024 * 1024)) 354 ) 355 print( 356 f"\rPartition {p} RomFS: Decrypting: {i} / {romfsSizeM + 1} mb" 357 ) 358 if romfsSizeB > 0: 359 g.write(romfsctrmode.decrypt(f.read(romfsSizeB))) 360 361 print( 362 f"\rPartition {p} RomFS: Decrypting: {romfsSizeM + 1} / {romfsSizeM + 1} mb... Done" 363 ) 364 365 else: 366 print(f"Partition {p} RomFS: No Data... Skipping...") 367 368 g.seek((part_off[0] * sectorsize) + 0x18B) 369 g.write( 370 struct.pack("<B", int(0x00)) 371 ) # set crypto-method to 0x00 372 g.seek((part_off[0] * sectorsize) + 0x18F) 373 flag = int(partition_flags[7]) # read partition flag 374 flag = flag & ( 375 (0x01 | 0x20) ^ 0xFF 376 ) # turn off 0x01 = FixedCryptoKey and 0x20 = CryptoUsingNewKeyY 377 flag = flag | 0x04 # turn on 0x04 = NoCrypto 378 g.write(struct.pack("<B", int(flag))) # write flag 379 380 else: 381 print(f"Partition {p} Unable to read NCCH header") 382 else: 383 print(f"Partition {p} Not found... Skipping...") 384 print("Done...") 385 else: 386 print("Error: Not a 3DS Rom?") 387 388 # raw_input('Press Enter to Exit...')