Skip to main content

Reversing ELF

Crackme1

To get the first flag we just need to run the binary. But first we need to give it permissions to run with chmod +x crackme1.

Crackme2

The second crackme asks for a password as an argument. If the password is wrong it outputs "Access denied". Running the strings command reveals us the super secret password. Now we can run the binary with the super secret password as argument to get the flag.

Crackme3

This crackme, as the previous one, asks for the right password as an argument. Running the strings command on the binary reveals a base64 encoded string. Decoding the string gives us the flag.

Crackme4

1. Initial Analysis

First, we make the binary executable and run it to understand its basic functionality:

chmod +x crackme4
./crackme4

The output indicates the expected usage and hints at the presence of a hidden string compared using strcmp:

# Usage : ./crackme4 password
# This time the string is hidden and we used strcmp

We then consult the man page for strcmp to refresh our understanding of its behavior:

man strcmp

strcmp compares two strings and returns 0 if they are equal, a negative value if the first string is lexicographically less than the second, and a positive value otherwise.

2. Disassembly and Function Analysis with Radare2

We start Radare2 in debug mode:

r2 -d crackme4

Next, we perform an initial analysis using the aaa command. This command analyzes the binary, identifies functions, and performs various other analyses:

aaa

The aaa command performs numerous analyses, including:

  • af: Analyzing functions (locating entry points, symbols, etc.)
  • aac: Analyzing function calls
  • aar: Analyzing references
  • avrr: Finding and parsing C++ vtables. (Less relevant for simple C programs but important to be aware of)

After the analysis, we list the identified functions using afl:

afl

This command displays a list of functions in the binary. The most important one is typically main. We analyze the main function using the pdf command (print disassembled function):

pdf @main

The disassembled main function reveals the program's entry point and initial logic:

            ; DATA XREF from entry0 @ 0x40055d(r)
┌ 74: int main (int argc, char **argv);
│ `- args(rdi, rsi) vars(2:sp[0xc..0x18])
│ 0x00400716 55 push rbp
│ 0x00400717 4889e5 mov rbp, rsp
│ 0x0040071a 4883ec10 sub rsp, 0x10
│ 0x0040071e 897dfc mov dword [var_4h], edi ; argc
│ 0x00400721 488975f0 mov qword [var_10h], rsi ; argv
│ 0x00400725 837dfc02 cmp dword [var_4h], 2
│ ┌─< 0x00400729 741b je 0x400746
│ │ 0x0040072b 488b45f0 mov rax, qword [var_10h]
│ │ 0x0040072f 488b00 mov rax, qword [rax]
│ │ 0x00400732 4889c6 mov rsi, rax
│ │ 0x00400735 bf10084000 mov edi, str.Usage_:__s_password_nThis_time_the_string_is_hidden_and_we_used_strcmp_n ; 0x400810 ; "Usage : %s password\nThis time the string is hidden and we used strcmp\n"
│ │ 0x0040073a b800000000 mov eax, 0
│ │ 0x0040073f e8bcfdffff call sym.imp.printf ; int printf(const char *format)
│ ┌──< 0x00400744 eb13 jmp 0x400759
│ │└─> 0x00400746 488b45f0 mov rax, qword [var_10h]
│ │ 0x0040074a 4883c008 add rax, 8
│ │ 0x0040074e 488b00 mov rax, qword [rax]
│ │ 0x00400751 4889c7 mov rdi, rax
│ │ 0x00400754 e821ffffff call sym.compare_pwd
│ │ ; CODE XREF from main @ 0x400744(x)
│ └──> 0x00400759 b800000000 mov eax, 0
│ 0x0040075e c9 leave
└ 0x0040075f c3 ret

Key observations from main:

  • It checks if the program is called with one argument (the password).
  • If not, it prints the usage message.
  • Crucially, it calls the sym.compare_pwd function, passing the provided password as an argument. This strongly suggests that the password comparison logic resides within this function.

Now, we examine the sym.compare_pwd function:

pdf @sym.compare_pwd

The disassembled code for sym.compare_pwd is:

            ; CALL XREF from main @ 0x400754(x)
┌ 156: sym.compare_pwd (int64_t arg1);
│ `- args(rdi) vars(6:sp[0x10..0x30])
│ 0x0040067a 55 push rbp
│ 0x0040067b 4889e5 mov rbp, rsp
│ 0x0040067e 4883ec30 sub rsp, 0x30
│ 0x00400682 48897dd8 mov qword [var_28h], rdi ; arg1
│ 0x00400686 64488b0425.. mov rax, qword fs:[0x28]
│ 0x0040068f 488945f8 mov qword [var_8h], rax
│ 0x00400693 31c0 xor eax, eax
│ 0x00400695 48b8495d7b.. movabs rax, 0x7b175614497b5d49 ; 'I]{I\x14V\x17{'
│ 0x0040069f 488945e0 mov qword [var_20h], rax
│ 0x004006a3 48b8574147.. movabs rax, 0x547b175651474157 ; 'WAGQV\x17{T'
│ 0x004006ad 488945e8 mov qword [var_18h], rax
│ 0x004006b1 66c745f05340 mov word [var_10h], 0x4053 ; 'S@'
│ 0x004006b7 c645f200 mov byte [var_eh], 0
│ 0x004006bb 488d45e0 lea rax, [var_20h]
│ 0x004006bf 4889c7 mov rdi, rax
│ 0x004006c2 e866ffffff call sym.get_pwd
│ 0x004006c7 488b55d8 mov rdx, qword [var_28h]
│ 0x004006cb 488d45e0 lea rax, [var_20h]
│ 0x004006cf 4889d6 mov rsi, rdx
│ 0x004006d2 4889c7 mov rdi, rax
│ 0x004006d5 e846feffff call sym.imp.strcmp ; int strcmp(const char *s1, const char *s2)
│ 0x004006da 85c0 test eax, eax
│ ┌─< 0x004006dc 750c jne 0x4006ea
│ │ 0x004006de bfe8074000 mov edi, str.password_OK ; 0x4007e8 ; "password OK"
│ │ 0x004006e3 e8f8fdffff call sym.imp.puts ; int puts(const char *s)
│ ┌──< 0x004006e8 eb16 jmp 0x400700
│ │└─> 0x004006ea 488b45d8 mov rax, qword [var_28h]
│ │ 0x004006ee 4889c6 mov rsi, rax
│ │ 0x004006f1 bff4074000 mov edi, str.password___s__not_OK_n ; 0x4007f4 ; "password "%s" not OK\n"
│ │ 0x004006f6 b800000000 mov eax, 0
│ │ 0x004006fb e800feffff call sym.imp.printf ; int printf(const char *format)
│ │ ; CODE XREF from sym.compare_pwd @ 0x4006e8(x)
│ └──> 0x00400700 488b45f8 mov rax, qword [var_8h]
│ 0x00400704 6448330425.. xor rax, qword fs:[0x28]
│ ┌─< 0x0040070d 7405 je 0x400714
│ │ 0x0040070f e8dcfdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ └─> 0x00400714 c9 leave
└ 0x00400715 c3 ret

The disassembly confirms that strcmp is used for password comparison at address 0x004006d5.

3. Dynamic Analysis and Breakpointing

To determine the expected password, we set a breakpoint just before the strcmp call, at the mov rdi, rax instruction (0x004006d2). This is important because rdi will contain the address of the string being compared against the user's input.

db 0x004006d2

We execute the program with a dummy argument using ood and then run it with dc:

ood 'random'
dc

The program will pause execution at the breakpoint.

4. Examining Register Values

At the breakpoint, we inspect the value of the rdi register using px @rdi:

px @rdi

This command prints the memory pointed to by the rdi register in hexadecimal format. The output should reveal the hidden password string.

- offset -      A0A1 A2A3 A4A5 A6A7 A8A9 AAAB ACAD AEAF  0123456789ABCDEF
0x7ffef2fd69a0 6d79 5f6d 3072 335f 7365 6375 7233 5f70 my_m0r3_secur3_p
0x7ffef2fd69b0 7764 0000 0000 0000 00a8 7a7e 02ca 1c59 wd........z~...Y
0x7ffef2fd69c0 e069 fdf2 fe7f 0000 5907 4000 0000 0000 .i......Y.@.....
0x7ffef2fd69d0 f86a fdf2 fe7f 0000 706a fdf2 0200 0000 .j......pj......
0x7ffef2fd69e0 0200 0000 0000 0000 681d 37b3 b77f 0000 ........h.7.....
0x7ffef2fd69f0 e06a fdf2 fe7f 0000 1607 4000 0000 0000 .j........@.....
0x7ffef2fd6a00 4000 4000 0200 0000 f86a fdf2 fe7f 0000 @.@......j......
0x7ffef2fd6a10 f86a fdf2 fe7f 0000 1b98 edaa d6ad 310b .j............1.
0x7ffef2fd6a20 0000 0000 0000 0000 106b fdf2 fe7f 0000 .........k......
0x7ffef2fd6a30 0030 59b3 b77f 0000 0000 0000 0000 0000 .0Y.............
0x7ffef2fd6a40 1b98 0979 2c48 ccf4 1b98 a990 b8cb 5ef4 ...y,H........^.
0x7ffef2fd6a50 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x7ffef2fd6a60 0000 0000 0000 0000 0000 0000 0000 0000 ................
0x7ffef2fd6a70 106b fdf2 fe7f 0000 00a8 7a7e 02ca 1c59 .k........z~...Y
0x7ffef2fd6a80 0000 0000 0000 0000 251e 37b3 b77f 0000 ........%.7.....
0x7ffef2fd6a90 1607 4000 0000 0000 0000 0000 fe7f 0000 ..@.............

