Keygen challenge. The program generates a license key based on the
username. Reverse engineer the key generation algorithm and write a
keygen that produces valid keys for any username.
#!/usr/bin/env python3 """HackThisSite Application Challenge 17 keygen. Reconstructed from _Z13enc_and_checkPcS_ in app17unix (objdump -d -Mintel). The binary checks a key of the form HTS-XXXX-XXXX-... where the number of hex digits equals 2 * len(username). For each username byte u and the running state `acc` (starts at 0), it recomputes result = ((u - acc) >> 1) & ~(u << (acc mod 31)) # 32-bit, signed and demands that the two hex digits for that position equal `result`; the next state becomes `result`. `>>` is an arithmetic shift (x86 `sar`) and the shift count is taken modulo 32 (x86 `shl cl`). Every intermediate is 32-bit two's complement, but only values 0..0xFF can be written as two hex digits, so a username is solvable only when every step lands in 0..255. """
MASK = 0xFFFFFFFF
defs32(x): """Wrap to 32-bit signed.""" x &= MASK return x - 0x100000000if x & 0x80000000else x
defreduce31(acc): """Faithful port of the binary's `while (t > 31) t -= 31` for signed int.""" if acc <= 31: # negative acc never enters the loop return acc return acc % 31# for acc > 31 the loop yields acc in [0, 31]
defkey_bytes(username): """Return the list of per-character bytes the key has to encode.""" acc = 0 out = [] for ch in username.encode('latin-1'): u = ch - 256if ch >= 128else ch # movsx (signed char) t = reduce31(acc) res = s32(u - acc) >> 1# sar eax, 1 shifted = s32(u << (t & 31)) # shl edx, cl res = s32(res & s32(~shifted)) # not / and out.append(res) acc = res return out
defkeygen(username): payload = key_bytes(username) ifany(b < 0or b > 0xFFfor b in payload): raise ValueError( 'username %r is not representable in the 2-hex-digit format: %r' % (username, payload)) hexs = ''.join('%02X' % b for b in payload) return'HTS-' + '-'.join(hexs[i:i + 4] for i inrange(0, len(hexs), 4))
if __name__ == '__main__': import sys usernames = sys.argv[1:] or ['demo'] for name in usernames: print('%-14s -> %s' % (name, keygen(name)))
#!/usr/bin/env python3 """Feed keygen_final outputs to the real app17unix binary and report verdict.""" import os import pty import select import sys import time
HERE = os.path.dirname(os.path.abspath(__file__)) BIN = os.path.join(HERE, 'unix', 'app17unix') sys.path.insert(0, HERE) from keygen_final import keygen
if __name__ == '__main__': for name in (sys.argv[1:] or ['demo']): key = keygen(name) out = run(name, key) ok = b'Congratulations'in out print('username=%-12s key=%-28s -> %s' % (name, key, 'ACCEPTED'if ok else'rejected'))
1 2 3 4 5 6 7 8 9 10 11 12 13
$ python3 keygen_final.py demo testuser Hash-Cat a A demo -> HTS-1229-2206 testuser -> HTS-0A2D-2328-2626-1F29 Hash-Cat -> HTS-241E-2A1F-071E-2129 a -> HTS-10 A -> HTS-20