-
-
Save vient/0e9e103272cf660a3bc89c56529e2966 to your computer and use it in GitHub Desktop.
best_reverser_phd9_rom_v4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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