Step 217. 10 crackmes (Easy Difficulty) — Building the Speed to Recognize Patterns
Level 3 — Reversing Track | Difficulty ★★★☆☆ | Estimated time: 6 hours
Prerequisites: you’ve finished Step 178 (Reversing first taste), Step 215 (Advanced Ghidra), and Step 216 (Advanced x64dbg). The three-stage tool order of strings → objdump → gdb is second nature to you.
⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. crackmes are legal learning material "made to be solved," and today’s practice binaries are ones you compile yourself.
- What you need: WSL Ubuntu (measured: Ubuntu 24.04, gcc 13.3.0, gdb 15.1). The screens of the external platform crackmes.one are shown as examples, while solving practice is done hands-on with three crackmes you build yourself.
- Caution: only run downloaded binaries inside your lab or VM. Not "it’s a crackme so it must be safe" — as a rule, executables from untrusted sources are handled only in an isolated environment.
So far you’ve learned reversing techniques one at a time. Starting today, for two days, it’s repetition — how fast you can apply the same technique to an unfamiliar problem is the skill. Easy crackmes are really three repeating patterns, and today’s goal is recognizing the pattern within 30 seconds. Like a marathon runner reading the terrain, this is the day you open a binary and immediately think "ah, this type."
1. Learning Objectives
By the end of this chapter, you will be able to:
- Distinguish the three types of easy crackmes (direct comparison / transformed comparison / per-character branching)
- Immediately pick the right first tool for each type
- Build the rhythm of finishing "type classification → approach decision" within a 30-minute limit
- Record per-type solving times to identify your weaknesses as data
- Know the procedure for picking problems on crackmes.one and running them safely
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | C (for building problems) + WSL Ubuntu bash (measured: gcc 13.3.0, gdb 15.1); crackmes.one shown as screen examples |
| Today’s commands | strings | grep -iE pattern, objdump -d | grep -E "cmp|xor|sub|add", gdb break *main+offset + x/s $rdi / $rsi |
| Concepts needed | Division of labor between static/dynamic analysis (Step 178), XOR/addition hiding, per-character comparison (cmpb), keygen basics |
| Today’s deliverables | Type classification table + per-type average solving time record |
2-1. The Three Types of Easy crackmes
Filter through hundreds of beginner crackmes and the verification method is always one of three.
- Direct comparison — the answer string sits in plaintext in the binary and is compared with
strcmp. One line ofstringsends it. - Transformed comparison — the answer is hidden with XOR or addition and restored at runtime for comparison. strings stays silent, and the transformation operation shows in the disassembly.
- Per-character branching — the answer string is never stored whole; instead, comparisons like
pw[0] == 'g'are listed character by character. The answer is absent from strings, andcmpb $0x??repeats in the disassembly.
2-2. Recognizing the Type — The Rhythm of the First 3 Minutes
There’s a fixed order when you receive a binary.
1. Run it (inside the lab!) → check the prompt and verdict messages
2. strings → any answer candidates visible?
Visible → Type 1, practically done
Not visible → Type 2 or 3
3. objdump -d | grep -E "xor|sub|add" → any transformation operation? → Type 2
objdump -d | grep cmpb → repeated per-character comparisons? → Type 3
Why "run it" comes first: the input prompt and error messages tell you where the analysis starts. And running must always happen in the lab — this connects to the safety procedure in 2-4.
2-3. How to Use crackmes.one — Screen Example
Here’s the procedure for picking problems on the external platform. Since this environment doesn’t use external networks, we show it as a screen example:
# Screen example — browsing crackmes.one
1. Visit https://crackmes.one → crackmes list in the top menu
2. Filters: Difficulty 1~2, Platform: any OS (ELF/Linux for WSL, PE for a Windows VM)
3. Each row in the list: name / author / difficulty / rating — start with highly rated ones that have comments (solutions)
4. Unzip the downloaded zip in your lab folder and run it
How to pick: one at a time from difficulty 1, staying at the same difficulty until you solve it. When stuck, reading a solution from the solutions tab and reproducing it is also textbook learning — but wrestle with it alone for at least 20 minutes before reading.
2-4. Safety Rules for Downloaded Binaries
A crackme is legal learning material, but the fact that it’s an executable downloaded from the internet doesn’t change. Keep three rules: ① run only in an isolated lab/VM, ② before running, check its rough identity with file and strings, ③ if suspicious network or file access is suspected, static analysis only — no running. These are the same first rules real malware analysts follow.
2-5. Why Record Time
Today’s completion criteria include "per-type average time." Feeling by intuition that "I’m weak at transformation types" is different from knowing from records that "transformation types average 22 minutes, the rest 8 minutes." Once a weakness becomes a number, it becomes the target of your next practice.
3. Follow Along
3-1. Training Ground — Three Representative crackmes, One per Type
Before solving external problems, you build the "textbook specimens" of the three types yourself and drill the solving cycle into your body. You know how these three were made, but the rule is: solve them without looking at the source.
Input (e1_direct.c — Type 1: direct comparison)
#include <stdio.h>
#include <string.h>
int main(void){
char pw[64];
printf("key: ");
scanf("%63s", pw);
if(strcmp(pw, "c0ffee-t1me") == 0)
puts("correct! FLAG{e1_strcmp_1s_0ver}");
else
puts("wrong.");
return 0;
}
Input (e2_add.c — Type 2: transformed comparison)
#include <stdio.h>
#include <string.h>
int main(void){
char pw[64];
/* store each answer character +3 — no plaintext exists */
char enc[] = {0x70, 0x36, 0x66, 0x75, 0x36, 0x77, 0x30,
0x73, 0x37, 0x77, 0x6b, 0x00};
char decoded[64];
int i;
for(i = 0; enc[i]; i++) decoded[i] = enc[i] - 3;
decoded[i] = 0;
printf("key: ");
scanf("%63s", pw);
if(strcmp(pw, decoded) == 0)
puts("correct! FLAG{e2_m1nus_thr33}");
else
puts("wrong.");
return 0;
}
Input (e3_chars.c — Type 3: per-character branching)
#include <stdio.h>
int main(void){
char pw[64];
printf("key: ");
scanf("%63s", pw);
if(pw[0] != 0x67) goto fail;
if(pw[1] != 0x6f) goto fail;
if(pw[2] != 0x6c) goto fail;
if(pw[3] != 0x64) goto fail;
if(pw[4] != 0x33) goto fail;
if(pw[5] != 0x6e) goto fail;
if(pw[6] != 0x00) goto fail;
puts("correct! FLAG{e3_cmp_by_cmp}");
return 0;
fail:
puts("wrong.");
return 0;
}
cd ~/lab214_218
gcc -O1 -o e1_direct e1_direct.c
gcc -O1 -o e2_add e2_add.c
gcc -O1 -o e3_chars e3_chars.c
(Build and behavior verified on 2026-09-09.)
From now on, you are a player who just received these three binaries for the first time. Cover the source.
3-2. Solving e1 — A Landslide Victory for strings (Type 1)
strings ./e1_direct | grep -iE "key|wrong|correct|0ffee"
key:
c0ffee-t1me
wrong.
correct! FLAG{e1_strcmp_1s_0ver}
(Measured 2026-09-09.)
How to read the output: c0ffee-t1me, wedged between the prompt (key:) and the verdict messages — the comparison target is stored in plaintext. Verify:
echo c0ffee-t1me | ./e1_direct
key: correct! FLAG{e1_strcmp_1s_0ver}
(Measured 2026-09-09.)
Time taken: 1 minute. Type 1 isn’t "solving," it’s "spotting." A large share of difficulty-1 problems on real platforms are this, which is why the first tool is always strings.
3-3. Solving e2 — strings Silent, Operation Found, gdb Capture (Type 2)
strings ./e2_add | grep -iE "key|wrong|correct|m3cr3t"
key:
correct! FLAG{e2_m1nus_thr33}
wrong.
(Measured 2026-09-09. There are no answer candidates — it’s hidden.)
Type 2 or 3. Hunt for the transformation operation:
objdump -d ./e2_add | grep -E "sub.*0x3"
1216: 83 e8 03 sub $0x3,%eax
(Measured 2026-09-09.)
How to read the output: a loop that subtracts 3 from something — a signal that "it gets restored at runtime." Two ways to see the restored value. ① Static back-calculation: find the enc array in the binary and subtract 3 from each byte. ② Dynamic capture: read it with gdb at the moment of comparison. The latter is faster:
gdb -batch -ex "disassemble main" ./e2_add | grep strcmp
0x000000000000126a <+161>: call 0x10b0 <strcmp@plt>
(Measured 2026-09-09.) Set a break at main+161 and read the two arguments:
(gdb) break *main+161
(gdb) run < in2.txt ← in2.txt contains any characters (AAAA)
(gdb) x/s $rdi
(gdb) x/s $rsi
Breakpoint 1, 0x000055555555526a in main ()
0x7fffffffe5e0: "AAAA"
0x7fffffffe620: "m3cr3t-p4th"
(Measured 2026-09-09. Pass gdb commands via a file like in Step 178, but beware of $ disappearing — the script was generated with Python’s chr(36).)
Feeding the restored answer m3cr3t-p4th straight in produced correct! (measured 2026-09-09). The textbook truth of Type 2: hiding is only valid inside the file — to compare, it must be revealed at runtime.
3-4. Solving e3 — Collecting Characters from the cmpb Chain (Type 3)
If strings stays silent and no transformation operation shows, look for per-character comparisons:
objdump -d ./e3_chars | grep cmpb
11e6: 80 3c 24 67 cmpb $0x67,(%rsp)
11ec: 80 7c 24 01 6f cmpb $0x6f,0x1(%rsp)
11f3: 80 7c 24 02 6c cmpb $0x6c,0x2(%rsp)
11fa: 80 7c 24 03 64 cmpb $0x64,0x3(%rsp)
1201: 80 7c 24 04 33 cmpb $0x33,0x4(%rsp)
1208: 80 7c 24 05 6e cmpb $0x6e,0x5(%rsp)
120f: 80 7c 24 06 00 cmpb $0x0,0x6(%rsp)
(Measured 2026-09-09.)
How to read the output: buffer position 0 is 0x67, position 1 is 0x6f… — the answer is scattered one character at a time. Collect it with Python:
python3 -c "print(''.join(chr(v) for v in [0x67,0x6f,0x6c,0x64,0x33,0x6e]))"
gold3n
(Measured 2026-09-09. echo gold3n | ./e3_chars → correct! FLAG{e3_cmp_by_cmp} confirmed.)
Type 3 often needs no gdb at all — the disassembly itself is the answer sheet. When the character count grows into the dozens, though, scraping the constants with Python is more accurate.
3-5. Type Classification Table — Settling the Three
| Problem | strings | Decisive clue | Solving tool | Type |
|---|---|---|---|---|
| e1 | Answer exposed | (strings itself is the clue) | strings | Direct comparison |
| e2 | Silent | sub $0x3 transformation loop |
objdump → gdb | Transformed comparison |
| e3 | Silent | cmpb repetition |
objdump + Python | Per-character branching |
This flow — strings → operation search → (if needed) gdb — is the all-purpose order for easy difficulty. Once this order is in your body, the 10-problem practice becomes repetition training.
3-6. Main Training — 10 Problems from the Platform
Pick 10 problems of difficulty 1~2 from crackmes.one (see the screen example in 2-3). Rules:
- 30-minute limit per problem — start a timer
- Spend the first 3 minutes on type recognition (the rhythm from 2-2)
- If unsolved in 30 minutes, record it as "unsolved" and move on — don’t spend a day on one
- When done with all, compute per-type average times
If you have no external platform access, substitute by modifying the 3-1 sources (change the answer, transformation key, character array) and solving your own problems — though a problem made by your past self reveals where its clues are, so making them and solving them a few days later is the real training.
4. Missions & Exercises
Mission — A 10-Problem Solving Log and Type Classification Table
- Pick 10 problems of difficulty 1~2 on crackmes.one (substitute modified self-made ones if you have no access)
- For each problem, record: type verdict (and how many minutes it took), the tool order used, total time, success/unsolved
- Compute per-type average times
- For the slowest type, write "why it was slow" in two lines — that’s the target of your next practice
Exercises
Exercise 1. Explain why e2’s answer didn’t show up in strings, from the perspective of the difference between a C string literal and array initialization.
Exercise 2. In Type 3 (per-character branching), why does the author compare one character at a time instead of storing the answer whole? And why is that defense powerless in front of objdump?
Exercise 3. To solve e2 without gdb, using only objdump information, what else must you find? Write out the procedure.
Exercise 4. Why are the "30-minute limit" and "unsolved record" important as a learning method? Answer by comparing with dwelling long on a single problem.
5. Model Answers & Completion Criteria
Mission Model Answer
An example solving log (one line per problem):
#01 easy_crack | Type1 direct cmp (verdict 1 min) | strings | 3 min | success
#02 xor_baby | Type2 transform (verdict 3 min) | objdump→gdb | 11 min | success
#03 charbychar | Type3 per-char (verdict 4 min) | objdump+python | 14 min | success
#04 mysterybox | verdict failed — looked like a transform but was a hash | — | 30 min | unsolved
...
Per-type averages: direct 4 min / transform 12 min / per-char 13 min
Slowest type: transform — after finding the operation in objdump, I wasted
time hesitating between "back-calculation" and "gdb capture." New rule:
try gdb capture first.
How to verify: ① are all 10 rows present (an unsolved entry is still a row)? ② does each row have a type and a tool order? ③ are the average calculations and the "why slow" sentence present? The log’s completeness itself is the grading criterion — solving all 10 is not required.
Exercise Answers
Answer 1. e1’s answer is a string literal, so it’s stored as plaintext in the binary’s data section and gets caught by strings. e2, by contrast, stores the answer as a byte array with +3 added, so only transformed values exist in the file. What strings catches is "runs of readable characters," and transformed bytes don’t look like a human-readable string, so they aren’t caught. The core of hiding is "don’t keep plaintext in the file."
Answer 2. Storing the answer string whole would expose it to strings, so the intent is to scatter the comparison constants character by character and neutralize string scanning. But those constants end up verbatim inside the machine-code comparison instructions, so scraping them in order with objdump -d | grep cmpb restores the answer. It’s a half-baked hiding scheme — it dodges file scanning but stays exposed in the disassembly.
Answer 3. You need to find the bytes of the pre-transformation array (enc) in the binary. Procedure: ① find the sub $0x3 loop with objdump. ② Find the address of the array the loop reads, in the disassembly (lea or a data reference). ③ Read the bytes at that address with objdump -s (data dump) or gdb’s x/12bx. ④ Subtract 3 from each byte and restore with Python. gdb capture is faster, but static back-calculation is the only path for "binaries you can’t run," so learn both.
Answer 4. Because the purpose of 10 crackmes isn’t each problem’s answer but automating type recognition and tool selection. Spend two hours on one problem and you learn that one problem; cap it at 30 minutes and see six problems, and you experience six variations. And recording the unsolved ones turns them into an exact list of your weaknesses — "problems I couldn’t solve" aren’t failures but the curriculum for the next training session.
Completion Criteria Checklist
- [ ] I can name the three types of easy crackmes and the first tool for each
- [ ] I solved e1 with strings
- [ ] I solved e2 with objdump (operation discovery) → gdb (capturing the comparison moment)
- [ ] I solved e3 by collecting characters with Python from the cmpb chain
- [ ] I know crackmes.one’s difficulty filters and the safe execution procedure
- [ ] I wrote a 10-problem solving log (unsolved included) with per-type average times
- [ ] I recorded my slowest type and the reason
6. Common Pitfalls & Fixes
Wall 1. strings outputs so much that I can’t find the clue
Symptom: hundreds of lines get printed.
Cause: system library strings are mixed in. That’s normal.
Fix: only look near the verdict messages — strings ./problem | grep -iE "key|pass|wrong|correct|flag|try". If that’s still too much, filter by length with strings -n 6 to cut the noise.
Wall 2. I thought it was Type 2, but gdb won’t stop at strcmp
Symptom: you set break strcmp but only the loader’s calls get caught, or it doesn’t stop at all.
Cause: the former is the same as Wall 3 of Step 178 (the loader also uses strcmp); the latter means the binary doesn’t use strcmp — per-character comparison (Type 3) or its own comparison loop.
Fix: don’t break on the function name; find the comparison call site with disassemble main and break on break *main+offset. If there’s no strcmp call at all, switch to Type 3 and look for cmpb.
Wall 3. The downloaded crackme won’t run
Symptom: Permission denied or cannot execute binary file.
Cause: no execute permission / a binary for a different architecture or OS, respectively.
Fix: chmod +x for the former. For the latter, check with file ./problem — if it’s PE (for Windows), you can’t run it in WSL. This is why you pick by platform (2-3).
Wall 4. I found the transformation operation, but the back-calculated value is garbage
Symptom: you subtracted 3 and got broken characters.
Cause: you read the transformation direction backwards, or the loop applies to only part of the array, not the whole thing.
Fix: re-read the disassembly and confirm "what operation is being applied to the stored values." If confused, abandon static back-calculation and go with gdb capture — one moment of runtime truth beats ten guesses.
Wall 5. The 30 minutes are up but I can’t let go
Symptom: you spend a whole day on one problem, thinking "just a little more and I’ve got it."
Cause: the feeling of almost solving it is often an illusion, and even when it’s real, it conflicts with today’s goal (speed).
Fix: follow the rule — recording it as unsolved and moving on is today’s correct answer. Look at that problem again after learning tomorrow’s tools in Step 218 (medium difficulty), and your view will have changed.
7. Summary
Today’s Concepts
| Concept | One-line explanation |
|---|---|
| Direct comparison | Plaintext answer stored + strcmp — caught by strings |
| Transformed comparison | Hidden with XOR/addition → restored at runtime — gdb capture is the textbook move |
| Per-character branching | A chain of cmpb constants — objdump itself is the answer sheet |
| Type recognition | The rhythm of deciding "which type is this" in the first 3 minutes after opening a binary |
| Solving log | A record of type, tools, and time — a tool that turns weaknesses into numbers |
| crackmes.one | A repository of crackmes by difficulty — isolated-environment execution is the iron rule |
Today’s Commands
| Command | What it does |
|---|---|
strings ./problem | grep -iE "pattern" |
Type 1 verdict and solution in one |
objdump -d ./problem | grep -E "xor|sub|add" |
Type 2 verdict — finding the transformation operation |
objdump -d ./problem | grep cmpb |
Type 3 verdict — the per-character comparison chain |
gdb -batch -ex "disassemble main" |
Find the comparison call site |
break *main+offset → x/s $rdi x/s $rsi |
Capture the two strings at the moment of comparison |
file ./problem |
Check the platform of a downloaded binary |
An Instinct More Important Than Commands
The real reward of 10 easy problems isn’t ten flags — it’s automating the first 3 minutes. Run it, run strings, search for operations — repeating until this order comes out without thinking is all of today.
And don’t forget why you keep a log. With one recorded line — "I hesitated on transformation types" — tomorrow’s you wakes up knowing what to practice. Reversing skill isn’t talent; it’s the sum of recorded repetitions.
Once every box is checked, Step 217 is complete.