Crackme 14: geyslan’s crackme.02.32
Link: http://crackmes.cf/users/geyslan/crackme.02.32/ (binary)
$ ./crackme.02.32
Please tell me my password: 1234
No! No! No! No! Try again.
Hopper finds no strings, no symbols. strings
tells us that there are probably some protections in place:
$ strings ./crackme.02.32
[...]
I'm sorry GDB! You are not allowed!
Tracing is not allowed... Bye!
Please tell me my password:
The password is correct!
Congratulations!!!
No! No! No! No! Try again.
The strings are mostly around 0x08048858.
GDB does not recognize the executable, which seems to be corrupted:
$ file ./crackme.02.32
crackme.02.32: ELF 32-bit LSB executable, Intel 80386, (SYSV), too many section (65535)
$ gdb ./crackme.02.32
[...]
"./crackme.02.32": not in executable format: File format not recognized
Hopper shows a reference in EntryPoint to 0x80486fd, which is recognized as data but that we can switch to code by pressing ‘c’. Immediately, we see references to “Please tell me my password: “, “The password is correct!\nCongratulations!!!\n”, and “No! No! No! No! Try again.\n”. The code at 0x08048768 performs the comparison:
08048768 test eax, eax
0804876a jne EntryPoint+787
If we were to trivially patch this test by replacing test eax, eax
with xor eax, eax
, it would defeat it. Just like in crackme 13, we can replace 0x85 0xC0 with 0x31 0xC0 and save it as a new binary.
$ ./crackme.02.xortest
Please tell me my password: hello
The password is correct!
Congratulations!!!
That was a bit too easy, so let’s dig into the binary and see what it’s actually doing.
Let’s start with renaming 0x8048410 as printf
since it’s called with string parameters:
0804871f mov dword [esp], aPleaseTellMeMy ; "Please tell me my password: "
08048726 call 0x8048410
[...]
08048785 mov dword [esp], aThePasswordIsC ; "The password is correct!\\nCongratulations!!!\\n"
0804878c call 0x8048410
[...]
080487ac mov dword [esp], aNoNoNoNoTryAga ; "No! No! No! No! Try again.\\n"
080487b3 call 0x8048410
Let’s also try to fix the number of sections, with the hope that GDB will load the file. Wikipedia has a description of the ELF header, listing e_shentsize
(the size of the section entry) at offset 0x2E. We can observe that the 2 bytes at that offset are 0xFF 0xFF, matching our 65535 sections from file
. readelf
confirms the corrupt header:
$ readelf -a crackme.02.header
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0xffffffff
Entry point address: 0x8048480
Start of program headers: 52 (bytes into file)
Start of section headers: 4294967295 (bytes into file)
Flags: 0x0
Size of this header: 65535 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 8
Size of section headers: 10240 (bytes)
Number of section headers: 10496
Section header string table index: 65535 <corrupt: out of range>
readelf: Warning: The e_shentsize field in the ELF header is larger than the size of an ELF section header
readelf: Error: Reading 0x6680000 bytes extends past end of file for section headers
readelf: Error: Section headers are not available!
“Size of this header” refers to e_ehsize
at offset 0x28, it’s usually 52 for a 32-bit executable. We can replace the two bytes there with 0x34 0x00.
Next, section headers. They should be 0x28 bytes so we can replace the 0xFF 0xFF at offset 0x2E with 0x28 0x00. Let’s also reset e_shoff
to zero, as well as e_shnum
and e_shstrndx
. readelf
now reports a clean header (even if we might be missing a couple of things) when we save a new binary:
$ readelf -a ./crackme.02.header
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Intel 80386
Version: 0xffffffff
Entry point address: 0x8048480
Start of program headers: 52 (bytes into file)
Start of section headers: 0 (bytes into file)
Flags: 0x0
Size of this header: 52 (bytes)
Size of program headers: 32 (bytes)
Number of program headers: 8
Size of section headers: 40 (bytes)
Number of section headers: 0
Section header string table index: 0
There are no sections in this file.
There are no sections to group in this file.
GDB loads it too:
$ gdb ./crackme.02.header
[...]
gdb$ r
Starting program: ./crackme.02.header
Tracing is not allowed... Bye!
[Inferior 1 (process 106) exited with code 01]
Interestingly, strace
shows a call to ptrace(PTRACE_TRACEME)
which returns -1 EPERM (Operation not permitted) before the program exist. We’ll need to patch this if we want to debug the program.
The tracing error message is referenced from 0x08048613:
08048613 test eax, eax
08048615 jns EntryPoint+431
08048617 mov dword [esp], aTracingIsNotAl ; "Tracing is not allowed... Bye!"
0804861e call printf+16
We can patch it and XOR eax before the JNS as in earlier crackmes, saving a new binary.
After reading the password, esp
+0x15 contains our input (ending with a new line). A static buffer is also loaded before calling a procedure:
08048754 mov dword [esp+4], 0x8049bb8
0804875c lea eax, dword [esp+0x15]
08048760 mov dword [esp], eax
08048763 call EntryPoint+554
0x8049bb8 contains: [0xf7, 0xf8, 0xf1, 0xf4, 0xf1, 0xf8, 0xb3, 0xfc, 0xfc, 0x00]. This is not a readable string.
The loop that follows goes over our input and calls 0x08048631 on each character before comparing the values to the array above. The code is slightly obfuscated with a few bytes inserted throughout the code, likely to confuse tools and prevent disassembly:
08048631 push ebp
08048632 mov ebp, esp
08048634 sub esp, 0x4
08048637 mov eax, dword [ebp+8]
0804863a mov byte [ebp-4], al
0804863d movsx eax, byte [ebp-4]
08048641 mov dword [dword_8049c08], eax
08048646 jmp EntryPoint+458
08048648 db 0xc9 ; '.' ; garbage
08048649 db 0x35 ; '5' ; garbage
0804864a push edx
0804864b mov edx, dword [dword_8049c08]
08048651 jmp EntryPoint+469
08048653 db 0x08 ; '.' ; garbage
08048654 db 0x5a ; 'Z' ; garbage
08048655 or edx, 0x90 ; OR 0x90 on each byte
0804865b mov dword [dword_8049c08], edx
08048661 pop edx
08048662 mov eax, dword [dword_8049c08]
08048667 leave
We can reverse the OR by doing an AND ~0x90 on the array found above:
>>> ''.join(map(lambda c: chr(c & ~0x90), [0xf7, 0xf8, 0xf1, 0xf4, 0xf1, 0xf8, 0xb3, 0xfc, 0xfc]))
'ghadah#ll'
Let’s try it out:
$ ./crackme.02.32
Please tell me my password: ghadah#ll
The password is correct!
Congratulations!!!