Crackme: The Licence Check

A Python licence checker accepts exactly one 16-character serial. Read it, invert its two stages, and recover the serial instead of guessing.

200 ptsEstimated time: 40 min

Statement

The story

You are handed crackme.py, the licence check of a small note-taking app. It reads one line (the serial) from standard input and prints licence accepted or invalid serial. You have no vendor, no keygen and no patience for 62^16 guesses. You do have the source, so you can reason about it.

Your task

Find the one serial that makes the program print licence accepted. A serial is 16 characters from A-Z, a-z and 0-9. The flag is IMB{, the serial, then }.

You can run the program in Code Lab (Python), feed it candidate serials on standard input, and add print calls to look inside. Typing random serials will not get you anywhere. The check has structure you can undo:

  • Read it as a sequence of stages and work out which operations are reversible.
  • Undo the stages in the opposite order to the one the program runs them.
  • Some state (the variable the loop carries from one character to the next) depends on characters you have already recovered, so rebuild it as you go.

Bit tricks used, with an example

  • ^ is XOR. It undoes itself: (a ^ k) ^ k == a.
  • & 0xFF keeps a value inside one byte. Addition modulo 256 is undone by subtracting modulo 256.
  • A rotate left by 3 inside one byte moves the top three bits to the bottom: 0x96 (10010110) becomes 0xB4 (10110100). Rotating right by 3 undoes it.

There is exactly one accepted serial.

Flag format: IMB{...}

Files

Public puzzle material. Download it or read it right here.

crackme.py31 lines

The licence checker. Reads the serial from standard input.

"""SnapNote Pro licence check (demo build).

Type your 16-character serial on one line, for example:  echo ABCDEFGHJKLMNPQR | python crackme.py
"""
import sys

_A = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
_T = bytes.fromhex("4050605844e2dc532748589511b71a96")


def _rol(v, n):
    return ((v << n) | (v >> (8 - n))) & 0xFF


def check(serial):
    if len(serial) != 16 or any(c not in _A for c in serial):
        return False
    acc, out = 0x5A, []
    for i, ch in enumerate(serial):
        c = ord(ch)
        out.append((_rol((c ^ acc) & 0xFF, 3) + 7 * i) & 0xFF)
        acc = (acc * 3 + c + i) & 0xFF
    for i in range(14, -1, -1):
        out[i] ^= out[i + 1]
    return bytes(out) == _T


if __name__ == "__main__":
    entered = sys.stdin.readline().strip()
    print("licence accepted" if check(entered) else "invalid serial")

Hints

Stuck? Hints are optional, and each one lowers the points you earn for solving this challenge.

  1. Hint 1Costs 20 points

    Sign in to reveal hints.

  2. Hint 2Costs 25 points

    Reveal the previous hint first

  3. Hint 3Costs 30 points

    Reveal the previous hint first