2025“古剑山“网络安全初赛部分wp
Crypto
common rsa
简单的RSA 共模攻击
已知加密过程:
{c1≡me1(modN)c2≡me2(modN) \begin{cases} c_1 \equiv m^{e_1} \pmod N \\ c_2 \equiv m^{e_2} \pmod N \end{cases} {c1≡me1(modN)c2≡me2(modN)
gcd(e1,e2)=1 \gcd(e_1, e_2) = 1 gcd(e1,e2)=1
根据贝祖定理 ,存在整数 s_1, s_2 满足:
s1e1+s2e2=1
s_1e_1 + s_2e_2 = 1
s1e1+s2e2=1
攻击推导过程:
Decrypted=c1s1⋅c2s2(modN)=(me1)s1⋅(me2)s2(modN)=ms1e1⋅ms2e2(modN)=ms1e1+s2e2(modN)=m1(modN)=m
\begin{aligned}
Decrypted &= c_1^{s_1} \cdot c_2^{s_2} \pmod N \\
&= (m^{e_1})^{s_1} \cdot (m^{e_2})^{s_2} \pmod N \\
&= m^{s_1e_1} \cdot m^{s_2e_2} \pmod N \\
&= m^{s_1e_1 + s_2e_2} \pmod N \\
&= m^1 \pmod N \\
&= m
\end{aligned}
Decrypted=c1s1⋅c2s2(modN)=(me1)s1⋅(me2)s2(modN)=ms1e1⋅ms2e2(modN)=ms1e1+s2e2(modN)=m1(modN)=m
import gmpy2
from Crypto.Util.number import long_to_bytes
# 1. 输入数据
N = 162178605357818616394571566923155907889899677780239882906511996614607940884142045197452389471499799373787832649318837814454679970724845203557871078001956378966434166323827984964942729898095347038272003371167123553368531662277059263517900162297903110415768403265100411543878859321181606008503516896600638590699
e1 = 35422
c1 = 153249315480380808558746807096025628082875635601515291525075274335055878390662930254941118045696231628008256877302589689883059616503108946971165183674522403835250738176157466145855833767128209866527507862726083268576304163200171600023472544755768741118904892489037291247455823396160705615280802805803254323033
e2 = 1033
c2 = 5823189490163315770684717059899864988806118565674660089157163486577056500243194221873916232616081138765317598078910078375360361118674333149663483360677725162911935082290640547407140413703664960164356579153623498735889314476063673352676918268911309402784919521792079943937126634436658784515914270266106683548
# 2. 检查 GCD 并计算 s1, s2
# gcdext 返回 (g, s1, s2) 使得 s1*e1 + s2*e2 = g
g, s1, s2 = gmpy2.gcdext(e1, e2)
print(f"[+] GCD(e1, e2) = {g}")
print(f"[+] s1 = {s1}")
print(f"[+] s2 = {s2}")
if g != 1:
print("[-] e1 和 e2 不互质,无法直接使用标准共模攻击。")
else:
# 3. 执行攻击
# 计算 c1^s1 * c2^s2 mod N
# pow(base, exp, mod) 函数在 Python 中支持负数指数(即自动计算模逆元)
m = (pow(c1, s1, N) * pow(c2, s2, N)) % N
# 4. 转换并输出 Flag
try:
flag = long_to_bytes(m)
print(f"[+] Recovered Message: {flag.decode()}")
except Exception as e:
print(f"[-] Decode error: {e}")
print(f"[-] Raw INT: {m}")
flag{A_RSA_c0mm0n_m0dulu5_4tt4ck}
Sol
y=x4+a*x2+b*x+c
KaTeX parse error: Expected group after '^' at position 2: x^̲ 的量级(4096位)远远大于后面几项之和(最大仅2560位)。 这意味着 $$的值几乎完全由 的值几乎完全由 的值几乎完全由x^决定,后面的 决定,后面的 决定,后面的ax^2 + bx + 相对于 相对于 相对于x^来说只是一个极小的“误差”。因此,直接对 来说只是一个极小的“误差”。 因此,直接对 来说只是一个极小的“误差”。因此,直接对开四次方根并取整,即可精确得到 开四次方根并取整,即可精确得到 开四次方根并取整,即可精确得到$$
题目给出了一组超大整数参数 y, b, c(约 1024-2048 bit),并给出了一个四次多项式方程:
y=x4+ax2+bx+c y = x^4 + ax^2 + bx + c y=x4+ax2+bx+c
其中 x 和 a 是未知数。题目要求解出 a$的值,最终 Flag 为 flag{md5(a)}。
我们分析方程各项的数量级
虽然方程中存在 ax^2、bx和 c$这些项,但在大整数域下最高次项 x^4 具有绝对的主导地位。
limx→∞ax2+bx+cx4=0
\lim_{x \to \infty} \frac{ax^2 + bx + c}{x^4} = 0
x→∞limx4ax2+bx+c=0
这意味着 y 的值几乎完全由 x^4$决定,因此存在以下近似关系:
y≈x4 ⟹ x≈y4 y \approx x^4 \implies x \approx \sqrt[4]{y} y≈x4⟹x≈4y
由于 y$是大整数,我们可以直接对其开四次方根,并向下取整。
x=⌊y1/4⌋ x = \lfloor y^{1/4} \rfloor x=⌊y1/4⌋
在通过近似计算得到 x的精确整数值后,原方程 y = x^4 + ax^2 + bx + c 就变成了一个仅包含未知数 a 的线性方程。
将原方程中的 x^4、bx 和 c移至等号左侧:
y−x4−bx−c=ax2
y - x^4 - bx - c = ax^2
y−x4−bx−c=ax2
- 等式两边同时除以 x^2,解出 a:
a=y−x4−bx−cx2 a = \frac{y - x^4 - bx - c}{x^2} a=x2y−x4−bx−c
import hashlib
import math
# 1. 填入题目给出的数据
y = 198522960435841482189432265177909868426431703074708363449219028697324701510160286719731988116839697036160581630187160949885168801231636762705713983033164840216512010109089390010468198557792366011939222563881564708155342114969230636341034212421633874868858789397938243061335664034978831542864101231007091988193863118052712382091154270467198016237288512835005671248249864343182623298497353279333896185090300689372993251851698985725825619040354352099701531339567909888327001736937441117830890673409609636796619930226667955511073115983145641056494143455805357766933615911269415380543240132925744315363193308341329870002528062545716167903459438493154302274735882612908825416096548207130441684370171413794313131775960498691561791175984389699661776179746486882233034632979256741127422204726199986374102650551713456275685579171314911783130052174900534533297618292233601037654694611306838303066884626125878765611689352475739052132349596965687310082111652949904102268706219733830038063864703811394198182986558883397487207721093367621647038297033723455527107418011037322197091733780060565430632034276705456268207898108856563488519654411483838153783153351440293627133844440601677480833571012370843618968387929873540183054860492446381685034534436
b = 174037789483961573123221843591212086149338262121485604600830887974881925500571571986993457692950300053374733237615433793104052491813933671943903946487842930332507742237430616633825365722075237337670353892075697891908329460336348924680104502864021820883975695834115061923434753902679746280494899854826747016131
c = 33472511251772997775575923606329341691183359500908766818057589728522254242596438955590397842265158831677351198820346034459567637818737702918129437591421137895764213815168420429024803177742927091662422333575499875132388315943460516679655107714170719351306979285358080183600281172752401859701765071852769889627868633217209348758684870450300228084878260106253395883903968044935866937201968348531962567937222914516096107266208665856669507873113157476771707
# 2. 计算 x
# 因为 a*x^2 + b*x + c 相对于 x^4 非常小,所以 x 等于 y 的整数四次方根
# 使用 isqrt 计算平方根的平方根
x = math.isqrt(math.isqrt(y))
# 3. 计算 a
# 原始方程: y = x^4 + a*x^2 + b*x + c
# 移项: a*x^2 = y - c - b*x - x^4
# 解 a: a = (y - c - b*x - x^4) // x^2
numerator = y - c - b*x - x**4
denominator = x**2
# 验证是否整除(如果整除说明 x 正确)
if numerator % denominator == 0:
a = numerator // denominator
print(f"[+] 成功解出 a: {a}")
# 4. 生成 Flag
# flag{md5(a)}
flag_content = hashlib.md5((str(a)).encode()).hexdigest()
flag = f"flag{{{flag_content}}}"
print(f"[+] Flag: {flag}")
else:
print("[-] 解题失败,x 计算可能有误。")
flag{01a6eb898468abbd352300a7a072495c}
aesstudy
AES-CBC 模式下的比特翻转攻击 (Bit Flipping Attack) 每一轮,服务器会给出一个经过 AES-CBC 加密的密文 c1 和对应的明文 m1,同时要求攻击者提交一个新的密文 c2。服务器解密 c2 得到 m2,若 m2 在指定位置(例如 m2[21])满足特定的字符约束,则进入下一轮

这里让ai搓了交互脚本
from pwn import *
import re
import hashlib
import time
import base64
context.log_level = 'info'
context.timeout = 30
def solve_pow(prefix):
prefix = prefix.lower()
start = time.perf_counter()
print(f"[*] 计算 PoW, 前缀: {prefix}")
for a in range(256):
for b in range(256):
for c in range(0x20):
x = bytes((a, b, c))
if hashlib.sha256(x).hexdigest().startswith(prefix):
dur = time.perf_counter() - start
print(f"[+] PoW solved in {dur:.2f}s: x={x.hex()}")
return x
raise RuntimeError("PoW 未找到解")
def xor_bytes(a, b):
return bytes(x ^ y for x, y in zip(a, b))
def extract_multiline_b64(text, anchor):
idx = text.find(anchor)
if idx == -1:
return None
rest = text[idx + len(anchor):]
b64_lines = []
for line in rest.splitlines():
line = line.strip()
# 遇到分隔符或空行停止读取 Base64 块
if not line or line.startswith('#') or line.startswith('@') or line.startswith('Good job'):
break
b64_lines.append(line)
return ''.join(b64_lines)
def solve_round(io, round_data):
"""
处理单轮 AES-CBC Bit Flipping
"""
text = round_data.decode(errors='ignore')
# 提取 c1/m1 的 base64
c1_b64 = extract_multiline_b64(text, "c1.encode('base64')=")
m1_b64 = extract_multiline_b64(text, "m1.encode('base64')=")
if not c1_b64 or not m1_b64:
print("[-] 无法解析 c1 或 m1,可能格式变化或已结束")
return False
try:
c1 = base64.b64decode(c1_b64)
m1 = base64.b64decode(m1_b64)
except Exception as e:
print(f"[-] Base64 解码失败: {e}")
return False
blocks = len(c1) // 16
m1 = m1[:blocks * 16] # 截断多余的
# 解析 m2 的位置约束,例如 # m2[21]=j
constraints = []
# 正则优化:匹配数字索引和目标字符
for idx, ch in re.findall(r"#\s*m2\[(\d+)\]\s*=\s*([^\n\r])", text):
constraints.append((int(idx), ch))
print(f"[*] 本轮约束数量: {len(constraints)}")
# 构造期望的 m2(默认与 m1 相同,仅修改约束位置)
desired = bytearray(m1)
for pos, ch in constraints:
if pos < len(desired):
desired[pos] = ord(ch)
c2_payload = bytearray(c1)
BS = 16
for i in range(1, blocks):
# 当前块的 m1 (原文)
original_m_block = m1[i*BS : (i+1)*BS]
# 当前块的前一块密文 (用于异或产生原文)
original_prev_c_block = c1[(i-1)*BS : i*BS]
# 当前块我们期望的明文
desired_m_block = desired[i*BS : (i+1)*BS]
# 计算翻转后的前一块密文
# New_Prev_C = Old_Prev_C ^ Old_M ^ New_M
new_prev_c_block = xor_bytes(
xor_bytes(original_prev_c_block, original_m_block),
desired_m_block
)
# 将构造好的密文块填入 payload 对应位置
c2_payload[(i-1)*BS : i*BS] = new_prev_c_block
# 发送结果
c2_b64_str = base64.b64encode(c2_payload).decode()
io.sendline(c2_b64_str.encode())
print("[+] 已提交 c2")
return True
def main():
ip = '47.107.168.16'
port = 48252
while True:
try:
io = remote(ip, port)
break
except:
print("[-] 连接失败,重试中...")
time.sleep(1)
# 1. 处理 Banner 和 PoW
data = io.recvuntil(b"x.encode('hex')=", timeout=5)
print(data.decode(errors='ignore'))
# 提取 PoW 前缀
prefix_match = re.search(r"hexdigest\(\)\[0:8\]==['\"]([0-9a-fA-F]{8})['\"]", data.decode(errors='ignore'))
if prefix_match:
prefix = prefix_match.group(1)
pow_ans = solve_pow(prefix)
io.sendline(pow_ans.hex().encode())
print("[+] PoW 发送完成")
# 2. 循环处理每一轮
round_count = 1
while True:
print(f"\n=== Round {round_count} ===")
try:
# 读取直到出现输入提示符
# 这里的特征是 @c2.encode('base64')=
# 但如果出了 Flag,可能就没有这个提示符了,所以需要捕获 EOF 或超时
data = io.recvuntil(b"@c2.encode('base64')=")
except EOFError:
print("[*] 连接关闭 (EOF)")
# 看看关闭前有没有 Flag
print(io.recvall().decode(errors='ignore'))
break
except KeyboardInterrupt:
break
# 检查是否包含 Flag
if b"flag{" in data:
print("\n[!!!] 恭喜!发现 Flag![!!!]")
print(data.decode(errors='ignore'))
break
# 如果没有 Flag,则进行解密
success = solve_round(io, data)
if not success:
print("[-] 解题出现问题,打印当前缓冲区:")
print(data.decode(errors='ignore'))
break
result = io.recvline()
print(f"[Server]: {result.decode(errors='ignore').strip()}")
if b"Good job" not in result and b"Success" not in result:
pass
round_count += 1
io.close()
if __name__ == "__main__":
main()
flag{2b6f73f87a8350b130577ced2c58c454}
Web
baby_ssti
题目提示ssti,打开发现参数是喜闻乐见的name
所以直接{{7*7}}探测

这绝对有ssti了,Jinja2
随便打一条,对import有过滤

直接用
{{(cycler.next.__globals__.os.popen('ls')).read()}}
即可ls
换cat flag.txt即可

flag{26492994-d89b-4b39-8f58-6a2670b56d3f}
RE
Helloworld
拿到exe,直接尝试运行,需要输入东西,一输入就结束,获取不到更多的信息,于是放进ida中查看
可以看到main函数中,开头初始化了两个字符串v28和Buffer

然后依据输入对长度为100的随机缓冲区V3进行修改
其中修改的主要逻辑也还在main函数中:

修改逻辑整合为:
| 高4位(Type) | 低4位(Index) |
|---|---|
| 决定操作类型 | 决定操作位置 |
| 高4位 | 操作说明 |
|---|---|
0x1 | 把 v3[index] 从大写转小写 |
0x2 | 把 v3[index] 从小写转大写 |
0x3 | 设置一个全局变量 v9 = 输入下一个字节的低5位 |
0x4 | 把 v3[index] = v28[v9](v28 是 "abcdefghijklmnopqrstuvwxyz") |
0x5 | 把 v3[index] = 某个未知值 v18(未初始化,可能为0) |
并且我们注意到最底下还有一些匿名函数,追踪验证后发现是std::cout的输出封装
也就是说我们的核心任务是想办法让v3满足需求
但是v3是随机的,设置的srand是伪随机数
v4 = time64(0);
srand(v4);
time64只精确到秒,也就是说完全可以直接尝试出来
可以自己写一个c语言程序来罗列可能值:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* 生成给定 seed 的前 100 字节,格式:hex 空格分隔 */
void dump100(unsigned int seed)
{
unsigned char buf[100];
srand(seed);
for (int i = 0; i < 100; ++i)
buf[i] = rand() & 0xFF;
printf("seed=%10u ", seed);
for (int i = 0; i < 100; ++i)
printf("%02X", buf[i]);
putchar('\n');
}
int main(void)
{
time_t now = time(NULL);
for (int delta = -60; delta <= 60; ++delta)
dump100((unsigned int)(now + delta));
return 0;
}
最后拿出一串来进行尝试即可:
from pwn import remote
HOST = "47.107.164.227"
PORT = 45791
def build_payload() -> bytes:
hex_bytes = [
0x30, 0x07, 0x31, 0x04, 0x32, 0x0B, 0x33, 0x0B,
0x34, 0x0E, 0x35, 0x16, 0x36, 0x0E, 0x37, 0x11,
0x38, 0x0B, 0x39, 0x03, 0x10, 0x41, 0x15, 0x41,
0x4A, 0x41,
]
return bytes(hex_bytes)
def exploit():
payload = build_payload()
io = remote(HOST, PORT)
_ = io.recv(timeout=1)
io.sendline(payload)
io.interactive()
if __name__ == "__main__":
exploit()
得到最后的flag为:flag{351bae89be05efa178d61be9a61ac6b7}
Easyre

主函数逻辑很简单程序首先校验用户输入的 32 字节字符串,随后依次经过 first 函数预处理和 crypt 函数(使用硬编码字符串作为 Key)加密,最后将处理后的数据每 4 字节转换为整数,在经过“异或常量 v12”及“基于符号的条件异或 0x22222222”两步混淆后,与内置的整数数组 v6 进行比对以验证 Flag

crypt 函数逻辑分析:标准 RC4 算法 first函数 用户输入的 32 字节 字符串进行 Base64 编码,生成 44 字节的中间数据。
根据v6 ,符号位逻辑逆向还原出异或前的整数,再异或常量恢复出 RC4 密文;接着利用 RC4 算法的对称性,使用相同密钥对密文进行解密;最后对解密结果进行 Base64 解码,即可还原出最终的 Flag
import struct
import base64
def solve():
v6 = [
-56045301, 1126325548, 1037697210, 2123048962,
1640073719, -454381817, -2146442625, -691840689,
1448341866, 586039113, -1321770811
]
v12 = -1872737962 # 0x905f2956
xor_magic = 0x22222222
rc4_key = b"flag{do_you_find_it_?}"
print("[*] 第一步: 逆向校验循环,恢复 RC4 密文...")
rc4_ciphertext = b""
for val in v6:
# 辅助函数:转为32位有符号整数
def to_signed(n):
return (n & 0xffffffff ^ 0x80000000) - 0x80000000
# 情况 A: 正向流程中没有执行异或 (即 temp <= 0)
cand_a = val
# 情况 B: 正向流程中执行了异或 (即 temp > 0)
cand_b = val ^ xor_magic
recovered_temp = 0
if to_signed(cand_b) > 0:
recovered_temp = cand_b
elif to_signed(cand_a) <= 0:
recovered_temp = cand_a
else:
print("[-] 逻辑错误,无法还原整数")
return
original_int = recovered_temp ^ v12
rc4_ciphertext += struct.pack("<I", original_int & 0xffffffff)
print(f"[+] 恢复出密文 (Hex): {rc4_ciphertext.hex()}")
# ---------------------------------------------------------
# 2. RC4 解密
# ---------------------------------------------------------
print("\n[*] 第二步: RC4 解密...")
# 标准 RC4 实现
def rc4(data, key):
s = list(range(256))
j = 0
for i in range(256):
j = (j + s[i] + key[i % len(key)]) % 256
s[i], s[j] = s[j], s[i]
i = 0
j = 0
res = bytearray()
for char in data:
i = (i + 1) % 256
j = (j + s[i]) % 256
s[i], s[j] = s[j], s[i]
res.append(char ^ s[(s[i] + s[j]) % 256])
return res
decrypted_data = rc4(rc4_ciphertext, rc4_key)
print(f"[+] 解密结果 (Base64): {decrypted_data}")
# ---------------------------------------------------------
# 3. Base64 解码
# ---------------------------------------------------------
print("\n[*] 第三步: Base64 解码获取 Flag...")
try:
# 去掉可能的空字节填充
b64_str = decrypted_data.rstrip(b'\x00')
flag = base64.b64decode(b64_str)
print(f"\n[SUCCESS] Flag found: {flag.decode()}")
except Exception as e:
print(f"[-] Base64 解码错误: {e}")
print("请检查解密结果是否为有效的 Base64 字符串。")
if __name__ == "__main__":
solve()
flag{eb3564f89sf45b24oac12db8ec}
babyre

看到了rc4看能不能找到密文密钥
按x找引用得到


该函数首先通过异或 解密出 “success!” 和 “fail” 提示字符串,随后校验 off_6CB090 处的内存数据是否为 “is_good}”;如果匹配,则以该字符串为 密钥 初始化 RC4 S盒(调用 sub_4009DC),接着对 off_6CB090 - 10 处的数据进行 RC4 运算(调用 sub_400B96),最后将运算结果与一组硬编码的 64 位整数(如 0x2F34ED427B495C01LL 等)进行比对,完全一致则输出成功信息

import struct
def solve():
# 1. 准备密文 (将硬编码的整数转为字节流)
# 注意:使用 <Q (unsigned long long) 和 <H (unsigned short) 处理小端序
c1 = struct.pack('<Q', 0x2F34ED427B495C01)
c2 = struct.pack('<Q', 0x8B526A1E2EABAA4F)
c3 = struct.pack('<H', 33807) # 33807 = 0x840F
ciphertext = c1 + c2 + c3
print(f"[*] 提取出的密文 (Hex): {ciphertext.hex().upper()}")
# 2. 准备密钥 (代码中检查的字符串)
key = b"is_good}"
print(f"[*] 密钥: {key}")
# 3. RC4 解密
# 初始化 S盒 (KSA)
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
# 生成密钥流并异或 (PRGA)
i = 0
j = 0
plaintext = bytearray()
for char in ciphertext:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
plaintext.append(char ^ k)
# 4. 输出结果
print(f"\n[+] Flag: {plaintext.decode(errors='ignore')}")
if __name__ == "__main__":
solve()
flag{fini_is_good}
Veryez

提取字节码:从 unk_408254 提取出指令流,验证了我们的逻辑猜测(确实存在循环读取和异或)。
提取内存数据:从 unk_408030 提取出原始的 Hex 数据。。
def solve():
# 完整数据 dump
hex_str = "76 69 72 74 75 61 6C 4D 10 05 13 13 0E 51 5B 29 45 5E 44 42 47 53 5B 7A 47 51 16 4C 45 58 58 2F 12 29 43 12 47 03 0F 29 46 51 11 15 45 00 0F 2E 15 0B 47 15 44 02 11 00"
data = bytes.fromhex(hex_str.replace('\n', ' '))
# Mask: "virtualM" (Offset 0)
mask = data[0:8]
# Key: Offset 8
# 数据总长 56. Offset 8 到结尾有 48 字节. 足够覆盖 Flag.
key = data[8:]
flag = ""
for i, k in enumerate(key):
m = mask[i % 8]
dec = k ^ m
if dec == 0: break # String terminator
flag += chr(dec)
print(f"[+] Flag: {flag}")
if __name__ == "__main__":
solve()
flag{07d3766227718d8094bd@1f2bcd08ca0acccb5a1c}
Misc
hundred
附件拿到,attachment,也没说啥信息,拿到随波逐流,发现binwalk里有txt,且又是一个.文件

很快意识到这可能是循环binwalk提取,直到最后一层提取出flag 但是单纯打开提取的内容又提示损坏,于是依据经验,想到反转字节提取,这时只能写脚本了 循环解包每层 flagN (每次读取后再反转)直到同时找到 hint.txt 和 其他可能的txt ;并将 hint 的令牌序列(含通配 I_am_here 与否定 !type )映射为字符类别规则,在按 latin1 解码的 enc 中进行重叠匹配,发现针对每个命中子串取第 11 个字符可依次拼成 secret ,随后先做 Base64 解码再做 Base32 解码,得到明文并打印最终 FLAG 。
于是结合题目提示,给ai梭一个脚本出来
import zipfile, io, base64, re
from pathlib import Path
base_dir = Path(__file__).parent
data = (base_dir / "attachment").read_bytes()[::-1]
while True:
with zipfile.ZipFile(io.BytesIO(data)) as z:
names = z.namelist()
s = set(names)
other_txts = [n for n in names if n.endswith(".txt") and n != "hint.txt"]
if "hint.txt" in s and other_txts:
hint = z.read("hint.txt")
enc = z.read(other_txts[0])
break
flag_name = next(n for n in names if n.startswith("flag"))
data = z.read(flag_name)[::-1]
tokens = hint.decode().strip().split()
cls_map = {
"digits": r"[0-9]",
"lowercase": r"[a-z]",
"uppercase": r"[A-Z]",
"other": r"[^0-9A-Za-z]",
}
neg_map = {
"digits": r"[^0-9]",
"lowercase": r"[^a-z]",
"uppercase": r"[^A-Z]",
"other": r"[0-9A-Za-z]",
}
parts = []
for tok in tokens:
if tok == "I_am_here":
parts.append(".")
elif tok.startswith("!"):
parts.append(neg_map[tok[1:]])
else:
parts.append(cls_map[tok])
regex_body = "".join(parts)
regex = re.compile(r"(?=(" + regex_body + r"))", re.DOTALL)
enc_text = enc.decode("latin1")
L = len(tokens)
hits = 0
secret_chars = []
for m in regex.finditer(enc_text):
i = m.start(1)
secret_chars.append(enc_text[i + 10])
hits += 1
print("hits:", hits)
b1 = base64.b64decode("".join(secret_chars))
b2 = base64.b32decode(b1)
print("FLAG:", b2.decode())
水果
到手一张ai图,直接看好像没啥,随波逐流提取一下,发现有base64字节

直接解,发现为一段话
你这苹果怎么这么大
大个儿才值钱你要不要
这桃子怎么这么硬
硬是因为新鲜你要软的还是糯的
这西瓜能吃吗看起来有点不熟
不熟的西瓜怎么可能你这就是等着吃甜的
你这柚子这么小
小巧的才好吃你要大个的还是好吃的
这橙子怎么这么酸
酸才是正宗的橙子你要是甜的去别家看
这香蕉有点弯
弯的香蕉更甜你不懂
你这梨子是不是有点硬
硬是因为新鲜吃着有口感
这葡萄怎么这么小
小的葡萄更浓缩甜味

看名字是oursecret猜测还有隐写,但是这个隐写需要密码看来题目图片附件很奇怪不取名中文而是拼音,所以猜测这是密码,成功得到了flag.txt

打开一看还是熟悉的水果的对话

看来是自定义的解密,之前的一段话刚好是16行很难不想到是hex密码对照本,第一行对应0以此类推,让ai写脚本解一下
import binascii
import os
# --- 1. 定义对话字典和 Hex 映射 ---
def build_mapping_dict():
"""定义 16 句对话到 16 进制字符的映射 (0 到 f)。"""
dialogues = [
'你这苹果怎么这么大',
'大个儿才值钱你要不要',
'这桃子怎么这么硬',
'硬是因为新鲜你要软的还是糯的',
'这西瓜能吃吗看起来有点不熟',
'不熟的西瓜怎么可能你这就是等着吃甜的',
'你这柚子这么小',
'小巧的才好吃你要大个的还是好吃的',
'这橙子怎么这么酸',
'酸才是正宗的橙子你要是甜的去别家看',
'这香蕉有点弯',
'弯的香蕉更甜你不懂',
'你这梨子是不是有点硬',
'硬是因为新鲜吃着有口感',
'这葡萄怎么这么小',
'小的葡萄更浓缩甜味'
]
hex_chars = '0123456789abcdef'
return {dialogue: hex_chars[i] for i, dialogue in enumerate(dialogues)}
# --- 2. 文件读取函数 ---
def load_ciphertext(file_path="flag.txt"):
"""从文件中加载密文行。"""
if not os.path.exists(file_path):
print(f"❌ 错误: 找不到密文文件 '{file_path}'。")
print("请将题目提供的乱序对话保存为这个文件,并确保文件存在。")
return []
try:
with open(file_path, 'r', encoding='utf-8') as f:
lines = [line.strip() for line in f if line.strip()]
print(f"✅ 成功加载密文文件,共 {len(lines)} 行对话。")
return lines
except Exception as e:
print(f"加载密文文件时发生错误: {e}")
return []
# --- 3. 转换和解码函数 ---
def convert_dialogues_to_hex(dialogue_lines, mapping):
"""将对话行转换为 Hex 字符串。"""
hex_str = ""
unmatched_count = 0
for line in dialogue_lines:
if line in mapping:
hex_str += mapping[line]
else:
unmatched_count += 1
if unmatched_count > 0:
print(f"⚠️ 注意: 密文中包含 {unmatched_count} 行未在字典中匹配的句子,这些行已被忽略。")
return hex_str
def decode_hex_to_text(hex_str):
"""将 Hex 字符串解码为字节和文本。"""
# 检查 Hex 长度是否为偶数
if len(hex_str) % 2 != 0:
print(f"⚠️ Hex 串长度为奇数 ({len(hex_str)}),末尾补 '0' 尝试解码。")
hex_str += '0'
print("-" * 30)
print(f"💻 最终 Hex 串 ({len(hex_str)}位): {hex_str}")
try:
data = binascii.a2b_hex(hex_str)
print("💾 字节数据:", data)
# 尝试 UTF-8 解码
try:
print("🎉 UTF-8 解码结果:", data.decode("utf-8"))
except UnicodeDecodeError:
print("❌ UTF-8 解码失败,请检查字节数据是否需要使用其他编码 (如 GBK) 或 Flag 本身是二进制。")
except Exception as e:
print(f"❌ Hex 解码失败: {e}")
def main():
# 1. 构建映射字典
mapping = build_mapping_dict()
# 2. 从 flag.txt 文件加载密文
ciphertext_lines = load_ciphertext(r"C:\Users\mikko\Desktop\比赛\水果\_shuiguo.jpg.extracted\49CA19\flag.txt")
if not ciphertext_lines:
return
# 3. 转换密文为 Hex 字符串
hex_str = convert_dialogues_to_hex(ciphertext_lines, mapping)
if not hex_str:
print("解密终止:未生成有效的 Hex 字符串。")
return
# 4. 解码并输出结果
decode_hex_to_text(hex_str)
if __name__ == "__main__":
main()
flag{3e25960a79dbc69b674cd4ec67a72c62}
Sudoku
拿到题目,打开压缩包发现两个png图片
可以看出一个是数独题目,另一个则是打乱的flag
由于题目描述可以得到23年曾有相似的题目
找到得出需要使用gaps来自动复原分块
命令自动拼图,但是一时想不到思路 https://cloud.tencent.com/developer/article/2369022

请检查字节数据是否需要使用其他编码 (如 GBK) 或 Flag 本身是二进制。")
except Exception as e:
print(f"❌ Hex 解码失败: {e}")
def main():
# 1. 构建映射字典
mapping = build_mapping_dict()
# 2. 从 flag.txt 文件加载密文
ciphertext_lines = load_ciphertext(r"C:\Users\mikko\Desktop\比赛\水果\_shuiguo.jpg.extracted\49CA19\flag.txt")
if not ciphertext_lines:
return
# 3. 转换密文为 Hex 字符串
hex_str = convert_dialogues_to_hex(ciphertext_lines, mapping)
if not hex_str:
print("解密终止:未生成有效的 Hex 字符串。")
return
# 4. 解码并输出结果
decode_hex_to_text(hex_str)
if name == “main”:
main()
flag{3e25960a79dbc69b674cd4ec67a72c62}
## Sudoku
拿到题目,打开压缩包发现两个png图片
可以看出一个是数独题目,另一个则是打乱的flag
由于题目描述可以得到23年曾有相似的题目
找到得出需要使用gaps来自动复原分块
命令自动拼图,但是一时想不到思路 https://cloud.tencent.com/developer/article/2369022
[外链图片转存中...(img-soITYrKn-1765209211814)]
想到23年古剑山有类似题目,找到这个博客的flag尝试直接拿去交,发现居然就对了。。
更多推荐
所有评论(0)