From the memory dump, we can identify the password: my_m0r3_secur3_pwd.

Crackme5

1. Initial Execution and Analysis

First, we make the binary executable and run it to understand its basic behavior:

chmod +x crackme5
./crackme5

The output indicates that the program prompts for input and prints "Always dig deeper" if the input is incorrect.

Enter your input:
test
Always dig deeper

Next, we start Radare2 in debug mode and perform initial analysis:

r2 -d crackme5
aaa
afl
pdf @main

The pdf @main command disassembles the main function:

            ; DATA XREF from entry0 @ 0x4005fd(r)
; CALL XREF from sym.check @ 0x4008c3(r)
┌ 251: int main (int argc, char **argv);
│ `- args(rdi, rsi) vars(33:sp[0x10..0x78])
│ 0x00400773 55 push rbp
│ 0x00400774 4889e5 mov rbp, rsp
│ 0x00400777 4883ec70 sub rsp, 0x70
│ 0x0040077b 897d9c mov dword [var_64h], edi ; argc
│ 0x0040077e 48897590 mov qword [var_70h], rsi ; argv
│ 0x00400782 64488b0425.. mov rax, qword fs:[0x28]
│ 0x0040078b 488945f8 mov qword [var_8h], rax
│ 0x0040078f 31c0 xor eax, eax
│ 0x00400791 c645d04f mov byte [var_30h], 0x4f ; 'O' ; 79
│ 0x00400795 c645d166 mov byte [var_2fh], 0x66 ; 'f' ; 102
│ 0x00400799 c645d264 mov byte [var_2eh], 0x64 ; 'd' ; 100
│ 0x0040079d c645d36c mov byte [var_2dh], 0x6c ; 'l' ; 108
│ 0x004007a1 c645d444 mov byte [var_2ch], 0x44 ; 'D' ; 68
│ 0x004007a5 c645d553 mov byte [var_2bh], 0x53 ; 'S' ; 83
│ 0x004007a9 c645d641 mov byte [var_2ah], 0x41 ; 'A' ; 65
│ 0x004007ad c645d77c mov byte [var_29h], 0x7c ; '|' ; 124
│ 0x004007b1 c645d833 mov byte [var_28h], 0x33 ; '3' ; 51
│ 0x004007b5 c645d974 mov byte [var_27h], 0x74 ; 't' ; 116
│ 0x004007b9 c645da58 mov byte [var_26h], 0x58 ; 'X' ; 88
│ 0x004007bd c645db62 mov byte [var_25h], 0x62 ; 'b' ; 98
│ 0x004007c1 c645dc33 mov byte [var_24h], 0x33 ; '3' ; 51
│ 0x004007c5 c645dd32 mov byte [var_23h], 0x32 ; '2' ; 50
│ 0x004007c9 c645de7e mov byte [var_22h], 0x7e ; '~' ; 126
│ 0x004007cd c645df58 mov byte [var_21h], 0x58 ; 'X' ; 88
│ 0x004007d1 c645e033 mov byte [var_20h], 0x33 ; '3' ; 51
│ 0x004007d5 c645e174 mov byte [var_1fh], 0x74 ; 't' ; 116
│ 0x004007d9 c645e258 mov byte [var_1eh], 0x58 ; 'X' ; 88
│ 0x004007dd c645e340 mov byte [var_1dh], 0x40 ; elf_phdr
│ 0x004007e1 c645e473 mov byte [var_1ch], 0x73 ; 's' ; 115
│ 0x004007e5 c645e558 mov byte [var_1bh], 0x58 ; 'X' ; 88
│ 0x004007e9 c645e660 mov byte [var_1ah], 0x60 ; '`' ; 96
│ 0x004007ed c645e734 mov byte [var_19h], 0x34 ; '4' ; 52
│ 0x004007f1 c645e874 mov byte [var_18h], 0x74 ; 't' ; 116
│ 0x004007f5 c645e958 mov byte [var_17h], 0x58 ; 'X' ; 88
│ 0x004007f9 c645ea74 mov byte [var_16h], 0x74 ; 't' ; 116
│ 0x004007fd c645eb7a mov byte [var_15h], 0x7a ; 'z' ; 122
│ 0x00400801 bf54094000 mov edi, str.Enter_your_input: ; 0x400954 ; "Enter your input:"
│ 0x00400806 e865fdffff call sym.imp.puts ; int puts(const char *s)
│ 0x0040080b 488d45b0 lea rax, [var_50h]
│ 0x0040080f 4889c6 mov rsi, rax
│ 0x00400812 bf66094000 mov edi, 0x400966 ; "%s"
│ 0x00400817 b800000000 mov eax, 0
│ 0x0040081c e89ffdffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ 0x00400821 488d55d0 lea rdx, [var_30h]
│ 0x00400825 488d45b0 lea rax, [var_50h]
│ 0x00400829 4889d6 mov rsi, rdx
│ 0x0040082c 4889c7 mov rdi, rax
│ 0x0040082f e8a2feffff call sym.strcmp_
│ 0x00400834 8945ac mov dword [var_54h], eax
│ 0x00400837 837dac00 cmp dword [var_54h], 0
│ ┌─< 0x0040083b 750c jne 0x400849
│ │ 0x0040083d bf69094000 mov edi, str.Good_game ; 0x400969 ; "Good game"
│ │ 0x00400842 e829fdffff call sym.imp.puts ; int puts(const char *s)
│ ┌──< 0x00400847 eb0a jmp 0x400853
│ │└─> 0x00400849 bf73094000 mov edi, str.Always_dig_deeper ; 0x400973 ; "Always dig deeper"
│ │ ; CODE XREF from main @ 0x400847(x)
│ └──> 0x00400853 b800000000 mov eax, 0
│ 0x00400858 488b4df8 mov rcx, qword [var_8h]
│ 0x0040085c 6448330c25.. xor rcx, qword fs:[0x28]
│ ┌─< 0x00400865 7405 je 0x40086c
│ │ 0x00400867 e824fdffff call sym.imp.__stack_chk_fail ; void __stack_chk_fail(void)
│ └─> 0x40086c c9 leave
└ 0x0040086d c3 ret

2. Identifying the Password

The main function performs the following actions:

  • Prompts the user for input using puts("Enter your input:").
  • Reads the input using scanf("%s", [var_50h]) and stores it in the memory location [var_50h].
  • Initializes a series of bytes in memory locations [var_30h] through [var_15h]. These bytes represent characters of the correct input.
  • Calls a function named sym.strcmp_ to compare the user's input with the pre-initialized string.
  • Prints "Good game" if strcmp_ returns 0 (strings are equal), otherwise prints "Always dig deeper."

The key to solving this challenge is to reconstruct the string that is being compared against the user's input. We can do this by extracting the byte values from the mov byte instructions in the main function:

  • 0x00400791: mov byte [var_30h], 0x4f ; 'O'
  • 0x00400795: mov byte [var_2fh], 0x66 ; 'f'
  • 0x00400799: mov byte [var_2eh], 0x64 ; 'd'
  • 0x0040079d: mov byte [var_2dh], 0x6c ; 'l'
  • 0x004007a1: mov byte [var_2ch], 0x44 ; 'D'
  • 0x004007a5: mov byte [var_2bh], 0x53 ; 'S'
  • 0x004007a9: mov byte [var_2ah], 0x41 ; 'A'
  • 0x004007ad: mov byte [var_29h], 0x7c ; '|'
  • 0x004007b1: mov byte [var_28h], 0x33 ; '3'
  • 0x004007b5: mov byte [var_27h], 0x74 ; 't'
  • 0x004007b9: mov byte [var_26h], 0x58 ; 'X'
  • 0x004007bd: mov byte [var_25h], 0x62 ; 'b'
  • 0x004007c1: mov byte [var_24h], 0x33 ; '3'
  • 0x004007c5: mov byte [var_23h], 0x32 ; '2'
  • 0x004007c9: mov byte [var_22h], 0x7e ; '~'
  • 0x004007cd: mov byte [var_21h], 0x58 ; 'X'
  • 0x004007d1: mov byte [var_20h], 0x33 ; '3'
  • 0x004007d5: mov byte [var_1fh], 0x74 ; 't'
  • 0x004007d9: mov byte [var_1eh], 0x58 ; 'X'
  • 0x004007dd: mov byte [var_1dh], 0x40 ; '@'
  • 0x004007e1: mov byte [var_1ch], 0x73 ; 's'
  • 0x004007e5: mov byte [var_1bh], 0x58 ; 'X'
  • 0x004007e9: mov byte [var_1ah], 0x60 ; '`'
  • 0x004007ed: mov byte [var_19h], 0x34 ; '4'
  • 0x004007f1: mov byte [var_18h], 0x74 ; 't'
  • 0x004007f5: mov byte [var_17h], 0x58 ; 'X'
  • 0x004007f9: mov byte [var_16h], 0x74 ; 't'
  • 0x004007fd: mov byte [var_15h], 0x7a ; 'z'

