3ds_decrypt_v2

3ds cart image decryptor (github.com/lusi1990 fork)
git clone git://bsandro.tech/3ds_decrypt_v2
Log | Files | Refs | README

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...')