unlink

HEAP UNLINK ATTACK

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#define unlink(P, BK, FD) {
FD = P->fd;
BK = P->bk;
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P);
else {
FD->bk = BK;
BK->fd = FD;
if (!in_smallbin_range (P->size)
&& __builtin_expect (P->fd_nextsize != NULL, 0)) {
assert (P->fd_nextsize->bk_nextsize == P);
assert (P->bk_nextsize->fd_nextsize == P);
if (FD->fd_nextsize == NULL) {
if (P->fd_nextsize == P)
FD->fd_nextsize = FD->bk_nextsize = FD;
else {
FD->fd_nextsize = P->fd_nextsize;
FD->bk_nextsize = P->bk_nextsize;
P->fd_nextsize->bk_nextsize = FD;
P->bk_nextsize->fd_nextsize = FD;
}
} else {
P->fd_nextsize->bk_nextsize = P->bk_nextsize;
P->bk_nextsize->fd_nextsize = P->fd_nextsize;
}
}
}
}
FD->bk == P && BK->fd == P

(2) prev/after chunk is marked as free()’d

When GLIBC free a chunk, it will going to see whether the chunk before/after is free. If it is, then the chunk before/after will be unlink()’d off its double-linked list and these two chunks merge into one chunk.

HOW TO BYPASS CHECK

(1) got a address of the PTR to the address of &LABEL(fake chunk) ( &chunk header + word_size * 2 )

PTR(the ptr to the fake chunk LABEL)

Before Overflow

PREV_SIZE(get prev LABEL here)SIZE(lowest bit PREV_USE)
the user data(malloc return)user data
…………
PREV_SIZE(get prev LABEL here) SIZE(lowest bit PREV_USE)

After Overflow(along with with fake chunk make)

PREV_SIZE(get prev LABEL here)SIZE(lowest bit PREV_USE)
PREV_SIZE (faked)SIZE (faked)
FD(faked)(malloc_return - word_size * 3)BK(faked)(malloc_return - word_size * 2)
…………
PREV_SIZE (&LABEL_FREEING - &LABEL_fake) SIZE(lowest bit set to 0)

So, we get the PTR to the LABEL of the fake chunk by using the malloc return of previous chunk .

(2) write heap to make a fake LABEL

PREV_SIZE: Any Value
SIZE: Any Value
FD: the address of PTR(mentioned above) - word_size * 3 (we need FD->bk)
BK: the address of PTR(mentioned above) - word_size * 2 (we need BK->fd)

(3) overwrite the chunk FREEING

  • &LABEL(FREEING chunk) - PREV_SIZE(FREEING chunk) == &LABEL(fake chunk)
    • So, PREV_SIZE(FREEING chunk) should be the value of &LABEL(FREEING chunk) minus &LABEL(fake chunk)
  • The lowest bit of SIZE(FREEING chunk) which marking whether the PREV_chunk been used as 0
  • The FD->bk (PTR - word_size * 3 + word_size * 3) is set as BK, which is PTR - word_size * 2
  • The BK->fd (PTR - word_size * 2 + word_size * 2 is set as FD, which is PTR - word_size * 3
  • So the ptr list has been destroyed, but the mem between ptrs has been free()’d.
  • Next time we call malloc, we will get the mem and can write it easily;