Concatenating these characters gives us the correct input: OfdlDSA|3tXb32~X3tX@sX4tXtz`.

3. Dynamic Analysis (Optional, but Verifying)

While the static analysis is sufficient to find the password, let's confirm with dynamic analysis. We set a breakpoint at 0x0040082c, right before the call to sym.strcmp_.

db 0x0040082c
ood 'random'
dc

Then, we examine the contents of the rsi register, which holds the address of the string being compared against our input.

px @rsi

The output:

- offset -      F0F1 F2F3 F4F5 F6F7 F8F9 FAFB FCFD FEFF  0123456789ABCDEF
0x7fffb29c15f0 4f66 646c 4453 417c 3374 5862 3332 7e58 OfdlDSA|3tXb32~X
0x7fffb29c1600 3374 5840 7358 6034 7458 747a 7c7f 0000 3tX@sX`4tXtz|...
...

This confirms our statically derived password.

Crackme6

chmod +x crackme6
./crackme6
Usage : ./crackme6 password
Good luck, read the source
r2 -d crackme6
aaa
pdf @main
            ; DATA XREF from entry0 @ 0x4004ad(r)
┌ 74: int main (int argc, char **argv);
│ `- args(rdi, rsi) vars(2:sp[0xc..0x18])
│ 0x00400711 55 push rbp
│ 0x00400712 4889e5 mov rbp, rsp
│ 0x00400715 4883ec10 sub rsp, 0x10
│ 0x00400719 897dfc mov dword [var_4h], edi ; argc
│ 0x0040071c 488975f0 mov qword [var_10h], rsi ; argv
│ 0x00400720 837dfc02 cmp dword [var_4h], 2
│ ┌─< 0x00400724 741b je 0x400741
│ │ 0x00400726 488b45f0 mov rax, qword [var_10h]
│ │ 0x0040072a 488b00 mov rax, qword [rax]
│ │ 0x0040072d 4889c6 mov rsi, rax
│ │ 0x00400730 bf10084000 mov edi, str.Usage_:__s_password_nGood_luck__read_the_source_n ; 0x400810 ; "Usage : %s password\nGood luck, read the source\n"
│ │ 0x00400735 b800000000 mov eax, 0
│ │ 0x0040073a e821fdffff call sym.imp.printf ; int printf(const char *format)
│ ┌──< 0x0040073f eb13 jmp 0x400754
│ │└─> 0x00400741 488b45f0 mov rax, qword [var_10h]
│ │ 0x00400745 4883c008 add rax, 8
│ │ 0x00400749 488b00 mov rax, qword [rax]
│ │ 0x0040074c 4889c7 mov rdi, rax
│ │ 0x0040074f e87dffffff call sym.compare_pwd
│ │ ; CODE XREF from main @ 0x40073f(x)
│ └──> 0x00400754 b800000000 mov eax, 0
│ 0x00400759 c9 leave
└ 0x0040075a c3 ret
pdf @sym.compare_pwd
            ; CALL XREF from main @ 0x40074f(x)
┌ 64: sym.compare_pwd (int64_t arg1);
│ `- args(rdi) vars(1:sp[0x10..0x10])
│ 0x004006d1 55 push rbp
│ 0x004006d2 4889e5 mov rbp, rsp
│ 0x004006d5 4883ec10 sub rsp, 0x10
│ 0x004006d9 48897df8 mov qword [var_8h], rdi ; arg1
│ 0x004006dd 488b45f8 mov rax, qword [var_8h]
│ 0x004006e1 4889c7 mov rdi, rax
│ 0x004006e4 e894feffff call sym.my_secure_test
│ 0x004006e9 85c0 test eax, eax
│ ┌─< 0x004006eb 750c jne 0x4006f9
│ │ 0x004006ed bfe8074000 mov edi, str.password_OK ; 0x4007e8 ; "password OK"
│ │ 0x004006f2 e859fdffff call sym.imp.puts ; int puts(const char *s)
│ ┌──< 0x004006f7 eb16 jmp 0x40070f
│ │└─> 0x004006f9 488b45f8 mov rax, qword [var_8h]
│ │ 0x004006fd 4889c6 mov rsi, rax
│ │ 0x00400700 bff4074000 mov edi, str.password___s__not_OK_n ; 0x4007f4 ; "password \"%s\" not OK\n"
│ │ 0x00400705 b800000000 mov eax, 0
│ │ 0x0040070a e851fdffff call sym.imp.printf ; int printf(const char *format)
│ │ ; CODE XREF from sym.compare_pwd @ 0x4006f7(x)
│ └──> 0x0040070f c9 leave
└ 0x00400710 c3 ret
pdf @sym.my_secure_test
            ; CALL XREF from sym.compare_pwd @ 0x4006e4(x)
