Poison null byte



Project Zero released a post about a GLIBC NULL byte off-by-one exploitation in 2014,Poison null byte 是一种利用off-by-null即可实现的heap based漏洞利用技术,它的基本思想是通过one-by-null覆盖next chunk的SIZE,构造fake chunk利用unlink,最终构造Chunk overlap。
它是Shrink freed chunk的加强版,能够bypass libc unlink中对的nextchunk的prev_size与chunk的size的检查。

原理

为了了解Position Null byte,首先需要了解freed chunk的合并consolidate机制,该部分内容在unlink中有所涉及,但是现在我们研究的角度不同。
下面提到的forward与backword都是相对于heap的增长方向而言……

consolidate_forward

下面是一个consolidate_forward的示例

建立的两个heap,通过overflow溢出,修改p2 chunk的Prev_inuse bit为0,从而在free(p2)使得glibc认为prev chunk是一个freed chunk,触发consolidate_forward向前合并,此时p1,p2的状态如下所示:

p2的PREV_SIZE是"AAAAAAAA",在unlink时候因为p = chunk_at_offset(p, -((long) prevsize))计算得到无效地址,造成程序crash.

consolidate_backward

同样,下面是一个consolidate_backward 向后合并的示例

分配了p1,p2,p3三个chunk,并通过漏洞溢出修改了p3 Size的Prev_inuse bit=0,当我们free(p)时,glibc通过判断p的next chunk=p2next chunk=p3的in_use bit 为0,判定p2 为freed chunk,进而consolidate_backward
合并处堆排布如下:

可见同样由于PREV_SIZE的问题,unlink时程序crash

patch in unlink

如上,由于prev_size与size的检查不通过,在unlink中crash掉
在学习shrink freed chunk的时候,如果unlink中没有对PREV_SIZE与SIZE,shrink freed chunk能够正确利用
但在glibc unlink中打了如下patch

在unlink时,首先检查P的sizenextchunkprev_size是否相等,之后再进行unlink的正常内容
正常情况下,对于一个Freed chunk ,chunk->sizenextchunk->prev_size是一定相等的
(unlink前会将P指针定位到Freed chunk在进行合并)
所以,shrink freed chunk包括上面的consolidate_forward与consolidate_backward都会造成crash

Posotion null byte 的基本思想:

在shrink freed chunk基础上,构造fake chunk,在unlink时满足chunksize(P) != prev_size (next_chunk(P))
从而达到shrink freed chunk构造出overlapping chunk的漏洞利用效果。

How2Heap

同样我们以shellfish的How2Heap为例子

类似shrink freed chunk ,position的流程也可以通过下图的流程示意:

首先分配三个 chunk,a,b,c
第一个 chunk 类型无所谓,存在off-by-null。
后两个不能是 fast chunk,因为 fast chunk 在释放后不会被合并。chunk c 的作用是帮助伪造 fake chunk。在b上的构造fake chunk(实际上是在伪造C的fake chunk),目的是Bypass unlink中对chunksizenextchunkprevsize的检查

free b后,利用a溢出off-by-null修改freed chunk b的size=0x200,通过next_chunk宏计算可得到fake chunk
这样再次malloc会进行unlink,而glibc的chunk边界都是通过边际计算的
在图上step 4,通过上述代码可以计算得到:

chunksize(P) == *((size_t*)(b-0x8)) & (~ 0x7) == 0x200

prev_size (next_chunk(P)) == *(size_t*)(b-0x10 + 0x200) ==0x200
即可得到chunksize(P) == prev_size (next_chunk(P)),可顺利绕过unlink的patch

由于malloc的chunk全部是利用边界标记法确定,glibc会误认为fake chunk才是chunk c的头部,分配堆块后,发生变化的metadata实际是 fake c.prev_size,而不是 c.prev_size。所以 chunk c 依然认为 chunk b 的地方有一个大小为 0x210 的 freed chunk。但其实这片内存已经被分配给了 chunk b1。

两个相邻的 small chunk 被释放后会被合并在一起。首先释放 chunk b1,伪造出* fake chunk b* 是 freed chunk 的样子。然后释放 chunk c,这时glibc检查chunk c的PREV_INUSE,确定 chunk c 的前一个 chunk b1 是一个 freed chunk,将计算size=prev_size+size,并触发unlink consolidate_forward。在consolidate forward的过程中,对于unlink的各项判断都是可以bypass的。

unlink后,新的bins的size大小是0x110+0x210……

从 unsorted bin 中取出来合并进入 top chunk,top chunk 上移。chunk b2 位于 chunk b1chunk c 之间,被直接无视了,现在 malloc 认为这整块 区域都是未分配的,新的 top chunk 指针已经说明了一切。这样,再次malloc就可以造成d指针与b2 构成overlap。

程序的运行结果:

总结

  • Poison bull byte 是shrink freed chunk的加强版,能够绕过对nextchunk 的prevsize对chunk的size的检查。
  • Poison bull byte的主要思想是构造fake chunk,使得glibc认为nextchunk的prevsize与chunk size相等,从而合理利用unlink,创造unlink。
  • 由之可总结出glibc的chunk边界利用边界标记法进行标记,一旦发生对chunk metadata的改写,都可能造成chunk确定边界的"失误",为漏洞利用创造机会。

参考资料

Linux堆漏洞之off-by-one
CTF-All-In-One
Add consistency check between size and next->prev->size

发表评论