DEV Community

ddupard
ddupard

Posted on Edited on

Crackmes: The Basics

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  |..........@.....|
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
topstar_ai profile image
Luis Cruz

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?

Collapse
 
ddupard profile image
ddupard

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.

Collapse
 
ddupard profile image
ddupard

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.