297 lines
10 KiB
Python
297 lines
10 KiB
Python
from .kodump import kod_hexdump
|
|
from .hexdump import unhex, tohex
|
|
from .readers import ByteReader
|
|
from .Database import Database
|
|
from .Datamodel import TableDefinition
|
|
|
|
|
|
def destruct_sys3_def(rd):
|
|
# todo
|
|
pass
|
|
|
|
|
|
def destruct_sys4_def(rd):
|
|
"""
|
|
decode type 4 of the records found in CroSys.
|
|
|
|
This function is only useful for reverse-engineering the CroSys format.
|
|
"""
|
|
n = rd.readdword()
|
|
for _ in range(n):
|
|
marker = rd.readdword()
|
|
description = rd.readlongstring()
|
|
path = rd.readlongstring()
|
|
marker2 = rd.readdword()
|
|
|
|
print("%08x;%08x: %-50s : %s" % (marker, marker2, path, description))
|
|
|
|
|
|
def destruct_sys_definition(args, data):
|
|
"""
|
|
Decode the 'sys' / dbindex definition
|
|
|
|
This function is only useful for reverse-engineering the CroSys format.
|
|
"""
|
|
rd = ByteReader(data)
|
|
|
|
systype = rd.readbyte()
|
|
if systype == 3:
|
|
return destruct_sys3_def(rd)
|
|
elif systype == 4:
|
|
return destruct_sys4_def(rd)
|
|
else:
|
|
raise Exception("unsupported sys record")
|
|
|
|
|
|
def cro_dump(kod, args):
|
|
"""handle 'crodump' subcommand"""
|
|
if args.maxrecs:
|
|
args.maxrecs = int(args.maxrecs, 0)
|
|
else:
|
|
# an arbitrarily large number.
|
|
args.maxrecs = 0xFFFFFFFF
|
|
|
|
db = Database(args.dbdir, args.compact, kod)
|
|
db.dump(args)
|
|
|
|
|
|
def stru_dump(kod, args):
|
|
"""handle 'strudump' subcommand"""
|
|
db = Database(args.dbdir, args.compact, kod)
|
|
db.strudump(args)
|
|
|
|
|
|
def sys_dump(kod, args):
|
|
"""hexdump all CroSys records"""
|
|
# an arbitrarily large number.
|
|
args.maxrecs = 0xFFFFFFFF
|
|
|
|
db = Database(args.dbdir, args.compact, kod)
|
|
if db.sys:
|
|
db.sys.dump(args)
|
|
|
|
|
|
def rec_dump(kod, args):
|
|
"""hexdump all records of the specified CroXXX.dat file."""
|
|
if args.maxrecs:
|
|
args.maxrecs = int(args.maxrecs, 0)
|
|
else:
|
|
# an arbitrarily large number.
|
|
args.maxrecs = 0xFFFFFFFF
|
|
|
|
db = Database(args.dbdir, args.compact, kod)
|
|
db.recdump(args)
|
|
|
|
|
|
def destruct(kod, args):
|
|
"""
|
|
decode the index#1 structure information record
|
|
Takes hex input from stdin.
|
|
"""
|
|
import sys
|
|
|
|
data = sys.stdin.buffer.read()
|
|
data = unhex(data)
|
|
|
|
if args.type == 1:
|
|
# create a dummy db object
|
|
db = Database(".", args.compact)
|
|
db.dump_db_definition(args, data)
|
|
elif args.type == 2:
|
|
tbdef = TableDefinition(data)
|
|
tbdef.dump(args)
|
|
elif args.type == 3:
|
|
destruct_sys_definition(args, data)
|
|
|
|
|
|
def strucrack(kod, args):
|
|
"""
|
|
This function derives the KOD key from the assumption that most bytes in
|
|
the CroStru records will be zero, given a sufficient number of CroStru
|
|
items, statistically the most common bytes will encode to '0x00'
|
|
"""
|
|
|
|
# start without 'KOD' table, so we will get the encrypted records
|
|
db = Database(args.dbdir, args.compact, None)
|
|
if args.sys:
|
|
table = db.sys
|
|
if not db.sys:
|
|
print("no CroSys.dat file found in %s" % args.dbdir)
|
|
return
|
|
else:
|
|
table = db.stru
|
|
if not db.stru:
|
|
print("no CroStru.dat file found in %s" % args.dbdir)
|
|
return
|
|
|
|
xref = [ [0]*256 for _ in range(256) ]
|
|
for i, data in enumerate(table.enumrecords()):
|
|
if not data: continue
|
|
for ofs, byte in enumerate(data):
|
|
xref[(ofs+i+1)%256][byte] += 1
|
|
|
|
KOD = [0] * 256
|
|
for i, xx in enumerate(xref):
|
|
k, v = max(enumerate(xx), key=lambda kv: kv[1])
|
|
KOD[k] = i
|
|
|
|
if not args.silent:
|
|
print(tohex(bytes(KOD)))
|
|
|
|
return KOD
|
|
|
|
def dbcrack(kod, args):
|
|
"""
|
|
This function derives the KOD key from the assumption that most records in CroIndex
|
|
and CroBank will be compressed, and start with:
|
|
uint16 size
|
|
byte 0x08
|
|
byte 0x00
|
|
|
|
So because the fourth byte in each record will be 0x00 when kod-decoded, I can
|
|
use this as the inverse of the KOD table, adjusting for record-index.
|
|
|
|
"""
|
|
# start without 'KOD' table, so we will get the encrypted records
|
|
db = Database(args.dbdir, args.compact, None)
|
|
xref = [ [0]*256 for _ in range(256) ]
|
|
|
|
for dbfile in db.bank, db.index:
|
|
if not dbfile:
|
|
print("no data file found in %s" % args.dbdir)
|
|
return
|
|
for i in range(1, min(10000, dbfile.nrofrecords)):
|
|
rec = dbfile.readrec(i)
|
|
if rec and len(rec)>11:
|
|
xref[(i+3)%256][rec[3]] += 1
|
|
|
|
KOD = [0] * 256
|
|
for i, xx in enumerate(xref):
|
|
k, v = max(enumerate(xx), key=lambda kv: kv[1])
|
|
KOD[k] = i
|
|
|
|
if not args.silent:
|
|
print(tohex(bytes(KOD)))
|
|
|
|
return KOD
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="CRO hexdumper")
|
|
subparsers = parser.add_subparsers(title='commands',
|
|
help='Use the --help option for the individual sub commands for more details')
|
|
parser.set_defaults(handler=lambda *args: parser.print_help())
|
|
parser.add_argument("--debug", action="store_true", help="break on exceptions")
|
|
parser.add_argument("--kod", type=str, help="specify custom KOD table")
|
|
parser.add_argument("--strucrack", action="store_true", help="infer the KOD sbox from CroStru.dat")
|
|
parser.add_argument("--dbcrack", action="store_true", help="infer the KOD sbox from CroBank.dat + CroIndex.dat")
|
|
parser.add_argument("--nokod", "-n", action="store_true", help="don't KOD decode")
|
|
parser.add_argument("--compact", action="store_true", help="save memory by not caching the index, note: increases convert time by factor 1.15")
|
|
|
|
p = subparsers.add_parser("kodump", help="KOD/hex dumper")
|
|
p.add_argument("--offset", "-o", type=str, default="0")
|
|
p.add_argument("--length", "-l", type=str)
|
|
p.add_argument("--width", "-w", type=str)
|
|
p.add_argument("--endofs", "-e", type=str)
|
|
p.add_argument("--nokod", "-n", action="store_true", help="don't KOD decode")
|
|
p.add_argument("--unhex", "-x", action="store_true", help="assume the input contains hex data")
|
|
p.add_argument("--shift", "-s", type=str, help="KOD decode with the specified shift")
|
|
p.add_argument("--increment", "-i", action="store_true",
|
|
help="assume data is already KOD decoded, but with wrong shift -> dump alternatives.")
|
|
p.add_argument("--ascdump", "-a", action="store_true", help="CP1251 asc dump of the data")
|
|
p.add_argument("--invkod", "-I", action="store_true", help="KOD encode")
|
|
p.add_argument("filename", type=str, nargs="?", help="dump either stdin, or the specified file")
|
|
p.set_defaults(handler=kod_hexdump)
|
|
|
|
p = subparsers.add_parser("crodump", help="CROdumper")
|
|
p.add_argument("--verbose", "-v", action="store_true")
|
|
p.add_argument("--ascdump", "-a", action="store_true")
|
|
p.add_argument("--maxrecs", "-m", type=str, help="max nr or recots to output")
|
|
p.add_argument("--nodecompress", action="store_false", dest="decompress", default="true")
|
|
p.add_argument("dbdir", type=str)
|
|
p.set_defaults(handler=cro_dump)
|
|
|
|
p = subparsers.add_parser("sysdump", help="SYSdumper")
|
|
p.add_argument("--verbose", "-v", action="store_true")
|
|
p.add_argument("--ascdump", "-a", action="store_true")
|
|
p.add_argument("--nodecompress", action="store_false", dest="decompress", default="true")
|
|
p.add_argument("dbdir", type=str)
|
|
p.set_defaults(handler=sys_dump)
|
|
|
|
p = subparsers.add_parser("recdump", help="record dumper")
|
|
p.add_argument("--verbose", "-v", action="store_true")
|
|
p.add_argument("--ascdump", "-a", action="store_true")
|
|
p.add_argument("--maxrecs", "-m", type=str, help="max nr or recots to output")
|
|
p.add_argument("--find1d", action="store_true", help="Find records with 0x1d in it")
|
|
p.add_argument("--stats", action="store_true", help="calc table stats from the first byte of each record",)
|
|
p.add_argument("--index", action="store_true", help="dump CroIndex")
|
|
p.add_argument("--stru", action="store_true", help="dump CroIndex")
|
|
p.add_argument("--bank", action="store_true", help="dump CroBank")
|
|
p.add_argument("--sys", action="store_true", help="dump CroSys")
|
|
p.add_argument("dbdir", type=str)
|
|
p.set_defaults(handler=rec_dump)
|
|
|
|
p = subparsers.add_parser("strudump", help="STRUdumper")
|
|
p.add_argument("--verbose", "-v", action="store_true")
|
|
p.add_argument("--ascdump", "-a", action="store_true")
|
|
p.add_argument("dbdir", type=str)
|
|
p.set_defaults(handler=stru_dump)
|
|
|
|
p = subparsers.add_parser("destruct", help="Stru dumper")
|
|
p.add_argument("--verbose", "-v", action="store_true")
|
|
p.add_argument("--ascdump", "-a", action="store_true")
|
|
p.add_argument("--type", "-t", type=int, help="what type of record to destruct")
|
|
p.set_defaults(handler=destruct)
|
|
|
|
p = subparsers.add_parser("strucrack", help="Crack v4 KOD encrypion, bypassing the need for the database password.")
|
|
p.add_argument("--sys", action="store_true", help="Use CroSys for cracking")
|
|
p.add_argument("--silent", action="store_true", help="no output")
|
|
p.add_argument("dbdir", type=str)
|
|
p.set_defaults(handler=strucrack)
|
|
|
|
p = subparsers.add_parser("dbcrack", help="Crack v4 KOD encrypion, bypassing the need for the database password.")
|
|
p.add_argument("--silent", action="store_true", help="no output")
|
|
p.add_argument("dbdir", type=str)
|
|
p.set_defaults(handler=dbcrack)
|
|
|
|
args = parser.parse_args()
|
|
|
|
import crodump.koddecoder
|
|
if args.kod:
|
|
if len(args.kod)!=512:
|
|
raise Exception("--kod should have a 512 hex digit argument")
|
|
kod = crodump.koddecoder.new(list(unhex(args.kod)))
|
|
elif args.nokod:
|
|
kod = None
|
|
elif args.strucrack:
|
|
class Cls: pass
|
|
cargs = Cls()
|
|
cargs.dbdir = args.dbdir
|
|
cargs.sys = False
|
|
cargs.silent = True
|
|
cracked = strucrack(None, cargs)
|
|
if not cracked:
|
|
return
|
|
kod = crodump.koddecoder.new(cracked)
|
|
elif args.dbcrack:
|
|
class Cls: pass
|
|
cargs = Cls()
|
|
cargs.dbdir = args.dbdir
|
|
cargs.sys = False
|
|
cargs.silent = True
|
|
cracked = dbcrack(None, cargs)
|
|
if not cracked:
|
|
return
|
|
kod = crodump.koddecoder.new(cracked)
|
|
else:
|
|
kod = crodump.koddecoder.new()
|
|
|
|
if args.handler:
|
|
args.handler(kod, args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|