About

This post covers a small x86 32-bit crackme written in SASM1, then analyzed and patched in Cutter2. The ouroboros theme stays in the banner and naming, while the password check itself is a direct byte-by-byte comparison using pointer increments. The password vorares is stored in plaintext in the .data section, which makes the strings view useful right away.

Introduction

I kept this one small and easy to follow. The crackme prints the ouroboros banner, reads one input string, and walks it against a hardcoded secret one byte at a time. From there I moved on to Cutter to isolate the mismatch branch and patch it.

Part 1: Building the CrackMe

The Code
extern _printf
extern _scanf

section .data

    banner      db 10
                db "                         10000", 10
                db "                    0101000011", 10
                db "                 0011110000110", 10
                db "             000110111011101010110", 10
                db "        0101101111100010011111110110000", 10
                db "      111111000000011100010110001001000010", 10
                db "   0111011010101000011111111   00110001101100", 10
                db " 00111101000     01010111111       00101100110", 10
                db " 010011010           000011010        01010110", 10
                db " 1001100000             011100000      00011101", 10
                db "  0100001000                    000      1010010", 10
                db "  0000001010                     10       101111", 10
                db "  000001010                     010        000001", 10
                db "   00010000                      0 0     01010010", 10
                db "   00100000                               00010010", 10
                db "   10010100                               0101110", 10
                db "   100011000                               1011000", 10
                db "   11000101                               01011000", 10
                db "   110000000                              0101010", 10
                db "   000110101                              1110011", 10
                db "   001001000                             000111000", 10
                db "    00000010010                          10100111", 10
                db "    00001010000                        0101000100", 10
                db "     010 01100000                    01001000110", 10
                db "      0010000100000                 0101001000", 10
                db "        0010010100000            010001000000", 10
                db "          1100000100001100  00 000000010001010", 10
                db "           00010011001000001001000000110111101", 10
                db "              00010101010010101101100110100000100", 10
                db "                 00010000010000000101000001000", 10
                db "                        000001111100000", 10
                db 10
                db "          [  O U R O B O R O S  ]", 10
                db 10, 0

    prompt      db "  > enter the cycle : ", 0
    fmt_in      db "%31s", 0

    secret      db "vorares", 0

    msg_open    db 10
                db "    the cycle completes.", 10
                db "    you may pass.", 10, 10, 0

    msg_break   db 10
                db "    the ring is broken.", 10, 10, 0

section .bss
    input_buf   resb 32

section .text
    global main

main:
    push ebp
    mov  ebp, esp

    push banner
    call _printf
    add  esp, 4

    push prompt
    call _printf
    add  esp, 4

    push input_buf
    push fmt_in
    call _scanf
    add  esp, 8

    mov  esi, input_buf
    mov  edi, secret

eat_tail:
    mov  al, [esi]
    mov  bl, [edi]
    cmp  al, bl
    jne  broken
    test al, al
    je   complete
    inc  esi
    inc  edi
    jmp  eat_tail

complete:
    push msg_open
    call _printf
    add  esp, 4
    jmp  done

broken:
    push msg_break
    call _printf
    add  esp, 4

done:
    mov  esp, ebp
    pop  ebp
    xor  eax, eax
    ret

The flow stays simple. main prints the banner, prints the prompt, reads input with scanf, then loads the input buffer into esi and the secret into edi. From there the compare loop walks both strings one byte at a time.

Inside the loop, mov al, [esi] reads the current input byte and mov bl, [edi] reads the current secret byte. The real gate is cmp al, bl, followed by jne broken, which sends execution to the failure message on any mismatch.34 If the bytes match, test al, al checks whether the current byte is the null terminator, and je complete ends the loop only when both strings have matched all the way to the end.5

The data section holds the banner, the prompt, the format string, the secret, and both result messages. In this version the secret contains vorares directly as plaintext. The bss section reserves input_buf as a 32-byte buffer, which matches the %31s limit and leaves room for the terminating zero. The text section keeps the program inside main, using the usual cdecl convention with caller stack cleanup after each function call. The syntax follows NASM style, which is the form used here in SASM.6

One extra thing I wanted here was the ASCII art banner. It is meant to be an ouroboros, the snake eating its own tail, because it fit the whole idea of the crackme with the cycle, the ring, and the loop in the password check. I also had to keep it inside an 80-character width, so I could not make it too detailed. That is why the ASCII loses some quality in places, but for a small console crackme it was enough and it still gave the program the look I wanted.

Running the Program

I wrote the crackme in SASM1 first and checked the editor view before moving on to the binary.

Then I ran it with vorares and confirmed it reached the success message.

After that I used a wrong input and confirmed it dropped into the failure path.


Part 2: Analyzing the Binary in Cutter

The compiled exe was easy to locate from SASM’s build output. Once it was built, the final file was just ouroboros.exe, which is the same binary I moved into Cutter2 for the rest of the analysis.