┌ 340: sym.my_secure_test (int64_t arg1);
│ `- args(rdi) vars(1:sp[0x10..0x10])
│ 0x0040057d 55 push rbp
│ 0x0040057e 4889e5 mov rbp, rsp
│ 0x00400581 48897df8 mov qword [var_8h], rdi ; arg1
│ 0x00400585 488b45f8 mov rax, qword [var_8h]
│ 0x00400589 0fb600 movzx eax, byte [rax]
│ 0x0040058c 84c0 test al, al
│ ┌─< 0x0040058e 740b je 0x40059b
│ │ 0x00400590 488b45f8 mov rax, qword [var_8h]
│ │ 0x00400594 0fb600 movzx eax, byte [rax]
│ │ 0x00400597 3c31 cmp al, 0x31 ; '1' ; 49
│ ┌──< 0x00400599 740a je 0x4005a5
│ │└─> 0x0040059b b8ffffffff mov eax, 0xffffffff ; -1
│ │┌─< 0x004005a0 e92a010000 jmp 0x4006cf
│ └──> 0x004005a5 488b45f8 mov rax, qword [var_8h]
│ │ 0x004005a9 4883c001 add rax, 1
│ │ 0x004005ad 0fb600 movzx eax, byte [rax]
│ │ 0x004005b0 84c0 test al, al
│ ┌──< 0x004005b2 740f je 0x4005c3
│ ││ 0x004005b4 488b45f8 mov rax, qword [var_8h]
│ ││ 0x004005b8 4883c001 add rax, 1
│ ││ 0x004005bc 0fb600 movzx eax, byte [rax]
│ ││ 0x004005bf 3c33 cmp al, 0x33 ; '3' ; 51
│ ┌───< 0x004005c1 740a je 0x4005cd
│ │└──> 0x004005c3 b8ffffffff mov eax, 0xffffffff ; -1
│ │┌──< 0x004005c8 e902010000 jmp 0x4006cf
│ └───> 0x004005cd 488b45f8 mov rax, qword [var_8h]
│ ││ 0x004005d1 4883c002 add rax, 2
│ ││ 0x004005d5 0fb600 movzx eax, byte [rax]
│ ││ 0x004005d8 84c0 test al, al
│ ┌───< 0x004005da 740f je 0x4005eb
│ │││ 0x004005dc 488b45f8 mov rax, qword [var_8h]
│ │││ 0x004005e0 4883c002 add rax, 2
│ │││ 0x004005e4 0fb600 movzx eax, byte [rax]
│ │││ 0x004005e7 3c33 cmp al, 0x33 ; '3' ; 51
│ ┌────< 0x004005e9 740a je 0x4005f5
│ │└───> 0x004005eb b8ffffffff mov eax, 0xffffffff ; -1
│ │┌───< 0x004005f0 e9da000000 jmp 0x4006cf
│ └────> 0x004005f5 488b45f8 mov rax, qword [var_8h]
│ │││ 0x004005f9 4883c003 add rax, 3
│ │││ 0x004005fd 0fb600 movzx eax, byte [rax]
│ │││ 0x00400600 84c0 test al, al
│ ┌────< 0x00400602 740f je 0x400613
│ ││││ 0x00400604 488b45f8 mov rax, qword [var_8h]
│ ││││ 0x00400608 4883c003 add rax, 3
│ ││││ 0x0040060c 0fb600 movzx eax, byte [rax]
│ ││││ 0x0040060f 3c37 cmp al, 0x37 ; '7' ; 55
│ ┌─────< 0x00400611 740a je 0x40061d
│ │└────> 0x00400613 b8ffffffff mov eax, 0xffffffff ; -1
│ │┌────< 0x00400618 e9b2000000 jmp 0x4006cf
│ └─────> 0x0040061d 488b45f8 mov rax, qword [var_8h]
│ ││││ 0x00400621 4883c004 add rax, 4
│ ││││ 0x00400625 0fb600 movzx eax, byte [rax]
│ ││││ 0x00400628 84c0 test al, al
│ ┌─────< 0x0040062a 740f je 0x40063b
│ │││││ 0x0040062c 488b45f8 mov rax, qword [var_8h]
│ │││││ 0x00400630 4883c004 add rax, 4
│ │││││ 0x00400634 0fb600 movzx eax, byte [rax]
│ │││││ 0x00400637 3c5f cmp al, 0x5f ; '_' ; 95
│ ┌──────< 0x00400639 740a je 0x400645
│ │└─────> 0x0040063b b8ffffffff mov eax, 0xffffffff ; -1
│ │┌─────< 0x00400640 e98a000000 jmp 0x4006cf
│ └──────> 0x00400645 488b45f8 mov rax, qword [var_8h]
│ │││││ 0x00400649 4883c005 add rax, 5
│ │││││ 0x0040064d 0fb600 movzx eax, byte [rax]
│ │││││ 0x00400650 84c0 test al, al
│ ┌──────< 0x00400652 740f je 0x400663
│ ││││││ 0x00400654 488b45f8 mov rax, qword [var_8h]
│ ││││││ 0x00400658 4883c005 add rax, 5
│ ││││││ 0x0040065c 0fb600 movzx eax, byte [rax]
│ ││││││ 0x0040065f 3c70 cmp al, 0x70 ; 'p' ; 112
│ ┌───────< 0x00400661 7407 je 0x40066a
│ │└──────> 0x00400663 b8ffffffff mov eax, 0xffffffff ; -1
│ │┌──────< 0x00400668 eb65 jmp 0x4006cf
│ └───────> 0x0040066a 488b45f8 mov rax, qword [var_8h]
│ ││││││ 0x0040066e 4883c006 add rax, 6
│ ││││││ 0x00400672 0fb600 movzx eax, byte [rax]
│ ││││││ 0x00400675 84c0 test al, al
│ ┌───────< 0x00400677 740f je 0x400688
│ │││││││ 0x00400679 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x0040067d 4883c006 add rax, 6
│ │││││││ 0x00400681 0fb600 movzx eax, byte [rax]
│ │││││││ 0x00400684 3c77 cmp al, 0x77 ; 'w' ; 119
│ ────────< 0x00400686 7407 je 0x40068f
│ └───────> 0x00400688 b8ffffffff mov eax, 0xffffffff ; -1
│ ┌───────< 0x0040068d eb40 jmp 0x4006cf
│ ────────> 0x0040068f 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00400693 4883c007 add rax, 7
│ │││││││ 0x00400697 0fb600 movzx eax, byte [rax]
│ │││││││ 0x0040069a 84c0 test al, al
│ ────────< 0x0040069c 740f je 0x4006ad
│ │││││││ 0x0040069e 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x004006a2 4883c007 add rax, 7
│ │││││││ 0x004006a6 0fb600 movzx eax, byte [rax]
│ │││││││ 0x004006a9 3c64 cmp al, 0x64 ; 'd' ; 100
│ ────────< 0x004006ab 7407 je 0x4006b4
│ ────────> 0x004006ad b8ffffffff mov eax, 0xffffffff ; -1
│ ────────< 0x004006b2 eb1b jmp 0x4006cf
│ ────────> 0x004006b4 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x004006b8 4883c008 add rax, 8
│ │││││││ 0x004006bc 0fb600 movzx eax, byte [rax]
│ │││││││ 0x004006bf 84c0 test al, al
│ ────────< 0x004006c1 7407 je 0x4006ca
│ │││││││ 0x004006c3 b8ffffffff mov eax, 0xffffffff ; -1
│ ────────< 0x004006c8 eb05 jmp 0x4006cf
│ ────────> 0x004006ca b800000000 mov eax, 0
│ │││││││ ; XREFS: CODE 0x004005a0 CODE 0x004005c8 CODE 0x004005f0 CODE 0x00400618 CODE 0x00400640 CODE 0x00400668
│ │││││││ ; XREFS: CODE 0x0040068d CODE 0x004006b2 CODE 0x004006c8
│ └└└└└└└─> 0x004006cf 5d pop rbp
└ 0x004006d0 c3 ret

Open function in graph mode:

agf @sym.my_secure_test

Graph mode makes things more easy to understand. We can see that this function compares the least significant 8 bit of the rax register (al) with some hex value, then if its equal it increments the rax register and compares it again with another hex value. It seems that it is comparing every char in our input with these hex values.

If we join all these values together and decode them into their ASCII equivalent we get the password.

bytes.fromhex('313333375f707764').decode('utf8') # 1337_pwd

First, we make the binary executable and execute it to understand its basic behavior:

chmod +x crackme6
./crackme6

The output shows that the program expects a password as a command-line argument.

Usage : ./crackme6 password
Good luck, read the source

Next, we load the binary into Radare2 and perform initial analysis:

r2 -d crackme6
aaa
pdf @main

The disassembled main function:

            ; DATA XREF from entry0 @ 0x4004ad(r)
┌ 74: int main (int argc, char **argv);
│ `- args(rdi, rsi) vars(2:sp[0xc..0x18])
│ 0x00400711 55 push rbp
│ 0x00400712 4889e5 mov rbp, rsp
│ 0x00400715 4883ec10 sub rsp, 0x10
│ 0x00400719 897dfc mov dword [var_4h], edi ; argc
│ 0x0040071c 488975f0 mov qword [var_10h], rsi ; argv
│ 0x00400720 837dfc02 cmp dword [var_4h], 2
│ ┌─< 0x00400724 741b je 0x400741
│ │ 0x00400726 488b45f0 mov rax, qword [var_10h]
│ │ 0x0040072a 488b00 mov rax, qword [rax]
│ │ 0x0040072d 4889c6 mov rsi, rax
│ │ 0x00400730 bf10084000 mov edi, str.Usage_:__s_password_nGood_luck__read_the_source_n ; 0x400810 ; "Usage : %s password\nGood luck, read the source\n"
│ │ 0x00400735 b800000000 mov eax, 0
│ │ 0x0040073a e821fdffff call sym.imp.printf ; int printf(const char *format)
│ ┌──< 0x0040073f eb13 jmp 0x400754
│ │└─> 0x00400741 488b45f0 mov rax, qword [var_10h]
│ │ 0x00400745 4883c008 add rax, 8
│ │ 0x00400749 488b00 mov rax, qword [rax]
│ │ 0x0040074c 4889c7 mov rdi, rax
│ │ 0x0040074f e87dffffff call sym.compare_pwd
│ │ ; CODE XREF from main @ 0x40073f(x)
│ └──> 0x00400754 b800000000 mov eax, 0
│ 0x00400759 c9 leave
└ 0x0040075a c3 ret

The main function:

  • Checks if the correct number of arguments are provided (two, including the program name).
  • Retrieves the password argument.
  • Calls the sym.compare_pwd function with the password.

Let's examine sym.compare_pwd:

pdf @sym.compare_pwd
            ; CALL XREF from main @ 0x40074f(x)
