Skip to content

Instantly share code, notes, and snippets.

@tesnos6921
Last active August 26, 2018 09:17
Show Gist options
  • Save tesnos6921/531c3fdf68bb936aadd9add6d071558a to your computer and use it in GitHub Desktop.
Save tesnos6921/531c3fdf68bb936aadd9add6d071558a to your computer and use it in GitHub Desktop.
Python 2.7. Requires lz4. Place in a folder with hactool, BOOT0.bin, and BCPKG2-1-Normal-Main.bin and let the magic happen.
import hashlib, binascii
import sys, subprocess, os
# NXO64 Stuff here
#
#
#--------------------------------------------------
# Copyright 2017 Reswitched Team
#
# Permission to use, copy, modify, and/or distribute this software for any purpose with or
# without fee is hereby granted, provided that the above copyright notice and this permission
# notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
# SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE
# OR PERFORMANCE OF THIS SOFTWARE.
# nxo64.py: IDA loader (and library for reading nso/nro files)
import struct
import lz4.block
uncompress = lz4.block.decompress
def kip1_blz_decompress(compressed):
compressed_size, init_index, uncompressed_addl_size = struct.unpack('<III', compressed[-0xC:])
decompressed = compressed[:] + '\x00' * uncompressed_addl_size
decompressed_size = len(decompressed)
if len(compressed) != compressed_size:
assert len(compressed) > compressed_size
compressed = compressed[len(compressed) - compressed_size:]
if not (compressed_size + uncompressed_addl_size):
return ''
compressed = map(ord, compressed)
decompressed = map(ord, decompressed)
index = compressed_size - init_index
outindex = decompressed_size
while outindex > 0:
index -= 1
control = compressed[index]
for i in xrange(8):
if control & 0x80:
if index < 2:
raise ValueError('Compression out of bounds!')
index -= 2
segmentoffset = compressed[index] | (compressed[index+1] << 8)
segmentsize = ((segmentoffset >> 12) & 0xF) + 3
segmentoffset &= 0x0FFF
segmentoffset += 2
if outindex < segmentsize:
raise ValueError('Compression out of bounds!')
for j in xrange(segmentsize):
if outindex + segmentoffset >= decompressed_size:
raise ValueError('Compression out of bounds!')
data = decompressed[outindex+segmentoffset]
outindex -= 1
decompressed[outindex] = data
else:
if outindex < 1:
raise ValueError('Compression out of bounds!')
outindex -= 1
index -= 1
decompressed[outindex] = compressed[index]
control <<= 1
control &= 0xFF
if not outindex:
break
return ''.join(map(chr, decompressed))
class BinFile(object):
def __init__(self, li):
self._f = li
def read(self, arg):
if isinstance(arg, str):
fmt = '<' + arg
size = struct.calcsize(fmt)
raw = self._f.read(size)
out = struct.unpack(fmt, raw)
if len(out) == 1:
return out[0]
return out
elif arg is None:
return self._f.read()
else:
out = self._f.read(arg)
return out
def read_from(self, arg, offset):
old = self.tell()
try:
self.seek(offset)
out = self.read(arg)
finally:
self.seek(old)
return out
def seek(self, off):
self._f.seek(off)
def close(self):
self._f.close()
def tell(self):
return self._f.tell()
def kip_get_full(fileobj):
f = BinFile(fileobj)
if f.read_from('4s', 0) != 'KIP1':
raise NxoException('Invalid KIP magic')
tloc, tsize, tfilesize = f.read_from('III', 0x20)
rloc, rsize, rfilesize = f.read_from('III', 0x30)
dloc, dsize, dfilesize = f.read_from('III', 0x40)
toff = 0x100
roff = toff + tfilesize
doff = roff + rfilesize
bsssize = f.read_from('I', 0x18)
#print 'load text: '
text = kip1_blz_decompress(str(f.read_from(tfilesize, toff)))
ro = kip1_blz_decompress(str(f.read_from(rfilesize, roff)))
data = kip1_blz_decompress(str(f.read_from(dfilesize, doff)))
full = text
if rloc >= len(full):
full += '\0' * (rloc - len(full))
else:
#print 'truncating?'
full = full[:rloc]
full += ro
if dloc >= len(full):
full += '\0' * (dloc - len(full))
else:
#print 'truncating?'
full = full[:dloc]
full += data
return full
# -------------------------------------------------------------------------
# Constants (Check out http://switchbrew.org/index.php?title=Flash_Filesystem and https://gist.github.com/roblabla/d8358ab058bbe3b00614740dcba4f208)
PKG11_REALBEGIN = 0x100000
PKG11_LENGTH = 0x40000
PKG21_BEGIN = 0x4000
FNULL = open(os.devnull, "wb")
HACTOOL_PATH = "hactool"
KEY_HASHES = {
"keyblob_mac_key_source" : "B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0",
"keyblob_key_source_00" : "8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786",
"keyblob_key_sources" :
{
"keyblob_key_source_01" : "2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B",
"keyblob_key_source_02" : "61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064",
"keyblob_key_source_03" : "8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903",
"keyblob_key_source_04" : "95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"
},
"master_key_source" : "7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4",
"master_key_00" : "0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA",
"master_keys" :
{
"master_key_01" : "4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E",
"master_key_02" : "79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09",
"master_key_03" : "4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754",
"master_key_04" : "75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"
},
"package1_key_00" : "4543CD1B7CAD7EE0466A3DE2086A0EF923805DCEA6C741541CDDB14F54F97B40",
"package1_keys" :
{
"package1_key_01" : "984F1916834540FF3037D65133F374BD9E715DC3B162AAC77C8387F9B22CF909",
"package1_key_02" : "9E7510E4141AD89D0FB697E817326D3C80F96156DCE7B6903049AC033E95F612",
"package1_key_03" : "E65C383CDF526DFFAA77682868EBFA9535EE60D8075C961BBC1EDE5FBF7E3C5F",
"package1_key_04" : "28AE73D6AE8F7206FCA549E27097714E599DF1208E57099416FF429B71370162"
},
"package2_key_source" : "21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603",
"aes_kek_generation_source" : "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085",
"aes_key_generation_source" : "FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585",
"titlekek_source" : "C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87",
"key_area_key_application_source" : "04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E",
"key_area_key_ocean_source" : "FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417",
"key_area_key_system_source" : "1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7",
"header_kek_source" : "1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2",
"header_key_source" : "8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6",
"sd_card_kek_source" : "6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7",
"sd_card_save_key_source" : "D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C",
"sd_card_nca_key_source" : "2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"
}
KEY_SIZES = {
"keyblob_mac_key_source" : 0x10,
"keyblob_key_source_00" : 0x10,
"keyblob_key_sources" : 0x10,
"master_key_source" : 0x10,
"master_key_00" : 0x10,
"master_keys" : 0x10,
"package1_key_00" : 0x10,
"package1_keys" : 0x10,
"package2_key_source" : 0x10,
"aes_kek_generation_source" : 0x10,
"aes_key_generation_source" : 0x10,
"titlekek_source" : 0x10,
"key_area_key_application_source" : 0x10,
"key_area_key_ocean_source" : 0x10,
"key_area_key_system_source" : 0x10,
"header_kek_source" : 0x10,
"header_key_source" : 0x20,
"sd_card_kek_source" : 0x10,
"sd_card_save_key_source" : 0x20,
"sd_card_nca_key_source" : 0x20
}
keyz = {}
# Utilities
def quit_with_errormsg(message):
print message
sys.exit(1)
def find_via_hash(data, hash, size):
#print hash
for i in range(len(data) - size):
m = hashlib.sha256()
m.update(data[i : i + size])
if m.hexdigest() == hash.lower():
#print "key found"
return binascii.hexlify(data[i : i + size]).upper()
#print binascii.hexlify(data[i : i + len(hash)]).upper()
#print m.hexdigest()
#break
return ""
def find_via_hashset(data, hashset, size):
for name, hash in hashset.iteritems():
result = find_via_hash(data, hashset[name], size)
if result != "":
return name, result
return ""
def checkfound(possibleblank, name):
if possibleblank == "":
quit_with_errormsg("Could not find " + name + "! Please check the integrity of the data used in the current stage!")
return possibleblank
def parse_hactool(output):
output = output.replace(" ", "")
for line in output.splitlines():
if "=" not in line:
continue
keyname, hactoolkey = line.split("=")
#print keyname
if keyname not in keyz and "encrypted_keyblob" not in keyname:
keyz[keyname] = hactoolkey
def update_keyfile():
#Overwrite keys.txt if it already exists
keys_f = open("keys.txt", "wb+")
for keyname, key in keyz.iteritems():
keys_f.write(keyname + " = " + key + "\n")
keys_f.flush()
keys_f.close()
def find_and_add_key(data, keyname):
key = checkfound(find_via_hash(data, KEY_HASHES[keyname], KEY_SIZES[keyname]), keyname)
keyz[keyname] = key
# Real code
if len(sys.argv) == 1:
print "kezplez.py : a badly written way to get all of your switch keys, legally!"
print "made by: tesnos6921, improved by Stay off my cock#6239 (nickname Shadow) on the reswitched Discord"
print ""
print "All this awesome scene development wouldn't be happening without the ReSwitched team. Thanks to them for nxo64.py, rajkosto for HacDiskMount and biskeydump (+hints for .kip1s), and to roblada for the original keybingo gist!"
print ""
print "Usage: "
print "Place hactool, BOOT0.bin (can be dumped from hekate) and BCPKG2-1-Normal-Main.bin (use HacDiskMount on your RawNand.bin) in the same folder and run"
print "python kezplez.py your-sbk-here your-tsec-key-here"
print ""
print "This process is very inefficient and can take a few minutes."
sys.exit(1)
keyz["secure_boot_key"] = sys.argv[1]
keyz["tsec_key"] = sys.argv[2]
BOOT0_f = open("BOOT0.bin", "rb")
BOOT0_data = BOOT0_f.read()
BOOT0_f.close()
if os.path.exists("rawnand.bin"):
RAWNAND_f = open("rawnand.bin", "rb")
RAWNAND_f.seek(0x800000)
PKG21PART_data = RAWNAND_f.read(0x800000)
RAWNAND_f.close()
else:
PKG21PART_f = open("BCPKG2-1-Normal-Main.bin", "rb")
PKG21PART_data = PKG21PART_f.read()
PKG21PART_f.close()
PKG21_f = open("package2.bin", "wb+")
PKG21_data = PKG21PART_data[PKG21_BEGIN : len(PKG21PART_data)]
PKG21_f.write(PKG21_data)
PKG21_f.close()
# Stage 0 : Extract package1 from BOOT0.bin and get keyblob_mac_key_source, master_key_source, and keyblob_key_source_xx from it
PKG11_f = open("package1.bin", "wb+")
if "PK11" not in BOOT0_data:
quit_with_errormsg("package1 was not found. Please check the integrity of your BOOT0.bin")
print "Using BOOT0.bin to get keys from package1..."
PKG11_begin = BOOT0_data.find("PK11")
PKG11_data = BOOT0_data[PKG11_begin : PKG11_begin + PKG11_LENGTH]
PKG11_f.write(PKG11_data)
PKG11_f.close()
#print len(PKG11_data)
find_and_add_key(PKG11_data, "keyblob_mac_key_source")
find_and_add_key(PKG11_data, "master_key_source")
find_and_add_key(PKG11_data, "keyblob_key_source_00")
# Firmware dependent, so it varies
# keyblob_key_source_id, keyblob_key_source_xx = checkfound(find_via_hashset(PKG11_data, KEY_HASHES["keyblob_key_sources"], KEY_SIZES["keyblob_key_sources"]), "keyblob_key_source_xx")
# keyz[keyblob_key_source_id] = keyblob_key_source_xx
print "Injecting keyblob_key_sources"
#Thanks Shadow + SciresM! these can be found from https://raw.githubusercontent.com/Atmosphere-NX/Atmosphere/master/fusee/fusee-secondary/src/key_derivation.c and are plaintext within package1
keyz["keyblob_key_source_01"] = "0C25615D684CEB421C2379EA822512AC"
keyz["keyblob_key_source_02"] = "337685EE884AAE0AC28AFD7D63C0433B"
keyz["keyblob_key_source_03"] = "2D1F4880EDECED3E3CF248B5657DF7BE"
keyz["keyblob_key_source_04"] = "BB5A01F988AFF5FC6CFF079E133C3980"
update_keyfile()
# Now with these keys, we can run hactool to derive package1_key_00/package1_key_xx and master_key_00/master_key_xx
print "Deriving keys..."
stage0_results = subprocess.check_output([HACTOOL_PATH, "--keyset=keys.txt", "--intype=keygen", "BOOT0.bin"])
parse_hactool(stage0_results)
update_keyfile()
# Decrypt package1 with our newly derived keys
print "Decrypting package1..."
# Impostor!
PKG11_f = open("package1.bin", "wb+")
PKG11_data = BOOT0_data[PKG11_REALBEGIN : PKG11_REALBEGIN + PKG11_LENGTH]
PKG11_f.write(PKG11_data)
PKG11_f.close()
subprocess.call([HACTOOL_PATH, "--keyset=keys.txt", "--package1dir=package1", "--intype=pk11", "package1.bin"], stdout=FNULL)
# After package1 is decrypted, we move on to...
# Stage 1 : Extract titlekek_source, aes_kek_generation_source, and package2_key_source from Secure_Monitor.bin (TrustZone code)
print "Using Secure_Monitor.bin to get keys to decrypt package2..."
TZ_f = open("package1/Secure_Monitor.bin", "rb")
TZ_data = TZ_f.read()
TZ_f.close()
find_and_add_key(TZ_data, "titlekek_source")
find_and_add_key(TZ_data, "aes_kek_generation_source")
find_and_add_key(TZ_data, "package2_key_source")
update_keyfile()
# About halfway there, now to decrypt package2
print "Decrypting package2..."
subprocess.call([HACTOOL_PATH, "--keyset=keys.txt", "--package2dir=package2", "--ini1dir=ini1", "--intype=pk21", "package2.bin"], stdout=FNULL)
# Stage 2 : All kip1s from package2 are compressed via lz4 somehow. I don't care enough to understand how, but thanks again to reswitched, this time for their IDA "nxo64.py" loader (I just stripped everything but the kip bits)
print "Decompressing spl.kip1 and FS.kip1..."
SPL_KIP1_f = open("ini1/spl.kip1", "rb")
FS_KIP1_f = open("ini1/FS.kip1", "rb")
SPL_KIP1_data = kip_get_full(SPL_KIP1_f)
FS_KIP1_data = kip_get_full(FS_KIP1_f)
SPL_KIP1_f.close()
FS_KIP1_f.close()
# Now for the final keys...
print "Getting keys from spl..."
find_and_add_key(SPL_KIP1_data, "aes_key_generation_source")
print "Getting keys from FS..."
find_and_add_key(FS_KIP1_data, "key_area_key_application_source")
find_and_add_key(FS_KIP1_data, "key_area_key_ocean_source")
find_and_add_key(FS_KIP1_data, "key_area_key_system_source")
find_and_add_key(FS_KIP1_data, "header_kek_source")
find_and_add_key(FS_KIP1_data, "header_key_source")
find_and_add_key(FS_KIP1_data, "sd_card_kek_source")
find_and_add_key(FS_KIP1_data, "sd_card_save_key_source")
find_and_add_key(FS_KIP1_data, "sd_card_nca_key_source")
update_keyfile()
# Stage 3 : a bit of extra derivation can never hurt, right?
print "Doing final key derivation..."
stage3_results = subprocess.check_output([HACTOOL_PATH, "--keyset=keys.txt", "--intype=keygen", "BOOT0.bin"])
parse_hactool(stage3_results)
update_keyfile()
print "If there were no warnings, we found all the keys!"
print "Now you can do hactool --keyset=keys.txt to use them!"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment