Crackme 14: geyslan’s crackme.02.32
Link: http://crackmes.cf/users/geyslan/crackme.02.32/ (now dead, see archive alt.) (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!!!