前言
感觉今年比去年难了好多 xd,有种力不从心的感觉了。。。
题目
check-little
task.py
from Crypto.Util.number import *from Crypto.Util.Padding import padfrom Crypto.Cipher import AESimport 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: breakN = p * qc = 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 AESimport gmpy2
N =c =iv =ciphertext =e = 3
p = gmpy2.gcd(c,N)q = N // pd = 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 structfrom tqdm import tqdmfrom random import Randomfrom Crypto.Util.number import long_to_bytes, bytes_to_longfrom sage.all import *from gf2bv import LinearSystemfrom 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 randintfrom Crypto.Util.number import getPrime, inverse, long_to_bytes, bytes_to_longfrom math import gcdimport signalfrom 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在这两个式子里面,我们知道还有,后面的那一堆可以认为是小参数,我们可以通过格去恢复,然后再恢复 pt。
exp.py
from sage.all import *from pwn import *import astfrom 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**384L = M.LLL()res = Nonefor row in L: if row[-2] == 0 and row[-1] == 0 and row[-3] == 1 and row[-4] == 1: print("[+] Found!") res = row breakassert res is not Nonew00, w01, w02, w10, w11, w12 = res[:6]
m = (w01 * inverse(w00, p)) % pprint(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 - PPf2 = 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 prandomimport randomfrom Crypto.Cipher import AESfrom Crypto.Hash import SHA256from Crypto.Util.Padding import pad
q = 1342261n = 1031PR = 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师傅的思路
首先会有
x=-1 的时候
又因为在环 R 上面,-1 的时候就是 g(-1),那么就只剩下了一个简单的格了
我们求第一行就行,同时 NTRU 出来的值比较小,取 100 为界限即可,并且不允许有一个 bit 错误,所以 01 都要考虑。
from sage.all import *from pwn import *from tqdm import trangefrom gf2bv import LinearSystemfrom gf2bv.crypto.mt import MT19937from Crypto.Util.number import *from Crypto.Cipher import AESfrom Crypto.Hash import SHA256
q = 1342261n = 1031PR = 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 或者是开发吧。。。安服好像也不是不行?总之先为了工作吧。。。
Some information may be outdated