┌ 64: sym.compare_pwd (int64_t arg1);
│ `- args(rdi) vars(1:sp[0x10..0x10])
│ 0x004006d1 55 push rbp
│ 0x004006d2 4889e5 mov rbp, rsp
│ 0x004006d5 4883ec10 sub rsp, 0x10
│ 0x004006d9 48897df8 mov qword [var_8h], rdi ; arg1
│ 0x004006dd 488b45f8 mov rax, qword [var_8h]
│ 0x004006e1 4889c7 mov rdi, rax
│ 0x004006e4 e894feffff call sym.my_secure_test
│ 0x004006e9 85c0 test eax, eax
│ ┌─< 0x004006eb 750c jne 0x4006f9
│ │ 0x004006ed bfe8074000 mov edi, str.password_OK ; 0x4007e8 ; "password OK"
│ │ 0x004006f2 e859fdffff call sym.imp.puts ; int puts(const char *s)
│ ┌──< 0x004006f7 eb16 jmp 0x40070f
│ │└─> 0x004006f9 488b45f8 mov rax, qword [var_8h]
│ │ 0x004006fd 4889c6 mov rsi, rax
│ │ 0x00400700 bff4074000 mov edi, str.password___s__not_OK_n ; 0x4007f4 ; "password \"%s\" not OK\n"
│ │ 0x00400705 b800000000 mov eax, 0
│ │ 0x0040070a e851fdffff call sym.imp.printf ; int printf(const char *format)
│ │ ; CODE XREF from sym.compare_pwd @ 0x4006f7(x)
│ └──> 0x0040070f c9 leave
└ 0x00400710 c3 ret

The sym.compare_pwd function:

  • Receives the password.
  • Calls sym.my_secure_test with the password.
  • Checks the return value of sym.my_secure_test. If it's non-zero, the password is incorrect; otherwise, it's correct.

Now, the interesting function: sym.my_secure_test:

pdf @sym.my_secure_test
            ; CALL XREF from sym.compare_pwd @ 0x4006e4(x)
┌ 340: sym.my_secure_test (int64_t arg1);
│ `- args(rdi) vars(1:sp[0x10..0x10])
│ 0x0040057d 55 push rbp
│ 0x0040057e 4889e5 mov rbp, rsp
│ 0x00400581 48897df8 mov qword [var_8h], rdi ; arg1
│ 0x00400585 488b45f8 mov rax, qword [var_8h]
│ 0x00400589 0fb600 movzx eax, byte [rax]
│ 0x0040058c 84c0 test al, al
│ ┌─< 0x0040058e 740b je 0x40059b
│ │ 0x00400590 488b45f8 mov rax, qword [var_8h]
│ │ 0x00400594 0fb600 movzx eax, byte [rax]
│ │ 0x00400597 3c31 cmp al, 0x31 ; '1' ; 49
│ ┌──< 0x00400599 740a je 0x4005a5
│ │└─> 0x0040059b b8ffffffff mov eax, 0xffffffff ; -1
│ │┌─< 0x004005a0 e92a010000 jmp 0x4006cf
│ └──> 0x004005a5 488b45f8 mov rax, qword [var_8h]
│ │ 0x004005a9 4883c001 add rax, 1
│ │ 0x004005ad 0fb600 movzx eax, byte [rax]
│ │ 0x004005b0 84c0 test al, al
│ ┌──< 0x004005b2 740f je 0x4005c3
│ ││ 0x004005b4 488b45f8 mov rax, qword [var_8h]
│ ││ 0x004005b8 4883c001 add rax, 1
│ ││ 0x004005bc 0fb600 movzx eax, byte [rax]
│ ││ 0x004005bf 3c33 cmp al, 0x33 ; '3' ; 51
│ ┌───< 0x004005c1 740a je 0x4005cd
│ │└──> 0x4005c3 b8ffffffff mov eax, 0xffffffff ; -1
│ │┌──< 0x4005c8 e902010000 jmp 0x4006cf
│ └───> 0x004005cd 488b45f8 mov rax, qword [var_8h]
│ ││ 0x004005d1 4883c002 add rax, 2
│ ││ 0x004005d5 0fb600 movzx eax, byte [rax]
│ ││ 0x004005d8 84c0 test al, al
│ ┌───< 0x4005da 740f je 0x4005eb
│ │││ 0x4005dc 488b45f8 mov rax, qword [var_8h]
│ │││ 0x4005e0 4883c002 add rax, 2
│ │││ 0x4005e4 0fb600 movzx eax, byte [rax]
│ │││ 0x4005e7 3c33 cmp al, 0x33 ; '3' ; 51
│ ┌────< 0x4005e9 740a je 0x4005f5
│ │└───> 0x4005eb b8ffffffff mov eax, 0xffffffff ; -1
│ │┌───< 0x4005f0 e9da000000 jmp 0x4006cf
│ └────> 0x004005f5 488b45f8 mov rax, qword [var_8h]
│ │││ 0x004005f9 4883c003 add rax, 3
│ │││ 0x004005fd 0fb600 movzx eax, byte [rax]
│ │││ 0x00400600 84c0 test al, al
│ ┌────< 0x400602 740f je 0x400613
│ ││││ 0x400604 488b45f8 mov rax, qword [var_8h]
│ ││││ 0x400608 4883c003 add rax, 3
│ ││││ 0x40060c 0fb600 movzx eax, byte [rax]
│ ││││ 0x40060f 3c37 cmp al, 0x37 ; '7' ; 55
│ ┌─────< 0x400611 740a je 0x40061d
│ │└────> 0x400613 b8ffffffff mov eax, 0xffffffff ; -1
│ │┌────< 0x400618 e9b2000000 jmp 0x4006cf
│ └─────> 0x0040061d 488b45f8 mov rax, qword [var_8h]
│ ││││ 0x00400621 4883c004 add rax, 4
│ ││││ 0x00400625 0fb600 movzx eax, byte [rax]
│ ││││ 0x00400628 84c0 test al, al
│ ┌─────< 0x40062a 740f je 0x40063b
│ │││││ 0x40062c 488b45f8 mov rax, qword [var_8h]
│ │││││ 0x400630 4883c004 add rax, 4
│ │││││ 0x400634 0fb600 movzx eax, byte [rax]
│ │││││ 0x400637 3c5f cmp al, 0x5f ; '_' ; 95
│ ┌──────< 0x400639 740a je 0x400645
│ │└─────> 0x40063b b8ffffffff mov eax, 0xffffffff ; -1
│ │┌─────< 0x400640 e98a000000 jmp 0x4006cf
│ └──────> 0x00400645 488b45f8 mov rax, qword [var_8h]
│ │││││ 0x00400649 4883c005 add rax, 5
│ │││││ 0x0040064d 0fb600 movzx eax, byte [rax]
│ │││││ 0x00400650 84c0 test al, al
│ ┌──────< 0x400652 740f je 0x400663
│ ││││││ 0x400654 488b45f8 mov rax, qword [var_8h]
│ ││││││ 0x400658 4883c005 add rax, 5
│ ││││││ 0x40065c 0fb600 movzx eax, byte [rax]
│ ││││││ 0x40065f 3c70 cmp al, 0x70 ; 'p' ; 112
│ ┌───────< 0x400661 7407 je 0x40066a
│ │└──────> 0x400663 b8ffffffff mov eax, 0xffffffff ; -1
│ │┌──────< 0x400668 eb65 jmp 0x4006cf
│ └───────> 0x0040066a 488b45f8 mov rax, qword [var_8h]
│ ││││││ 0x0040066e 4883c006 add rax, 6
│ ││││││ 0x00400672 0fb600 movzx eax, byte [rax]
│ ││││││ 0x00400675 84c0 test al, al
│ ┌───────< 0x400677 740f je 0x400688
│ │││││││ 0x400679 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x40067d 4883c006 add rax, 6
│ │││││││ 0x400681 0fb600 movzx eax, byte [rax]
│ │││││││ 0x400684 3c77 cmp al, 0x77 ; 'w' ; 119
│ ────────< 0x400686 7407 je 0x40068f
│ └───────> 0x400688 b8ffffffff mov eax, 0xffffffff ; -1
│ ┌───────< 0x40068d eb40 jmp 0x4006cf
│ ────────> 0x0040068f 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x00400693 4883c007 add rax, 7
│ │││││││ 0x00400697 0fb600 movzx eax, byte [rax]
│ │││││││ 0x0040069a 84c0 test al, al
│ ────────< 0x40069c 740f je 0x4006ad
│ │││││││ 0x40069e 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x4006a2 4883c007 add rax, 7
│ │││││││ 0x4006a6 0fb600 movzx eax, byte [rax]
│ │││││││ 0x4006a9 3c64 cmp al, 0x64 ; 'd' ; 100
│ ────────< 0x4006ab 7407 je 0x4006b4
│ ────────> 0x4006ad b8ffffffff mov eax, 0xffffffff ; -1
│ ────────< 0x4006b2 eb1b jmp 0x4006cf
│ ────────> 0x004006b4 488b45f8 mov rax, qword [var_8h]
│ │││││││ 0x004006b8 4883c008 add rax, 8
│ │││││││ 0x004006bc 0fb600 movzx eax, byte [rax]
│ │││││││ 0x004006bf 84c0 test al, al
│ ────────< 0x4006c1 7407 je 0x4006ca
│ │││││││ 0x4006c3 b8ffffffff mov eax, 0xffffffff ; -1
│ ────────< 0x4006c8 eb05 jmp 0x4006cf
│ ────────> 0x004006ca b800000000 mov eax, 0
│ │││││││ ; XREFS: CODE 0x4005a0 CODE 0x4005c8 CODE 0x4005f0 CODE 0x400618 CODE 0x400640 CODE 0x400668
│ │││││││ ; XREFS: CODE 0x40068d CODE 0x4006b2 CODE 0x4006c8
│ └└└└└└└─> 0x4006cf 5d pop rbp
└ 0x4006d0 c3 ret

This function performs a character-by-character comparison. For each character in the password, it checks if it matches a specific value. If any character fails the check, the function returns -1; otherwise, it returns 0. This makes it a character-by-character comparison.

To get a better visual understanding of the code (graph mode) we can use the command:

agf @sym.my_secure_test

Which shows the functions assembly code in graph mode. This mode helps to visualize the instructions and jumps with more ease.

  • 0x00400589: Loads the first byte of the input into al.
  • 0x00400597: Compares the first byte with 0x31 ('1'). If equal, jump to 0x004005a5.
  • 0x004005a9: Increments the pointer to the next byte in the input.
  • 0x004005bf: Compares the second byte with 0x33 ('3'). If equal, jump to 0x004005cd.
  • 0x004005e7: Compares the third byte with 0x33 ('3'). If equal, jump to 0x004005f5.
  • 0x0040060f: Compares the fourth byte with 0x37 ('7'). If equal, jump to 0x0040061d.
  • 0x00400637: Compares the fifth byte with 0x5f ('_'). If equal, jump to 0x00400645.
  • 0x0040065f: Compares the sixth byte with 0x70 ('p'). If equal, jump to 0x0040066a.
  • 0x00400684: Compares the seventh byte with 0x77 ('w'). If equal, jump to 0x0040068f.
  • 0x004006a9: Compares the eighth byte with 0x64 ('d'). If equal, jump to 0x004006b4.

So the password is "1337_pwd". We can find the same password using the python interpreter:

bytes.fromhex('313333375f707764').decode('utf8') # 1337_pwd

Crackme7

chmod +x crackme7
./crackme7
Menu:
[1] Say hello
[2] Add numbers
[3] Quit
[>] 1
What is your name? c
Hello, c!
Menu:
[1] Say hello
[2] Add numbers
[3] Quit
[>] 2
Enter first number: 23
Enter second number: 3
23 + 3 = 26
Menu:
[1] Say hello
[2] Add numbers
[3] Quit
[>] 3
Goodbye!
r2 -d crackme7
pdf @main
            ; DATA XREF from entry0 @ 0x80483d7(w)
