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
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
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]
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!!!