Double Free

Support HackTricks

Basic Information

If you free a block of memory more than once, it can mess up the allocator's data and open the door to attacks. Here's how it happens: when you free a block of memory, it goes back into a list of free chunks (e.g. the "fast bin"). If you free the same block twice in a row, the allocator detects this and throws an error. But if you free another chunk in between, the double-free check is bypassed, causing corruption.

Now, when you ask for new memory (using malloc), the allocator might give you a block that's been freed twice. This can lead to two different pointers pointing to the same memory location. If an attacker controls one of those pointers, they can change the contents of that memory, which can cause security issues or even allow them to execute code.

Example:

#include <stdio.h>
#include <stdlib.h>

int main() {
    // Allocate memory for three chunks
    char *a = (char *)malloc(10);
    char *b = (char *)malloc(10);
    char *c = (char *)malloc(10);
    char *d = (char *)malloc(10);
    char *e = (char *)malloc(10);
    char *f = (char *)malloc(10);
    char *g = (char *)malloc(10);
    char *h = (char *)malloc(10);
    char *i = (char *)malloc(10);

    // Print initial memory addresses
    printf("Initial allocations:\n");
    printf("a: %p\n", (void *)a);
    printf("b: %p\n", (void *)b);
    printf("c: %p\n", (void *)c);
    printf("d: %p\n", (void *)d);
    printf("e: %p\n", (void *)e);
    printf("f: %p\n", (void *)f);
    printf("g: %p\n", (void *)g);
    printf("h: %p\n", (void *)h);
    printf("i: %p\n", (void *)i);

    // Fill tcache
    free(a);
    free(b);
    free(c);
    free(d);
    free(e);
    free(f);
    free(g);

    // Introduce double-free vulnerability in fast bin
    free(h);
    free(i);
    free(h);


    // Reallocate memory and print the addresses
    char *a1 = (char *)malloc(10);
    char *b1 = (char *)malloc(10);
    char *c1 = (char *)malloc(10);
    char *d1 = (char *)malloc(10);
    char *e1 = (char *)malloc(10);
    char *f1 = (char *)malloc(10);
    char *g1 = (char *)malloc(10);
    char *h1 = (char *)malloc(10);
    char *i1 = (char *)malloc(10);
    char *i2 = (char *)malloc(10);

    // Print initial memory addresses
    printf("After reallocations:\n");
    printf("a1: %p\n", (void *)a1);
    printf("b1: %p\n", (void *)b1);
    printf("c1: %p\n", (void *)c1);
    printf("d1: %p\n", (void *)d1);
    printf("e1: %p\n", (void *)e1);
    printf("f1: %p\n", (void *)f1);
    printf("g1: %p\n", (void *)g1);
    printf("h1: %p\n", (void *)h1);
    printf("i1: %p\n", (void *)i1);
    printf("i2: %p\n", (void *)i2);

    return 0;
}

In this example, after filling the tcache with several freed chunks (7), the code frees chunk h, then chunk i, and then h again, causing a double free (also known as Fast Bin dup). This opens the possibility of receiving overlapping memory addresses when reallocating, meaning two or more pointers can point to the same memory location. Manipulating data through one pointer can then affect the other, creating a critical security risk and potential for exploitation.

Executing it, note how i1 and i2 got the same address:

Initial allocations:
a: 0xaaab0f0c22a0
b: 0xaaab0f0c22c0
c: 0xaaab0f0c22e0
d: 0xaaab0f0c2300
e: 0xaaab0f0c2320
f: 0xaaab0f0c2340
g: 0xaaab0f0c2360
h: 0xaaab0f0c2380
i: 0xaaab0f0c23a0
After reallocations:
a1: 0xaaab0f0c2360
b1: 0xaaab0f0c2340
c1: 0xaaab0f0c2320
d1: 0xaaab0f0c2300
e1: 0xaaab0f0c22e0
f1: 0xaaab0f0c22c0
g1: 0xaaab0f0c22a0
h1: 0xaaab0f0c2380
i1: 0xaaab0f0c23a0
i2: 0xaaab0f0c23a0

Examples

  • Dragon Army. Hack The Box

    • We can only allocate Fast-Bin-sized chunks except for size 0x70, which prevents the usual __malloc_hook overwrite.

    • Instead, we use PIE addresses that start with 0x56 as a target for Fast Bin dup (1/2 chance).

    • One place where PIE addresses are stored is in main_arena, which is inside Glibc and near __malloc_hook

    • We target a specific offset of main_arena to allocate a chunk there and continue allocating chunks until reaching __malloc_hook to get code execution.

  • zero_to_hero. PicoCTF

    • Using Tcache bins and a null-byte overflow, we can achieve a double-free situation:

      • We allocate three chunks of size 0x110 (A, B, C)

      • We free B

      • We free A and allocate again to use the null-byte overflow

      • Now B's size field is 0x100, instead of 0x111, so we can free it again

      • We have one Tcache-bin of size 0x110 and one of size 0x100 that point to the same address. So we have a double free.

    • We leverage the double free using Tcache poisoning

References

Support HackTricks

Last updated