Protostar Stack 7 Walkthrough

I’ve been meaning to level up my understanding of binary exploitation techniques for a while, and started doing Protostar challenges from Exploit Exercises (https://exploit-exercises.com/protostar/). This is a walkthrough of the last challenge in the Stack series — Stack 7 (https://exploit-exercises.com/protostar/stack7/).

A couple notes before we start. I am writing this mainly for myself, so I can come back to these notes and find everything in one place — techniques I used, commands etc. Also, I obviously did not invent any of the methods described here.

You will need a very basic understanding of how CPU works, registers, and assembly. I wrote this walkthrough in a step-by-step fashion and encourage you to follow along.

Stack 7 Challenge

Here’s the source code for the Protostar Stack 7 challenge:

Machine image can be found here: https://exploit-exercises.com/download/. The binary is located in /opt/protostar/bin/stack7 and is SUID root. The objective is to exploit the binary to gain root shell on the system.

Stack Layout

When getpath() is called from main, the following is the stack layout:

  • In main, when the call instruction is executed return address is pushed to the top of the stack. If getpath() had any arguments, they would be placed onto the stack as well before the return address.
  • In getpath(), the function preamble would push the value of EBP (stack base pointer register) to the stack, and move ESP (stack pointer) up, to a lower memory address, to make room for function’s local variables, in this case buffer and ret.

If we write enough data to the buffer, we can overwrite the return address with an address of our choosing and hijack the code execution flow when ret is executed in getpath(). In this challenge, there is an additional condition that when we overwrite the return address, it cannot start with 0xb, otherwise the program will exit (before returning from getpath).

Taming the GDB

Let’s open the file in gdb:

break getpath sets a breakpoint at the start of getpath(), run will start the program in the debugger. info registers shows state of the CPU registers. I use set disassembly-flavor intel (more appealing visually) and disas getpath to disassemble getpath().

x command is used to display values in the program memory.x/i $eip will display the current CPU instruction (EIP, or Index Pointer, register contains the memory address where the next instruction to be executed is located), / is used as the modifier to tell x how you want the data to be interpreted, here’s some useful options:

  • i — CPU instruction
  • s — C string (NULL terminated)
  • x — integer as hex

x/80x $esp will print 80 int (4 byte) values from the top of the stack — the number after the forward slash indicates how many items to display. In the beginning of getpath we see that 0x68 is subtracted from ESP to make room for local variables, so we can find where saved EBP and return address are located — highlighted in bold in the listing above.

As we continue analyzing how the program works, it would be useful to always have the registers, current instruction and the stack displayed on the screen. I would usually install PEDA (Python Exploit Development Assistance for GDB) which will do it for me, but couldn’t get it to work here so let’s configure GDB to execute the commands above every time a breakpoint is reached:

Confirming the Overflow

Let’s set a breakpoint right after the call to gets, continue execution of the program using c (short for continue), enter a long string of characters when asked, and examine the stack:

The return address is overwritten with 0x55555555. When the execution is continued we see that the program attempted to jump to 0x555555 (check EIP value below), which caused a segmentation fault (SIGSEGV) as it is not a valid memory address:

0x55 is ASCII code for ‘U’, and the the first part of the exploit is clear now — we’ll use a long string just like the above and replace ‘UUUU’ with the return address we need. Here’s the skeleton exploit code in python:

ret2libc

We’ll use ret2libc technique for this exploit. The plan is to put the address of libc function system as the return address, and manipulate the stack in a way to pass “/bin/sh” as the argument. Strings in C are passed as pointers, i.e. the stack should contain the address of the string. Similar to the stack layout picture above, this is what we want the stack to look like when we reach ret command in getpath() (note that in the picture above, EBP is pushed to the stack by the function’s preamble and not before the call to the function):

We’ll write the address of system to the location of the return address that we found. When ret command is executed, the stack pointer would move down to point to “return address 2”, which is where system function will expect its return address to be located (we’ll ignore it for now). The 4 bytes after that would be the first argument to system, so we need to put the address of string “/bin/sh” there. Let’s find these two addresses:

GDB’s print command (or p for short) can be used to display the address of system — 0xb7ecffb0. This needs to be done while the program is running.

Now let’s find where libc is loaded in memory:

As it happens, “/bin/sh” string is also present in libc. Let’s try to use GDB’s find command to locate it:

WTF?!?! GDB said it found the pattern at the address 0xb7fba23f, but when we tried to display it, it wasn’t anything like “/bin/sh”! Oh well, we’ll use strings command to examine libc file outside of GDB:

-a tells strings to scan the whole file, -t x will make it display offset in hex. So we have “/bin/sh” at offset 0x11f3bf in libc, and libc is loaded at 0xb7e97000 (see above), which means the string is at the address 0xb7fb63bf in memory. Let’s check in GDB:

Yay!!!

Here’s the updated exploit:

There’s one more thing to explain here. On 32 bit systems memory addresses are 4 bytes long. When storing 4 bytes in memory, it can be done by either storing the least significant byte first, or the most significant byte first — see https://en.wikipedia.org/wiki/Endianness. Intel x86 platform is little-endian, which means the least significant byte is stored first. This comes into play when encoding addresses in strings. The address of system0xb7ecffb0 — should be encoded in the string as follows: "\xb0\xff\xec\xb7". We can either do it manually, or use python’s struct module as shown above. 'I' as the first argument tells struct to “pack” the value as unsigned int (4 bytes).

How to Run the Exploit?

The program reads into the buffer from the standard input, so to execute the exploit we would need to run:

But we’re not done yet — there’s also the restriction on the return address that we haven’t satisfied yet, as the address of system starts with 0xb.

Jump ROP

Let’s see what happens when ret CPU command is executed. The processor will move the address at the top of the stack into the EIP, and increment (stack grows towards lower addresses) ESP by 4. Now if the address at the top of the stack points to another ret instruction, when it is executed these steps happen again and the execution will continue at the next address that is on the stack. This might sound a bit convoluted, but essentially if we use an address of a ret instruction as the return address, the execution will jump to the next address on the stack.

So our plan to circumvent the return address checking is to modify the exploit to put address of a ret instruction as the return address, before the address of the system. We’ll use the ret instruction at the end of getpath(), which is located at 0x08048544 from the disassembly dump above. Here’s what we want to be on the stack:

Here’s the final exploit:

Running the Exploit

Now we’re ready to run the exploit:

What happened? Did it not work? Let’s check it in GDB. I don’t know how to redirect input in GDB, so we’ll run the exploit to save the output to a file and use it as the input:

Now back to GDB:

We removed all the previous breakpoints using delete, added breakpoint at the system function in libc and started the program again using /tmp/stack7 file as input. And the breakpoint worked, we ended up in system!!!

So does the exploit work or not? It does, actually, but when /bin/sh is executed, the input stream is closed (exploit is all our script outputs!), so it exits right away. So to make it work we’ll use cat:

cat without parameters redirects it’s standard input to it’s standard output, so after the exploit is executed we’re able to enter commands — we’ve got shellz!

SIGSEGV

But what about the segmentation fault we’ve seen earlier when running the exploit without the cat? Remember “return address 3” on the last picture outlining the stack? I’ve used ‘AAAA’ there and said it doesn’t matter what it is. This is the return address for system. The segmentation fault doesn’t matter as it happens after the shell dies, but if you want to make the exploit “clean”, you can put the address of exit there.

Am I a Hacker Now?

Doing Protostar is not the same as exploiting modern systems. The challenges are compiled on older 32 bit systems and are lacking exploit mitigation techniques that are common practice today, such as:

  • Non-executable stack (NX) not enabled
  • No stack canaries
  • The system has ASLR (address space randomization) disabled

References

LiveOverflow Binary Hacking course — https://www.youtube.com/watch?v=iyAyN3GFM7A&list=PLhixgUqwRTjxglIswKp9mpkfPNfHkzyeN

Random rumblings about #InfoSec. The opinions expressed here are my own and not necessarily those of my employer.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store