┌ 491: int main (char **argv);
│ `- args(sp[0x4..0x4]) vars(5:sp[0x10..0x80])
│ 0x080484bb 8d4c2404 lea ecx, [argv]
│ 0x080484bf 83e4f0 and esp, 0xfffffff0
│ 0x080484c2 ff71fc push dword [ecx - 4]
│ 0x080484c5 55 push ebp
│ 0x080484c6 89e5 mov ebp, esp
│ 0x080484c8 57 push edi
│ 0x080484c9 51 push ecx
│ 0x080484ca 83ec70 sub esp, 0x70
│ ; CODE XREFS from main @ 0x8048590(x), 0x8048643(x)
│ ┌┌─> 0x080484cd 83ec0c sub esp, 0xc
│ ╎╎ 0x080484d0 68e0870408 push str.Menu:_n_n_1__Say_hello_n_2__Add_numbers_n_3__Quit ; 0x80487e0 ; "Menu:\n\n[1] Say hello\n[2] Add numbers\n[3] Quit"
│ ╎╎ 0x080484d5 e896feffff call sym.imp.puts ; int puts(const char *s)
│ ╎╎ 0x080484da 83c410 add esp, 0x10
│ ╎╎ 0x080484dd 83ec0c sub esp, 0xc
│ ╎╎ 0x080484e0 680e880408 push str._n___ ; 0x804880e ; "\n[>] "
│ ╎╎ 0x080484e5 e876feffff call sym.imp.printf ; int printf(const char *format)
│ ╎╎ 0x080484ea 83c410 add esp, 0x10
│ ╎╎ 0x080484ed 83ec08 sub esp, 8
│ ╎╎ 0x080484f0 8d45f4 lea eax, [var_ch]
│ ╎╎ 0x080484f3 50 push eax
│ ╎╎ 0x080484f4 6814880408 push 0x8048814 ; "%u"
│ ╎╎ 0x080484f9 e8a2feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ ╎╎ 0x080484fe 83c410 add esp, 0x10
│ ╎╎ 0x08048501 83f801 cmp eax, 1 ; 1
│ ┌───< 0x08048504 741a je 0x8048520
│ │╎╎ 0x08048506 83ec0c sub esp, 0xc
│ │╎╎ 0x08048509 6817880408 push str.Unknown_input_ ; 0x8048817 ; "Unknown input!"
│ │╎╎ 0x0804850e e85dfeffff call sym.imp.puts ; int puts(const char *s)
│ │╎╎ 0x08048513 83c410 add esp, 0x10
│ │╎╎ 0x08048516 b801000000 mov eax, 1
│ ┌────< 0x0804851b e97c010000 jmp 0x804869c
│ │└───> 0x08048520 8b45f4 mov eax, dword [var_ch]
│ │ ╎╎ 0x08048523 83f801 cmp eax, 1 ; 1
│ │┌───< 0x08048526 756d jne 0x8048595
│ ││╎╎ 0x08048528 83ec0c sub esp, 0xc
│ ││╎╎ 0x0804852b 6826880408 push str.What_is_your_name_ ; 0x8048826 ; "What is your name? "
│ ││╎╎ 0x08048530 e82bfeffff call sym.imp.printf ; int printf(const char *format)
│ ││╎╎ 0x08048535 83c410 add esp, 0x10
│ ││╎╎ 0x08048538 8d5588 lea edx, [var_78h]
│ ││╎╎ 0x0804853b b800000000 mov eax, 0
│ ││╎╎ 0x08048540 b919000000 mov ecx, 0x19 ; 25
│ ││╎╎ 0x08048545 89d7 mov edi, edx
│ ││╎╎ 0x08048547 f3ab rep stosd dword es:[edi], eax
│ ││╎╎ 0x08048549 83ec08 sub esp, 8
│ ││╎╎ 0x0804854c 8d4588 lea eax, [var_78h]
│ ││╎╎ 0x0804854f 50 push eax
│ ││╎╎ 0x08048550 683a880408 push str._99s ; 0x804883a ; "%99s"
│ ││╎╎ 0x08048555 e846feffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ ││╎╎ 0x0804855a 83c410 add esp, 0x10
│ ││╎╎ 0x0804855d 83f801 cmp eax, 1 ; 1
│ ┌─────< 0x08048560 741a je 0x804857c
│ │││╎╎ 0x08048562 83ec0c sub esp, 0xc
│ │││╎╎ 0x08048565 683f880408 push str.Unable_to_read_name_ ; 0x804883f ; "Unable to read name!"
│ │││╎╎ 0x0804856a e801feffff call sym.imp.puts ; int puts(const char *s)
│ │││╎╎ 0x0804856f 83c410 add esp, 0x10
│ │││╎╎ 0x08048572 b801000000 mov eax, 1
│ ┌──────< 0x08048577 e920010000 jmp 0x804869c
│ │└─────> 0x0804857c 83ec08 sub esp, 8
│ │ ││╎╎ 0x0804857f 8d4588 lea eax, [var_78h]
│ │ ││╎╎ 0x08048582 50 push eax
│ │ ││╎╎ 0x08048583 6854880408 push str.Hello___s__n ; 0x8048854 ; "Hello, %s!\n"
│ │ ││╎╎ 0x08048588 e8d3fdffff call sym.imp.printf ; int printf(const char *format)
│ │ ││╎╎ 0x0804858d 83c410 add esp, 0x10
│ │ ││└──< 0x08048590 e938ffffff jmp 0x80484cd
│ │ │└───> 0x08048595 8b45f4 mov eax, dword [var_ch]
│ │ │ ╎ 0x08048598 83f802 cmp eax, 2 ; 2
│ │ │ ┌──< 0x0804859b 0f85a7000000 jne 0x8048648
│ │ │ │╎ 0x080485a1 83ec0c sub esp, 0xc
│ │ │ │╎ 0x080485a4 6860880408 push str.Enter_first_number: ; 0x8048860 ; "Enter first number: "
│ │ │ │╎ 0x080485a9 e8b2fdffff call sym.imp.printf ; int printf(const char *format)
│ │ │ │╎ 0x080485ae 83c410 add esp, 0x10
│ │ │ │╎ 0x080485b1 83ec08 sub esp, 8
│ │ │ │╎ 0x080485b4 8d45f0 lea eax, [var_10h]
│ │ │ │╎ 0x080485b7 50 push eax
│ │ │ │╎ 0x080485b8 6875880408 push 0x8048875 ; "%d"
│ │ │ │╎ 0x080485bd e8defdffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ │ │ │╎ 0x080485c2 83c410 add esp, 0x10
│ │ │ │╎ 0x080485c5 83f801 cmp eax, 1 ; 1
│ │ │┌───< 0x080485c8 741a je 0x80485e4
│ │ │││╎ 0x080485ca 83ec0c sub esp, 0xc
│ │ │││╎ 0x080485cd 6878880408 push str.Unable_to_read_number_ ; 0x8048878 ; "Unable to read number!"
│ │ │││╎ 0x080485d2 e899fdffff call sym.imp.puts ; int puts(const char *s)
│ │ │││╎ 0x080485d7 83c410 add esp, 0x10
│ │ │││╎ 0x080485da b801000000 mov eax, 1
│ │┌─────< 0x080485df e9b8000000 jmp 0x804869c
│ │││└───> 0x080485e4 83ec0c sub esp, 0xc
│ │││ │╎ 0x080485e7 688f880408 push str.Enter_second_number: ; 0x804888f ; "Enter second number: "
│ │││ │╎ 0x080485ec e86ffdffff call sym.imp.printf ; int printf(const char *format)
│ │││ │╎ 0x080485f1 83c410 add esp, 0x10
│ │││ │╎ 0x080485f4 83ec08 sub esp, 8
│ │││ │╎ 0x080485f7 8d45ec lea eax, [var_14h]
│ │││ │╎ 0x080485fa 50 push eax
│ │││ │╎ 0x080485fb 6875880408 push 0x8048875 ; "%d"
│ │││ │╎ 0x08048600 e89bfdffff call sym.imp.__isoc99_scanf ; int scanf(const char *format)
│ │││ │╎ 0x08048605 83c410 add esp, 0x10
│ │││ │╎ 0x08048608 83f801 cmp eax, 1 ; 1
│ │││┌───< 0x0804860b 7417 je 0x8048624
│ │││││╎ 0x0804860d 83ec0c sub esp, 0xc
│ │││││╎ 0x08048610 6878880408 push str.Unable_to_read_number_ ; 0x8048878 ; "Unable to read number!"
│ │││││╎ 0x08048615 e856fdffff call sym.imp.puts ; int puts(const char *s)
│ │││││╎ 0x0804861a 83c410 add esp, 0x10
│ │││││╎ 0x0804861d b801000000 mov eax, 1
│ ┌───────< 0x08048622 eb78 jmp 0x804869c
│ ││││└───> 0x08048624 8b55f0 mov edx, dword [var_10h]
│ ││││ │╎ 0x08048627 8b45ec mov eax, dword [var_14h]
│ ││││ │╎ 0x0804862a 8d0c02 lea ecx, [edx + eax]
│ ││││ │╎ 0x0804862d 8b55ec mov edx, dword [var_14h]
│ ││││ │╎ 0x08048630 8b45f0 mov eax, dword [var_10h]
│ ││││ │╎ 0x08048633 51 push ecx
│ ││││ │╎ 0x08048634 52 push edx
│ ││││ │╎ 0x08048635 50 push eax
│ ││││ │╎ 0x08048636 68a5880408 push str._d___d___d_n ; 0x80488a5 ; "%d + %d = %d\n"
│ ││││ │╎ 0x0804863b e820fdffff call sym.imp.printf ; int printf(const char *format)
│ ││││ │╎ 0x08048640 83c410 add esp, 0x10
│ ││││ │└─< 0x08048643 e985feffff jmp 0x80484cd
│ ││││ └──> 0x08048648 8b45f4 mov eax, dword [var_ch]
│ ││││ 0x0804864b 83f803 cmp eax, 3 ; 3
│ ││││ ┌─< 0x0804864e 7512 jne 0x8048662
│ ││││ │ 0x08048650 83ec0c sub esp, 0xc
│ ││││ │ 0x08048653 68b3880408 push str.Goodbye_ ; 0x80488b3 ; "Goodbye!"
│ ││││ │ 0x08048658 e813fdffff call sym.imp.puts ; int puts(const char *s)
│ ││││ │ 0x0804865d 83c410 add esp, 0x10
│ ││││ ┌──< 0x08048660 eb35 jmp 0x8048697
│ ││││ │└─> 0x08048662 8b45f4 mov eax, dword [var_ch]
│ ││││ │ 0x08048665 3d697a0000 cmp eax, 0x7a69 ; 'iz'
│ ││││ │┌─< 0x0804866a 7517 jne 0x8048683
│ ││││ ││ 0x0804866c 83ec0c sub esp, 0xc
│ ││││ ││ 0x0804866f 68bc880408 push str.Wow_such_h4x0r_ ; 0x80488bc ; "Wow such h4x0r!"
│ ││││ ││ 0x08048674 e8f7fcffff call sym.imp.puts ; int puts(const char *s)
│ ││││ ││ 0x08048679 83c410 add esp, 0x10
│ ││││ ││ 0x0804867c e825000000 call sym.giveFlag
│ ││││┌───< 0x08048681 eb14 jmp 0x8048697
│ ││││││└─> 0x08048683 8b45f4 mov eax, dword [var_ch]
│ ││││││ 0x08048686 83ec08 sub esp, 8
│ ││││││ 0x08048689 50 push eax
│ ││││││ 0x0804868a 68cc880408 push str.Unknown_choice:__d_n ; 0x80488cc ; "Unknown choice: %d\n"
│ ││││││ 0x0804868f e8ccfcffff call sym.imp.printf ; int printf(const char *format)
│ ││││││ 0x08048694 83c410 add esp, 0x10
│ ││││││ ; CODE XREFS from main @ 0x8048660(x), 0x8048681(x)
│ ││││└└──> 0x08048697 b800000000 mov eax, 0
│ ││││ ; CODE XREFS from main @ 0x804851b(x), 0x8048577(x), 0x80485df(x), 0x8048622(x)
│ └└└└────> 0x0804869c 8d65f8 lea esp, [var_8h]
│ 0x0804869f 59 pop ecx
│ 0x080486a0 5f pop edi
│ 0x080486a1 5d pop ebp
│ 0x080486a2 8d61fc lea esp, [ecx - 4]
└ 0x080486a5 c3 ret

The interesting part is where the string "Wow such h4x0r" is called. This string is called after eax is compared to 0x7a69. If we decode it to its ASCII equivalent and try to use it as input it displays the message "Unknown input!".

Menu:
[1] Say hello
[2] Add numbers
[3] Quit
[>] iz
Unknown input!

But if we decode it to decimal and use it as input, it reveals the flag.

string = '7a69'
dec = int(string, 16)
print(dec) # 31337

We start by making the binary executable and running it:

chmod +x crackme7
./crackme7

The program presents a menu with three options: "Say hello," "Add numbers," and "Quit." It prompts for a choice and executes the corresponding action.

Menu:
[1] Say hello
[2] Add numbers
[3] Quit
[>] 1
What is your name? c
Hello, c!
Menu:
[1] Say hello
[2] Add numbers
[3] Quit
[>] 2
Enter first number: 23
Enter second number: 3
23 + 3 = 26
Menu:
[1] Say hello
[2] Add numbers
[3] Quit
[>] 3
Goodbye!

We load the binary into Radare2 and disassemble the main function:

r2 -d crackme7
pdf @main

The disassembled main function (partially shown for brevity, with added comments for clarity):

┌ 491: int main (char **argv);
│ ; ... (setup code) ...
│ ; CODE XREFS from main @ 0x8048590(x), 0x8048643(x)
│ ┌┌─> 0x080484cd 83ec0c sub esp, 0xc
│ ╎╎ 0x080484d0 68e0870408 push str.Menu:_n_n_1__Say_hello_n_2__Add_numbers_n_3__Quit ; Prints the menu
│ ╎╎ ; ...
│ ╎╎ 0x080484e0 680e880408 push str._n___ ; Prompts for input "[>] "
│ ╎╎ ; ...
│ ╎╎ 0x080484f4 6814880408 push 0x8048814 ; "%u" ; Format string for unsigned integer input
│ ╎╎ 0x080484f9 e8a2feffff call sym.imp.__isoc99_scanf ; Reads the menu choice
│ ╎╎ ; ...
│ ╎╎ 0x08048501 83f801 cmp eax, 1 ; Check if scanf was successful
│ ┌───< 0x08048504 741a je 0x8048520 ; If successful, proceed
│ │╎╎ ; ... (handles "Unknown input!" if scanf fails) ...
│ │└───> 0x08048520 8b45f4 mov eax, dword [var_ch] ; Load the user's choice into eax
│ │ ╎╎ 0x08048523 83f801 cmp eax, 1 ; Check if choice is 1 (Say hello)
│ │┌───< 0x08048526 756d jne 0x8048595 ; If not 1, jump to handle other choices
│ ││╎╎ ; ... (handles "Say hello" option) ...
│ │ ││└──< 0x08048590 e938ffffff jmp 0x80484cd ; Jump back to the menu loop
│ │ │└───> 0x08048595 8b45f4 mov eax, dword [var_ch] ; Load user's choice again
│ │ │ ╎ 0x08048598 83f802 cmp eax, 2 ; Check if choice is 2 (Add numbers)
│ │ │ ┌──< 0x0804859b 0f85a7000000 jne 0x8048648 ; If not 2, jump to handle other choices
│ │ │ │╎ ; ... (handles "Add numbers" option) ...
│ ││││ │└─< 0x08048643 e985feffff jmp 0x80484cd ; Jump back to the menu loop
│ ││││ └──> 0x08048648 8b45f4 mov eax, dword [var_ch] ; Load user's choice
│ ││││ 0x0804864b 83f803 cmp eax, 3 ; Check if choice is 3 (Quit)
│ ││││ ┌─< 0x0804864e 7512 jne 0x8048662 ; If not 3, jump to handle other choices
│ ││││ │ ; ... (handles "Goodbye!" option) ...
│ ││││ ┌──< 0x08048660 eb35 jmp 0x8048697 ; Exit the program
│ ││││ │└─> 0x08048662 8b45f4 mov eax, dword [var_ch] ; Load user's choice
│ ││││ │ 0x08048665 3d697a0000 cmp eax, 0x7a69 ; **HIDDEN CHECK!**
│ ││││ │┌─< 0x0804866a 7517 jne 0x8048683 ; If not equal, jump to "Unknown choice"
│ ││││ ││ 0x0804866c 83ec0c sub esp, 0xc
│ ││││ ││ 0x0804866f 68bc880408 push str.Wow_such_h4x0r_ ; Push "Wow such h4x0r!"
│ ││││ ││ 0x08048674 e8f7fcffff call sym.imp.puts ; Print the message
│ ││││ ││ 0x0804867c e825000000 call sym.giveFlag ; **CALL GIVEFLAG!**
│ ││││┌───< 0x08048681 eb14 jmp 0x8048697 ; Exit the program
│ ││││││└─> 0x08048683 8b45f4 mov eax, dword [var_ch] ; Load user's choice
│ ││││││ ; ... (handles "Unknown choice: %d\n") ...
│ ; ... (cleanup code) ...
└ 0x080486a5 c3 ret

The crucial part of the code is:

│ ││││ │    0x08048665      3d697a0000     cmp eax, 0x7a69             ; **HIDDEN CHECK!**
│ ││││ │┌─< 0x0804866a 7517 jne 0x8048683 ; If not equal, jump to "Unknown choice"
│ ││││ ││ ; ... (calls sym.giveFlag if equal) ...

This code compares the user's input (stored in eax) with the hexadecimal value 0x7a69. If they are equal, the program calls sym.giveFlag, which likely prints the flag. If they are not equal, it prints "Unknown choice." The key is that the input is read as an unsigned integer (%u in scanf).

The program compares the decimal equivalent of 0x7a69 with the user input. It's essential to understand this. If you enter "7a69" (the hex string), scanf with %u will not interpret it as hexadecimal.

  1. Convert Hex to Decimal: We need to convert the hexadecimal value 0x7a69 to its decimal equivalent. We can use a Python one-liner or any hex-to-decimal converter:

    print(int("7a69", 16))  # Output: 31337
  2. Provide the Decimal Input: Run the program and enter the decimal value 31337 when prompted for the menu choice:

    ./crackme7
    Menu:
    [1] Say hello
    [2] Add numbers
    [3] Quit
    [>] 31337
    Wow such h4x0r!
    # ... (the flag will be printed here) ...

Crackme8

chmod +x crackme8
./crackme8 test # Access denied.
r2 -d crackme8
aaa
pdf @main
            ; DATA XREF from entry0 @ 0x80483b7(w)
┌ 137: int main (char **argv);
│ `- args(sp[0x4..0x4]) vars(1:sp[0xc..0xc])
│ 0x0804849b 8d4c2404 lea ecx, [argv]
│ 0x0804849f 83e4f0 and esp, 0xfffffff0
│ 0x080484a2 ff71fc push dword [ecx - 4]
│ 0x080484a5 55 push ebp
│ 0x080484a6 89e5 mov ebp, esp
│ 0x080484a8 51 push ecx
│ 0x080484a9 83ec04 sub esp, 4
│ 0x080484ac 89c8 mov eax, ecx
│ 0x080484ae 833802 cmp dword [eax], 2
│ ┌─< 0x080484b1 741d je 0x80484d0
│ │ 0x080484b3 8b4004 mov eax, dword [eax + 4]
│ │ 0x080484b6 8b00 mov eax, dword [eax]
│ │ 0x080484b8 83ec08 sub esp, 8
│ │ 0x080484bb 50 push eax
│ │ 0x080484bc 6860860408 push str.Usage:__s_password_n ; 0x8048660 ; "Usage: %s password\n"
│ │ 0x080484c1 e87afeffff call sym.imp.printf ; int printf(const char *format)
│ │ 0x080484c6 83c410 add esp, 0x10
│ │ 0x080484c9 b801000000 mov eax, 1
│ ┌──< 0x080484ce eb4c jmp 0x804851c
│ │└─> 0x080484d0 8b4004 mov eax, dword [eax + 4]
│ │ 0x080484d3 83c004 add eax, 4
│ │ 0x080484d6 8b00 mov eax, dword [eax]
│ │ 0x080484d8 83ec0c sub esp, 0xc
│ │ 0x080484db 50 push eax
│ │ 0x080484dc e89ffeffff call sym.imp.atoi ; int atoi(const char *str)
│ │ 0x080484e1 83c410 add esp, 0x10
│ │ 0x080484e4 3d0df0feca cmp eax, 0xcafef00d
│ │┌─< 0x080484e9 7417 je 0x8048502
│ ││ 0x080484eb 83ec0c sub esp, 0xc
│ ││ 0x080484ee 6874860408 push str.Access_denied. ; 0x8048674 ; "Access denied."
│ ││ 0x080484f3 e858feffff call sym.imp.puts ; int puts(const char *s)
│ ││ 0x080484f8 83c410 add esp, 0x10
│ ││ 0x080484fb b801000000 mov eax, 1
│ ┌───< 0x08048500 eb1a jmp 0x804851c
│ ││└─> 0x08048502 83ec0c sub esp, 0xc
│ ││ 0x08048505 6883860408 push str.Access_granted. ; 0x8048683 ; "Access granted."
│ ││ 0x0804850a e841feffff call sym.imp.puts ; int puts(const char *s)
│ ││ 0x0804850f 83c410 add esp, 0x10
│ ││ 0x08048512 e80d000000 call sym.giveFlag
│ ││ 0x08048517 b800000000 mov eax, 0
│ ││ ; CODE XREFS from main @ 0x80484ce(x), 0x8048500(x)
│ └└──> 0x0804851c 8b4dfc mov ecx, dword [var_4h]
│ 0x0804851f c9 leave
│ 0x08048520 8d61fc lea esp, [ecx - 4]
└ 0x08048523 c3 ret

