← writeups

Reversing a license-check crackme with Ghidra

A small Windows crackme: enter a name and a serial, get told yes or no. No packing, no obfuscation — a clean target for working through the standard approach end to end.

First pass

Loaded the binary into Ghidra, let auto-analysis finish, and went straight to Defined Strings looking for anything that reads like feedback text:

"Invalid serial. Try again."
"Access granted."

Cross-referencing "Access granted." leads straight to the comparison that decides the outcome — usually the fastest path into a crackme like this, faster than trying to find main and read forward.

The validation routine

The function above that string reference takes the name and serial as two character buffers. Renamed variables as I went (Ghidra’s defaults — local_38, uVar2 — get replaced with name_buf, computed_check etc. for readability). The core logic, cleaned up:

int check = 0;
for (int i = 0; name[i] != '\0'; i++) {
    check += (int)name[i] * (i + 1);
}
check = check ^ 0x5A5A;

int serial_int = atoi(serial);
if (serial_int == check) {
    // "Access granted."
}

So the serial isn’t checked against a stored constant — it’s derived from the name. Each character’s ASCII value is multiplied by its 1-indexed position, summed, then XORed with 0x5A5A.

Keygen

def make_serial(name: str) -> int:
    check = sum(ord(c) * (i + 1) for i, c in enumerate(name))
    return check ^ 0x5A5A

print(make_serial("test"))

Ran it for a few different names, fed the output back into the binary as the serial. Access granted. every time.

Notes for next time

The naming-as-you-go approach in Ghidra’s decompiler view paid for itself here — by the time the loop above was fully renamed, the logic was obvious without needing to step through it in a debugger at all. Worth defaulting to static analysis first on anything this size before reaching for x64dbg.