Reverse Engineering a Crackme: Static, Dynamic, and Patching
Article stats
- Read time
- 2 min
- Words
- 383
- Headings
- 15
- Code blocks
- 7
- Images
- 1
Scope and ethics
This walkthrough uses a toy crackme. Only analyze software you own or have explicit permission to test.
First look: metadata and hints
Start with cheap wins:
file crackme
strings -n 6 crackme | head -n 40
readelf -h crackme
readelf -s crackme | head -n 40
checksec --file=crackme
If the binary is stripped, symbols are gone but not the logic.
Build a hypothesis
Look for:
- usage text, error messages, or format strings
- references to strcmp, memcmp, or crypto routines
- suspicious constants (magic numbers, hashes)
A common flow is: parse input -> transform -> compare -> win/fail.
Static analysis in Ghidra
Open the binary, identify main, and follow calls. The compare is often a short basic block:
call strcmp
test eax, eax
jne fail
The conditional jump is your first target. If you understand the compare, you can build a keygen. If you only want a bypass, a single byte patch can flip the branch.
Example C source (recovered)
int check(char *input) {
uint32_t h = 0x1337;
for (size_t i = 0; i < strlen(input); i++) {
h = (h << 5) + h + (uint8_t)input[i];
}
return h == 0x6f6f6f;
}
Dynamic analysis in gdb
Confirm control flow and watch values:
gdb -q ./crackme
(gdb) break *0x4011a3
(gdb) run
(gdb) x/16i $rip-8
(gdb) info registers
If the compare depends on a hash, dump it before the branch and reproduce it offline.
Patch the branch
Assume jne at offset 0x11a3. Change 0x75 to 0x74 (jne -> je):
printf '\\x74' | dd of=crackme bs=1 seek=$((0x11a3)) conv=notrunc
This bypasses the check for a lab binary. For a real reverse engineering task, prefer understanding and documenting the algorithm.
Build a keygen
If the hash is simple, you can brute force. If it is linear, you can invert. For the sample hash above, a Python brute force might work for short inputs:
import string
def h(s):
v = 0x1337
for ch in s:
v = (v << 5) + v + ord(ch)
v &= 0xffffffff
return v
target = 0x006f6f6f
alphabet = string.ascii_letters + string.digits
for a in alphabet:
for b in alphabet:
s = a + b + "X"
if h(s) == target:
print(s)
raise SystemExit
Visual control flow map
main
-> parse_args
-> read_input
-> check_key
-> transform
-> compare
-> win
Stripped binaries and position-independent code
When symbols are gone and PIE is enabled, focus on patterns:
- function prologues (push rbp; mov rbp, rsp)
- cross-references to strings
- imports in the PLT/GOT
In gdb, use info proc mappings and set breakpoints on PLT stubs like strcmp@plt.
Video reference (placeholder link)
Tooling cheat sheet
| Task | Tool | Why |
|---|---|---|
| Strings and symbols | strings, nm | Quick hints |
| Disassembly | objdump, Ghidra | Control flow |
| Debugging | gdb, lldb | Runtime state |
| Patching | radare2, hexedit | Quick edits |
Anti-reversing tricks you might see
- Opaque predicates to confuse the graph
- Self-modifying code
- Timing checks that detect single stepping
- Packed sections that require unpacking
Checklist
- Identify compare branch
- Confirm values at runtime
- Reconstruct transform function
- Build a keygen or solver
- Document the algorithm