Here we see two interesting instructions. The first one is the call of the atoi function. A quick look at atoi's man page shows us that this function will convert our input string to an integer.

The other instruction is the compare of eax with the cafef00d. If the eax register is equal to this hexadecimal value, the function will print the "Access granted" message that we are looking for. If we try to convert the string to its normal decimal value we have 3405705229, but once we use it as input we get the "Access denied message". Converting this hex value to binary shows that its most significant bit is 1, so the value represented is actually negative. Using an online converter we can get this negative value, which is the decimal from signed 2's complement.

Using it as input gives us the flag.

1. Initial Analysis

Make the binary executable and run it with an arbitrary argument:

chmod +x crackme8
./crackme8 test

The output shows "Access denied." This suggests the program checks the provided argument.

2. Disassembling main

Load the binary into Radare2 and disassemble the main function:

r2 -d crackme8
aaa
pdf @main

The disassembled main function:

            ; DATA XREF from entry0 @ 0x80483b7(w)
┌ 137: int main (char **argv);
│ `- args(sp[0x4..0x4]) vars(1:sp[0xc..0xc])
│ 0x0804849b 8d4c2404 lea ecx, [argv]
│ 0x0804849f 83e4f0 and esp, 0xfffffff0
│ 0x080484a2 ff71fc push dword [ecx - 4]
│ 0x080484a5 55 push ebp
│ 0x080484a6 89e5 mov ebp, esp
│ 0x080484a8 51 push ecx
│ 0x080484a9 83ec04 sub esp, 4
│ 0x080484ac 89c8 mov eax, ecx
│ 0x080484ae 833802 cmp dword [eax], 2 ; Check argc (number of arguments)
│ ┌─< 0x080484b1 741d je 0x80484d0 ; Jump if argc == 2
│ │ ; ... (Prints "Usage: %s password\n" if argc != 2) ...
│ │└─> 0x080484d0 8b4004 mov eax, dword [eax + 4] ; eax = argv[1] (address of the password string)
│ │ 0x080484d3 83c004 add eax, 4 ; no need to add 4
│ │ 0x080484d6 8b00 mov eax, dword [eax] ; eax = argv[1]
│ │ 0x080484d8 83ec0c sub esp, 0xc
│ │ 0x080484db 50 push eax ; Push the password string onto the stack
│ │ 0x080484dc e89ffeffff call sym.imp.atoi ; Call atoi to convert the string to an integer
│ │ 0x080484e1 83c410 add esp, 0x10
│ │ 0x080484e4 3d0df0feca cmp eax, 0xcafef00d ; Compare the result with 0xcafef00d
│ │┌─< 0x080484e9 7417 je 0x8048502 ; Jump if equal (access granted)
│ ││ ; ... (Prints "Access denied." if not equal) ...
│ ││└─> 0x08048502 83ec0c sub esp, 0xc
│ ││ 0x08048505 6883860408 push str.Access_granted. ; Push "Access granted."
│ ││ 0x0804850a e841feffff call sym.imp.puts ; Print the message
│ ││ 0x0804850f 83c410 add esp, 0x10
│ ││ 0x08048512 e80d000000 call sym.giveFlag ; Call giveFlag
│ ││ ; ... (rest of the function) ...
└ 0x08048523 c3 ret

3. Key Instructions and atoi

The critical parts of the code are:

  • call sym.imp.atoi: This calls the atoi function. atoi (ASCII to Integer) converts a string representation of a number into its integer equivalent. It stops reading the string at the first character it cannot recognize as part of a number. Importantly, atoi can handle leading + or - signs.
  • cmp eax, 0xcafef00d: This compares the integer returned by atoi (now in the eax register) with the hexadecimal value 0xcafef00d.

4. The Two's Complement Trick

The challenge lies in understanding how atoi will handle the large hexadecimal value 0xcafef00d and how integer comparisons work. Here's the breakdown:

  1. Decimal Conversion (Incorrect Approach): If you directly convert 0xcafef00d to decimal, you get 3405705229. However, if you try to use 3405705229 as input, you'll get "Access denied."

  2. Two's Complement Representation: The hexadecimal value 0xcafef00d represents a negative number in two's complement representation (which is how signed integers are typically stored in computers). The most significant bit (the leftmost bit) is a '1', indicating a negative value.

  3. Finding the Negative Value: To find the actual decimal value, we need to consider the two's complement. There are a few ways to do this:

    • Using Python: Python can handle this conversion directly:

      print(int.from_bytes(bytes.fromhex('cafef00d'), byteorder='little', signed=True))
      # Output: -889262067

      # Or even more directly
      print(int('0xcafef00d', 16) - 2**32) # Subtract 2^32 to get into the signed range.

      We use little endian because x86 architecture uses this format, so, in the memory, the hex value 0xcafef00d is stored as 0d f0 fe ca.

    • Manual Two's Complement Conversion:

      1. Invert the bits: 0xcafef00d becomes 0x35010ff2.
      2. Add 1: 0x35010ff2 + 1 = 0x35010ff3.
      3. Convert to Decimal: 0x35010ff3 is 889262067. Since we started with a negative two's complement number, the final result is -889262067.
  4. atoi's Behavior: The atoi function will correctly convert the string "-889262067" to the integer -889262067. It recognizes the leading minus sign.

5. The Solution

The correct input is the decimal representation of 0xcafef00d as a signed integer: -889262067.

./crackme8 -889262067

This will print "Access granted." followed by the flag.