#!/usr/bin/python
# SCO UNIX / OpenDesktop / OpenServer 3.2v4.2 "keygen" stuff
#
# By aaSSfxxx :þ
#
# Those UNIXes are not maintained and abandonware, unfortunately some dumps
# don't have any valid serial info to complete the installation. I hope this
# program would be useful to forgotten ancient UNIXes nerds like me ^^
#
# It's somewhat a "successor" to the brandy tool made for XENIX
#
# Special thanks to Ilfak for IDA Free x86 "cloud" decompiler available,
# the NSA for Ghidra, and guys behind "brandy" tool
#
# Code released under BeerWare / WTFPL as usual

import struct

# Activation key encryption to hide productkey bundled into it
def encrypt_ak(ak):
    a = 97
    b_ak = bytes(ak, 'ascii') + b"\x00"
    acc = 0
    enc = []
    for i in b_ak[::-1]:
        i = a + (i - a + acc + 26) % 26
        enc.append(i)
        acc = (acc + i) % 26
    return ''.join([chr(x) for x in enc])[::-1][:-1]

# SCO guys didn't like modular arithmetic and made if/else atrocity
def decrypt_ak(ak):
    a = 97
    b_ak = bytes(ak, 'ascii') + b"\x00"
    acc = 0
    enc = []
    for i in b_ak[::-1]:
        j = a + (i - a - acc + 26) % 26
        acc = (acc + i) % 26
        enc.append(j)
    return ''.join([chr(x) for x in enc])[::-1][:-1]

# Checksum function to checksum sn / ak couple to verify its validity
def checksum_ak(sn, ak):
    csum = 0
    rounds = ord(sn[8]) % 16

    for c in (sn + ak[0:6]):
        tmp = (csum + ord(c)) & 0xffff
        csum = ((tmp << 1) | (tmp >> 15)) &0xffff

    for i in range(rounds, 0, -1):
        csum = ((csum << 1) | (csum >> 15)) &0xffff

    cs = [((csum // 26) % 26) + 97, (csum % 26) + 97]
    return ''.join([chr(x) for x in cs])


# UUencoded string from "serial" parameters in bundle perms file, with some
# tweaks (like substitution of chars "x", "y", "z" and skipping of lowercase
# letters)
def uudecode_custom(s):
    decoded = []
    mapping = { ord("x"): ord('"'), ord("y"): ord("\\"), ord("z"): ord(":")}
    acc = 0
    shift = 0
    for c in bytes(s, "ascii"):
        if c in mapping:
            c = mapping[c]
        if c >= ord('!') and c < 97:
            acc = ((acc << 6) | ((c - ord('!')) & 63)) & 0xffffffff
            shift += 6
            while shift >= 8:
                shift -= 8
                decoded.append((acc >> shift) & 0xff)
    return bytes(decoded)

# I even don't know why it works but ¯\_(ツ)_/¯
def signmod(x, m):
    if x & 0x80000000 > 0:
        return 0x10000 + ((x - (1 << 32)) % -m)
    else:
        return x % m

def subst_table(key):
    # Compute a checksum of the key
    chksum = 123
    for i, c in enumerate(key):
        chksum = (i + (chksum * c)) & 0xffffffff
    # Initialize tbl1 and tbl2
    tbl1 = list(range(256))
    tbl2 = [0]*256

    # Do magic
    for i in range(256):
        chksum = ((chksum * 5) + key[(i % 3) + 2]) & 0xffffffff # wtf dudes
        k = 255 - i
        rand = signmod(chksum, 65521)
        b = ((rand & 0xff) % (k + 1))
        tmp = tbl1[k]
        tbl1[k] = tbl1[b]
        tbl1[b] = tmp
        # Build reflector table
        if tbl2[k] == 0:
            # Unicorns magic in the wonderful world of t4pz
            m = (rand >> 8) % k
            while tbl2[m]:
                m = (m + 1) % k
            tbl2[k] = m
            tbl2[m] = k

    tbl3 = [0]*256
    for i in range(256):
        tbl3[tbl1[i]] = i

    return tbl1, tbl2, tbl3

def enigma(buf, key, len):
    table1, table2, table3 = subst_table(key)
    out = []
    for i in range(len):
        flag = (i // 256) % 256
        idx = (buf[i] + (i & 0xff)) & 0xff
        idx2 = (0x100 + table2[flag + table1[idx]] - flag) & 0xff
        out.append((table3[(0x100 + idx2) & 0xff] - (i % 256)) & 0xff)
    return bytes(out)

def keygen(serial, bf_ak):
    p_key = "".join([chr(x) for x in bf_ak[0:3] + b"aaa"])
    chksum = checksum_ak(serial, p_key)
    ek = encrypt_ak(p_key + chksum)
    print("=> %s:%s looks like a valid key" % (serial, ek))

if __name__ == "__main__":
    import sys
    if len(sys.argv) != 3:
        print("Usage: %s <sn> <bundle serial>" % sys.argv[0])
        print("Where \"bundle serial\" can be found in the M1 floppy (tar " +
            "archive), in /tmp/perms/bundle/ for each components")
        sys.exit(1)
    ser = uudecode_custom(sys.argv[2])
    for s in range(26**3):
        i = s % 26
        j = (s // 26) % 26
        k = (s // 676) % 26
        key = bytes([97+i,97+j,97+k]) + b"Tb"
        res = enigma(ser, key, len(ser))
        validchars = b"abcdefghijklmnopqrstuvwxyz"
        if (res[0] in validchars and res[1] in validchars and
          res[2] in validchars and res[3] == ord(",")):
            print(res)
            print("Found component product key %s" % str(res[0:3], 'ascii'))
            keygen(sys.argv[1], key)
