1510 字
8 分钟
黄河流域CTF-crypto
前言
和睿抗校赛撞了,赛后看看题复现一下,感觉质量很一般
题目
Latice
task.py
from Crypto.Util.number import *from Crypto.Cipher import AESimport osfrom secret import flagimport numpy as np
def gen(q, n, N, sigma): t = np.random.randint(0, high=q // 2, size=n) s = np.concatenate([np.ones(1, dtype=np.int32), t]) A = np.random.randint(0, high=q // 2, size=(N, n)) e = np.round(np.random.randn(N) * sigma**2).astype(np.int32) % q b = ((np.dot(A, t) + e).reshape(-1, 1)) % q P = np.hstack([b, -A]) return P, s
def enc(P, M, q): N = P.shape[0] n = len(M) r = np.random.randint(0, 2, (n, N)) Z = np.zeros((n, P.shape[1]), dtype=np.int32) Z[:, 0] = 1 C = np.zeros((n, P.shape[1]), dtype=np.int32) for i in range(n): C[i] = (np.dot(P.T, r[i]) + (np.floor(q / 2) * Z[i] * M[i])) % q return C
q = 127n = 3N = int(1.1 * n * np.log(q))sigma = 1.0
P, s = gen(q, n, N, sigma)
def prep(s): return np.array([int(b) for char in s for b in f"{ord(char):08b}"], dtype=np.int32)
C = enc(P, prep(hint), q)P = P.tolist()C = C.tolist()print(f"{P=}")print(f"{C=}")
第一眼看上去是格+aes,但是注意代码
encrypted = AES.new(key=key, iv=iv, mode=AES.MODE_CBC).encrypt(b"".join([pad(i.encode(), 16) for i in flag]))
flag头已知的情况下,根据CBC的性质,有m[:16]
和c[:16]
,异或就可以得到iv了
exp.py
import numpy as npfrom Crypto.Cipher import AES
leak = -3.257518803980229925210589904230583482986646342139415561576950148286382674434770529248486501793457710730252401258721482142654716015216299244487794967600132597049154513815052213387666360825101667524635777006510550117512116441539852315185793280311905620746025669520152068447372368293640072502196959919309286241key =encrypted =iv = encrypted[:16]cipher = AES.new(key, AES.MODE_CBC, iv)decrypted = cipher.decrypt(encrypted[16:])print(decrypted.decode().strip())
sandwitch
task.py
from Crypto.Util.number import *import gmpy2flag = b'flag{fake_flag}'assert len(flag) == 39p = getPrime(512)q = getPrime(512)n = p * qe = 0x3pad1 = b'easy_problem'pad2 = b'How_to_solve_it'c = pow(bytes_to_long(pad1 + flag + pad2),e,n)print(f'n = {n}')print(f'c = {c}')
很经典的copper,高低位已知爆破中间,且已知len(flag)=39
和len(pad2)=15
,那么就可以写出如下的式子
exp.py
from Crypto.Util.number import *import hashlibfrom tqdm import *
def hash(x): return hashlib.sha256(x.encode()).digest()
e = 3n =c =pad1 = b'easy_problem'pad2 = b'How_to_solve_it'
pad1 = bytes_to_long(pad1)pad2 = bytes_to_long(pad2)
PR.<x> = PolynomialRing(Zmod(n))
f = (pad1 * 256 ** 54 + x * 256 ** 15 + pad2 )^e - cf = f.monic()res = f.small_roots(X=256^39,beta=1,epsilon=0.04)if(res != []): print(long_to_bytes(int(res[0])))
Happy
task.py
import os, utilsfrom secret import flagassert flag.startswith(b'flag{') and flag.endswith(b'}')
seed = int(os.urandom(16).hex(), 16)gen = utils.Gen(seed)msg = b'Happy4321: ' + flagenc = bytes(m ^ next(gen) for m in msg).hex()print(enc)
utils.py
class Gen: def __init__(self, state): self.nbits = 128 self.state = state & ((1 << self.nbits) - 1) self.mask = 109908700282042807039366676242995409413
def func0(self, steps=1): for _ in range(steps): res = self.state & self.mask bit = sum([(res >> i) & 1 for i in range(self.nbits)]) & 1 self.state = ((self.state << 1) ^ bit) & ((1 << self.nbits) - 1) return bit
def __next__(self): out = 0 for _ in range(8): bit = self.func0(2023) out = (out << 1) ^ bit return out
和NKCTF2023的题差不多,而且难度减小了一点点,参考XMCVE
exp.py
# SageMathclass Gen: def __init__(self, state): self.nbits = 128 self.state = state & ((1 << self.nbits) - 1) self.mask = 109908700282042807039366676242995409413
def func0(self, steps=1): for _ in range(steps): res = self.state & self.mask bit = sum([(res >> i) & 1 for i in range(self.nbits)]) & 1 self.state = ((self.state << 1) ^ bit) & ((1 << self.nbits) - 1) return bit
def __next__(self): out = 0 for _ in range(8): bit = self.func0(2023) out = (out << 1) ^ bit return out
n = 128msg = b'Happy4321: flag{'mask = 109908700282042807039366676242995409413
enc = ''enc = bytes.fromhex(enc)Round = 2023
# 构建M矩阵M = matrix(GF(2), n, n)for i in range(n): if i+1<n: M[i+1, i] = 1 if mask&(1<<(n-i-1)): M[i, -1] = 1 else: M[i, -1] = 0
# seed * M2 = output_sM2 = matrix(GF(2), n, n)
for i in range(n): tmp = M^(Round*(i+1)) for y in range(n): M2[y, i] = tmp[y, -1]
# 构建output_s矩阵output_s = []
for i in range(len(msg)): tmp = msg[i]^^enc[i] for x in range(8): if tmp&(1<<(8-x-1)): output_s.append(1) else: output_s.append(0)
output_s = vector(GF(2), output_s)
S = M2.solve_left(output_s)seed = int(''.join([str(each) for each in S]), 2)print(seed)
import os
enc = ''enc = bytes.fromhex(enc)seed = 16527323701539137374460041583215952894
gen = Gen(seed)flag = bytes(c ^ next(gen) for c in enc)print(flag)
因式分解
task.py
from Crypto.Util.number import *from gmpy2 import*from secret import flag,a,b,c
m = bytes_to_long(flag)p = getPrime(256)q = getPrime(256)n = p * qe = 65537_q = int(bin(q)[2:][::-1] , 2)c = pow(m,e,n)
print('n =',n)print('c =',c)
assert a**3+b**3+c**3 == 3*a*b*cgift = secert**3 - 9*secert + 8print(gift)
assert 3*(p ^ _q) == a + b + c
tellasecret.py
import string
from secret import hintfrom secret import encrypt
import random
dicts = string.ascii_lowercase +"{=}"
key = (''.join([random.choice(dicts) for i in range(4)])) * 8
assert(len(hint) == 32)
assert(len(key) == 32)
cipher = encrypt(hint, key)
print(cipher)
这个东西我是真的看不懂,没get到他到底想让我干什么,对于_q
肯定是个剪枝了,可以参考鸡块神的文章,但是这个serect我是真的没看懂,看了别的师傅的博客我才知道是维吉尼亚爆破,真难绷
exp1.py
from itertools import product
dicts = "abcdefghijklmnopqrstuvwxyz{=}"cipher = "cp=wmaunapgimjfpopeblvup=aywqygb"
# 暴力破解 4 字节 keyfor key in product(dicts, repeat=4): key = ''.join(key) * 8 plain = '' for i in range(len(cipher)): c = dicts.index(cipher[i]) k = dicts.index(key[i]) p = (c - k) % 29 plain += dicts[p] if "secret" in plain: print("Key:", key[:4]) print("Plaintext:", plain) break# tellasecret{a=secert}keepsilentt
这是第一部分,那么就解出来a=secert
,看abc的关系式,很经典的轮换式,直接想到a=b=c
,那么可知p^_q=a
,解方程求a然后剪枝就行了
exp2.py
gift =k = var('secret')
p = k
polys = (p**3 - 9*p + 8 == gift)x = solve(polys, k)print(x)
from Crypto.Util.number import *import sys
sys.setrecursionlimit(1500)
pxorq =n =c =e = 65537pxorq = str(bin(pxorq)[2:]).zfill(256)
def find(ph, qh, pl, ql): l = len(ph) tmp0 = ph + (256 - 2 * l) * "0" + pl tmp1 = ph + (256 - 2 * l) * "1" + pl tmq0 = qh + (256 - 2 * l) * "0" + ql tmq1 = qh + (256 - 2 * l) * "1" + ql if (int(tmp0, 2) * int(tmq0, 2) > n): return if (int(tmp1, 2) * int(tmq1, 2) < n): return if (int(pl, 2) * int(ql, 2) % (2 ** (l - 1)) != n % (2 ** (l - 1))): return
if (l == 128): pp0 = int(tmp0, 2) if (n % pp0 == 0): pf = pp0 qf = n // pp0 phi = (pf - 1) * (qf - 1) d = inverse(e, phi) m1 = pow(c, d, n) print(long_to_bytes(m1)) exit()
else: if (pxorq[l] == "1" and pxorq[255 - l] == "1"): find(ph + "1", qh + "0", "1" + pl, "0" + ql) find(ph + "0", qh + "0", "1" + pl, "1" + ql) find(ph + "1", qh + "1", "0" + pl, "0" + ql) find(ph + "0", qh + "1", "0" + pl, "1" + ql) elif (pxorq[l] == "1" and pxorq[255 - l] == "0"): find(ph + "1", qh + "0", "0" + pl, "0" + ql) find(ph + "0", qh + "0", "0" + pl, "1" + ql) find(ph + "1", qh + "1", "1" + pl, "0" + ql) find(ph + "0", qh + "1", "1" + pl, "1" + ql) elif (pxorq[l] == "0" and pxorq[255 - l] == "1"): find(ph + "0", qh + "0", "1" + pl, "0" + ql) find(ph + "0", qh + "1", "0" + pl, "0" + ql) find(ph + "1", qh + "0", "1" + pl, "1" + ql) find(ph + "1", qh + "1", "0" + pl, "1" + ql) elif (pxorq[l] == "0" and pxorq[255 - l] == "0"): find(ph + "0", qh + "0", "0" + pl, "0" + ql) find(ph + "1", qh + "0", "0" + pl, "1" + ql) find(ph + "0", qh + "1", "1" + pl, "0" + ql) find(ph + "1", qh + "1", "1" + pl, "1" + ql)
find("1", "1", "1", "1")
总结
题目质量还行吧?因式分解这玩意前面是维吉尼亚真没想到xd