Heap

Slowly working through https://github.com/shellphish/how2heap

fastbin_dup

Tricking malloc into returning a nearly-arbitrary pointer by abusing the fastbin freelist.

wargame sim

This file demonstrates a simple double-free attack with fastbins.
Fill up tcache first.

It then mallocs 8 chunks of size 8 and frees 7 of them:

1
2
3
4
5
6
7
void *ptrs[8];
for (int i=0; i<8; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}

This fills up tcache with 7 freed chunks, so freed chunks from now on are recorded down in fastbins (which is what we are exploiting)

(This is only needed if the glibc uses tcache, which it sometimes doesn’t like the below section)

wdb> heap chunks
address        prev_size      size inuse             fd             bk
0x83e000                     0x290 used (initial chunk allocated from other things)
0x83e290                      0x20 free           0x83e
0x83e2b0                      0x20 free        0x83ea9e
0x83e2d0                      0x20 free        0x83eafe
0x83e2f0                      0x20 free        0x83eade
0x83e310                      0x20 free        0x83eb3e
0x83e330                      0x20 free        0x83eb1e
0x83e350                      0x20 free        0x83eb7e
0x83e370                      0x20 used
wdb> heap tcache
(0x20)    tcache[0](7): 0x83e350 -> 0x83e330 -> 0x83e310 -> 0x83e2f0 -> 0x83e2d0
 -> 0x83e2b0 -> 0x83e290

We then allocate three buffers:

(in this calloc(1, n) is basically identical to malloc(n) I think?)

1
2
3
int *a = calloc(1, 8);
int *b = calloc(1, 8);
int *c = calloc(1, 8);
Allocating 3 buffers.
wdb> heap chunks
address        prev_size      size inuse             fd             bk
0x83e000                     0x290 used                               
0x83e290                      0x20 free           0x83e               
0x83e2b0                      0x20 free        0x83ea9e               
0x83e2d0                      0x20 free        0x83eafe               
0x83e2f0                      0x20 free        0x83eade               
0x83e310                      0x20 free        0x83eb3e               
0x83e330                      0x20 free        0x83eb1e               
0x83e350                      0x20 free        0x83eb7e               
0x83e370                      0x20 used                               
0x83e390                      0x20 used (a)
0x83e3b0                      0x20 used (b)
0x83e3d0                      0x20 used (c)
1st calloc(1, 8): 0x83e3a0
2nd calloc(1, 8): 0x83e3c0
3rd calloc(1, 8): 0x83e3e0

We then free a, then b, then double free a

1
2
3
free(a);
free(b);
free(a);

Note: the addresses got moved cuz I restarted the binary

Freeing the first one...
wdb> heap chunks
address        prev_size      size inuse             fd             bk
0x7a8000                     0x290 used                               
0x7a8290                      0x20 free           0x7a8               
0x7a82b0                      0x20 free        0x7a8508               
0x7a82d0                      0x20 free        0x7a8568               
0x7a82f0                      0x20 free        0x7a8548               
0x7a8310                      0x20 free        0x7a84a8               
0x7a8330                      0x20 free        0x7a8488               
0x7a8350                      0x20 free        0x7a84e8               
0x7a8370                      0x20 used                               
0x7a8390                      0x20 free           0x7a8  (a)
0x7a83b0                      0x20 used                  (b)
0x7a83d0                      0x20 used                  (c)
wdb> heap fast
(0x20)      fastbin[0]: 0x7a8390 (a gets freed so it gets put on the fastbin free list)
(0x30)      fastbin[1]: 0x0
(0x40)      fastbin[2]: 0x0
(0x50)      fastbin[3]: 0x0
(0x60)      fastbin[4]: 0x0
(0x70)      fastbin[5]: 0x0
(0x80)      fastbin[6]: 0x0
If we free a (0x7a83a0) again, things will crash because a (0x7a83a0) is at the top of
 the free list.
So, instead, we'll free b (0x7a83c0).
wdb> heap chunks
address        prev_size      size inuse             fd             bk
  ⋮                             ⋮                      ⋮
0x7a8370                      0x20 used                               
0x7a8390                      0x20 free           0x7a8               
0x7a83b0                      0x20 free        0x7a8438               
0x7a83d0                      0x20 used                               
wdb> heap fast
(0x20)      fastbin[0]: 0x7a83b0 -> 0x7a8390 (a -> b)
  ⋮              ⋮
(0x80)      fastbin[6]: 0x0
Now, we can free a (0x7a83a0) again, since it's not the head of the free list.
wdb> heap fast
(0x20)      fastbin[0]: 0x7a8390 -> 0x7a83b0 -> 0x7a8390 (duplicate entry) (a -> b -> a)
Now the free list has [ 0x7a83a0, 0x7a83c0, 0x7a83a0 ]. If we malloc 3 times, we'll get 0x7a83a0 twice!
(malloc uses the last element on the free list [a, b, a], hence we get a (0x7a83a0) twice)
(0x20)      fastbin[0]: 0x7a8390 -> 0x7a83b0 -> 0x7a8390 (duplicate entry) (a -> b -> a)
1st calloc(1, 8): 0x7a83a0 (there is an offset of 0x10 for the header metadata)
(0x20)      fastbin[0]: 0x7a8390 -> 0x7a83b0 -> (invalid memory)
2nd calloc(1, 8): 0x7a83c0
(0x20)      fastbin[0]: 0x7a8390 -> (invalid memory)
3rd calloc(1, 8): 0x7a83a0
(0x20)      fastbin[0]: (invalid memory)

