Writeup for CrackIt task at Google CodeIn 2019
easy, you can use
strings 1stcrackme to list all strings in binary
Enter password: FEDORAGCIPASSEASY Success! Error! Wrong password!
Because the password should be around the prompt and the so we could try
FEDORAGCIPASSEASY because it looks like a password
(my intuition, please don’t ask)
And it works
[user@archlinux Crackit-GCI]$ ./1stcrackme Enter password: FEDORAGCIPASSEASY Success! Enter password:
When I enter the string again, It didn’t work. At this point I look the
strings output again and then I saw 2 more suspicious strings
Yeah, Who doesn’t want to be l33t ;)
So I opened it in Ghidra to checkout the whole logic.
Look at the
s2 parameter of
strcmp call that compares our input with that hardcoded password,
we solved all 3 password challenges of this program.
[user@archlinux Crackit-GCI]$ ./1stcrackme Enter password: FEDORAGCIPASSEASY Success! Enter password: 0x1337 Success! Enter password: 0x133337 Success!
[user@archlinux Crackit-GCI]$ ./2ndcrackme usage: ./2ndcrackme <password>
okok, so the binary requires us to put the password on the CLI paramter. I put a random string there.
[user@archlinux Crackit-GCI]$ ./2ndcrackme a Error! Wrong Password!
strings command output doesn’t show any suspicious strings like before, so I use
Here is the output
[user@archlinux Crackit-GCI]$ ltrace ./2ndcrackme a strcmp("a", "FEd0raGCIt@sk") = 27 puts("Error! Wrong Password!"Error! Wrong Password! ) = 23 +++ exited (status 0) +++
We can see that the program does
strcmp our supplied password with another string
that seems to be hardcoded in a way that it doesn’t show up in
[user@archlinux Crackit-GCI]$ ./2ndcrackme FEd0raGCIt@sk Success!
Okay, I open the binary in GHIDRA
gets our password from
stdin and then
memcmp with a pointer(
&local_28) and the size is 0x16
There’re 5 integers which is intialized with 5 hexadecimal integers
By intuition, I immediately realized that those hexs are actually ASCII character.
Because Ghidra doesn’t know the types of those variable, It splits them into 5 integers.
The integers are named after their locations on the stack (offset from stack base).
Also, because characters are represented in memory just like integers, it’s just the way we understand it matters.
Knowning that, we convert those integers to characters.
Don’t forget our architecture is Little endian so we have to reverse the byte order…
Anyway, I checkout the assembly code of that pseudo code.
001011af 48 b8 30 MOV RAX,"00g61@k0" 30 67 36 31 40 6b 30 001011b9 48 ba 30 MOV RDX,"0land151" 6c 61 6e 64 31 35 31 001011c3 48 89 45 e0 MOV qword ptr [RBP + local_28],RAX 001011c7 48 89 55 e8 MOV qword ptr [RBP + local_20],RDX 001011cb c7 45 f0 MOV dword ptr [RBP + local_18],"4cel" 34 63 65 6c 001011d2 66 c7 45 MOV word ptr [RBP + local_14],'!' f4 21 00
and let Ghidra do the conversation for me.
So in the program memory, It should be
(plus is understood as string concatenation)
memcmp get the pointer to the first character in the
Knowing the value that our password is going to be compare with, we solved the problem.
[user@archlinux Crackit-GCI]$ ./3rdcrackme Enter the password! 00g61@k00land1514cel! Checking password... Successfully logged in! Good job!
Wait, there’s more.
The program used
gets to read a string to the stack without size limitations.
This exposes the program to a buffer-over-flow vulnerbility.
We can overwrite those variables, including the correct password.
Here is a better pseudo code to look at.
At here, you can see the intialized correct password.
By the way, strcpy might have been optimized so you won’t see it in the binary.
The input is located on top of the password. Which means that on the right of the
input is the intialized
Low------------------->High |input(32bytes)|password(28 bytes)|v6(4 bytes)|sp(8 bytes)|ip(8 bytes)|
So we can overwrite the password and make the input identical with the
[user@archlinux Crackit-GCI]$ ./3rdcrackme Enter the password! AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Checking password... Successfully logged in! Good job!
Not so fast, Until I checkout the function list, I found a function named
secret which seems to be our real destination.
The buffer-over-flow vulnerbility here is so powerful that it can drive us into any functions.
P/s: The memory representation up there still applied here.
IP = Instruction Pointer. It is saved there so we can return to our previous function. But if we overwrite it, we can go anywhere we want!
P/s 2: For the next part, I’m gonna disable Linux’s ASLR:
Run this command from root shell.
# echo 0 > /proc/sys/kernel/randomize_va_space
So if we overwrite
secret’s address, we can get there.
secret is at 0x555555555175, we convert that address to bytes then put it at the
ip, which is at 66 bytes from the start of our
Oh, by the way, If
exit(0) is called,
ip won’t be used as
exit use a short-circuit path to exit the program.
So you will need the password to be incorrect to reach there.
Because the comparison result(
v6) is also saved on the stack,
we need to overwrite it with 4 zero bytes (because it is an integer) to force it to be zero.
If you overwrite it with non-zero bytes, it will
exit(0) and we can’t get
Here is the command to prepare the input and pipe it in to the program.
$ python -c "print('\x41'*0x20+'\x42'*0x1c+'\x00'*4+'\xde\xad\xbe\xef'+'\x75\x51\x55\x55\x55\x55')" \ | ./3rdcrackme Enter the password! Checking password... Login failed! You found the secret function! Congrats! The password is: FEDORAPASSWORDGCI!
Thanks for reading!