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.