强网杯 2025 线上赛

Ramoor 碎碎念

本次以 Dawn 战队的密码手出征 qwb S9,线上赛基本已经告一段落了,怎么说呢,在此期间也经历了很多,自己在某些方面也成熟了很多。在比赛剩几分钟快结束的时候,看着排名一直从19降到29的时候,心是悬的。密码两天只做出了两道简单题(不想提一上午看了一道题结果题目撤回的这件事了),看着那么多成百上千解的 misc 题,我毫无思路的时候,心如死灰,害怕因为自己做不出 50 pts 的题而葬送了队友的努力。不过结果是好的,比赛结束的最后一刻极限定格到了 29 名。

原本觉得事情就此结束了,结果后续又经历了误判风波,在提交完申诉书焦虑等待了好几天之后终于有了结果,证实了确实是误判,最终的成绩是 21 名。感谢猛猛带飞的队友们,我以后肯定会更加努力地 CTF 的!\\\٩( ‘ω’ )و ////

Crypto

check-little

分析:
e=3 较小,若 key 较小的话应该直接开方就行,但是尝试了之后不可行,毕竟不会这么直接,也就是说 $key^3>N$,key应该是一个较大的数,爆破了很长时间也打不出来,尝试转换思路。
后来经过多次尝试发现 N 和 c 是不互素的,直接求解最大公因数就能求出 p,q,之后就能求解出 flag 了。
exp.py:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Util.number import *
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
import gmpy2
N = 18795243691459931102679430418438577487182868999316355192329142792373332586982081116157618183340526639820832594356060100434223256500692328397325525717520080923556460823312550686675855168462443732972471029248411895298194999914208659844399140111591879226279321744653193556611846787451047972910648795242491084639500678558330667893360111323258122486680221135246164012614985963764584815966847653119900209852482555918436454431153882157632072409074334094233788430465032930223125694295658614266389920401471772802803071627375280742728932143483927710162457745102593163282789292008750587642545379046283071314559771249725541879213
c = 10533300439600777643268954021939765793377776034841545127500272060105769355397400380934565940944293911825384343828681859639313880125620499839918040578655561456321389174383085564588456624238888480505180939435564595727140532113029361282409382333574306251485795629774577583957179093609859781367901165327940565735323086825447814974110726030148323680609961403138324646232852291416574755593047121480956947869087939071823527722768175903469966103381291413103667682997447846635505884329254225027757330301667560501132286709888787328511645949099996122044170859558132933579900575094757359623257652088436229324185557055090878651740
iv = b'\x91\x16\x04\xb9\xf0RJ\xdd\xf7}\x8cW\xe7n\x81\x8d'
ciphertext = 0xbf87027bc63e69d3096365703a6d47b559e0364b1605092b6473ecde6babeff2
e = 3
p = gmpy2.gcd(N,c)
q = N // p
phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi)
key1 = pow(c,d,N)
print(key1)
plaintext = AES.new(key = long_to_bytes(key1)[:16], iv = iv, mode = AES.MODE_CBC).decrypt(long_to_bytes(ciphertext))
print(plaintext)

The_Interrogation_Room

exp.py

1
2
3
4
5
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
from pwn import remote, context, log
import re, itertools, hashlib, time, string, sys

context.log_level = 'info'

# fixed target
host = "8.147.132.101"
port = 26651

ALPHANUM = string.ascii_letters + string.digits
WHITE_TOKENS = set(['==','(',')','S0','S1','S2','S3','S4','S5','S6','S7','0','1','and','or'])
POW_RE = re.compile(r"sha256\(XXXX\+([^\)]+)\)\s*==\s*([0-9a-f]{64})")

M = [
[1,0,0,0,0,0,0,0],
[0,1,0,0,0,0,0,0],
[0,0,1,0,0,0,0,0],
[0,0,0,1,0,0,0,0],
[0,0,0,0,1,0,0,0],
[0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,1,0],
[0,0,0,0,0,0,0,1],
[1,1,1,1,1,0,0,0],
[1,0,1,0,1,1,0,0],
[0,1,1,0,0,1,1,0],
[1,1,0,0,1,1,1,0],
[1,1,1,0,0,1,1,1],
[0,1,1,1,1,0,0,1],
[1,0,1,1,0,1,0,1],
[1,1,0,1,0,0,1,1],
[1,1,1,1,1,1,1,1],
]
assert len(M) == 17 and all(len(r)==8 for r in M)

# token helpers
def token_wrap(toklist):
if len(toklist) == 1:
return toklist[:]
return ['('] + toklist[:] + [')']

def xor_two_tokens(A, B):
Aw = token_wrap(A); Bw = token_wrap(B)
inner = ['('] + Aw + ['=='] + Bw + [')']
expr = ['('] + inner + ['==', '0'] + [')']
return expr

def xor_subset_tokens(indices):
if not indices:
return ['0']
toks = ['S' + str(indices[0])]
for idx in indices[1:]:
toks = xor_two_tokens(toks, ['S' + str(idx)])
return toks

def tokens_to_query(tokens):
for t in tokens:
if t == "":
raise ValueError("Empty token produced")
if not (t in WHITE_TOKENS or re.fullmatch(r"S[0-7]", t)):
raise ValueError("Invalid token produced: " + repr(t))
return " ".join(tokens)

# evaluator (internal)
def eval_expression(expr, candidate):
tokens = []; i = 0; s = expr
while i < len(s):
c = s[i]
if c.isspace():
i += 1; continue
if c in '()':
tokens.append(c); i += 1; continue
if c in '01':
tokens.append(True if c == '1' else False); i += 1; continue
j = i
while j < len(s) and (s[j].isalnum() or s[j] in '_='):
j += 1
word = s[i:j]; i = j
if re.fullmatch(r"S[0-7]", word):
tokens.append(bool(candidate[int(word[1])]))
elif word in ('and','or','=='):
tokens.append(word)
elif word.lower() in ('true','false'):
tokens.append(True if word.lower()=='true' else False)
else:
raise ValueError("Unknown token in eval: "+repr(word))
prec = {'==':3, 'and':2, 'or':1}
output = []; ops = []
for t in tokens:
if t is True or t is False:
output.append(t)
elif t == '(':
ops.append(t)
elif t == ')':
while ops and ops[-1] != '(':
output.append(ops.pop())
if not ops or ops[-1] != '(':
raise ValueError("Mismatched parentheses")
ops.pop()
elif t in prec:
while ops and ops[-1] != '(' and prec.get(ops[-1], -1) >= prec.get(t, -1):
output.append(ops.pop())
ops.append(t)
else:
raise ValueError("Unknown token in shunting-yard eval: "+str(t))
while ops:
if ops[-1] == '(':
raise ValueError("Mismatched parentheses at end")
output.append(ops.pop())
stack = []
for t in output:
if t is True or t is False:
stack.append(t)
elif t == '==':
b = stack.pop(); a = stack.pop(); stack.append(a == b)
elif t == 'and':
b = stack.pop(); a = stack.pop(); stack.append(a and b)
elif t == 'or':
b = stack.pop(); a = stack.pop(); stack.append(a or b)
else:
raise ValueError("Unknown RPN op: "+str(t))
if len(stack) != 1:
raise ValueError("RPN ended with stack size !=1")
return stack[0]

# POW
def solve_pow_from_text(text):
m = POW_RE.search(text)
if not m:
raise ValueError("POW not found")
suffix = m.group(1); target = m.group(2)
log.info("POW found; trying numeric then alnum brute-force...")
for i in range(10000):
cand = f"{i:04}".encode()
if hashlib.sha256(cand + suffix.encode()).hexdigest() == target:
log.success("POW numeric found: " + cand.decode()); return cand.decode()
total = len(ALPHANUM)**4; tried = 0
for comb in itertools.product(ALPHANUM, repeat=4):
tried += 1
cand = ''.join(comb).encode()
if hashlib.sha256(cand + suffix.encode()).hexdigest() == target:
log.success("POW found: " + cand.decode()); return cand.decode()
if tried % 2000000 == 0:
log.info(f"POW progress: tried {tried}/{total}")
raise RuntimeError("POW not found")

# parse response
def parse_prisoner_response(text):
if text is None:
raise ValueError("Empty")
s = text.replace("\n"," ").replace("!"," ").strip()
if "refuse to answer" in s or "I refuse to answer" in s:
raise RuntimeError("Server refused phrasing: " + s)
m = re.search(r"Prisoner's response:\s*([A-Za-z0-9]+)", s, re.IGNORECASE)
if m:
tok = m.group(1)
if tok.lower() == 'true' or tok == '1': return True
if tok.lower() == 'false' or tok == '0': return False
m2 = re.search(r"\b(True|False|1|0)\b", s, re.IGNORECASE)
if m2:
tok = m2.group(1).lower()
return tok == 'true' or tok == '1'
for ch in s:
if ch == '1': return True
if ch == '0': return False
raise ValueError("Cannot parse prisoner response: "+repr(text))

# recovery
def recover_from_observed(observed):
n = 17
def encode(msg_bits):
out = []
for row in M:
s = 0
for j,b in enumerate(row):
if b and msg_bits[j]:
s ^= 1
out.append(bool(s))
return out
for k in range(3):
for flips in itertools.combinations(range(n), k):
corr = observed[:]
for idx in flips:
corr[idx] = not corr[idx]
for mask in range(256):
msg = [bool((mask>>i)&1) for i in range(8)]
if encode(msg) == corr:
return [1 if x else 0 for x in msg]
raise ValueError("No valid decoding under ≤2 flips assumption")

# --- 关键:使用全局缓冲 leftover 来保存未消费的数据,避免吞掉下一轮提示 ---
def wait_for_round_prompt_with_leftover(r, leftover, timeout=30):
"""
leftover: a string that may already contain previously-read-but-unprocessed data.
Returns (accumulated_text, updated_leftover) where accumulated_text contains at least
the prompt ('Ask your question:' or 'Now reveal the true secrets') or server end messages.
updated_leftover contains any extra text beyond what caller needs to process now (so we don't lose it).
If timeout and no data -> returns (None, leftover).
"""
acc = leftover or ""
start = time.time()
# if acc already contains prompt or end message, return immediately without consuming more
if acc and ('Ask your question:' in acc or 'Now reveal the true secrets' in acc or 'fell for my deception' in acc or 'you win' in acc.lower() or 'confesses all his secrets' in acc.lower()):
return acc, ""
while True:
try:
chunk = r.recv(timeout=1)
except Exception:
chunk = b""
if chunk:
try:
s = chunk.decode(errors='ignore')
except:
s = str(chunk)
acc += s
# if we've got interesting markers, return but keep no leftover (we'll return whole acc and empty leftover)
if 'Ask your question:' in acc or 'Now reveal the true secrets' in acc or 'fell for my deception' in acc or 'you win' in acc.lower() or 'confesses all his secrets' in acc.lower():
# we may have extra data beyond the first occurrence; but caller expects full acc to search in it
return acc, ""
else:
# no chunk read this iteration
if time.time() - start > timeout:
# timeout: if acc non-empty return it, else return None to indicate no data
if acc:
return acc, ""
else:
return None, leftover

def recv_until_prompt_or_collect(r, leftover, timeout=8):
"""
Read from socket (appended to leftover) until we can parse a prisoner response or timeout.
Returns (collected_text, updated_leftover). collected_text may contain the response and possibly extra.
"""
acc = leftover or ""
start = time.time()
while True:
# try to parse current acc for prisoner response
try:
# if we can parse, return acc and empty leftover
_ = parse_prisoner_response(acc)
return acc, ""
except Exception:
pass
try:
chunk = r.recv(timeout=1)
except Exception:
chunk = b""
if chunk:
try:
s = chunk.decode(errors='ignore')
except:
s = str(chunk)
acc += s
# detect refusal immediate
if "The prisoner smirks" in acc or "I refuse to answer" in acc:
return acc, ""
# try parse again in next loop
else:
if time.time() - start > timeout:
# timeout: return whatever we've got (maybe empty)
return acc, ""
# unreachable

# main run with leftover buffer
def run():
r = remote(host, port, timeout=10)
log.info(f"Connected to {host}:{port}")
data = b""; pow_line = None; t0 = time.time()
while time.time() - t0 < 5:
try:
chunk = r.recv(timeout=1)
except Exception:
chunk = b""
if chunk:
data += chunk
txt = data.decode(errors='ignore')
if 'sha256(' in txt:
m = POW_RE.search(txt)
if m:
pow_line = m.group(0); break
if not pow_line:
try:
more = r.recvrepeat(timeout=2)
if more:
data += more
txt = data.decode(errors='ignore')
m = POW_RE.search(txt)
if m:
pow_line = m.group(0)
except Exception:
pass
if not pow_line:
log.failure("POW not found; server banner:\n" + data.decode(errors='ignore'))
r.close(); return
log.info("POW challenge: " + pow_line)
try:
prefix = solve_pow_from_text(pow_line)
except Exception as e:
log.failure("POW failed: " + str(e)); r.close(); return
r.sendline(prefix)
log.info("Sent POW solution")

rounds = 0
leftover = "" # IMPORTANT: persist unconsumed server text across rounds
try:
while True:
txt, leftover = wait_for_round_prompt_with_leftover(r, leftover, timeout=30)
if txt is None:
log.info("No prompt received and no data; assuming connection closed.")
break
log.debug("Pre-round text (accumulated): " + (txt.replace("\n"," | ")[:1000]))
if 'fell for my deception' in txt or 'laughs triumphantly' in txt:
log.failure("Server indicated failure: " + txt.replace("\n"," | "))
break
if 'you win' in txt.lower() or 'confesses all his secrets' in txt.lower():
log.success("Server indicates success/flag: " + txt.replace("\n"," | "))
# continue to try to read next round if server continues

observed = []
queries_texts = []

for qi, row in enumerate(M):
indices = [i for i,b in enumerate(row) if b]
toklist = xor_subset_tokens(indices)
qstr = tokens_to_query(toklist)
queries_texts.append(qstr)
# wait for per-question prompt using leftover if any
# if leftover contains the prompt, consume it
if leftover and 'Ask your question:' in leftover:
# consume up to the prompt (we simply drop it from leftover; the send will follow)
# (leftover already includes the prompt text)
# clear leftover because we've used it
leftover = "" # safe: we keep any extra in leftover management later
else:
try:
r.recvuntil(b"Ask your question:", timeout=6)
except Exception:
log.debug("Did not see per-question prompt before sending; proceeding.")
log.info(f"Sending query [{qi+1}/17]: {qstr!r}")
r.sendline(qstr)

# read and accumulate until we can parse the prisoner's response
# use recv_until style but keep any extra in leftover
acc = ""
got = False
t1 = time.time()
while time.time() - t1 < 8:
try:
chunk = r.recv(timeout=1)
except Exception:
chunk = b""
if chunk:
try:
s = chunk.decode(errors='ignore')
except:
s = str(chunk)
acc += s
# detect refusal early
if "The prisoner smirks" in acc or "I refuse to answer" in acc:
log.failure("Server refused phrasing: " + acc.replace("\n"," | "))
r.close(); return
try:
val = parse_prisoner_response(acc)
# If there is extra trailing data after matched response, try to preserve it into leftover.
# We cannot precisely split, but we can attempt: find first occurrence of "Prisoner's response:"
# and keep text after that line as leftover.
# Find index of the match end by searching for the token (True/False/1/0) appearance.
# Simpler: we will set leftover = "" and rely on wait_for_round_prompt_with_leftover to read further.
observed.append(val)
got = True
break
except RuntimeError as rexc:
log.failure(str(rexc)); r.close(); return
except Exception:
pass
else:
pass
if not got:
try:
tail = r.recvrepeat(timeout=1).decode(errors='ignore')
acc += tail
val = parse_prisoner_response(acc)
observed.append(val)
except Exception as e:
log.failure("Failed to parse response for query %s collected=%r err=%s" % (qstr, acc, str(e)))
r.close(); return
log.info(f"Got [{qi+1}/17]: {observed[-1]}")
time.sleep(0.02)

# Wait for reveal prompt but avoid consuming next-round welcome into nowhere:
# Try to read until we see 'Now reveal the true secrets' or small timeout; keep any extra into leftover.
acc2 = ""
t2 = time.time()
while time.time() - t2 < 4:
try:
chunk = r.recv(timeout=1)
except Exception:
chunk = b""
if chunk:
try:
s = chunk.decode(errors='ignore')
except:
s = str(chunk)
acc2 += s
if 'Now reveal the true secrets' in acc2:
# keep acc2 appended to leftover for next loop (we consumed it here)
# but we don't want to lose anything after the phrase; we capture all into leftover
leftover = acc2.split('Now reveal the true secrets',1)[1]
break
else:
# no more data right now
pass
# attempt recovery
try:
recovered = recover_from_observed(observed)
except Exception as e:
log.exception("Recovery failed: " + str(e))
r.close(); return

ans_line = " ".join(map(str, recovered))
log.success("Recovered secrets: " + ans_line)
r.sendline(ans_line)

# After submission: read a bit but DO NOT discard data — store into leftover
acc_after = ""
t3 = time.time()
while time.time() - t3 < 4:
try:
chunk = r.recv(timeout=0.8)
except Exception:
chunk = b""
if chunk:
try:
s = chunk.decode(errors='ignore')
except:
s = str(chunk)
acc_after += s
else:
# no chunk
pass
# Put everything we read into leftover for next round handling
leftover = (leftover or "") + acc_after
log.info("Server after submission (buffered):\n" + (acc_after.strip()[:1000] if acc_after else "<none>"))
# If server indicates failure explicitly, break
if acc_after and ('fell for my deception' in acc_after or 'laughs triumphantly' in acc_after):
log.failure("Server indicated round failure; stopping. " + acc_after.replace("\n"," | "))
break
if acc_after and ('you win' in acc_after.lower() or 'confesses all his secrets' in acc_after.lower()):
log.success("Server indicates success/flag: " + acc_after.replace("\n"," | "))
# continue; next loop will process leftover which may contain next-round welcome
rounds += 1
log.info("Completed round #%d" % rounds)
time.sleep(0.05)

except Exception as e:
log.exception("Exception: " + str(e))
finally:
try: r.close()
except: pass
log.info("Connection closed. Rounds: %d" % rounds)

if __name__ == "__main__":
run()

ezran

在网上找了类似题目的解题思路。
参考:
2024-同济大学第二届网络安全新生赛CatCTF-wp-crypto | 糖醋小鸡块的blog
文章 - 停电之后 暂时摆脱了 | NSSCTF

1
2
for i in range(3108):
x=(pow(r1, 2*i, 257) & 0xff) ^ r2

根据分析,我们知道,并上 0xff 说明只有 8 位会对 r2 造成影响,所以 r2 的高 8 位我们是可用直接得到的。
根据对鸡块师傅思路的学习,我们知道了对 257 这个素数进行费马小定理,有:
$$
a ^{256} = 1 \mod 257
$$
由二次剩余的欧拉准则能进一步推导:
$$
a^{128} = 1 \mod 257
$$
$$
a^{128} = -1 \mod 257
$$
此时前者结果是1,后者结果是0。而如果a=0,那么结果是0。也就是说,当i为 64 的整数倍时,本轮的计算结果只能是 0 或 1,也就是说明 r2 = getrandbits(16) 的高 15 位就是密文的高 15 位。
虽然明显比特数是足够的,但为了更好的约束,还是要把能取的都取上。
这样我们就能选取 19968 个来组成向量 b 解线性方程,而获取 T 的思路便与原题一致。
有了 T,b 之后我们就可以解出 state,然后我们就能运行一遍gift,变成与题中 shuffle 前相同的状态,之后我们就能根据之前的 shuffle 操作来恢复 flag。
exp.sage

1
2
3
4
5
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

from Crypto.Util.number import *
from random import *
from tqdm import *

gift = ...
c = ')9Lsu_4s_eb__otEli_nhe_tes5gii5sT@omamkn__ari{efm0__rmu_nt(0Eu3_En_og5rfoh}nkeoToy_bthguuEh7___u'

gift_bytes = long_to_bytes(gift)
RNG = Random()
NUM_ITERATIONS = 3108
MT_STATE_BITS = 19968


def construct_a_row(RNG):
row = []
for i in range(NUM_ITERATIONS):
RNG.getrandbits(8)
r2_val = RNG.getrandbits(16)

r2_high_byte = r2_val >> 8
row += list(map(int, (bin(r2_high_byte)[2:].zfill(8))))

#当 i 为 64 的倍数时,取高 15 位
if (i % 64 == 0):
r2_low_byte = r2_val & 0xff
r2_low_bits_7 = r2_low_byte >> 1
row += list(map(int, (bin(r2_low_bits_7)[2:].zfill(7))))
return row

L = []
for i in trange(MT_STATE_BITS):
state = [0] * 624
temp = "0" * i + "1" + "0" * (MT_STATE_BITS - 1 - i)
for j in range(624):
state[j] = int(temp[32*j : 32*j+32], 2)

RNG.setstate((3, tuple(state + [624]), None))
L.append(construct_a_row(RNG))

L = Matrix(GF(2), L)

R = []
for i in range(NUM_ITERATIONS):
x_high_byte = gift_bytes[2*i]
R += list(map(int, (bin(x_high_byte)[2:].zfill(8))))

if (i % 64 == 0):
x_low_byte = gift_bytes[2*i+1]
x_low_bits_7 = x_low_byte >> 1
R += list(map(int, (bin(x_low_bits_7)[2:].zfill(7))))

R = vector(GF(2), R)

s_particular = L.solve_left(R)

kernel_basis = L.left_kernel().basis()
k_dim = len(kernel_basis)
num_solutions = 2**k_dim


if num_solutions > 1024:
print(f"解空间太大 ({num_solutions}),脚本将只尝试前 1024 个解。")
num_solutions = 1024

print(f"开始爆破 {num_solutions} 个可能的解...")

for j in trange(num_solutions):
s_kernel_part = vector(GF(2), [0] * MT_STATE_BITS)
bin_j = bin(j)[2:].zfill(k_dim)
for i in range(k_dim):
if bin_j[i] == '1':
s_kernel_part += kernel_basis[i]

current_s = s_particular + s_kernel_part

init_bits = "".join(list(map(str, current_s)))
state = []
for i in range(624):
state.append(int(init_bits[32*i : 32*i+32], 2))

recovered_state = (3, tuple(state + [624]), None)

RNG1 = Random()
RNG1.setstate(recovered_state)

#重新运行 gift 生成循环中的所有 PRNG 调用以同步状态
for i in range(NUM_ITERATIONS):
RNG1.getrandbits(8)
RNG1.getrandbits(16)

x = [i for i in range(len(c))]
SHUFFLE_COUNT = 2025
for i in range(SHUFFLE_COUNT):
RNG1.shuffle(x)

flag = ""
for i in range(len(c)):
flag += c[x.index(i)]

if "flag" in flag:
print("\n" + "="*30)
print(f"找到flag !(解 {j}):")
print(flag)
print("="*30)
break

强网杯 2025 线上赛
http://ramoor.github.io/2025/11/05/强网杯 2025 线上赛/
作者
Ramoor
发布于
2025年11月5日
许可协议