xous-core/tools/pddbdbg.py
2023-04-05 23:30:21 +08:00

205 lines
8.8 KiB
Python
Executable file

#! /usr/bin/env python3
import argparse
import logging
from pddbcommon import *
def main():
global DO_CI_TESTS
parser = argparse.ArgumentParser(description="Debug PDDB Images")
parser.add_argument(
"--name", required=False, help="pddb disk image root name", type=str, nargs='?', metavar=('name'), const='./pddb'
)
parser.add_argument(
"--loglevel", required=False, help="set logging level (INFO/DEBUG/WARNING/ERROR)", type=str, default="INFO",
)
parser.add_argument(
"--dump", required=False, help="Only dump the image, skip the automated CI checks", action="store_true"
)
parser.add_argument(
"--renode", required=False, help="Override flex-size settings and read a Renode bin file", action="store_true"
)
parser.add_argument(
"--smalldb", required=False, help="Override default size of PDDB and set it to 4MiB (for images built to that size only)", action="store_true"
)
parser.add_argument(
"--basis", type=str, help="Extra Bases to unlock, as `name:pass`. Each additional basis requires another --basis separator. Note that : is not legal to use in a Basis name.", action="append", nargs="+"
)
parser.add_argument(
"-p", "--pin", help="Unlock PIN", type=str, default='a'
)
parser.add_argument(
"--ci", help="Run in CI test mode. Requires all keys to have a specific checksum structure for automated checking.", action="store_true"
)
args = parser.parse_args()
numeric_level = getattr(logging, args.loglevel.upper(), None)
if args.dump:
numeric_level = getattr(logging, 'DEBUG', None)
else:
numeric_level = getattr(logging, args.loglevel.upper(), None)
if not isinstance(numeric_level, int):
raise ValueError('Invalid log level: %s' % args.loglevel)
logging.basicConfig(level=numeric_level)
set_ci_tests_flag(args.ci)
if args.renode is True:
keyfile = "./emulation/renode-keybox.bin"
imagefile = "./tools/pddb-images/renode.bin"
basis_credentials = []
if args.basis:
for pair in args.basis:
credpair = pair[0].split(':', 1)
if len(credpair) != 2:
logging.error("Basis credential pair with name {} has a formatting problem, aborting!".format(credpair[0]))
exit(1)
basis_credentials += [credpair]
with open(keyfile, 'rb') as key_f:
keyrom = key_f.read()
with open(imagefile, 'rb') as img_f:
raw_img = img_f.read()
if args.smalldb:
raw_img = raw_img[0x01D80000:0x1D8_0000 + 1024 * 1024 * 4]
else:
raw_img = raw_img[0x01D80000:0x07F80000]
keys = extract_keys(keyrom, raw_img, args.pin, basis_credentials)
else:
if args.name == None:
keyfile = './tools/pddb-images/pddb.key'
imagefile = './tools/pddb-images/pddb.bin'
else:
keyfile = './tools/pddb-images/{}.key'.format(args.name)
imagefile = './tools/pddb-images/{}.bin'.format(args.name)
if args.dump:
DO_CI_TESTS = False
keys = {}
with open(keyfile, 'rb') as key_f:
raw_key = key_f.read()
num_keys = int.from_bytes(raw_key[:4], 'little')
for i in range(num_keys):
name_all = raw_key[4 + i*128 : 4 + i*128 + 64]
name_bytes = bytearray(b'')
for b in name_all:
if b != 0:
name_bytes.append(b)
else:
break
name = name_bytes.decode('utf8', errors='ignore')
key_data = raw_key[4 + i*128 + 64 : 4 + i*128 + 96]
key_pt = raw_key[4 + i*128 + 96 : 4 + i*128 + 128]
keys[name] = [key_pt, key_data]
logging.info("Found basis keys (pt, data):")
logging.info(str(keys))
# tunable parameters for a filesystem
global MBBB_PAGES
FSCB_PAGES = 16
FSCB_LEN_PAGES = 2
KEY_PAGES = 1
global PAGE_SIZE
global VPAGE_SIZE
MAX_DICTS = 16383
with open(imagefile, 'rb') as img_f:
raw_img = img_f.read()
if args.renode:
if args.smalldb:
raw_img = raw_img[0x01D80000:0x1D8_0000 + 1024 * 1024 * 4]
else:
raw_img = raw_img[0x01D80000:0x07F80000]
pddb_len = len(raw_img)
pddb_size_pages = pddb_len // PAGE_SIZE
logging.info("Disk size: 0x{:x}".format(pddb_len))
mbbb_offset = pddb_size_pages * Pte.PTE_LEN + PAGE_SIZE * KEY_PAGES
if mbbb_offset & (PAGE_SIZE - 1) != 0:
mbbb_offset = (mbbb_offset + PAGE_SIZE) & 0xFFFF_F000 # round up to nearest page
logging.info("MBBB: 0x{:x}".format(mbbb_offset))
img_index = 0
tables = decode_pagetable(raw_img, pddb_size_pages, keys, raw_img[mbbb_offset:mbbb_offset + MBBB_PAGES * PAGE_SIZE])
img_index += pddb_size_pages * Pte.PTE_LEN
if img_index & (PAGE_SIZE - 1) != 0:
img_index = (img_index + PAGE_SIZE) & 0xFFFF_F000
rawkeys = raw_img[img_index : img_index + PAGE_SIZE * KEY_PAGES]
logging.debug("Keys: 0x{:x}".format(img_index))
img_index += PAGE_SIZE * KEY_PAGES
mbbb = raw_img[img_index : img_index + PAGE_SIZE * MBBB_PAGES]
logging.debug("MBBB check: 0x{:x}".format(img_index))
img_index += PAGE_SIZE * MBBB_PAGES
fscb = decode_fscb(raw_img[img_index: img_index + PAGE_SIZE * FSCB_PAGES], keys, FSCB_LEN_PAGES=FSCB_LEN_PAGES)
logging.debug("FSCB: 0x{:x}".format(img_index))
img_index += PAGE_SIZE * FSCB_PAGES
logging.debug("Data: 0x{:x}".format(img_index))
data = raw_img[img_index:]
for name, key in keys.items():
if name in tables:
logging.info("Basis {}, key_pt: {}, key_data: {}".format(name, key[0].hex(), key[1].hex()))
v2p_table = tables[name][0]
p2v_table = tables[name][1]
# v2p_table[0xfe0fe0] = 0x1200* 0x100
basis_data = bytearray()
pp_start = v2p_table[VPAGE_SIZE]
# print("pp_start: {:x}".format(pp_start))
pp_data = data[pp_start:pp_start + PAGE_SIZE]
try:
pt_data = keycommit_decrypt(key[1], basis_aad(name), pp_data)
basis_data.extend(bytearray(pt_data))
logging.debug("decrypted vpage @ {:x} ppage @ {:x}".format(VPAGE_SIZE, v2p_table[VPAGE_SIZE]))
# print([hex(x) for x in basis_data[:256]])
basis = Basis(basis_data)
logging.info(basis.as_str())
basis_dicts = {}
dicts_found = 0
dict_index = 0
while dict_index < MAX_DICTS and dicts_found < basis.num_dicts:
bdict = BasisDicts(dict_index, v2p_table, data, key[1], name)
if bdict.valid:
basis_dicts[bdict.name] = bdict
dicts_found += 1
dict_index += 1
if dicts_found != basis.num_dicts:
logging.error("Expected {} dictionaries, only found {}; searched {}".format(basis.num_dicts, dicts_found, dict_index))
found_all_dicts = False
else:
found_all_dicts = True
logging.debug(" Dictionaries: ")
for bdict in basis_dicts.values():
logging.debug(bdict.as_str())
#d2 = basis_dicts["dict2"]
#logging.info("listing {} keys".format(len(d2.keys)))
#for key in d2.keys:
# logging.info("{}".format(key))
if args.ci == True:
logging.info("CI checks:")
for bdict in basis_dicts.values():
bdict.ci_check()
if found_all_dicts:
logging.info("All dicts were found.") # this message is searched for in CI, don't change it
else:
logging.error("Missing dictionaries, something is wrong.")
else:
for bdict in basis_dicts.values():
logging.info("==================================================================")
logging.info("Dict {}".format(bdict.as_str()))
except ValueError:
logging.error("couldn't decrypt basis root vpage @ {:x} ppage @ {:x}".format(VPAGE_SIZE, v2p_table[VPAGE_SIZE]))
if __name__ == "__main__":
main()
exit(0)