TLDR
在选择更复杂的打法前,先看看能不能 IO leak 泄漏栈地址打 ROP(更容易调试)
「卷 Glibc 版本」这件事情和常见的用户态 PWN 题(尤其是国内比赛)几乎完全绑定在了一起,故在此记录 2.33 失去 __free_hook
之后的常规题做法。主要包括:
- 有公式做题就是快之「只能申请大堆块 - Largebin Attack」
- 有公式做题就是快之「新版本下的控制流劫持首选 - House of Apple2」
- 典中典之「强网杯必考 off by null」
其实 IO 里面也远不只是 Apple2 这条利用链,但是这条链是要求最少 + 资料最多的,所有 IO 劫持控制流都绕不开篡改 ((struct _IO_FILE_plus *) _IO_list_all)->vtable
,利用思路差不多,就没必要去卷其它链。
INFO
公式以外的情况多半时候都可以到持续更新的 how2heap 上找一些灵感,例如:
- 在没有泄漏的情况下通过双重异或盲绕过 tcache 异或保护 - safe link double protect
- 在没有泄漏的情况下把 libc 相关地址放入 tcache 中 - House of Water
- 没有显式 free 情况下的现代版 House of Orange - House of Tangerine
- 让 tcache double free 再次伟大 - House of Botcake
各种花里胡哨的利用方法也在持续更新,可以一直关注 how2heap,甚至还提供了网页端调试器,非常好 cheatsheet❤️
Largebin Attack
Largebin Attack 利用手段自 glibc 2.23 起一直存在,近期 IO 相关利用丰富起来后重新变得热门。
NOTE
古早时期的 Unsortedbin Attack 效果与 Largebin Attack 类似,都是向任意地址写入一个地址值,通常被用于赋写
mp_.tcache_bins
或者global_max_fast
为一个大数,进而打更容易利用的 tcache / fastbin 完成攻击。但是现在 Unsortedbin Attack 已经不再可行,取而代之的是条件更加严苛的 Largebin Attack,不过后者还可以确定写入的是指定堆块的地址,这也带来了一些 data-only attack 的机会,例如赋写
_IO_list_all
为堆块地址,并在其中伪造_IO_FILE_plus
结构体用 House of Apple2 劫持控制流。
在分配时会遍历当前 Unsortedbin,若没有满足条件的堆块,则会触发整理,将每个堆块放入相应的 bin 中。对于 largebin 范围的堆块,在处理 fd / bk 的同时还要填充 fd_nextsize
和 bk_nextsize
域,这其中缺少检查,导致了 largebin attack。
在 glibc-2.30 以前有如下两条利用路径:
-
待整理 chunk 小于 largebin 链表中的最小 chunk:
- 即满足:
(unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)
- 任意写代码:
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
- 这也是现在新版本的 largebin attack
- 任意写效果即:
*(fake_bk_nextsize + 0x20) = victim
- 对应分支:
- 即满足:
-
待整理 chunk 大于链表中的最小 chunk:
- 任意写 1:
victim->bk_nextsize->fd_nextsize = victim;
- 任意写 2:
bck = bck->bk; ...; bck = fwd->bk;
- 对应分支:
- 任意写 1:
但是在 glibc-2.30 及以后版本中 else 分支新增了如下两条检测,分别对应上面的两处任意写:
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
if (bck->fd != fwd)
Attention
最后总结出新版本(2.23 - current (2.39))可行的 Largebin Attack 公式:
- 选择一个合适的 largebin 分组,申请一个较大的堆块 A,要求 A 的
bk_nextsize
域释放后还能被篡改;- 申请一个与 A 同属一个分组,但是小于 A 的堆块 B,B 的地址将会被任意写到目标位置;
- 将 A 置入 Largebin,B 置入 Unsortedbin;
- UAF 篡改 A 的
bk_nextsize
域为target_addr - 0x20
,例如_IO_list_all - 0x20
;- 将 B 置入 Largebin,这时候就会触发任意写,使
*(size_t)target_addr = B
;
简化后的 POC 如下:
Apple2 板子
Apple2 俨然已经成为了新时代“free hook”,非常好用,还可以结合下述方法绕过沙盒:
- 通过 gadget 栈迁移,进而打常规 ROP;
- 通常用
svcudp_reply+26
:
- 通常用
- 通过控制寄存器 rdx 打
setcontext + 61
:- 可以找 gadget,使 rdi 或其他寄存器与 rdx 之间进行转换
公式如下:
Quote
对 fp 的设置如下:
_flags
设置为~(2 | 0x8 | 0x800)
,如果不需要控制 rdi,设置为0即可;如果需要获得 shell,可设置为sh;
,注意前面有两个空格vtable
设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap
地址(加减偏移),使其能成功调用_IO_wfile_overflow
即可_wide_data
设置为可控堆地址 A,即满足*(fp + 0xa0) = A
_wide_data->_IO_write_base
设置为 0,即满足*(A + 0x18) = 0
_wide_data->_IO_buf_base
设置为 0,即满足*(A + 0x30) = 0
_wide_data->_wide_vtable
设置为可控堆地址 B,即满足*(A + 0xe0) = B
_wide_data->_wide_vtable->doallocate
设置为地址 C 用于劫持 RIP,即满足*(B + 0x68) = C
_lock
设置为可写地址;mode
设置大于 0函数的调用链如下:
对于常规的 exit 中有如下调用利用链,可以下断点辅助调试:
板子如下:
此外再附上 _IO_FILE_plus
结构体中各个域的偏移:
Off by Null
就算是堆签到题,也有难易之分。对于需要构造堆块合并去 overlap,尤其是 off by null 这种有公式的题目,等比赛时再硬想就太耗时了,故可以总结 glibc-2.30 后的公式打法。
在早期版本,unlink 过程中并没有什么检查,只需要篡改:
- prev size
- prev inuse
两项就可以实现 chunk overlap。
但是在新版本中新增了两项检查:
- 要求
prev_size
与实际 size 一致:if (chunksize (p) != prev_size (next_chunk (p)))
- 要求双向链表通过完整性检查:
if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
于是 overlap 的构造就麻烦起来了,不过还是能在无爆破的情况下完成目标的:
- 先申请 8 个堆块备用,其中 1、4、7 作为 barrier:
这里的关键是调整 barrier 等堆块的大小,使得待伪造的堆块 C0(也就是 P)最低字节为 0,免去爆破
- 依次释放 A、C0、D,再释放 B0 触发合并:
借 Unsortedbin 设置好
P->fd = A; P->bk = D;
- 申请一个大于 A 和 D 的堆块 B1=B0+0x20,切割 BC 的同时修改之前 C0(P)留下的 size 为更大的 0x551,此外再把 bin 中的 C1=C0-0x20、D、A 依次申请回来:
- 继续绕双向链表的完整性检查,把刚刚拿到的 A 和 C1 依次释放,接下来再申请回 A,此时残留有 A→bk == P,再申请回 C1:
注意这里出现了一次
\x00
赋写 A→bk,使得残留的 C1 变成了(C1 & 0xff..ff00) == C0 == P
- 故技重施,依次释放 C1、D、H0,使合成 HD,借此保存 D→fd == C1,进而申请一个 H1=H0+0x70 来部分写成
D->fd = P
:
- 现在已经完成 size 伪造和双向链表的伪造,最后用 barrier-4 设置
fake_prev_size = 0x550 = fake_P_size = C0+barrier4
,此外借助 off by null 清空 prev inuse 域即可删除 H1 触发合并成功得到 overlap:
这也是为什么 H1 申请时要按照 0x500-8,免得大小不匹配
References
[1] CTF 中 glibc堆利用 及 IO_FILE 总结 . winmt
[2] House of apple 一种新的glibc中IO攻击方法 (2) . roderick01