CRYPTO#
签到#
题目描述#
欢迎参加第六届极客少年挑战赛中学组第一场比赛,本页面左侧包含CRYPTO/MISC/REVERSE/WEB/PWN 五个模块的题目,总计3000分,你可以根据特长自行选择答题顺序。
- 比赛时间:07月31日09:00-12:00;比赛时长:180分钟;
- 请记得按照参赛要求录制WriteUp并及时上传。 接下来试试这道题目来完成签到:按 flag:YYYYMMDD: 的格式作为题目答案填入。
分析#
依旧 YYYYMMDD 签到,秒了。
FLAG#
flag:20260731:text食素大帝#
题目描述#
小艾在学习古典密码时,听说凯撒大帝曾经用“整体后移”的方式保护消息。后来他突发奇想:如果不再只盯着 26 个字母,而是让字符在更大的 ASCII 世界里移动,又会留下怎样的密文呢?他把一段信息处理成了附件中的一串数字。请在获得授权的情况下分析这段密文,恢复并提交正确的 FLAG。
题目附件#
104, 111, 102, 110, 69, 93, 131, 68, 132, 130, 126, 89, 151, 143, 142, 120, 111, 162, 182, 168, 187, 174, 132, 172, 192, 184, 151, 218, 157, 224, 222, 198, 185, 250, 225, 246, 222, 17, 11, 12, 249, 10, 13, 251text分析#
很神秘的 ASCII码 数组啊,有超过上限的(126+)也有很小很小的数(10/11/12/13…),证明这个数发生了回绕。
通过上下文(其他题目的flag)可以推断前五个字符为:flag:
通过将密文与ASCII码中的位置逐一比对,可以发现这些数与明文的差值分别为:2/3/5/7/11
这些差值是一串 连续的素数,因此我们可以确认其加密方式是:每位加上第 i 个素数。
脚本#
cipher = [104,111,102,110,69,93,131,68,132,130,126,89,151,143,142,120,
111,162,182,168,187,174,132,172,192,184,151,218,157,224,222,
198,185,250,225,246,222,17,11,12,249,10,13,251]
def get_i(n):
i, a = [], 2
while len(i) < n:
if all(a % p != 0 for p in i):
i.append(a)
a += 1
return i
i = get_i(len(cipher))
plain = ''.join(chr((c - p) % 256) for c, p in zip(cipher, i))
print(plain)pythonFLAG#
flag:Pr1me_4nd_C4esar_1S_S0o0o_C0oL_And_FUN:text曲线拼钥#
题目描述#
三段弯曲的轨迹,投下的却是同一把钥匙的影子。小艾在一次获得授权的密码实验中,发现系统把同一个 secret 分别投到几组椭圆曲线点上,又用它派生 AES 密钥加密了真正的 FLAG。请下载附件,分析源码与输出,拼回隐藏的 secret,解出FLAG。
题目附件#
下载的压缩包内提供了两份文件:task.py & output.txt
task.py#
import os
import re
from hashlib import sha256
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
p = 87113925739942346296209491
a = 1
b = 0
subgroup_orders = [67121221, 134263411, 268514387]
generators = [
(8835499120057832060959132, 52500740221580755862828735),
(12676470863022452245035160, 13079413864233807183548491),
(59193223900763229979046869, 49434526165411473587880072),
]
O = None
def add(P, Q):
if P is O:
return Q
if Q is O:
return P
x1, y1 = P
x2, y2 = Q
if x1 == x2 and (y1 + y2) % p == 0:
return O
if P != Q:
m = ((y2 - y1) * pow((x2 - x1) % p, -1, p)) % p
else:
if y1 % p == 0:
return O
m = ((3 * x1 * x1 + a) * pow((2 * y1) % p, -1, p)) % p
x3 = (m * m - x1 - x2) % p
y3 = (m * (x1 - x3) - y1) % p
return x3, y3
def mul(k, P):
R = O
Q = P
while k:
if k & 1:
R = add(R, Q)
Q = add(Q, Q)
k >>= 1
return R
def load_flag():
flag = os.getenv("FLAG")
if flag is None:
raise ValueError("FLAG is missing")
if not re.fullmatch(r"flag:[A-Za-z0-9_]{5,64}:", flag):
raise ValueError("invalid FLAG format")
return flag.encode()
def load_secret(modulus):
raw = os.getenv("SECRET")
if raw is None:
raise ValueError("SECRET is missing")
secret = int(raw)
if not (0 < secret < modulus):
raise ValueError("SECRET out of range")
return secret
def main():
modulus = 1
for order in subgroup_orders:
modulus *= order
flag = load_flag()
secret = load_secret(modulus)
public_points = [mul(secret, G) for G in generators]
iv = os.urandom(16)
key = sha256(str(secret).encode()).digest()[:16]
ciphertext = AES.new(key, AES.MODE_CBC, iv).encrypt(pad(flag, 16))
print(f"p = {p}")
print(f"a = {a}")
print(f"b = {b}")
print(f"subgroup_orders = {subgroup_orders}")
for idx, point in enumerate(generators, start=1):
print(f"G{idx} = {point}")
for idx, point in enumerate(public_points, start=1):
print(f"Q{idx} = {point}")
print(f"iv = '{iv.hex()}'")
print(f"ciphertext = '{ciphertext.hex()}'")
if __name__ == "__main__":
main()
"""
p = 87113925739942346296209491
a = 1
b = 0
subgroup_orders = [67121221, 134263411, 268514387]
G1 = (8835499120057832060959132, 52500740221580755862828735)
Q1 = (82844645399930198619956258, 26527298059472557748427257)
G2 = (12676470863022452245035160, 13079413864233807183548491)
Q2 = (4154861569623257016541021, 82839717555129433959065352)
G3 = (59193223900763229979046869, 49434526165411473587880072)
Q3 = (2740429481111685734811186, 54173609107104505839853391)
iv = "a9e74ed50728475adaba196ddc4504ba"
ciphertext = "84845e2717b277a6b468b353888c33e0a2aa16d4271c4171a613ff6437507853"
"""pythonoutput.txt#
p = 87113925739942346296209491
a = 1
b = 0
subgroup_orders = [67121221, 134263411, 268514387]
G1 = (8835499120057832060959132, 52500740221580755862828735)
Q1 = (82844645399930198619956258, 26527298059472557748427257)
G2 = (12676470863022452245035160, 13079413864233807183548491)
Q2 = (4154861569623257016541021, 82839717555129433959065352)
G3 = (59193223900763229979046869, 49434526165411473587880072)
Q3 = (2740429481111685734811186, 54173609107104505839853391)
iv = "a9e74ed50728475adaba196ddc4504ba"
ciphertext = "84845e2717b277a6b468b353888c33e0a2aa16d4271c4171a613ff6437507853"text分析#
经过分析之后,可以知道源码将同一个 secret 分别乘以椭圆曲线上三个不同子群的生成元,得到三个公开点,再用 SHA256(secret)[:16] 作为 AES-CBC 密钥加密了 FLAG 。
其中有这两个值/组可以进行利用:
p = 87113925739942346296209491
subgroup_orders = [67121221, 134263411, 268514387]python我们可以使用 BSGS 和 CRT 的方法对 secret 的值进行还原,然后对其产出密钥并对输出结果进行解密,即可得到 FLAG 。
脚本#
以下脚本仅供参考,对原脚本进行了精简和提炼。
def bsgs(G, Q, n):
m = int(math.isqrt(n)) + 1
baby = {}
R = O
for j in range(m):
baby[R] = j
R = add(R, G)
neg_mG = mul(n - m, G)
gamma = Q
for i in range(m):
if gamma in baby:
return (i * m + baby[gamma]) % n
gamma = add(gamma, neg_mG)
remainders = [bsgs(G, Q, n) for G, Q, n in zip(generators, public_points, subgroup_orders)]
secret = crt(remainders, subgroup_orders)pythonFLAG#
flag:ECDLP_CRT_Subgr0up_R4c3:textMISC#
彩虹密语#
题目描述#
午夜的加密频道传来一张神秘图片,发件人只留下一句模糊的提示:“在最美的色彩里,藏着最深的秘密。“有人提出:人眼看不出细微变化,但机器可以。请找出隐藏在像素深处的秘密。
题目附件#
下载的压缩包内提供了一张图片:secret.png
您可以点击此链接 ↗下载题目原附件。
分析#
图片是一张彩虹渐变图,文件大小极小;通过肉眼观察和程序分析可知其R&G的变化是极其规律的,仅B可能存在变化并藏入 FLAG ,因此我们使用以下脚本对 B通道 进行分析:
Generate with Qwen3.8 Max Preview
from PIL import Image
import numpy as np
img = Image.open('secret.png')
arr = np.array(img)
bits = []
for row in range(400):
for col in range(400):
bits.append(int(arr[row, col, 2]) & 1)
result = bytearray()
for i in range(0, len(bits) - 7, 8):
byte = 0
for j in range(8):
byte = (byte << 1) | bits[i+j]
result.append(byte)
print(result)python输出结果如下:
bytearray(b'flag:LSB_is_easy_2026:\x008\xe3\x8f\(后续内容省略)text从中就可以看到完整的 FLAG 了。
FLAG#
flag:LSB_is_easy_2026:textUDP漂流记#
题目描述#
公司安全团队截获了一段可疑的UDP流量。黑客利用自研的自定义协议绕过防火墙,通过UDP传输了一个网页文件和一张含有核心机密的图片。由于UDP协议的无连接特性,数据包在传输过程中发生了乱序,并且混杂了部分干扰流量。请你从附件 challenge.pcap 中恢复出完整的图片。
题目附件#
下载的压缩包内提供了一个 .pcap 格式的附件,可知需要使用 WireShark/scapy 等取证工具对其进行流量分析。
您可以点击此链接 ↗下载题目原附件。
分析#
我们使用 scapy 工具读取 .pcap 文件:发现文件中包含 59 个数据包,其中有 31 个是我们需要分析并提取的 UDP 包。
观察发现 UDP 传输中存在两类流量:
- 干扰流量:payload 为
garbage_payload,使用随机端口传输,共 22 个包 - 有效流量:源端口为
54321,目的端口为8888,共 9 个包
有效数据包的 payload 使用一个固定不变的头部,长度为 5 个字节。再对其按 Type 分类后,按 Seq 排序拼接可以得到这两类的内容:
-
HTML
html<html><body><h1>Secret is in the image!</h1></body></html> -
图片 拼接后得到一个 791 字节的 PNG 文件(点击链接打开图片) ↗
脚本#
Generate with Qwen3.8 Max Preview
from scapy.all import *
pkts = rdpcap('challenge.pcap')
target_pkts = []
for pkt in pkts:
if UDP in pkt and pkt[UDP].sport == 54321 and pkt[UDP].dport == 8888:
target_pkts.append(bytes(pkt[UDP].payload))
image_fragments = {}
for p in target_pkts:
ptype = p[0]
seq = int.from_bytes(p[1:3], 'big')
data = p[5:]
if ptype == 0x02:
image_fragments[seq] = data
image_data = b''
for seq in sorted(image_fragments.keys()):
image_data += image_fragments[seq]
with open('recovered_image.png', 'wb') as f:
f.write(image_data)python通过人工读取脚本还原出来的 PNG 文件后即可获得 FLAG 。
FLAG#
flag:2026_asfdasfedt123:text侦探的压缩包#
题目描述#
学校推理社准备办一次“校园小侦探”主题展示,社长把最后一份线索资料打包后交给了值班同学。可惜文件在传输过程中出了问题,现在只剩下一个看起来像日志(.log)的附件,直接打开总是报错。熟悉社长习惯的人还提到,他常常会给重要压缩包再套上一层简单口令。请你在获得授权的情况下,对附件进行分析,先确认文件的真实类型,再从文件签名和压缩包结构入手修复损坏内容;如果修复后发现里面还有加密压缩包,也请继续尝试还原口令并找出最终的 Flag 提交。
题目附件#
下载的压缩包内提供了一份也许是日志的 detective_archive.log 文件。
您可以点击此链接 ↗下载题目原附件。
分析#
附件扩展名为 .log,通过在 WSL 环境中执行 file 命令仅识别为 data,且该文件无法直接打开。通过十六进制分析发现文件内部存在 memo.txt、final_notes.zip、flag.txt 等文件,以及大量 UT 标记,判断其真实类型为 ZIP 压缩包。
深入分析后得知此压缩包的签名存在损坏:标准 ZIP 文件使用 PK(50 4B)作为所有结构标记的前缀(引用资料:Hello-CTF/MISC/压缩包 ↗),而本题文件中对应位置的前缀均为 DG(44 47),因此需要我们对其进行改正。
人工修复后 ZIP 正常解压,压缩包内含以下文件:
memo.txt— 提示信息final_notes.zip— 内层加密压缩包(含flag.txt)
memo.txt 原文:
Case memo
Detective Lin left the final notes in a protected archive.
He always uses a four-digit numeric habit as the password.
Nothing fancy. Try from 0000 if you have to.text译文(使用 DeepL Free 翻译):
案件备忘录
林探员将最终的笔记存放在了一个受保护的档案库中。
他总是习惯用四位数字作为密码。
没什么花哨的。如果实在不行,就从0000开始试。text提示我们需要对内层压缩包进行 四位数字密码 的爆破。运行爆破工具后得知密码为 0421 。
使用此密码对内层压缩包进行解锁解压操作,读取 flag.txt 文件即可得到题目 FLAG 。
FLAG#
flag:Detective_Case_File_0421:textREVERSE#
钟楼签到#
题目描述#
在通往终点的最后一扇门前,立着一座沉默已久的钟楼。守门人不会直接读取旅人的口令,他会先把口令抄进一本由 64 个符号写成的古卷中,随后将古卷上的每一个字符都随着钟楼倒拨两格。只有当倒拨后的古卷内容与门上的封印完全一致时,钟楼才会再次响起。在获取授权的情况下你能让钟楼重新转动吗?正确的旅人口令就是你要提交的FLAG
题目附件#
下载的压缩包内提供了一份 exe 可执行程序:checkin.exe
您可以点击此链接 ↗下载题目原附件。
分析#
用 PowerShell 提取二进制中的可读字符串,发现关键提示信息:
[Gatekeeper] First, your words become a scroll of 64 runes.
[Gatekeeper] Then every rune is turned back by two ticks.text提示我们输入字符串后会进行 Base64 处理,然后对每个字符的 ASCII 码减去2,再与 gate_seal 进行比较结果是否一致。
逆向此程序,发现 gate_seal 通过连续 mov rax, imm64 指令加载到栈上:
48 B8 58 6B 76 66 58 78 6E 31 mov rax, "XkvfXxn1"
48 89 45 A0 mov [rbp-0x60], rax
48 B8 4B 30 76 68 4B 45 2F 6A mov rax, "K0vhKE/j"
48 89 45 A8 mov [rbp-0x58], rax
(后续省略)asm通过拼接可以得到完整的 gate_seal :
XkvfXxn1K0vhKE/jVxHdXkjsWUvdakT0XVHxXT7h_ETh_xDsV0LmLEvqXU3lXRm;text使用脚本求解即可得到 FLAG 。
脚本#
gate_seal = "XkvfXxn1K0vhKE/jVxHdXkjsWUvdakT0XVHxXT7h_ETh_xDsV0LmLEvqXU3lXRm;"
import base64
b64_str = ''.join(chr(ord(c) + 2) for c in gate_seal)
# ZmxhZzp3M2xjMG1lXzJfZmluYWxfcmV2ZXJzZV9jaGVjazFuX2NoNGxsZW5nZTo=
flag = base64.b64decode(b64_str).decode()
print(flag)python将运行出来的 FLAG 再放回程序中运行校验出正确结果:
[Clock Tower] Ding dong...
[Gatekeeper] The seal is broken. Welcome to Final Reverse!
Correct!textFLAG#
flag:w3lc0me_2_final_reverse_check1n_ch4llenge:text借书码终端#
题目描述#
图书馆自助借书机最近启用了新的管理员功能。请根据附件找出隐藏信息
题目附件#
下载的压缩包内提供了一份二进制可执行程序:library_card
您可以点击此链接 ↗下载题目原附件。
分析#
程序运行后要求用户输入一个 card code ,经过 check_code() 校验通过后,调用 print_flag() 解密输出 FLAG 。
check_code()#
输入长度必须为 24 字节,逐字节校验:
target[i] == ROL8( ((7*i + 49) ^ input[i]) + 11*i, i%5 + 1 )cprint_flag()#
FLAG 长度为 38 字节,由 enc_flag 数组异或一个 mask 得到:
flag[i] = enc_flag[i] ^ flag_mask(input, i)c其中:
flag_mask(input, i) = input[(7*i + 3) % 24] ^ (11*i + 35)c因此我们需要对 check_code 的变换进行取逆:
input[i] = (7*i + 49) ^ ( ROR8(target[i], i%5+1) - 11*i )c从 .rodata 段提取 target[24]:
E6 89 33 E4 D4 98 06 E2 85 30 2D A2 0C 44 67 F6 12 0C 49 57 C8 D9 2F CBtext逆推得到 card code:
Book7Admin_Qx92Luna_5K8mtext注: 这是什么勾八 card code 啊我图书管理员都想不出来这神秘编码
接着我们从 .rodata 段提取 enc_flag[38]:
2E 1D 36 61 18 04 47 25 58 86 8C 8C B4 E9 9D C2 E8 EE B5 B1 A6 56 65 6C 34 5E 78 67 08 33 12 78 8B 86 BA A8 B0 B8text将其代入前面提到的 FLAG 公式即可得到 FLAG 。
脚本#
Generate with Qwen3.8 Max Preview:
target = [0xe6, 0x89, 0x33, 0xe4, 0xd4, 0x98, 0x06, 0xe2, 0x85, 0x30,
0x2d, 0xa2, 0x0c, 0x44, 0x67, 0xf6, 0x12, 0x0c, 0x49, 0x57,
0xc8, 0xd9, 0x2f, 0xcb]
enc_flag = [0x2e, 0x1d, 0x36, 0x61, 0x18, 0x04, 0x47, 0x25, 0x58, 0x86,
0x8c, 0x8c, 0xb4, 0xe9, 0x9d, 0xc2, 0xe8, 0xee, 0xb5, 0xb1,
0xa6, 0x56, 0x65, 0x6c, 0x34, 0x5e, 0x78, 0x67, 0x08, 0x33,
0x12, 0x78, 0x8b, 0x86, 0xba, 0xa8, 0xb0, 0xb8]
def rol8(val, n):
n %= 8
return ((val << n) | (val >> (8 - n))) & 0xFF
def ror8(val, n):
n %= 8
return ((val >> n) | (val << (8 - n))) & 0xFF
card_code = []
for i in range(24):
shift = i % 5 + 1
val = ror8(target[i], shift)
val = (val - 11 * i) & 0xFF
val = val ^ ((7 * i + 49) & 0xFF)
card_code.append(val)
flag = []
for i in range(38):
mask = card_code[(7 * i + 3) % 24] ^ ((11 * i + 35) & 0xFF)
flag.append(enc_flag[i] ^ mask)
flag_str = ''.join(chr(c) for c in flag)
print("Flag:", flag_str)pythonFLAG#
flag:library_card_2048Iyt7Wi2c47YiLeS:textWEB#
图书搜索#
题目描述#
一个图书馆搜索系统使用的sqlite数据库,有没有漏洞呢?
分析#
第一次访问我以为我电脑炸了在想为什么这网页画质真低啊还不如云原神一看居然是神秘的 <canvas> 哎哟给我气笑了。

在网页中进行人工测试后可以找到一个 /api/search 的API端点,请求体为:{"q": "<XOR + Base64 编码后的搜索词>"}
根据题目描述可知后端数据库使用 sqlite,我们先使用 ' 进行查询尝试:

根据返回的结果可知其存在 SQL 注入漏洞。
脚本#
逐一执行以下 SQL 命令:
' UNION SELECT 1,2,3,4--
' UNION SELECT 1,name,sql,4 FROM sqlite_master WHERE type='table'--
' UNION SELECT 1,flag,3,4 FROM secrets--sql以上命令分别可以确认:数据库表存在 4 列,且存在 books 和 secrets 两个表,其中 FLAG 在 secrets 中,通过第三个命令可以获得。
FLAG#
flag:9ab8ab86ef513355fa570a5766ed875d:text网络诊断#
题目描述#
网络诊断小工具在上线前临时加了一层关键字黑名单补丁,拦掉了多种命令分隔符和文件读取工具。请点击下方链接生成环境,仔细寻找这层补丁的遗漏之处,找到服务器上的 flag。
分析#
这是一个 DNS 查询工具,PHP后端会执行 getent hosts <input> ,存在命令注入漏洞,但其添加了关键字黑名单:
$BAD_TOKENS = [';', '&', '|', '`', '$', '(', ')', '<', '>',
'cat', 'head', 'tail', 'more', 'less', 'nl', 'awk', 'cut',
'flag', ' '];php分析可得普通正常的什么拼接字符序列化什么的是行不通的,因此我们需要其他方式来拼接字符读取 FLAG 。
其中几个常见的命令分隔符(如 ;&|/ ),可以使用 %0a(换行符)分隔多条命令;使用 %09(TAB 字符)来替代空格;flag 关键字可使用通配符 f* 来匹配文件名。
文件读取思路: 访问 /tmp 目录,用 grep "" f* 读取所有以 f 开头的文件内容并在前端回显。
经过拼接可以得到 Payload :
?host=example.com%0acd%09..%0acd%09tmp%0agrep%09""%09f*text
FLAG#
flag:43a668f6c89884d2ccbf8e3426b5a25f:text