cronodump/crodump/crodump.py
Yevhenii Kutsenko bd10fad70a Initial commit
2024-12-28 15:35:05 +03:00

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()