To learn more about reverse engineering and how systems work I started doing the pwnable.kr challenges. I’ve decided to also write everything down as a sort of journal and notes for myself. As this is the learning technique that best suits me.
The first challenge is the fd challenge in the category “toddler’s bottle”. When following the instructions (connect to a host through ssh) you’ll be dropped in a /home/ folder where three files reside in.
fd@pwnable:~$ ls -ls
total 16
8 -r-sr-x--- 1 fd_pwn fd 7322 Jun 11 2014 fd
4 -rw-r--r-- 1 root root 418 Jun 11 2014 fd.c
4 -r--r----- 1 fd_pwn root 50 Jun 11 2014 flag
We’ve got fd, which is an executable by our user, fd.c which is readable by all and flag which we can’t read. The interesting permission here is the s flag on fd which means that other users can run this file with the permissions of the owner of the file. So the owner of fd is fd_pwn and the flag file also has the owner of fd_pwn. We can use this file to have the read rights on the flag file.
Now, they have included the source code of fd which makes it quite simple to solve this. My mistake was looking at it which kinda beats the purpose of my “reverse engineering” goal I’ve setup on myself. In this post I won’t go over the source code file. I will go over the disassembled code and try to understand what fd does with Ghidra.
Let’s talk about the obvious stuff, it is an ELF file that is compiled through gcc. The exported main function does not call any other function except some library functions so all code we need seems to be in the main section.
Let us first edit the function signature to “comply” with a typical C program. Ghidra analyses our main function as
int param_1, int param_2) undefined main(
For C main function the started is as following
int main(int argc, const char* argv[])
Now, const variables is not something assembly really understands. It can place a const variable in the read-only data section (.rodata for example) but that is not always the case as in a parameter list there is a high possibility that you don’t know what the const will be until execution. Const in a function just means that that function is not allowed to edit the value. Your compiler will complain when you edit the const variable but in assembly it is the wild wild west!
When we edit our function (Right-Click -> Edit Function or Shortcut F) we make the line look like
int main(int argc, char** argv)
This is almost identical to what C describes but Ghidra will understand this.
Now, we could use the de-compiled tab and easily see what is happening but to make it a bit harder we are only going to look through the dissembled code for now. Remember to comment everything you see so it becomes like “reading a book”. For now I even comment the very easy stuff just to get the hang of it.
First we have our typical function prologue
08048494 55 PUSH EBP
08048495 89 e5 MOV EBP,ESP
08048497 57 PUSH EDI
08048498 56 PUSH ESI
08048499 83 e4 f0 AND ESP,0xfffffff0
83 ec 20 SUB ESP,0x20 0804849c
The prologue is almost always the same idea, convey to some calling conventions and initialize the stack. It should be something to be seen diagonally.
Then we see the first lines of code.
CMP dword ptr [EBP + argc],0x1
0804849f JG LAB_080484bb
080484a3 MOV dword ptr [ESP]=>local_30,s_pass_argv[1]_a_num = "pass argv[1] a number"
080484a5 CALL puts
080484ac MOV EAX,0x0
080484b1 JMP LAB_08048559 080484b6
So this part of the code just checks if you have passed enough arguments to the program and if not, prints out an “error message” and returns the function (when main returns, it will exit). Let us go to the next part
MOV EAX,dword ptr [EBP + argv]
080484bb ADD EAX,0x4
080484be MOV EAX,dword ptr [EAX]
080484c1 MOV dword ptr [ESP]=>local_30,EAX
080484c3 CALL atoi
080484c6 SUB EAX,0x1234
080484cb MOV dword ptr [ESP + local_18],EAX
080484d0 MOV dword ptr [ESP + local_14],0x0
080484d4 MOV dword ptr [ESP + local_28],0x20
080484dc 080484e4 MOV dword ptr [ESP + local_2c],buf
MOV EAX,dword ptr [ESP + local_18]
080484ec MOV dword ptr [ESP]=>local_30,EAX
080484f0 CALL read 080484f3
So here is the first part we need to solve to continue in our program. Read is using our first argument we pass to know which file descriptor we must listen to. A file descriptor is a handle used to access a file or other I/O resources (pipes are a common file descriptor in Linux). You probably already know some of them like 0 is standard input, 1 is standard output and 2 is standard error. We noted that our argument will be subtracted by 0x1234 so if we want to say “read standard input” we must pass 0x1234 as argument. Now remember, some lines above the read call an atoi call happened. This means that an integer is expected. We convert the hex 0x1234 to decimal 4660 and we can use that as argument to pass the first hurdle.
MOV dword ptr [ESP + local_14],EAX
080484f8 MOV EDX,s_LETMEWIN_08048646 = "LETMEWIN\n"
080484fc 08048501 MOV EAX,buf = ??
08048506 MOV ECX,0xa
MOV ESI,EDX
0804850b MOV EDI,EAX
0804850d CMPSB.REPE ES:EDI=>buf,ESI=>s_LETMEWIN_08048646 0804850f
Following lines actually show us what the secret password is. You don’t really need to use some sophisticated thinking logic to see that from the data section of the program the string LETMEWIN is used. But for learning sake we will continue at looking what the program does
08048511 SETA DL
08048514 SETC AL
08048517 MOV ECX,EDX
08048519 SUB CL,AL
MOV EAX,ECX
0804851b MOVSX EAX,AL
0804851d 08048520 TEST EAX,EAX
08048522 JNZ LAB_08048548
08048524 MOV dword ptr [ESP]=>local_30,s_good_job_:)_08048650 = "good job :)"
CALL puts
0804852b 08048530 MOV dword ptr [ESP]=>local_30,s_/bin/cat_flag_0804 = "/bin/cat flag"
08048537 CALL system
0804853 MOV dword ptr [ESP]=>local_30,0x0
08048543 CALL exit
Alright, the last part which actually shows the interesting stuff. If our strings were equal (see above notes) than
I’m gonna skip the rest of the program as it is nothing more than the function epilogue with some extra text, but this is how you could analyze and find out how this program works without any source code available. Next one I won’t touch the source code like I foolishly did for this one.
A fun comparison between Ghidra generated C code and the real C code
Ghidra | Source |
---|---|
|
|