├── requirements.txt ├── README.md └── 3ds_decrypt_v2.py /requirements.txt: -------------------------------------------------------------------------------- 1 | pycrypto==2.6.1 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3ds_decrypt_v2 2 | 3 | 用于破解 3ds 文件 4 | 5 | for decrypt .3ds file 6 | 7 | 需要安装 python2 以及 依赖 8 | 9 | need instal python2 and dependencies 10 | 11 | ## TODO 12 | 13 | * update to py3 14 | -------------------------------------------------------------------------------- /3ds_decrypt_v2.py: -------------------------------------------------------------------------------- 1 | # /usr/bin/env python2 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: \ 8 | (val << r_bits % max_bits) & (2 ** max_bits - 1) | \ 9 | ((val & (2 ** max_bits - 1)) >> (max_bits - (r_bits % max_bits))) 10 | 11 | 12 | def to_bytes(num): 13 | numstr = '' 14 | tmp = num 15 | while len(numstr) < 16: 16 | numstr += chr(tmp & 0xFF) 17 | tmp >>= 8 18 | return numstr[::-1] 19 | 20 | 21 | # Setup Keys and IVs 22 | plain_counter = struct.unpack('>Q', '\x01\x00\x00\x00\x00\x00\x00\x00') 23 | exefs_counter = struct.unpack('>Q', '\x02\x00\x00\x00\x00\x00\x00\x00') 24 | romfs_counter = struct.unpack('>Q', '\x03\x00\x00\x00\x00\x00\x00\x00') 25 | Constant = struct.unpack('>QQ', 26 | '\x1F\xF9\xE9\xAA\xC5\xFE\x04\x08\x02\x45\x91\xDC\x5D\x52\x76\x8A') # 3DS AES Hardware Constant 27 | 28 | # Retail keys 29 | KeyX0x18 = struct.unpack('>QQ', 30 | '\x82\xE9\xC9\xBE\xBF\xB8\xBD\xB8\x75\xEC\xC0\xA0\x7D\x47\x43\x74') # KeyX 0x18 (New 3DS 9.3) 31 | KeyX0x1B = struct.unpack('>QQ', 32 | '\x45\xAD\x04\x95\x39\x92\xC7\xC8\x93\x72\x4A\x9A\x7B\xCE\x61\x82') # KeyX 0x1B (New 3DS 9.6) 33 | KeyX0x25 = struct.unpack('>QQ', '\xCE\xE7\xD8\xAB\x30\xC0\x0D\xAE\x85\x0E\xF5\xE3\x82\xAC\x5A\xF3') # KeyX 0x25 (> 7.x) 34 | KeyX0x2C = struct.unpack('>QQ', '\xB9\x8E\x95\xCE\xCA\x3E\x4D\x17\x1F\x76\xA9\x4D\xE9\x34\xC0\x53') # KeyX 0x2C (< 6.x) 35 | 36 | # Dev Keys: (Uncomment these lines if your 3ds rom is encrypted with Dev Keys) 37 | # 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) 38 | # 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) 39 | # 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) 40 | # 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) 41 | 42 | with open(argv[1], 'rb') as f: 43 | with open(argv[1], 'rb+') as g: 44 | print(argv[1]) # Print the filename of the file being decrypted 45 | f.seek(0x100) # Seek to start of NCSD header 46 | magic = f.read(0x04) 47 | if magic == "NCSD": 48 | 49 | f.seek(0x188) 50 | ncsd_flags = struct.unpack(' 0: # check if partition exists 65 | 66 | f.seek( 67 | ((part_off[0]) * sectorsize) + 0x100) # Find partition start (+ 0x100 to skip NCCH header) 68 | magic = f.read(0x04) 69 | 70 | if magic == "NCCH": # check if partition is valid 71 | f.seek(((part_off[0]) * sectorsize) + 0x0) 72 | part_keyy = struct.unpack('>QQ', f.read( 73 | 0x10)) # KeyY is the first 16 bytes of partition RSA-2048 SHA-256 signature 74 | 75 | f.seek(((part_off[0]) * sectorsize) + 0x108) 76 | tid = struct.unpack('QQQQ', f.read(0x20))) 85 | 86 | f.seek((part_off[0] * sectorsize) + 0x180) 87 | exhdr_len = struct.unpack('QQQQ', f.read(0x20))) 107 | 108 | f.seek((part_off[0] * sectorsize) + 0x1E0) # get romfs hash 109 | romfs_sbhash = str("%016X%016X%016X%016X") % (struct.unpack('>QQQQ', f.read(0x20))) 110 | 111 | plainIV = long(str("%016X%016X") % (plain_iv[::]), 16) 112 | exefsIV = long(str("%016X%016X") % (exefs_iv[::]), 16) 113 | romfsIV = long(str("%016X%016X") % (romfs_iv[::]), 16) 114 | KeyY = long(str("%016X%016X") % (part_keyy[::]), 16) 115 | Const = long(str("%016X%016X") % (Constant[::]), 16) 116 | 117 | KeyX2C = long(str("%016X%016X") % (KeyX0x2C[::]), 16) 118 | NormalKey2C = rol((rol(KeyX2C, 2, 128) ^ KeyY) + Const, 87, 128) 119 | 120 | if (partition_flags[3] == 0x00): # Uses Original Key 121 | KeyX = long(str("%016X%016X") % (KeyX0x2C[::]), 16) 122 | elif (partition_flags[3] == 0x01): # Uses 7.x Key 123 | KeyX = long(str("%016X%016X") % (KeyX0x25[::]), 16) 124 | elif (partition_flags[3] == 0x0A): # Uses New3DS 9.3 Key 125 | KeyX = long(str("%016X%016X") % (KeyX0x18[::]), 16) 126 | elif (partition_flags[3] == 0x0B): # Uses New3DS 9.6 Key 127 | KeyX = long(str("%016X%016X") % (KeyX0x1B[::]), 16) 128 | NormalKey = rol((rol(KeyX, 2, 128) ^ KeyY) + Const, 87, 128) 129 | 130 | if (partition_flags[7] & 0x01): # fixed crypto key (aka 0-key) 131 | NormalKey = 0x00 132 | NormalKey2C = 0x00 133 | 134 | if (exhdr_len[0] > 0): 135 | # decrypt exheader 136 | f.seek((part_off[0] + 1) * sectorsize) 137 | g.seek((part_off[0] + 1) * sectorsize) 138 | exhdr_filelen = 0x800 139 | exefsctr2C = Counter.new(128, initial_value=(plainIV)) 140 | exefsctrmode2C = AES.new(to_bytes(NormalKey2C), AES.MODE_CTR, counter=exefsctr2C) 141 | print("Partition %1d ExeFS: Decrypting: ExHeader" % p) 142 | g.write(exefsctrmode2C.decrypt(f.read(exhdr_filelen))) 143 | 144 | if (exefs_len[0] > 0): 145 | # decrypt exefs filename table 146 | f.seek((part_off[0] + exefs_off[0]) * sectorsize) 147 | g.seek((part_off[0] + exefs_off[0]) * sectorsize) 148 | exefsctr2C = Counter.new(128, initial_value=(exefsIV)) 149 | exefsctrmode2C = AES.new(to_bytes(NormalKey2C), AES.MODE_CTR, counter=exefsctr2C) 150 | g.write(exefsctrmode2C.decrypt(f.read(sectorsize))) 151 | print("Partition %1d ExeFS: Decrypting: ExeFS Filename Table" % p) 152 | 153 | if (partition_flags[3] == 0x01 or partition_flags[3] == 0x0A or partition_flags[ 154 | 3] == 0x0B): 155 | code_filelen = 0 156 | for j in range(10): # 10 exefs filename slots 157 | # get filename, offset and length 158 | f.seek(((part_off[0] + exefs_off[0]) * sectorsize) + j * 0x10) 159 | g.seek(((part_off[0] + exefs_off[0]) * sectorsize) + j * 0x10) 160 | exefs_filename = struct.unpack('<8s', g.read(0x08)) 161 | if str(exefs_filename[0]) == str(".code\x00\x00\x00"): 162 | code_fileoff = struct.unpack(' 0): 175 | for i in xrange(datalenM): 176 | g.write(exefsctrmode2C.encrypt( 177 | exefsctrmode.decrypt(f.read(1024 * 1024)))) 178 | print( 179 | "\rPartition %1d ExeFS: Decrypting: %8s... %4d / %4d mb..." % 180 | p, str(exefs_filename[0]), i, datalenM + 1), 181 | if (datalenB > 0): 182 | g.write(exefsctrmode2C.encrypt(exefsctrmode.decrypt(f.read(datalenB)))) 183 | print( 184 | "\rPartition %1d ExeFS: Decrypting: %8s... %4d / %4d mb... Done!" % 185 | p, str(exefs_filename[0]), datalenM + 1, datalenM + 1) 186 | 187 | # decrypt exefs 188 | exefsSizeM = ((exefs_len[0] - 1) * sectorsize) / (1024 * 1024) 189 | exefsSizeB = ((exefs_len[0] - 1) * sectorsize) % (1024 * 1024) 190 | ctroffset = (sectorsize / 0x10) 191 | exefsctr2C = Counter.new(128, initial_value=(exefsIV + ctroffset)) 192 | exefsctrmode2C = AES.new(to_bytes(NormalKey2C), AES.MODE_CTR, counter=exefsctr2C) 193 | f.seek((part_off[0] + exefs_off[0] + 1) * sectorsize) 194 | g.seek((part_off[0] + exefs_off[0] + 1) * sectorsize) 195 | if (exefsSizeM > 0): 196 | for i in xrange(exefsSizeM): 197 | g.write(exefsctrmode2C.decrypt(f.read(1024 * 1024))) 198 | print("\rPartition %1d ExeFS: Decrypting: %4d / %4d mb" % 199 | p, i, exefsSizeM + 1), 200 | if (exefsSizeB > 0): 201 | g.write(exefsctrmode2C.decrypt(f.read(exefsSizeB))) 202 | print("\rPartition %1d ExeFS: Decrypting: %4d / %4d mb... Done" % 203 | p, exefsSizeM + 1, exefsSizeM + 1) 204 | 205 | else: 206 | print("Partition %1d ExeFS: No Data... Skipping..." % p) 207 | 208 | if (romfs_off[0] != 0): 209 | romfsSizeM = (romfs_len[0] * sectorsize) / (1024 * 1024) 210 | romfsSizeB = (romfs_len[0] * sectorsize) % (1024 * 1024) 211 | 212 | romfsctr = Counter.new(128, initial_value=romfsIV) 213 | romfsctrmode = AES.new(to_bytes(NormalKey), AES.MODE_CTR, counter=romfsctr) 214 | 215 | f.seek((part_off[0] + romfs_off[0]) * sectorsize) 216 | g.seek((part_off[0] + romfs_off[0]) * sectorsize) 217 | if (romfsSizeM > 0): 218 | for i in xrange(romfsSizeM): 219 | g.write(romfsctrmode.decrypt(f.read(1024 * 1024))) 220 | print("\rPartition %1d RomFS: Decrypting: %4d / %4d mb" % 221 | p, i, romfsSizeM + 1), 222 | if (romfsSizeB > 0): 223 | g.write(romfsctrmode.decrypt(f.read(romfsSizeB))) 224 | 225 | print("\rPartition %1d RomFS: Decrypting: %4d / %4d mb... Done" % 226 | p, romfsSizeM + 1, romfsSizeM + 1) 227 | 228 | else: 229 | print("Partition %1d RomFS: No Data... Skipping..." % p) 230 | 231 | g.seek((part_off[0] * sectorsize) + 0x18B) 232 | g.write(struct.pack('