From there I checked the strings view first. vorares, the prompt, and both result strings stand out right away, which confirms that the password is present in plaintext in the data section.

Graph View

Even before zooming into individual instructions, the graph view of main already shows the whole shape of the crackme. The setup sits at the top, the compare loop is in the middle, and the success and failure blocks split off clearly from the same condition.

Assembly Literacy

The main routine is still easy to follow in disassembly because the pointer setup and the compare loop sit close together.

main prologue           push ebp
main prologue+1         mov ebp, esp
...                     mov esi, input_buf
...                     mov edi, secret
...                     mov al, byte ptr [esi]
...                     mov bl, byte ptr [edi]
mismatch check          cmp al, bl
failure branch          jne broken
...                     test al, al
...                     je complete
...                     inc esi
...                     inc edi
...                     jmp eat_tail

The first setup pair, mov esi, input_buf and mov edi, secret, loads the user buffer and the hardcoded password into the registers that drive the rest of the loop.

Here mov al, byte ptr [esi] pulls the current byte from the user input, while mov bl, byte ptr [edi] pulls the matching byte from the secret. Since both registers are working one byte at a time, the compare loop stays small and easy to follow.

cmp al, bl is the actual equality check inside the loop. It compares the current input byte against the current secret byte and only updates the flags, without writing any result back.3

jne broken is the part that matters most. If the bytes do not match, execution goes straight to the failure block. That is the instruction that really decides whether the loop keeps going or dies there.4

test al, al comes right after the successful compare. It checks whether the current byte is zero, which means the loop has reached the string terminator.5

je complete takes the success path only when the zero flag is set by that test, which means the entire string matched and the loop reached the end cleanly.5

inc esi and inc edi advance both pointers by one byte before the loop jumps back. That keeps the input pointer and the secret pointer aligned on each iteration.

Conditional Logic

The important conditional block inside eat_tail is small enough to isolate immediately.

...                     mov al, byte ptr [esi]
...                     mov bl, byte ptr [edi]
mismatch check          cmp al, bl
failure branch          jne broken
...                     test al, al
...                     je complete

The first decision here is the mismatch gate. cmp al, bl checks the current pair of bytes, and jne broken takes the failure path on the first difference. If the bytes match, execution falls through to test al, al and then je complete, which only ends the loop once the null terminator is reached.


Part 3: Patching the Binary

Once the compare loop was isolated, the patch point was small. The branch to focus on is the single jne at 0x004013d9, right after cmp al, bl at 0x004013d7. In the file itself that instruction sits at offset 0x000009d9, and its original bytes are 75 17.

Replacing those two bytes with 90 90 turns the branch into nop / nop.7 After that, the loop no longer exits on mismatches. Execution falls through into test al, al and keeps advancing both pointers until the input terminator is reached, which sends control to complete. Since scanf with %31s writes a null-terminated string into the 32-byte buffer, any normal input still reaches that terminator.

Before patching, jne 0x4013f2 is still there, so it still redirects execution into the failure block that prints the ring is broken.

After patching, that same spot becomes nop / nop, which forces the code to fall through into the rest of the loop instead of branching away on the first mismatch.

In graph view the result is also easy to see. Once the conditional edge is gone, the loop only keeps walking forward until the null terminator reaches the je 0x4013e3 success path.

Conclusion

This crackme stays simple by design. vorares is visible in the strings view, the validation logic is a standard byte-by-byte loop, and the patch only needs one jne to disappear before the program starts accepting wrong input. If I take it further, the next step is to obfuscate the password so it does not stand out in strings and the comparison becomes less obvious on a first pass.

© Credits: The writing and images on this page are the original work of the page author unless a source is explicitly cited.


  1. SASM is the IDE used here to write and build the 32-bit assembly source. Documentation: https://dman95.github.io/SASM/english.html ↩︎ ↩︎

  2. Cutter is the reverse engineering frontend used here for disassembly, decompiler output, and byte patching. Documentation: https://cutter.re/docs/ ↩︎ ↩︎

  3. Intel Software Developer’s Manual, Volume 2 documents cmp, which updates flags from a subtraction without writing a result back. Documentation: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html ↩︎ ↩︎

  4. Intel Software Developer’s Manual, Volume 2 documents jne, which branches when the zero flag is clear after the compare. Documentation: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html ↩︎ ↩︎

  5. Intel Software Developer’s Manual, Volume 2 documents test and je, which are what make the loop stop only when it reaches the terminating zero byte. Documentation: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html ↩︎ ↩︎ ↩︎

  6. NASM documentation covers the syntax used in this source file. Documentation: https://www.nasm.us/doc/ ↩︎

  7. Intel Software Developer’s Manual, Volume 2 documents nop, which is what makes the patched branch disappear without changing control flow in any other way. Documentation: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html ↩︎

© Credits: The writing and images on this page are the original work of the page author unless a source is explicitly cited.