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.
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.& 0xFFkeeps 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) becomes0xB4(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.
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.
- Hint 1Costs 20 points
- Hint 2Costs 25 points
Reveal the previous hint first
- Hint 3Costs 30 points
Reveal the previous hint first
Lessons that teach this
Stuck on the ideas rather than the puzzle? These lessons cover what you need.