堆bug利用总结
All bug的利用条件,相关检查,相关例子和小手法
配合heap bug note食用
off-by-one
off-by-one 利用思路
- 溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。也可使用 NULL 字节溢出的方法
- 溢出字节为 NULL 字节:在 size 为 0x100 的时候,溢出 NULL 字节可以使得
prev_in_use
位被清,这样前块会被认为是 free 块。(1) 这时可以选择使用 unlink 方法(见 unlink 部分)进行处理。(2) 另外,这时prev_size
域就会启用,就可以伪造prev_size
,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照prev_size
找到的块的大小与prev_size
是否一致。
check
高版本的unlink:
1 | /* consolidate backward */ |
例子
- for 循环的边界没有控制好导致写入多执行了一次
- strlen 和 strcpy 的行为不一致却导致了 off-by-one 的发生**。 **strlen 这个函数在计算字符串长度时是不把结束符
\x00
计算在内的,但是 strcpy 在复制字符串时会拷贝结束符\x00
Chunk Extend and Overlapping
利用条件
- 程序中存在基于堆的漏洞
- 漏洞可以控制 chunk header 中的数据
ptmalloc 通过 chunk header 的数据判断 chunk 的使用情况和对 chunk 的前后块进行定位。简而言之,chunk extend 就是通过控制 size 和 pre_size 域来实现跨越块操作从而导致 overlapping 的。
例子
- 对 inuse 的 fastbin 进行 extend
- 对 inuse 的 smallbin 进行 extend
- 对 free 的 smallbin 进行 extend
- 通过 extend 后向 overlapping
- 通过 extend 前向 overlapping
通过修改 pre_size 域可以跨越多个 chunk 进行合并实现 overlapping。
Unlink
只有free中的_int_free调用了unlink宏
check
1 | // 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致(size检查) |
实现
1 | FD=P->fd |
将 nextchunk 的 FD 指针指向了 fakeFD,将 nextchunk 的 BK 指针指向了 fakeBK 。那么为了通过验证,我们需要
fakeFD -> bk == P
<=>*(fakeFD + 12) == P
fakeBK -> fd == P
<=>*(fakeBK + 8) == P
当满足上述两式时,可以进入 Unlink 的环节,进行如下操作:
fakeFD -> bk = fakeBK
<=>*(fakeFD + 12) = fakeBK
fakeBK -> fd = fakeFD
<=>*(fakeBK + 8) = fakeFD
简而言之,unlink实现的效果就使得要unlink的chunk的fd和bk分别所指向的chunk的bk/fd发生改变
Use After Free
原理
内存块被释放后,其对应的指针没有被设置为 NULL ,然后在它下一次被使用之前,没有代码对这块内存块进行修改,那么程序很有可能可以正常运转。
示例
1 | +-----------------+ |
这个结构体的real content在free后没有置NULL
- 申请 note0,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)
- 申请 note1,real content size 为 16(大小与 note 大小所在的 bin 不一样即可)
- 释放 note0
- 释放 note1
- 此时,大小为 16 的 fast bin chunk 中链表为 note1->note0
- 申请 note2,并且设置 real content 的大小为 8,那么根据堆的分配规则
- note2 其实会分配 note1 对应的内存块。
- real content 对应的 chunk 其实是 note0。
- 如果我们这时候向 note2 real content 的 chunk 部分写入 magic 的地址,那么由于我们没有 note0 为 NULL。当我们再次尝试输出 note0 的时候,程序就会调用 magic 函数。
Fastbin Attack
利用前提
- 存在堆溢出、use-after-free 等能控制 chunk 内容的漏洞
- 漏洞发生于 fastbin 类型的 chunk 中
如果细分的话,可以做如下的分类:
- Fastbin Double Free
- House of Spirit
- Alloc to Stack
- Arbitrary Alloc
其中,前两种主要漏洞侧重于利用 free
函数释放真的 chunk 或伪造的 chunk,然后再次申请 chunk 进行攻击,后两种侧重于故意修改 fd
指针,直接利用 malloc
申请指定位置 chunk 进行攻击。
原理
fastbin attack 存在的原因在于 fastbin 是使用单链表来维护释放的堆块的,并且由 fastbin 管理的 chunk 即使被释放,其 next_chunk 的 prev_inuse 位也不会被清空。
Fastbin Double Free
check
1 | /* Another simple check: make sure the top of the bin is not the |
只检查了相邻的两个fastbin是不是一样的
原理
Fastbin为FIFO机制
第一次释放free(chunk1)
第二次释放free(chunk2)
第三次释放free(chunk1)
之后malloc一个将chunk1释放出去,然后再修改chunk1的fd内容构造main_arena=>chun2=>chunk1=>target_addr
但是_int_malloc 会对欲分配位置的 size 域进行验证,如果其 size 与当前 fastbin 链表应有 size 不符就会抛出异常
House Of Spirit
原理
该技术的核心在于在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的。
利用条件
- fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理。
- fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
- fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。
- fake chunk 的 **next chunk 的大小不能小于
2 * SIZE_SZ
**,同时也不能大于av->system_mem
。 - fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。
Alloc to Stack
原理
劫持 fastbin 链表中 chunk 的 fd 指针,把 fd 指针指向我们想要分配的栈上,从而实现控制栈中的一些关键数据,比如返回地址等。
示例
1 | typedef struct _chunk |
总结
通过该技术我们可以把 fastbin chunk 分配到栈中,从而控制返回地址等关键数据。要实现这一点我们需要劫持 fastbin 中 chunk 的 fd 域,把它指到栈上,当然同时需要栈上存在有满足条件的 size 值。
Arbitrary Alloc
Arbitrary Alloc 其实与 Alloc to stack 是完全相同的,唯一的区别是分配的目标不再是栈中。 事实上只要满足目标地址存在合法的 size 域(这个 size 域是构造的,还是自然存在的都无妨),我们可以把 chunk 分配到任意的可写内存中,比如 bss、heap、data、stack 等等。
小总结
Arbitrary Alloc 在 CTF 中用地更加频繁。我们可以利用字节错位等方法来绕过 size 域的检验,实现任意地址分配 chunk,最后的效果也就相当于任意地址写任意值。
Unsorted Bin Attack
Unsorted Bin 在使用的过程中,采用的遍历顺序是 FIFO
流程
初始状态时
unsorted bin 的 fd 和 bk 均指向 unsorted bin 本身。
执行 free(p)
由于释放的 chunk 大小不属于 fast bin 范围内,所以会首先放入到 unsorted bin 中。
修改 p[1]
经过修改之后,原来在 unsorted bin 中的 p 的 bk 指针就会指向 target addr-16 处伪造的 chunk,即 Target Value 处于伪造 chunk 的 fd 处。
申请 400 大小的 chunk
此时,所申请的 chunk 处于 small bin 所在的范围,其对应的 bin 中暂时没有 chunk,所以会去 unsorted bin 中找,发现 unsorted bin 不空,于是把 unsorted bin 中的最后一个 chunk 拿出来。
就会变成这样,因为有个条件没有满足,因此unsorted bin链表被破坏,但是target的值被改成了unsorted bin的地址,
作用
- 我们通过修改循环的次数来使得程序可以执行多次循环。
- 我们可以修改 heap 中的 global_max_fast 来使得更大的 chunk 可以被视为 fast bin,这样我们就可以去执行一些 fast bin attack 了。
Large Bin Attack
利用
1 | 1 // gcc -g -no-pie hollk.c -o hollk |
stack_var1和stack_var2中的值已经被修改成了P3的头指针
总结
- 可以修改一个 large bin chunk 的 data
- 从 unsorted bin 中来的 large bin chunk 要紧跟在被构造过的 chunk 的后面
- 通过 large bin attack 可以辅助 Tcache Stash Unlink+ 攻击
- 可以修改 _IO_list_all 便于伪造 _IO_FILE 结构体进行 FSOP。
Tcache attack
原理
内存分配的 malloc 函数中有多处,会将内存块移入 tcache 中。
(1)首先,申请的内存块符合 fastbin 大小时并且在 fastbin 内找到可用的空闲块时,会把该 fastbin 链上的其他内存块放入 tcache 中。
(2)其次,申请的内存块符合 smallbin 大小时并且在 smallbin 内找到可用的空闲块时,会把该 smallbin 链上的其他内存块放入 tcache 中。
(3)当在 unsorted bin 链上循环处理时,当找到大小合适的链时,并不直接返回,而是先放到 tcache 中,继续处理。
tcache poisoning
原理
通过覆盖 tcache 中的 next,不需要伪造任何 chunk 结构即可实现 malloc 到任何地址。
tcache的next指针与fd指针在同一位置
实现
1 | size_t target; |
tcache dup
原理
tcache_put() 的检查可以忽略不计
实现
因为没有任何检查,所以我们可以对同一个 chunk 多次 free,造成 cycliced list。
最新的 libc 2.29 的 commit 中更新了 Tcache 的 double free 的 check
目前为止,只看到了在 free 操作的时候的 check ,似乎没有对 get 进行新的 check。
tcache perthread corruption
原理
tcache_perthread_struct
是整个 tcache 的管理结构
实现
设想有如下的堆排布情况
1 | tcache_ +------------+ |
通过一些手段(如 tcache posioning
),我们将其改为了
1 | tcache_ +------------+<---------------------------+ |
这样,两次 malloc 后我们就返回了 tcache_perthread_struct
的地址,就可以控制整个 tcache 了。
tcache house of spirit
原理
由于tcache_put()函数检查不严格造成的,在释放的时候没有检查被释放的指针是否真的是堆块的malloc指针,如果我们构造一个size符合tcache bin size的fake_chunk,那么理论上讲其实可以将任意地址作为chunk进行释放。
libc leak
在 2.26 之后的 libc 版本后,我们首先得先把 tcache 填满
tcache stashing unlink attack
手法
简单来说就是在smallbin里面构造一个至少两个bin的chunk链,其中最后进入的(因为smallbin是FIFO)chunk的bk指向任意地址,那么smallbin就构造成了多一个chunk的链这时候calloc一个chunk,其余chunk进入tcache(保证刚好够得到任意地址进入tcache),那么此时再malloc一个chunk,就malloc到了任意地址,就是先对任意地址的修改