Skip to content

Instantly share code, notes, and snippets.

@vient

vient/keygen.py Secret

Created May 10, 2019 16:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vient/0e9e103272cf660a3bc89c56529e2966 to your computer and use it in GitHub Desktop.
Save vient/0e9e103272cf660a3bc89c56529e2966 to your computer and use it in GitHub Desktop.
best_reverser_phd9_rom_v4
#!/usr/bin/env python3
import struct
import logging
import ctypes
import codecs
import copy
import random
# logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.INFO)
# log = logging.getLogger('solver')
PROG = [
0x1E, 0x02, 0x11, 0x92, 0x41, 0x30, 0x32, 0x20, 0x20, 0x26, 0x40, 0x00, 0x44, 0x01, 0xA0, 0x20,
0x92, 0x8A, 0xDC, 0xDC, 0x94, 0x3B, 0xE4, 0xE4, 0xFC, 0xB3, 0xDC, 0xEE, 0xF4, 0xB4, 0xDC, 0xDE,
0xFE, 0x10, 0x88, 0x68, 0x4A, 0xBD, 0x91, 0xD5, 0x0A, 0x27, 0x2E, 0x31, 0xED, 0xFF, 0xC2, 0xA5,
0xCD, 0x98, 0xD6, 0xBF, 0xDE, 0xFA, 0xA6, 0x72, 0xBF, 0x1A, 0xF6, 0xFA, 0xE4, 0xE7, 0xFA, 0xF7,
0xF6, 0xD6, 0x91, 0xB4, 0xB4, 0xB5, 0xB4, 0xF4, 0xA4, 0xF4, 0xF4, 0xB7, 0xF6, 0x09, 0x20, 0xB7,
0x86, 0xF6, 0xE6, 0xF4, 0xE4, 0xC6, 0xFE, 0xF6, 0x79, 0x3D, 0x9D, 0x11, 0xD4, 0xFF, 0xB5, 0x68,
0x4A, 0xB8, 0xD4, 0xF7, 0x79, 0x41, 0xAE, 0xFF, 0x1C, 0xB7, 0x4C, 0xBF, 0xAD, 0x72, 0x4B, 0xBF,
0xAA, 0x3D, 0xB5, 0x7D, 0xB5, 0x3D, 0xB9, 0x7D, 0xD9, 0x7D, 0xB1, 0x13, 0xE1, 0xE1, 0x02, 0x15,
0xB3, 0xA3, 0xB3, 0x88, 0x64, 0xEB, 0x9E, 0x2C, 0xB0, 0x8F, 0x01
]
CRC16_consts = '0000C0C1C1810140C30103C00280C241C60106C00780C7410500C5C1C4810440CC010CC00D80CD410F00CFC1CE810E400A00CAC1CB810B40C90109C00880C841D80118C01980D9411B00DBC1DA811A401E00DEC1DF811F40DD011DC01C80DC411400D4C1D5811540D70117C01680D641D20112C01380D3411100D1C1D0811040F00130C03180F1413300F3C1F28132403600F6C1F7813740F50135C03480F4413C00FCC1FD813D40FF013FC03E80FE41FA013AC03B80FB413900F9C1F88138402800E8C1E9812940EB012BC02A80EA41EE012EC02F80EF412D00EDC1EC812C40E40124C02580E5412700E7C1E68126402200E2C1E3812340E10121C02080E041A00160C06180A1416300A3C1A28162406600A6C1A7816740A50165C06480A4416C00ACC1AD816D40AF016FC06E80AE41AA016AC06B80AB416900A9C1A88168407800B8C1B9817940BB017BC07A80BA41BE017EC07F80BF417D00BDC1BC817C40B40174C07580B5417700B7C1B68176407200B2C1B3817340B10171C07080B041500090C191815140930153C052809241960156C057809741550095C1948154409C015CC05D809D415F009FC19E815E405A009AC19B815B40990159C058809841880148C0498089414B008BC18A814A404E008EC18F814F408D014DC04C808C41440084C185814540870147C046808641820142C043808341410081C180814040'
CRC16_consts = codecs.decode(CRC16_consts, 'hex')
CRC16_consts = struct.unpack('>256H', CRC16_consts)
def crc16(buf, crc=0):
for x in buf:
crc = (crc >> 8) ^ CRC16_consts[(x ^ crc) & 0xFF]
return crc
class CtypesWrapper:
def __init__(self, x):
self.value = x
def _assign_op(self, obj, op):
t = obj
if isinstance(obj, CtypesWrapper):
t = obj.value.value
new_obj = CtypesWrapper(self.value)
new_obj.value = type(new_obj.value)(op(new_obj.value.value, t))
return new_obj
def __add__(self, obj):
return self._assign_op(obj, lambda a, b: a + b)
def __sub__(self, obj):
return self._assign_op(obj, lambda a, b: a - b)
def __div__(self, obj):
return self._assign_op(obj, lambda a, b: a // b)
def __mod__(self, obj):
return self._assign_op(obj, lambda a, b: a % b)
def __and__(self, obj):
return self._assign_op(obj, lambda a, b: a & b)
def __or__(self, obj):
return self._assign_op(obj, lambda a, b: a | b)
def __xor__(self, obj):
return self._assign_op(obj, lambda a, b: a ^ b)
def getval(self):
return self.value.value
def setval(self, x):
self.value = type(self.value)(x)
def __str__(self):
# prefix = 'UNK'
# if isinstance(self.value, ctypes.c_uint32):
# prefix = 'DWORD'
# if isinstance(self.value, ctypes.c_uint16):
# prefix = 'WORD'
# if isinstance(self.value, ctypes.c_uint8):
# prefix = 'BYTE'
precision = '8'
# if isinstance(self.value, ctypes.c_uint32):
# prefix = '8'
if isinstance(self.value, ctypes.c_uint16):
precision = '4'
if isinstance(self.value, ctypes.c_uint8):
precision = '2'
fmt = '0x{{:0{}X}}'.format(precision)
return fmt.format(self.value.value)
class RegRow:
def __init__(self):
self.w0 = CtypesWrapper(ctypes.c_uint16(0))
self.w2 = CtypesWrapper(ctypes.c_uint16(0))
self.w4 = CtypesWrapper(ctypes.c_uint16(0))
self.d6 = CtypesWrapper(ctypes.c_uint32(0))
self.w10 = CtypesWrapper(ctypes.c_uint16(0))
self.regs = (self.w0, self.w2, self.w4, self.d6, self.w10)
def __str__(self):
return ' '.join(map(str, self.regs))
class RegBank:
def __init__(self):
self.rows = [RegRow() for i in range(16)]
# def __getitem__(self, i):
# return self.rows[i]
def __str__(self):
res = ''
for i, row in enumerate(self.rows[:-1]):
res += '{:X}: {}\n'.format(i, str(row))
res += '{:X}: {}'.format(len(self.rows) - 1, str(self.rows[-1]))
return res
class BitReader:
def __init__(self, buf):
self.buf = copy.copy(buf)
self.buf += [0] * 4
self.fastbits = 0
self.fastbits_left = 0
self.buf_it = 0
def bit(self):
if self.fastbits_left == 0:
self.fastbits_left = 16
self.fastbits = (self.buf[self.buf_it + 1] << 8) | self.buf[self.buf_it]
self.buf_it += 2
res = self.fastbits & 1
self.fastbits >>= 1
self.fastbits_left -= 1
return res
def nbits(self, n):
res = 0
for i in range(n):
if self.bit():
res |= 1<<i
return res
def nbits_probe(self, n):
if self.fastbits_left < n:
self.fastbits = (self.fastbits & ((1 << self.fastbits_left) - 1)) | (((self.buf[self.buf_it + 1] << 8) | self.buf[self.buf_it]) << self.fastbits_left)
# self.fastbits_left = 16
# if self.fastbits_left < n:
# print('FUCK UP')
return self.fastbits & ((1 << n) - 1)
def pop_byte(self):
res = self.buf[self.buf_it]
self.buf_it += 1
return res
def brute_inner_crc():
prog_crc = crc16(PROG)
assert prog_crc == 0x9008
logging.info('prog crc 0x{:04X}'.format(prog_crc))
reader = BitReader(PROG)
_ = reader.bit()
t = reader.bit()
logging.debug(str(bool(t)))
b48, b108, b1C8 = RegBank(), RegBank(), RegBank()
def fill_reg_bank(bank):
for row in bank.rows:
row.w4.setval(0xFFFF)
vals = reader.nbits(5)
logging.debug(str(vals))
for i in range(vals):
t = reader.nbits(4)
logging.debug(str(t))
bank.rows[i].w10.setval(t)
n, t = 0, 0x80000000
for i in range(1, 16):
for j in range(vals):
if bank.rows[j].w10.getval() == i:
logging.debug('{} {}'.format(i, j))
res = n // t
res = int(bin(res)[2:].zfill(i)[::-1], 2)
bank.rows[j].d6.setval(res)
n += t
t >>= 1
fill_reg_bank(b48)
fill_reg_bank(b1C8)
fill_reg_bank(b108)
logging.debug(' b48\n' + str(b48))
logging.debug(' b108\n' + str(b108))
logging.debug(' b1C8\n' + str(b1C8))
t = reader.nbits(16)
logging.debug(str(t))
LOOPS = t
orig_reader = copy.deepcopy(reader)
logging.info(' brute-forcing serial XOR...')
for init_XOR in range(0xFE00, 0x10000):
if init_XOR & 0x7FF == 0:
print(hex(init_XOR))
reader = copy.deepcopy(orig_reader)
def fucked_up_read(bank):
for i in range(16):
if bank.rows[i].w10.getval() > 0 and reader.nbits_probe(bank.rows[i].w10.getval()) == bank.rows[i].d6.getval():
_ = reader.nbits(bank.rows[i].w10.getval())
if i > 0:
res = reader.nbits(i - 1) | (1 << (i - 1))
else:
res = 0
# logging.debug(' fucked_up_read: {} -> {}'.format(i, res))
logging.debug(' fucked_up_read - reader.fastbits after read {:04X}, available {}, buf_it {:X} (byte {:X})'.format(reader.fastbits, reader.fastbits_left, reader.buf_it, reader.buf[reader.buf_it]))
return res
raise Exception('fucked_up_read did not succeeded')
def ror16(x, n=1):
n %= 16
return ((x >> n) | (x << (16 - n))) & ((1 << 16) - 1)
inner_prog = []
serial_XOR = init_XOR
for LOOP in range(LOOPS - 1):
enc_key = serial_XOR & 0xFF
t = fucked_up_read(b48)
logging.debug(' bytes decrypted from RAM {}'.format(t))
bytes_to_dec = t
for i in range(bytes_to_dec):
x = reader.pop_byte()
inner_prog.append(x ^ enc_key)
serial_XOR = ror16(serial_XOR)
copy_offset = fucked_up_read(b1C8) + 1
logging.debug(' copy offset {}'.format(copy_offset))
copy_n = fucked_up_read(b108) + 2
logging.debug(' copy n {}'.format(copy_n))
for i in range(copy_n):
inner_prog.append(inner_prog[-copy_offset])
inner_prog_hexdump = ''
for i in range(0, len(inner_prog), 16):
inner_prog_hexdump += '{:04X}: '.format(i)
for j in range(i, min(i+16, len(inner_prog))):
inner_prog_hexdump += '{:02X} '.format(inner_prog[j])
inner_prog_hexdump = inner_prog_hexdump[:-1] + '\n'
logging.debug(' inner prog:\n' + inner_prog_hexdump)
if crc16(inner_prog) == 0xCB4C:
logging.info(' correct serial XOR 0x{:04X}'.format(init_XOR))
return init_XOR
def main():
inner_crc = brute_inner_crc()
email = 'VIENT@TUTA.IO'
serial = []
t = random.randrange(0x10000)
serial.append(t)
serial.append(inner_crc ^ serial[0])
t = random.randrange(0x10000)
serial.append(t ^ sum(map(ord, email)))
serial.append(t ^ ((len(email) - 1) << 8))
print('\nE-mail: {}\nSerial: {}'.format(email, codecs.encode(struct.pack('>4H', *serial), 'hex').decode()))
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment