2060 words
10 minutes
强网杯 2025
2025-11-04
统计加载中...

前言#

感觉今年比去年难了好多 xd,有种力不从心的感觉了。。。

题目#

check-little#

task.py

from Crypto.Util.number import *
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
import os
flag, key = open('secret').read().split('\n')
e = 3
while 1:
p = getPrime(1024)
q = getPrime(1024)
phi = (p - 1) * (q - 1)
if phi % e != 0:
break
N = p * q
c = pow(key, e, N)
iv = os.urandom(16)
ciphertext = AES.new(key = long_to_bytes(key)[:16], iv = iv, mode = AES.MODE_CBC).encrypt(pad(flag.encode(),16)).hex()
f = open('output.txt', 'w')
f.write(f'N = {N}\n')
f.write(f'c = {c}\n')
f.write(f'iv = {iv}\n')
f.write(f'ciphertext = {ciphertext}\n')

正常人拿到这玩意肯定想小 e 攻击的,发现出不来就加上爆破呗,然后发现还是不行。nmd 是哪个人的 key 是 p 啊?还是 GCD 求出来的

exp.py

from Crypto.Util.number import *
from Crypto.Cipher import AES
import gmpy2
N =
c =
iv =
ciphertext =
e = 3
p = gmpy2.gcd(c,N)
q = N // p
d = inverse(e,(p-1)*(q-1))
key = pow(c,d,N)
aes = AES.new(key = long_to_bytes(key)[:16], iv = iv, mode = AES.MODE_CBC)
flag = aes.decrypt(bytes.fromhex(ciphertext))
print(flag)

ezran#

task.py

from Crypto.Util.number import *
from random import *
f = open('flag.txt', 'r')
flag = f.read().encode()
gift=b''
for i in range(3108):
r1 = getrandbits(8)
r2 = getrandbits(16)
x=(pow(r1, 2*i, 257) & 0xff) ^ r2
c=long_to_bytes(x, 2)
gift+=c
m = list(flag)
for i in range(2025):
shuffle(m)
c = "".join(list(map(chr,m)))
f = open('output.txt', 'w')
f.write(f"gift = {bytes_to_long(gift)}\n")
f.write(f"c = {c}\n")

看见题目先搜到了鸡块的一个题,2024-同济大学第二届网络安全新生赛 CatCTF-wp-crypto,这里第一开始尝试拿博客中的板子去进行矩阵恢复,但是发现恢复出来的中间的 state 是错误的,貌似需要调整并且爆破。

这里翻以前的文章找到了gf2bv这个库,发现solve_all可以求出全部解,那我们只需要索引重新 shuffle2025 次就行了。这里喂给 ai 稍微爆破一点点

exp.py

import struct
from tqdm import tqdm
from random import Random
from Crypto.Util.number import long_to_bytes, bytes_to_long
from sage.all import *
from gf2bv import LinearSystem
from gf2bv.crypto.mt import MT19937
def recover_mt_state(observed_outputs):
system = LinearSystem([32] * 624)
mt_state_vars = system.gens()
mt_instance = MT19937(mt_state_vars)
constraint_list = []
for output_byte in observed_outputs:
mt_instance.getrandbits(8)
rand_16bit = mt_instance.getrandbits(16)
constraint_list.append((rand_16bit >> 8) ^ int(output_byte))
constraint_list.append(mt_state_vars[0] ^ 0x80000000)
solutions = system.solve_all(constraint_list)
return solutions
def decrypt_ciphertext(mt_seed, encrypted_text):
mt_rng = MT19937(mt_seed)
python_rng = mt_rng.to_python_random()
current_state = python_rng.getstate()[1][:-1]
restored_state = current_state + (len(current_state),)
python_rng.setstate((3, restored_state, None))
for _ in range(3108):
python_rng.getrandbits(8)
python_rng.getrandbits(16)
indices = list(range(len(encrypted_text)))
for iteration in range(2025):
python_rng.shuffle(indices)
decrypted = []
for position in range(len(encrypted_text)):
original_pos = indices.index(position)
decrypted.append(encrypted_text[original_pos])
return ''.join(decrypted)
ENCRYPTED_MESSAGE =
GIFT_VALUE =
def main_execution():
gift_bytes = long_to_bytes(int(GIFT_VALUE)) if GIFT_VALUE else b""
processed_gifts = []
byte_pairs = [gift_bytes[i:i+2] for i in range(0, len(gift_bytes), 2)]
for pair in byte_pairs:
numeric_value = bytes_to_long(pair)
processed_gifts.append(numeric_value >> 8)
recovered_states = recover_mt_state(processed_gifts)
for state_candidate in recovered_states:
decrypted_result = decrypt_ciphertext(state_candidate, ENCRYPTED_MESSAGE)
print(f"解密结果: {decrypted_result}")
if __name__ == "__main__":
main_execution()

sk(*)#

task.py

from random import randint
from Crypto.Util.number import getPrime, inverse, long_to_bytes, bytes_to_long
from math import gcd
import signal
from secret import flag
def gen_coprime_num(pbits):
lbits = 2 * pbits + 8
lb = 2**lbits
ub = 2 ** (lbits + 1)
while True:
r = randint(lb, ub)
s = randint(lb, ub)
if gcd(r, s) == 1:
return r, s
def mult_mod(A, B, p):
result = [0] * (len(A) + len(B) - 1)
for i in range(len(A)):
for j in range(len(B)):
result[i + j] = (result[i + j] + A[i] * B[j]) % p
return result
def gen_key(p):
f = [randint(1, 2**128) for i in ":)"]
h = [randint(1, 2**128) for i in ":("]
R1, S1 = gen_coprime_num(p.bit_length())
R2, S2 = gen_coprime_num(p.bit_length())
B = [[randint(1, p - 1) for i in ":("] for j in ":)"]
P = []
for b in B:
P.append(mult_mod(f, b, p))
Q = []
for b in B:
Q.append(mult_mod(h, b, p))
for i in range(len(P)):
for j in range(len(P[i])):
P[i][j] = P[i][j] * R1 % S1
Q[i][j] = Q[i][j] * R2 % S2
sk = [(R1, S1), (R2, S2), f, h, p]
pk = [P, Q, p]
return sk, pk
def encrypt(pk, pt):
P, Q, p = pk
pt = bytes_to_long(pt)
PP = 0
QQ = 0
for i in range(len(P)):
u = randint(1, p)
for j in range(len(P[0])):
PP = PP + P[i][j] * (u * pt**j % p)
QQ = QQ + Q[i][j] * (u * pt**j % p)
return PP, QQ
def decrypt(sk, ct):
RS1, RS2, f, h, p = sk
R1, S1 = RS1
R2, S2 = RS2
P, Q = ct
invR1 = inverse(R1, S1)
invR2 = inverse(R2, S2)
P = (P * invR1 % S1) % p
Q = (Q * invR2 % S2) % p
f0q = f[0] * Q % p
f1q = f[1] * Q % p
h0p = h[0] * P % p
h1p = h[1] * P % p
a = f1q + p - h1p % p
b = f0q + p - h0p % p
pt = -b * inverse(a, p) % p
pt = long_to_bytes(pt)
return pt
if __name__ == "__main__":
# signal.alarm(30)
p = getPrime(256)
sk, pk = gen_key(p)
ticket = long_to_bytes(randint(1, p))
enc_ticket = encrypt(pk, ticket)
print(pk)
print(enc_ticket)
for i in range(2):
op = int(input("op>").strip())
if op == 1:
msg = input("pt:").strip().encode()
ct = encrypt(pk, msg)
print(f"ct: {ct}")
elif op == 2:
user_input = input("ct:").strip().split(" ")
if len(user_input) == 2:
ct = [int(user_input[0]), int(user_input[1])]
else:
print("invalid ct.")
break
user_input = input("your f:").strip().split(" ")
if len(user_input) == 2:
user_f = [int(user_input[0]), int(user_input[1])]
else:
print("invalid f.")
break
user_input = input("your h:").strip().split(" ")
if len(user_input) == 2:
user_h = [int(user_input[0]), int(user_input[1])]
else:
print("invalid h.")
break
user_input = input("your R1 S1:").strip().split(" ")
if len(user_input) == 2:
user_r1s1 = [int(user_input[0]), int(user_input[1])]
else:
print("invalid R1 S1.")
break
user_input = input("your R2 S2:").strip().split(" ")
if len(user_input) == 2:
user_r2s2 = [int(user_input[0]), int(user_input[1])]
else:
print("invalid R2 S2.")
break
pt = decrypt((user_r1s1, user_r2s2, user_f, user_h, p), ct)
if pt == ticket:
print(flag)
else:
print(pt.hex())
else:
print("invalid op.")
break
print("bye!")

这个题主要的加密就是

def encrypt(pk, pt):
P, Q, p = pk
pt = bytes_to_long(pt)
PP = 0
QQ = 0
for i in range(len(P)):
u = randint(1, p)
for j in range(len(P[0])):
PP = PP + P[i][j] * (u * pt**j % p)
QQ = QQ + Q[i][j] * (u * pt**j % p)
return PP, QQ
PP=i=01j=02Pi,j(uiptjmodp)QQ=i=01j=02Qi,j(uiptjmodp)PP = \sum_{i=0}^{1} \sum_{j=0}^{2} P_{i,j} \cdot (u^i \cdot pt^j \bmod p) \\ QQ = \sum_{i=0}^{1} \sum_{j=0}^{2} Q_{i,j} \cdot (u^i \cdot pt^j \bmod p)

在这两个式子里面,我们知道Pi,jP_{i,j}还有Qi,jQ_{i,j},后面的那一堆可以认为是小参数,我们可以通过格去恢复,然后再恢复 pt。

exp.py

from sage.all import *
from pwn import *
import ast
from Crypto.Util.number import *
context.log_level = "debug"
io = process(["python", "task.py"])
io.sendlineafter(b"team token", "".encode())
P, Q, p = ast.literal_eval(io.recvline().decode().lstrip(":"))
pp, qq = ast.literal_eval(io.recvline().decode())
v_p = vector(ZZ, (P[0] + P[1] + [-pp] + [0]))
v_q = vector(ZZ, (Q[0] + Q[1] + [0] + [-qq]))
M = identity_matrix(ZZ, len(v_p))
M = M.augment(matrix(ZZ, [v_p, v_q]).transpose())
M[:, -2:] *= 2**384
L = M.LLL()
res = None
for row in L:
if row[-2] == 0 and row[-1] == 0 and row[-3] == 1 and row[-4] == 1:
print("[+] Found!")
res = row
break
assert res is not None
w00, w01, w02, w10, w11, w12 = res[:6]
m = (w01 * inverse(w00, p)) % p
print(m)
io.recvuntil(b"op>")
io.sendline(b"2")
io.sendline(f"1 {m}".encode())
io.sendline(b"1 0")
io.sendline(b"0 1")
io.sendline(f"1 {p}".encode())
io.sendline(f"1 {p}".encode())
io.interactive()

看很多师傅的博客都是用 cuso 去打的,关键部分是

upt0, upt1, upt2, upt3, upt4, upt5 = var('upt0, upt1, upt2, upt3, upt4, upt5')
f1 = P[0][0]*upt0 + P[0][1]*upt1 + P[0][2]*upt2 + P[1][0]*upt3 + P[1][1]*upt4 + P[1][2]*upt5 - PP
f2 = Q[0][0]*upt0 + Q[0][1]*upt1 + Q[0][2]*upt2 + Q[1][0]*upt3 + Q[1][1]*upt4 + Q[1][2]*upt5 - QQ
relations = [f1,f2]
bounds = {upt0:(0, 2**256), upt1:(0,p), upt2:(0,p), upt3:(0,2**256), upt4:(0,p), upt5:(0,p)}
roots = find_small_roots(
relations,
bounds,
)

这样去求解,感觉得学一下 cuso 了,和这个相比自己有点像原始人

Blurred(*)#

task.py

from sage.all import *
from sage.misc import prandom
import random
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
from Crypto.Util.Padding import pad
q = 1342261
n = 1031
PR = PolynomialRing(Zmod(q), "x")
x = PR.gens()[0]
Q = PR.quotient(x**n + 1)
flag = b"flag{*****************************}"
def sample(rand):
return (rand.getrandbits(1) - rand.getrandbits(1)) * (rand.getrandbits(1) * rand.getrandbits(1))
def GenNTRU():
f = [sample(prandom) for _ in range(n)]
g1 = [sample(prandom)for _ in range(n)]
g2 = [sample(prandom) for _ in range(n)]
g1x = Q(g1)
g2x = Q(g2)
while True:
fx = Q(f)
try:
h1 = g1x / fx
h2 = g2x / fx
return (h1.lift(), h2.lift(), fx)
except:
prandom.shuffle(f)
continue
for _ in range(20259):
c = int(input("c :"))
if c == 1:
coin = random.getrandbits(1)
if coin == 0:
pk1, pk2, fx = GenNTRU()
else:
pk1, pk2, fx = Q.random_element().lift(), Q.random_element().lift(), Q([sample(prandom) for _ in range(n)])
print("Hints:", pk1.list(), pk2.list())
elif c == 2:
SHA = SHA256.new()
SHA.update(str(random.getrandbits(256)).encode())
KEY = SHA.digest()
cipher = AES.new(KEY, AES.MODE_ECB)
flag = pad(flag, AES.block_size)
print("Flag:", cipher.encrypt(flag))
else:
break

