Crackmes are a rite of passage for anyone looking to become a reverse engineering professional. Becoming exceptionally good in this field is primarily about building solid habits and learning to recognize assembly patterns. There is no secret formula: you have to practice, practice, and practice some more.
Today, I am going to introduce you to the simplest crackmes you can find on https://crackmes.one. This site is truly fantastic—hats off to the creators behind the idea.
The crackmes range from Level 1 to Level 6, with Level 1 being the easiest. For the first crackmes, I will select the Assembly, x86_64, and Linux options. But for more advanced crackmes, I will choose Windows crackmes because they are much more numerous.
For our example today, I will pick BitFriends' nasm_crack.
Download it into a working directory and unpack it using the password crackmes.one. Don't do what I did on my very first crackme: having skipped the FAQ, I found myself disassembly-debugging the ZIP file itself and trying to brute-force the archive password! The actual files you need to crack are inside the ZIP archive, not the archive itself.
For context—even though I am a bit rusty—back in the 90s I was reversing the Windows kernel, and during the 80s I went through a phase of cracking video games, followed by a period analyzing viruses. I am not at the level of someone who does this day in and day out, but I am certainly no beginner either. Reversing some of these crackmes actually brings back memories of tricks that were already being used back in the video game era.
To streamline my disassembly process, I created a Python pipeline that outputs clean disassembly files (*.objdump_clean). Combined with custom syntax highlighting rules in VS Code, I get disassembly files where the key elements to inspect jump out immediately.
Note that I also reverse engineer binaries on other processor architectures (RISC-V, 8051, etc.), and I maintain dedicated syntax highlighting profiles for each target ISA.
I have also built a customized GDB environment (myGDB) on top of pwndbg, enhanced with my own custom commands, including:
- a command to query my personal local knowledge base (doc command)
- a command to invoke AI assistance (ia command)
That said, I only fire up myGDB when strictly necessary—I vastly prefer reading the raw disassembly directly whenever possible.
For this first example, however, we won't even need all of that tooling. My baseline rule whenever I approach a new binary is to run the following two commands first:
- hexdump -C XXXX
- readelf -a XXXX
(where XXXX is the name of the executable to reverse).
Running hexdump -C nasm_crack yields the following output, where we immediately recognize the ELF header right at the beginning:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 28 10 40 00 00 00 00 00 |..>.....(.@.....|
00000020 40 00 00 00 00 00 00 00 58 22 00 00 00 00 00 00 |@.......X"......|
00000030 00 00 00 00 40 00 38 00 03 00 40 00 06 00 05 00 |....@.8...@.....|
00000040 01 00 00 00 04 00 00 00 00 00 00 00 00 00 00 00 |................|
00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |..@.......@.....|
00000060 e8 00 00 00 00 00 00 00 e8 00 00 00 00 00 00 00 |................|
00000070 00 10 00 00 00 00 00 00 01 00 00 00 05 00 00 00 |................|
00000080 00 10 00 00 00 00 00 00 00 10 40 00 00 00 00 00 |..........@.....|
00000090 00 10 40 00 00 00 00 00 a2 00 00 00 00 00 00 00 |..@.............|
000000a0 a2 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 |................|
000000b0 01 00 00 00 06 00 00 00 00 20 00 00 00 00 00 00 |......... ......|
000000c0 00 20 40 00 00 00 00 00 00 20 40 00 00 00 00 00 |. @...... @.....|
000000d0 31 00 00 00 00 00 00 00 31 00 00 00 00 00 00 00 |1.......1.......|
000000e0 00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001000 b8 01 00 00 00 bf 01 00 00 00 48 be 16 20 40 00 |..........H.. @.|
00001010 00 00 00 00 ba 09 00 00 00 0f 05 b8 3c 00 00 00 |............<...|
00001020 bf 00 00 00 00 0f 05 c3 b8 01 00 00 00 bf 01 00 |................|
00001030 00 00 48 be 00 20 40 00 00 00 00 00 ba 16 00 00 |..H.. @.........|
00001040 00 0f 05 b8 00 00 00 00 bf 00 00 00 00 48 be 31 |.............H.1|
00001050 20 40 00 00 00 00 00 ba 10 00 00 00 0f 05 48 bf | @............H.|
00001060 26 20 40 00 00 00 00 00 48 be 31 20 40 00 00 00 |& @.....H.1 @...|
00001070 00 00 b9 0b 00 00 00 f3 a6 74 85 b8 01 00 00 00 |.........t......|
00001080 bf 01 00 00 00 48 be 1f 20 40 00 00 00 00 00 ba |.....H.. @......|
00001090 07 00 00 00 0f 05 b8 3c 00 00 00 bf 00 00 00 00 |.......<........|
000010a0 0f 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000010b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00002000 45 6e 74 65 72 20 79 6f 75 72 20 70 61 73 73 77 |Enter your passw|
00002010 6f 72 64 3a 20 00 43 6f 72 72 65 63 74 21 0a 57 |ord: .Correct!.W|
00002020 72 6f 6e 67 21 0a 73 75 70 65 72 73 65 63 72 65 |rong!.supersecre|
00002030 74 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |t...............|
00002040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00002050 00 00 00 00 03 00 01 00 00 10 40 00 00 00 00 00 |..........@.....|
The interesting part is located at address 00002000. We can see "Enter your password: ", followed by "Correct!", then "Wrong!", and finally "supersecret".
If you launch ./nasm_crack, it prompts you for your password. Entering supersecret yields the expected validation:
Enter your password: supersecret
Correct!
Under the hood
nasm_crack: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <correct_func>:
401000: mov eax,0x1 # eax = 1
401005: mov edi,0x1 # edi = 1
40100a: movabs rsi,0x402016 # Correct!
401014: mov edx,0x9 # we print 9 chars
401019: syscall
40101b: mov eax,0x3c # eax = 0x3c
401020: mov edi,0x0
401025: syscall # sys_exit
401027: ret
0000000000401028 <_start>:
401028: mov eax,0x1 # eax = 1
40102d: mov edi,0x1 # edi = 1
401032: movabs rsi,0x402000 # "Enter your password: "
40103c: mov edx,0x16 # we print 0x16 = 22 chars
401041: syscall # sys_write
401043: mov eax,0x0 # eax = 0
401048: mov edi,0x0
40104d: movabs rsi,0x402031 # 0x402031 where the password will be stored
401057: mov edx,0x10 # no more than 0x10 = 16 chars entered
40105c: syscall # sys_read
# loop of comparison between the password which have been stored in 0x402031
# and the password stored in 0x402026 for 0xb = 11 chars
40105e: movabs rdi,0x402026
401068: movabs rsi,0x402031
401072: mov ecx,0xb
401077: repz cmps BYTE PTR ds:[rsi],BYTE PTR es:[rdi]
401079: je 401000 <correct_func> # if the password entered and the password stored we go to Correct_func
40107b: mov eax,0x1
401080: mov edi,0x1
401085: movabs rsi,0x40201f
40108f: mov edx,0x7
401094: syscall
401096: mov eax,0x3c
40109b: mov edi,0x0
4010a0: syscall
The most essential takeaway for a beginner crackme analysis is understanding the Linux x86_64 system call interface:
sys_write
- eax = 1
- edi = 1 (standard output)
- rsi = address of the string to print
- edx = byte count to print
- syscall
sys_read
- eax = 0
- edi = 0 (standard input)
- rsi = target buffer address for entered string
- edx = maximum allowed byte count
- syscall
sys_exit
- eax = 0x3c
- edi = exit status code
- syscall



Top comments (3)
Your approach to using a Python pipeline for clean disassembly output is a smart way to streamline the process and enhance readability—definitely something I’ll consider implementing in my own workflow. I find that having tailored syntax highlighting significantly boosts efficiency when analyzing complex binaries. If you're looking for additional engineering support as you dive deeper into various architectures, I’d be happy to explore a paid collaboration to help enhance your tools further. What other features do you think would be valuable for your myGDB environment?
Developing custom tooling is precisely what separates a seasoned reverse engineer from a beginner. Throughout this series, I'll be demonstrating how to write custom scripts and extensions—both inside GDB and as standalone utilities—to solve specific reversing bottlenecks.
For example, when we analyze packed binaries, I’ll cover identification heuristics and show how to build an automated unpacker to extract the clean disassembly. In the third article, we’ll tackle a binary where brute-forcing is computationally infeasible by thoroughly auditing the password verification routine.
That’s why platforms like crackmes.one are so valuable: they let us classify binaries by specific anti-analysis techniques, enabling us to build targeted tools that automate the reversing workflow.
By the end of this article series, I will have shown how to build an industrial-grade reverse engineering system—leveraging both internal tooling and LLMs to solve CTF-style challenges. But for now, the goal is to take things step-by-step and start right from the fundamentals.