The danger here is that 2 different mallocs will be pointing to the same chunk

This can be exploited if 1 chunk that gets malloc()ed has sensitive information, and the user can access it by doing some operation on the program that runs another malloc() and leak that input

fastbin_dup_consolidate

1
2
void* p1 = malloc(0x40);
void* p2 = malloc(0x40);
Allocated two fastbins: p1=0xc0f010 p2=0xc0f060
wdb> heap chunks
address        prev_size      size inuse             fd             bk
0xc0f000                      0x50 used
0xc0f050                      0x50 used
1
free(p1);

the freed gets added to fastbin:

Now free p1!
wdb> heap chunks
address        prev_size      size inuse             fd             bk
0xc0f000                      0x50 free             0x0
0xc0f050                      0x50 used
wdb> heap bins
Heap Info for Arena 0x7f72c5904b20
                   top: 0xc0f0a0 (size: 0x20f60)
        last_remainder: 0x0
(0x20)      fastbin[0]: 0x0
(0x30)      fastbin[1]: 0x0
(0x40)      fastbin[2]: 0x0
(0x50)      fastbin[3]: 0xc0f000
(0x60)      fastbin[4]: 0x0
(0x70)      fastbin[5]: 0x0
(0x80)      fastbin[6]: 0x0
      unsorted bins[0]: 0x0
1
void* p3 = malloc(0x400);
Allocated large bin to trigger malloc_consolidate(): p3=0xc0f0b0
In malloc_consolidate(), p1 is moved to the unsorted bin.
wdb> heap chunks
address        prev_size      size inuse             fd             bk
0xc0f000                      0x50 free  0x7f72c5904bb8 0x7f72c5904bb8
0xc0f050                      0x50 used
0xc0f0a0                     0x410 used
wdb> heap bins
Heap Info for Arena 0x7f72c5904b20
                   top: 0xc0f4b0 (size: 0x20b50)
        last_remainder: 0x0
(0x20)      fastbin[0]: 0x0
(0x30)      fastbin[1]: 0x0
(0x40)      fastbin[2]: 0x0
(0x50)      fastbin[3]: 0x0
(0x60)      fastbin[4]: 0x0
(0x70)      fastbin[5]: 0x0
(0x80)      fastbin[6]: 0x0
      unsorted bins[0]: 0x0
(0x50)   small bins[4]: 0xc0f000
1
free(p1);

the freed chunk once again gets added to fastbin:

Trigger the double free vulnerability!
We can pass the check in malloc() since p1 is not fast top.
wdb> heap bins
Heap Info for Arena 0x7f72c5904b20
                   top: 0xc0f4b0 (size: 0x20b50)
        last_remainder: 0x0
(0x20)      fastbin[0]: 0x0
(0x30)      fastbin[1]: 0x0
(0x40)      fastbin[2]: 0x0
(0x50)      fastbin[3]: 0xc0f000
(0x60)      fastbin[4]: 0x0
(0x70)      fastbin[5]: 0x0
(0x80)      fastbin[6]: 0x0
      unsorted bins[0]: 0x0
(0x50)   small bins[4]: 0xc0f000 <-> 0x0 (invalid memory)
1
malloc(0x40)

first malloc takes it from fastbin:

Trigger the double free vulnerability!
We can pass the check in malloc() since p1 is not fast top.
wdb> heap bins
Heap Info for Arena 0x7f72c5904b20
                   top: 0xc0f4b0 (size: 0x20b50)
        last_remainder: 0x0
(0x20)      fastbin[0]: 0x0
(0x30)      fastbin[1]: 0x0
(0x40)      fastbin[2]: 0x0
(0x50)      fastbin[3]: 0x0
(0x60)      fastbin[4]: 0x0
(0x70)      fastbin[5]: 0x0
(0x80)      fastbin[6]: 0x0
      unsorted bins[0]: 0x0
(0x50)   small bins[4]: 0xc0f000 <-> 0x0 (invalid memory)
1
malloc(0x40)

then the next malloc takes it from the duplicated one in small bin

Trigger the double free vulnerability!
We can pass the check in malloc() since p1 is not fast top.
wdb> heap bins
Heap Info for Arena 0x7f72c5904b20
                   top: 0xc0f4b0 (size: 0x20b50)
        last_remainder: 0x0
(0x20)      fastbin[0]: 0x0
(0x30)      fastbin[1]: 0x0
(0x40)      fastbin[2]: 0x0
(0x50)      fastbin[3]: 0x0
(0x60)      fastbin[4]: 0x0
(0x70)      fastbin[5]: 0x0
(0x80)      fastbin[6]: 0x0
      unsorted bins[0]: 0x0
(0x50)   small bins[4]: 0xc0f000 <-> 0x0 (invalid memory)