很明显的思路是收集一堆 bit 去打 mt19937,但是有个 ntru 是真的不会了。

这里 copy 一下shin师傅的思路

首先会有

h1(x)g1(x)f1(x)h2(x)g2(x)f1(x)h_1(x) \equiv g_1(x)f^{-1}(x) \\ h_2(x) \equiv g_2(x)f^{-1}(x)

x=-1 的时候

h1(1)g1(1)f1(1)modqh2(1)g2(1)f1(1)modqh_1(-1) \equiv g_1(-1)f^{-1}(-1) \mod q \\ h_2(-1) \equiv g_2(-1)f^{-1}(-1) \mod q

又因为在环 R 上面,-1 的时候就是 g(-1),那么就只剩下了一个简单的格了

(1H1H20p000p)\left( \begin{array}{ccc} 1 & H_1 & H_2 \\ 0 & -p & 0 \\ 0 & 0 & -p \end{array} \right)

我们求第一行就行,同时 NTRU 出来的值比较小,取 100 为界限即可,并且不允许有一个 bit 错误,所以 01 都要考虑。

from sage.all import *
from pwn import *
from tqdm import trange
from gf2bv import LinearSystem
from gf2bv.crypto.mt import MT19937
from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Hash import SHA256
q = 1342261
n = 1031
PR = PolynomialRing(Zmod(q), "x")
x = PR.gens()[0]
Q = PR.quotient(x**n + 1)
sh = process(["python", "task.py"])
bits = []
for i in trange(19968):
sh.sendlineafter(b"c :",b'1')
res = sh.recvline().strip().decode().split(':')[-1]
start1 = res.find('[')
end1 = res.find(']')
h1 = Q(eval(res[start1:end1+1]))
start2 = res.find('[',end1)
end2 = res.find(']',end1+1)
h2 = Q(eval(res[start2:end2+1]))
h1_value = h1.lift()(-1)
h2_value = h2.lift()(-1)
L = Matrix(ZZ,[
[1,h1_value,h2_value],
[0,-q,0],
[0,0,-q]
])
line = L.LLL()[0]
if int(line.norm()) < 100:
bits.append(0)
else:
bits.append(1)
sh.sendlineafter(b"c :",b'2')
sh.recvuntil(b"Flag: ")
enc_flag = eval(sh.recvline().strip())
print(f"enc_flag = {enc_flag}")
print(f"bits = {bits}")
def mt19937(out, ch):
hig = [int(0x00000000), int(0x80000000)]
lin = LinearSystem([32] * 624)
mt = lin.gens()
rng = MT19937(mt)
zeros = []
for o in out:
zeros.append(rng.getrandbits(1) ^ int(o))
zeros.append(mt[0] ^ hig[ch])
for sol in lin.solve_all(zeros):
rng = MT19937(sol)
pyrand = rng.to_python_random()
RNG = pyrand
STATE = RNG.getstate()[1][:-1]
STATE = STATE + (len(STATE),)
RNG.setstate((3,STATE,None))
for i in trange(19968):
RNG.getrandbits(1)
SHA = SHA256.new()
SHA.update(str(RNG.getrandbits(256)).encode())
KEY = SHA.digest()
cipher = AES.new(KEY, AES.MODE_ECB)
flag = cipher.decrypt(enc_flag)
if b"flag" in flag:
print(f"high = {ch}")
print(f"{flag = }")
RNG = mt19937(bits, 0)
RNG = mt19937(bits, 1)

总结#

感觉自己还是太菜了,迟迟摸不到强者的尾气。。。害,也快退役了,不甘心啊。。。

后面可能做密码越来越少了,毕竟没有考研的打算,可能还是会走 web 或者是开发吧。。。安服好像也不是不行?总之先为了工作吧。。。

强网杯 2025
https://www.zhuangsanmeng.xyz/posts/qwb2025/
Author
zsm
Published at
2025-11-04
License
MIT

Some information may be outdated