复现平台:A1CTF
打星号(*)的是赛时未解出赛后复现的,打感叹号(!)是看了官方 WriteUp 有所修改的,打连接符(-)的是没复现出来咕咕咕的。
Crypto
Week 1
Basic Number theory
题目:
from Crypto.Util.number import *from secret import flag
def gift(m, prime): return pow(m, (prime + 1) // 2, prime)
m = bytes_to_long(flag)p = getPrime(256)q = getPrime(256)
print(f'p = {p}')print(f'q = {q}')print(f'gift1 = {gift(m, p)}')print(f'gift2 = {gift(m, q)}')
# p = ...# q = ...# gift1 = ...# gift2 = ...再CRT一下穷举四种可能就好了。
exp.py:
from sympy.ntheory.residue_ntheory import crtfrom Crypto.Util.number import long_to_bytes
p = ...q = ...gift1 = ...gift2 = ...
gift1_ = [gift1, -gift1]gift2_ = [gift2, -gift2]for i in gift1_: for k in gift2_: s, _ = crt([p, q], [i, k]) flag = long_to_bytes(s) if b'flag' in flag: print(flag.decode()) break# flag{Th3_c0rner5t0ne_0f_C2ypt0gr@phy}Strange Machine
题目:
from secret import msg_len, offset, plaintextfrom Crypto.Util.number import long_to_bytesfrom random import randbytesfrom base64 import b64encodefrom pwn import xorimport os
flag = os.getenv('FLAG')
def banner(): print("你发现了一个奇怪的机器,它对你的消息进行了加密。") print("你截获了这个机器第一次的密文,同时可以继续使用该机器进行加密。") print("注意:所有密文输出都经过 base64 编码,方便你复制分析。\n")
def menu(): print(f"1. 加密消息") print(f"2. 校验明文是否正确") print(f"3. 退出")
class Key: def __init__(self): self.seed = randbytes(msg_len)
def generate(self): self.seed = self.seed[offset:] + self.seed[:offset] return self.seed
def pad(msg): pad_len = msg_len - len(msg) return msg + pad_len * long_to_bytes(pad_len)
def encrypt(msg, key): cipher = xor(pad(msg), key) return b64encode(cipher)
def main(): banner() key = Key() cur_key = key.generate() cipher1 = encrypt(plaintext, cur_key) print(f'[*] 首次密文(base64):{cipher1}\n') while True: try: menu() choice = int(input(f"[?] 请输入你的选择:")) if choice == 1: msg = input(f"[?] 请输入要加密的消息(长度小于等于{msg_len}): ").encode() if len(msg) > msg_len: print(f"[!] 输入消息过长,最长为 {msg_len} 字节\n") continue
cur_key = key.generate() cipher = encrypt(msg, cur_key)
print(f"[*] 你的消息已加密(密文): {cipher}\n") continue
if choice == 2: msg = input(f"[?] 请输入待校验的明文: ").encode() if msg == plaintext: print(f"[*] 这是你的flag: {flag}\n") break print("[!] 校验错误!\n") continue
if choice == 3: print("再见~\n") break
print("[!] 无效输入\n") except Exception: print("[!] 出现错误!\n") break
if __name__ == "__main__": main()1 没有限制次数,可以多加密几次一样的明文,已知明文攻击,推出offset再推出key然后和首次密文异或求解,然后选 2 校验明文即可,带中文了交互脚本写的有点麻烦,手拿数据手输了。
exp.py:
from base64 import b64decodefrom pwn import xor
C1_b64 = "sHACJ0klMagdhYDm53eTNw=="known_b64_list = [ "29T2vjOCBs8oHm4WYCH+RA==", "Hm4WYCH+RNvU9r4zggbPKA==", "9r4zggbPKB5uFmAh/kTb1A==", "FmAh/kTb1Pa+M4IGzygebg==",]msg_len = 16C1 = b64decode(C1_b64)known_list = [b64decode(x) for x in known_b64_list]K_list = [xor(c, b'0' * msg_len) for c in known_list]offset = 0for i in range(len(K_list) - 1): idx = (K_list[i] + K_list[i]).find(K_list[i + 1]) offset = idx if idx != -1 else 0print(f"{offset = }")K1 = K_list[0][-offset:] + K_list[0][:-offset]P_padded = xor(C1, K1)pad_len = P_padded[-1]plaintext = P_padded[:-pad_len].decode()print(plaintext)# offset = 9# Oh,you find it!# flag{094dc384-ba84-4fdf-871d-8e4a3ed761c0}beyondHex
题目:
这是什么东西? 807G6F429C7FA2200F46525G1350AB20G339D2GB7D8
多了个G,加上题目名字超过 16 进制,猜测是 17 进制。
exp.py:
from Crypto.Util.number import long_to_bytes
s = "807G6F429C7FA2200F46525G1350AB20G339D2GB7D8"print(long_to_bytes(int(s, 17)).decode())# flag{welc0me_t0_?CTF!}certificate
题目:
private.pem:
-----BEGIN RSA PRIVATE KEY----- MIICewIBAAKBgQCdA0+pqynKvc3/BH5ojnUXBMdWy9Lzi9TwSaOgiJ6ky//QBrWG CNan98CYNcoKux2yOtHKIjxUrPh+LdgjmW+/paWPJyrnoQw5SqD+FvqNTjG7Akvx +TUXyMflL9qodrWBEbl/xmN01Qbivo36+U1mFDB+6LENk/3BwWXHVj0DvQIhAL7t Vc3/3jEFe+paWKNoTV++2B8D1T+ii7ZYsy3kU1yNAoGA???????????????????? ???????????????????????????????????????????????????????????????? ???????????????????????????????????????????????????????????????? ??????????????????????kCQQ?????????????????????????????????????? ????????????????????????????????????????????????AkE????????????? ???????????????????????????????????????????????????????????????? ?????????wJAJrKFhI5pl/oBN2BZqLTf+NacGqTFrzmbi7RVFaN43kXYXu11urXy LTncfJXBpRhtGFdsL31jiswhiYRp9yjT+wJB???????????????????????????? ??????????????????????????????????????????????????????????MCQQ?? ???????????????????????????????????????????????????????????????? ???????????????????? -----END RSA PRIVATE KEY-----
又又又又要经典的手撕私钥了,这边给个La佬和tover佬的链接。
3082 027b # Begin Sequence: len=0x027b
0201 # Version: (len=0x01) 00
0281 81 # n: (len=0x81) 009d034fa9ab29cabdcdff047e688e751704c756cbd2f38bd4f049a3a0889ea4cbffd006b58608d6a7f7c09835ca0abb1db23ad1ca223c54acf87e2dd823996fbfa5a58f272ae7a10c394aa0fe16fa8d4e31bb024bf1f93517c8c7e52fdaa876b58111b97fc66374d506e2be8dfaf94d6614307ee8b10d93fdc1c165c7563d03bd
02 21 # e: (len=0x21) 00beed55cdffde31057bea5a58a3684d5fbed81f03d53fa28bb658b32de4535c8d
0281 80 # d: (len=0x80) …9
02 41 # p: (len=0x41) 0…
02 41 # q: (len=0x41) …
# 加上w解base64会乱
wJAJrKFhI5pl/oBN2BZqLTf+NacGqTFrzmbi7RVFaN43kXYXu11urXyLTncfJXBpRhtGFdsL31jiswhiYRp9yjT+wJB c09009aca161239a65fe804dd8166a2d37fe35a706a9316bce66e2ed154568de37917617bb5d6ead7c8b4e771f257069461b4615db0bdf58e2b30862611a7dca34fec090
# 所以去了w解base64
JAJrKFhI5pl/oBN2BZqLTf+NacGqTFrzmbi7RVFaN43kXYXu11urXyLTncfJXBpRhtGFdsL31jiswhiYRp9yjT+wJB 24026b285848e6997fa01376059a8b4dff8d69c1aa4c5af399b8bb45515a378de45d85eed75bab5f22d39dc7c95c1a5186d18576c2f7d638acc21898469f728d3fb024
2 40 # dp: (len=0x40) 26b285848e6997fa01376059a8b4dff8d69c1aa4c5af399b8bb45515a378de45d85eed75bab5f22d39dc7c95c1a5186d18576c2f7d638acc21898469f728d3fb
02 4 # dq …3
0241 0…
变成了dp泄露,e很大,上轮子梭。
exp.py:
from Crypto.Util.number import *from gmpy2 import *
n = ...e = ...c = ...dp = ...m = 2p = gcd(pow(m, dp * e, n) - m, n)q = n // passert p * q == nphi = (p - 1) * (q - 1)d = invert(e, phi)m = pow(c, d, n)print(long_to_bytes(m).decode())# This is your flag:flag{4n_3xc3551v3ly_l4r93_publ1c_k3y_c4n_b3_pr0bl3m4t1c}two Es
题目:
from Crypto.Util.number import *import randomfrom secret import flag
p, q = getPrime(512), getPrime(512)n = p * q
e1 = random.getrandbits(32)e2 = random.getrandbits(32)
m = bytes_to_long(flag)c1 = pow(m, e1, n)c2 = pow(m, e2, n)
print(f'{n = }')print(f'{e1 = }')print(f'{e2 = }')print(f'{c1 = }')print(f'{c2 = }')
'''n = ...e1 = ...e2 = ...c1 = ...c2 = ...'''如题,两个e,共模攻击。
exp.py:
from gmpy2 import gcdext, irootfrom Crypto.Util.number import long_to_bytes
n = ...e1 = ...e2 = ...c1 = ...c2 = ...s, s1, s2 = gcdext(e1, e2) # 扩展欧几里得算法,求最大公约数m = iroot((pow(c1, s1, n) * pow(c2, s2, n) % n), s)[0]print(long_to_bytes(m).decode())# flag{s01v3_rO0T_bY_7he_S4mE_m0dU1u5}xorRSA
题目:
from Crypto.Util.number import *from secret import flag
p, q = getPrime(1024), getPrime(1024)n = p * qe = 65537m = bytes_to_long(flag)c = pow(m, e, n)p_q = p ^ q
print(f'{n = }')print(f'{e = }')print(f'{c = }')print(f'{p_q = }')
'''n = ...e = 65537c = ...p_q = ...'''泄露了p^q,给个DexterJie师傅的链接。
exp.py:
from Crypto.Util.number import *import syssys.setrecursionlimit(3000)
N = ...e = 65537c = ...gift = ...
# 低位往高位爆def findp(p, q): if len(p) == 1024: pp = int(p,2) if N % pp == 0: print("p = ",pp) print("q = ",N // pp) qq = N // pp d = inverse(65537,(pp-1)*(qq-1)) m = pow(c,d,N) print(long_to_bytes(m)) return else: l = len(p) pp = int(p,2) qq = int(q,2) if (pp ^ qq) % (2 ** l) == gift % (2 ** l) and pp * qq % (2 ** l) == N % (2**l): findp('1' + p,'1' + q) findp('1' + p,'0' + q) findp('0' + p,'1' + q) findp('0' + p,'0' + q)
findp('1','1')# flag{U5e_PruN1ng_41g0rI7hm_tO_sEarch}Week 2
AES_mode
题目:
from Crypto.Cipher import AESfrom Crypto.Util.Padding import padimport binasciifrom Crypto.Util.number import bytes_to_longfrom secret import flagimport os
iv = flag.strip(b'flag{').strip(b'}')
key = os.urandom(16)
hint = os.urandom(4) * 8print(bytes_to_long(hint)^bytes_to_long(key))
msg = b'Welcome to ?CTF! , I hope you can have fun!!!!!!'def encrypto(message): aes = AES.new(key,AES.MODE_CBC,iv) return aes.encrypt(message)
print(binascii.hexlify(encrypto(msg))[-32:])'''...b'...''''hint部分老熟了,一道经典老题的泄露方式。key只有 16 位,而hint有8 * 4 = 32位,异或后,生成的随机四字节会泄露,所以直接将第一个已知值转为字节,取前面 16 字节会发现是某四字节的四次重复,便可恢复hint并恢复key。然后题目给了CBC模式下加密的明文和最后一块的密文,有了key就可以一路倒推回去了。
exp.py:
from Crypto.Util.number import long_to_bytes, bytes_to_longfrom Crypto.Util.strxor import strxorfrom Crypto.Cipher import AES
hint_xor_key = ...# print(long_to_bytes(hint_xor_key))# b'\xca\xd8N\x97\xca\xd8N\x97\xca\xd8N\x97\xca\xd8N\x97Q\xe1\x80\x0b\x8c>y\x1f\x9a\xa0\xd8A\x0b\xb7c\xa4'c3 = bytes.fromhex('...')hint = b'\xca\xd8N\x97' * 8key = long_to_bytes(hint_xor_key ^ bytes_to_long(hint))msg = b'Welcome to ?CTF! , I hope you can have fun!!!!!!'cipher = AES.new(key, AES.MODE_ECB)c2 = strxor(msg[32:], cipher.decrypt(c3))c1 = strxor(msg[16:32], cipher.decrypt(c2))iv = strxor(msg[:16], cipher.decrypt(c1))print("flag{" + iv.decode() + "}")# flag{CBc_Us3s_Iv!=ECb}Common RSA
题目:
from Crypto.Util.number import *from secret import flag
assert flag.startswith(b'flag{') and flag.endswith(b'}')
p, q = getPrime(512), getPrime(512)n = p * qe = 65537m = bytes_to_long(flag)c = pow(m, e, n)
hint = pow(p + q, 2, n)
print(f'{n = }')print(f'{e = }')print(f'{c = }')print(f'{hint = }')
'''n = ...e = 65537c = ...hint = ...'''由题目给的hint可以很简单的通过平方和和n分解p和q,然后分解出p和q后发现gcd(e, p - 1)和gcd(e, q - 1)都为 65537,e太大了,考虑 AMM 算法打出m,p和q都挺大,打一个就行了。
exp.py:
# SageMath 10.7import timeimport randomfrom tqdm import trange, tqdmfrom gmpy2 import iroot, invertfrom Crypto.Util.number import *
def AMM(o, r, q): start = time.time()
g = GF(q) o = g(o) p = g(random.randint(1, q)) while p ^ ((q-1) // r) == 1: p = g(random.randint(1, q)) print('[+] Find p:{}'.format(p)) t = 0 s = q - 1 while s % r == 0: t += 1 s = s // r print('[+] Find s:{}, t:{}'.format(s, t)) k = 1 while (k * s + 1) % r != 0: k += 1 alp = (k * s + 1) // r print('[+] Find alp:{}'.format(alp)) a = p ^ (r**(t-1) * s) b = o ^ (r*alp - 1) c = p ^ s h = 1 for i in range(1, t): d = b ^ (r^(t-1-i)) if d == 1: j = 0 else: print('[+] Calculating DLP...') j = - discrete_log(d, a) print('[+] Finish DLP...') b = b * (c^r)^j h = h * c^j c = c^r result = o^alp * h end = time.time() print("Finished in {} seconds.".format(end - start)) print('Find one solution: {}'.format(result)) return result
def onemod(p,r): t=p-2 while pow(t,(p-1) // r,p)==1: t -= 1 return pow(t,(p-1) // r,p)
def solution(p,root,e): g = onemod(p,e) may = set() for i in range(e): may.add(root * pow(g,i,p)%p) return may
def union(x1, x2): a1, m1 = x1 a2, m2 = x2 d = gmpy2.gcd(m1, m2) assert (a2 - a1) % d == 0 p1,p2 = m1 // d,m2 // d _,l1,l2 = gmpy2.gcdext(p1,p2) k = -((a1 - a2) // d) * l1 lcm = gmpy2.lcm(m1,m2) ans = (a1 + k * m1) % lcm return ans,lcm
def excrt(ai,mi): tmp = zip(ai,mi) return reduce(union, tmp)
n = ...e = 65537c = ...hint = ...p, q = None, Nonefor k in trange(1000000): try: sq = hint + k * n res = iroot(sq, 2) if res[0] ** 2 == sq: sum = res[0] D = sum ** 2 - 4 * n diff = iroot(D, 2)[0] p = (sum + diff) // 2 q = (sum - diff) // 2 if p * q == n: break except: continuecp = c % pmp = AMM(cp, e, p)mps = solution(p, mp, e)for mpp in tqdm(mps): ai = [int(mpp)] mi = [p] m = CRT_list(ai,mi) flag = long_to_bytes(m) if b'flag' in flag: print(flag.decode()) exit(0)# flag{1t_i5_N0T_4_c0mmOn_R5A!}Furious BlackCopper
题目:
from Crypto.Util.number import *from secret import flag
p,q,r = [getPrime(1024) for _ in range(3)]n = p*q*re = 65537
c = pow(bytes_to_long(flag),e,n)
print(f"n = {n}")print(f"c = {c}")print(f"hint1 = {p % (1<<20)}")print(f"hint2 = {(p>>30) % (2**800)}")
'''n = ...c = ...hint1 = ...hint2 = ...'''就常规打,爆破 10 位就行。
exp.py:
# SageMath 10.7from tqdm import trangefrom Crypto.Util.number import *
e = 65537n = ...c = ...hint1 = ...hint2 = ...PR.<x> = PolynomialRing(Zmod(n))for i in trange(2 ** 10): pl = hint1 + i * 2 ** 20 + hint2 * 2 ** 30 f = x * 2 ** 830 + pl res = f.monic().small_roots(X=2**194, beta=0.19, epsilon=0.02) if len(res) != 0: p = res[0] * 2 ** 830 + pl dp = inverse(e, p - 1) m = pow(c, dp, p) print(long_to_bytes(int(m)).decode()) break# 27%|█████████████████████▍ | 275/1024 [00:05<00:14, 51.46it/s]# flag{C0pp3R_W0u1d_B3_T4sty_W1tH_F0rc3}baby Elgamal
题目:
from Crypto.Util.number import *import randomfrom secret import flag
p = getPrime(512)g = random.randint(2, p - 2)x = random.getrandbits(32)y = pow(g, x, p)
print(f'{p = }')print(f'{g = }')print(f'{y = }')
k = random.randint(2, p - 2)m = bytes_to_long(flag)c1 = pow(g, k, p)c2 = m ^ pow(y, k, p)
print(f'{c1 = }')print(f'{c2 = }')
'''p = ...g = ...y = ...c1 = ...c2 = ...'''都给了g了,bsgs 直接打就行。
exp.py:
from Crypto.Util.number import long_to_bytes
p = ...g = ...y = ...c1 = ...c2 = ...
N = 1 << 32m = 1 << 16
baby = {}cur = 1for j in range(m): baby[cur] = j cur = (cur * g) % p
gm = pow(g, m, p)gm_inv = pow(gm, -1, p)
cur = yx = Nonefor i in range((N + m - 1) // m + 1): if cur in baby: x = i * m + baby[cur] if x < N: break cur = (cur * gm_inv) % p
assert x is not None
s = pow(c1, x, p)m_int = c2 ^ sflag = long_to_bytes(m_int)print(x, flag.decode())# 1616680587 flag{31g4m41_D15cr373_10g}findKey in middle
题目:
from Crypto.Util.Padding import padfrom Crypto.Util.number import *from random import getrandbitsfrom Crypto.Cipher import AESfrom hashlib import sha256from secret import flag
def f(x, y): return (pow(3, x, p) * pow(5, y, p)) % p
def split_key(key): x, y = getPrime(16), getPrime(16) assert x * y > key k1, k2 = key % x, key % y return k1, k2, x, y
def aes_encrypt(key, flag): aes = AES.new(key, AES.MODE_ECB) return aes.encrypt(pad(flag, 16))
p = 1000000007
key = getrandbits(32)k1, k2, mod1, mod2 = split_key(key)x = f(k1, k2)
cipher = aes_encrypt(sha256(long_to_bytes(key)).digest()[:16], flag)
print(f'x = {x}')print(f'mod = {(mod1, mod2)}')print(f'cipher = {cipher}')# x = ...# mod = (..., ...)# cipher = ...简而言之密钥被两个模数拆分了,然后给了提示,要从提示推出两个拆分的密钥然后 crt 打出原密钥再派生出 AES 的密钥解密就行。
exp.py:
from Crypto.Cipher import AESfrom Crypto.Util.Padding import unpadfrom Crypto.Util.number import long_to_bytesfrom hashlib import sha256
p = ...x = ...mod1, mod2 = ..., ...cipher = ...pow3 = {}v = 1for i in range(mod1): pow3[v] = i v = (v * 3) % pk1 = k2 = Nonepow5 = 1inv5 = pow(5, p - 2, p)inv5_k = 1for j in range(mod2): t = (x * inv5_k) % p if t in pow3: k1 = pow3[t] k2 = j break inv5_k = (inv5_k * inv5) % pif k1 is None: raise RuntimeError("未找到(k1,k2)")def crt(a1, m1, a2, m2): n1 = m2 n2 = m1 inv_n1 = pow(n1, -1, m1) inv_n2 = pow(n2, -1, m2) x = (a1 * n1 * inv_n1 + a2 * n2 * inv_n2) % (m1 * m2) return xkey = crt(k1, mod1, k2, mod2)assert key < mod1 * mod2aes_key = sha256(long_to_bytes(key)).digest()[:16]pt = unpad(AES.new(aes_key, AES.MODE_ECB).decrypt(cipher), 16)print("k1 =", k1, "k2 =", k2)print("key =", key)print(pt.decode("utf-8"))# k1 = 12050 k2 = 27449# key = 1313232941# flag{e31343dd-4795-4236-bbec-11b8410b5ce6}strange random
题目:
from Crypto.Util.number import *import randomfrom sympy import primedef sssstranger(p,q): n = p*q list=[] for i in range(312): x=random.getrandbits(32) list.append(x) print(list) return n
p=getStrongPrime(512)q=getStrongPrime(512)r=getStrongPrime(512)n1=sssstranger(p,q)print(n1)n2=sssstranger(q,r)print(n2)e=random.getrandbits(32)m=bytes_to_long(b"xxx")c=pow(m,e,n1*n2//q)print(c)'''[...]...[...]......'''前半是个 MT19937,randcrack 创出e,然后会发现后面e和p,q,r都不互素,可以取r来打flag。
在模r下缩小范围从m ** 4求解m。
exp.py:
# SageMath 10.7from math import gcdfrom Crypto.Util.number import inverse, long_to_bytesfrom randcrack import RandCrack
outputs = [...]n1 = ...n2 = ...c = ...rc = RandCrack()for v in outputs: rc.submit(v)e = rc.predict_getrandbits(32)# print("predicted e =", e)# predicted e = 1297450108q = gcd(n1, n2)p = n1 // qr = n2 // qN = (n1 * n2) // qphi = (p - 1) * (q - 1) * (r - 1)# print(gcd(e, p - 1), gcd(e, q - 1), gcd(e, r - 1), gcd(e, phi))# 436 2 4 436g = gcd(e, r - 1)er = e // gphir = r - 1dr = inverse(er, phir)cr = pow(c, dr, r)PR.<x> = PolynomialRing(Zmod(r))f = x ** g - crres = f.roots(multiplicities=False)for i in res: m = long_to_bytes(int(i)) if all(1 <= c <= 127 for c in m): print(m.decode(errors="ignore"))# flag{wwooooow_u_no_the_randdoooooo0m!!!}Week 3
Backpack
题目:
import randomfrom secret import flag
assert flag.startswith(b'flag{') and flag.endswith(b'}')assert len(flag) == 18
nbits = 100a = [random.randint(1<<(nbits-1),1<<nbits) for i in range(len(flag))]b = sum([i*j for i,j in zip(a,flag)])
print(f'{a = }')print(f'{b = }')
'''a = [...]b = ...'''很简洁的题目,但是一开始思索了下是z3还是格攻击求解,但是约束应该都还是不太够,所以先通过flag{和}的固定首尾搭配来降维。然后常规背包格打就行。
exp.py:
# SageMath 10.7
a = [...]b = ...b = b - ord('f') * a[0] - ord('l') * a[1] - ord('a') * a[2] - ord('g') * a[3] - ord('{') * a[4] - ord('}') * a[-1]a = a[5:-1]n = len(a) # 18B = 1 << 0M = Matrix(ZZ, n + 1)for i in range(n): M[i, i] = B M[i, n] = a[i]M[n, n] = -bres = M.LLL()flag = "flag{"for i in res[0]: flag += chr(i)flag += "}"print(flag)# flag{342y_14771C3}Great Block Cipher
题目:
def encrypt(msg, key, iv): assert len(msg) % 16 == 0 assert len(key) == 16 and len(iv) == 16 xor = lambda a, b:[i^j for i,j in zip(a,b)] sbox = [...] cipher = [] ks = [key[:4],key[4:8],key[8:12],key[12:]] for i in range(16*4): nk = 0 for j in range(4): nk = int.from_bytes(ks[i+j],'big') ^ (3377808869 * nk % (1<<32)) ks.append(nk.to_bytes(4,'little')) block_count = len(msg)//16 for block in range(block_count): m = list(msg[16*block:16*(block+1)]) m = xor(m, iv) m = xor(m, b''.join(ks[:4])) for loop in range(16): m = [sbox[i] for i in m] for i in range(4): a = bin(sum([m[4*i+j]*256**j for j in range(4)]))[2:].zfill(32) b = [] d = i+3 c1, c2 = 32//d, 32%d k = 0 for j in range(d): if j < c2: b.append(a[k:k+c1+1]) k += c1+1 else: b.append(a[k:k+c1]) k += c1 c = '' for j in range(c1+1): for k in range(d): if len(b[k]) > j: c += b[k][j] m[4*i],m[4*i+1],m[4*i+2],m[4*i+3] = int(c,2).to_bytes(4,'big') if loop < 15: a = [] b = [10, 13, 3, 11, 7, 9, 0, 15, 5, 12, 8, 4, 6, 14, 2, 1] for i in range(4): for j in range(4): a.append(sum([b[4*i+k]*m[j+4*k] for k in range(4)])%256) m = a m = xor(m, b''.join(ks[4*(loop+1):4*(loop+2)])) cipher.append(bytes(m)) iv = m return b''.join(cipher)
def pad(msg): l = (len(msg)//16+1)*16 return msg.ljust(l,chr(l-len(msg)).encode())
flag = b'flag{*********}'
what = b'www.bilibili.com/video/BV1ox4y1e71S'key, iv = what[:16], what[-16:]cipher = encrypt(pad(flag), key, iv)print(cipher.hex())# ...题目给出的encrypt()函数自定义了一个分组对称加密算法,但是整体特征还是类似AES-CBC加密方式,只是用了自定义的S-box,按位重排函数,模 256 下的 4x4 矩阵混合以及自定义的密钥扩展方式。
大致流程是先将Block与iv异或,然后和key异或,接着进入 16 轮迭代:1. S-box 替换,2. bit 混合,3. 线性混合,4. 异或子密钥。
解密的话就是反向执行,ai 调了调,自己修了下出了(又像逆向题了emmm),唉我的 AES 是真菜。
exp.py:
def invert_matrix_mod256(M): """ 计算 4x4 矩阵在模 256 下的逆矩阵。 线性混合步骤的逆运算。 """ n = 4 # 构造增广矩阵 [M | I] A = [[M[i][j] % 256 for j in range(n)] + [1 if i == j else 0 for j in range(n)] for i in range(n)] # 模 256 下的逆元(利用扩展欧几里得) def inv(a): a %= 256 t, nt = 0, 1 r, nr = 256, a while nr: q = r // nr t, nt = nt, t - q * nt r, nr = nr, r - q * nr return None if r != 1 else t % 256 # 高斯消元法 row = 0 for col in range(n): pivot = None for r in range(row, n): if inv(A[r][col]) is not None: pivot = r break if pivot is None: return None # 不可逆 # 交换主元行 A[row], A[pivot] = A[pivot], A[row] ip = inv(A[row][col]) # 主元行归一化 for c in range(2 * n): A[row][c] = (A[row][c] * ip) % 256 # 其他行消元 for r in range(n): if r == row: continue f = A[r][col] % 256 if f: for c in range(2 * n): A[r][c] = (A[r][c] - f * A[row][c]) % 256 row += 1 # 取右半部分为逆矩阵 return [r[n:] for r in A]
def inv_bitmix_word(w, i): """ 逆位混合函数,对单个 4 字节(32bit)word 逆变换。 参数: w : list[int],长度为 4 i : 第 i 个 word 的索引 (0~3) """ d = i + 3 c1, c2 = 32 // d, 32 % d lens = [c1 + 1 if j < c2 else c1 for j in range(d)] # 根据加密阶段的分块顺序重建索引 idx = 0 bidx = [] for ln in lens: bidx.append(list(range(idx, idx + ln))) idx += ln # 加密时按列交织,现在要按列逆序恢复 order = [bidx[r][c] for c in range(max(lens)) for r in range(d) if c < len(bidx[r])] bits = bin(int.from_bytes(bytes(w), 'big'))[2:].zfill(32) restored = ['0'] * 32 for pc, pa in enumerate(order): restored[pa] = bits[pc] return list(int(''.join(restored), 2).to_bytes(4, 'little'))
ciphertext = bytes.fromhex("...")what = b'www.bilibili.com/video/BV1ox4y1e71S'key, iv = what[:16], what[-16:]xor = lambda a, b: [i ^ j for i, j in zip(a, b)]# 逆 S-boxsbox = [...]inv_sbox = [0] * 256for i, v in enumerate(sbox): inv_sbox[v] = i# 密钥扩展,与 encrypt() 相同ks = [key[:4], key[4:8], key[8:12], key[12:]]for i in range(64): nk = 0 for j in range(4): nk = int.from_bytes(ks[i + j], 'big') ^ (3377808869 * nk % (1 << 32)) ks.append(nk.to_bytes(4, 'little'))# 加密中使用的混合矩阵b = [[10, 13, 3, 11], [7, 9, 0, 15], [5, 12, 8, 4], [6, 14, 2, 1]]binv = invert_matrix_mod256(b)# 分块解密blocks = [ciphertext[i:i + 16] for i in range(0, len(ciphertext), 16)]result = []prev_iv = ivfor blk in blocks: m = list(blk) # 共 16 轮逆序进行 for round_idx in range(15, -1, -1): # 1. XOR 当前轮的子密钥 m = xor(m, b''.join(ks[4 * (round_idx + 1):4 * (round_idx + 2)])) # 2. 若不是最后一轮,则执行线性混合的逆运算 if round_idx < 15: temp = [0] * 16 for j in range(4): col = [m[j + 4 * k] for k in range(4)] col = [sum(binv[i][k] * col[k] for k in range(4)) % 256 for i in range(4)] for i in range(4): temp[i * 4 + j] = col[i] m = temp # 3. 位重排逆变换 for i in range(4): m[4 * i:4 * i + 4] = inv_bitmix_word(m[4 * i:4 * i + 4], i) # 4. S-box 逆变换 m = [inv_sbox[x] for x in m] # 5. 轮外 XOR 初始 key 部分 与 CBC iv m = xor(m, b''.join(ks[:4])) m = xor(m, prev_iv) result.append(bytes(m)) prev_iv = blk # CBC 链接plaintext = b''.join(result)padlen = plaintext[-1]print(plaintext[:-padlen].decode())# flag{7h3_Ch4rm_0f_SyMm3tr1c_EnCrYpT1On!!!}abc equation(!)
题目:
import os
flag = os.getenv('FLAG')
if __name__ == "__main__": a, b, c = int(input('a = ')), int(input('b = ')), int(input('c = ')) if a > 0 and b > 0 and c > 0: f1 = 2744*a**3 + 10648*b**3 + 17984728*c**3 + 3161480*a**2 + 12256398*a*b + 9908932*b**2 + 115107342*a*c + 185540278*b*c + 52709471256*c f2 = 10241*a**2*b + 16093*a*b**2 + 121961*a**2*c + 464002*a*b*c + 301169*b**2*c + 2282413*a*c**2 + 3586649*b*c**2 + 1698355526*c**2 + 1440958176*a + 2321077176*b + 538357358480 if f1 == f2: print(flag)题目主要就是解一个三元三次方程大于 0 的特解,由于不知道数多大,大概范围在哪,爆破成本很高,问题主要就来到了怎么降低计算量去求解了。
首先设:
题目要求就是要找这个方程的一组正整数解。我们可以考虑降维来看:
这样对每个固定的(b, c),我们只需找一个a使得这个方程成立即可。
接下来可以用一下模数筛的思想:
如果,则a必是此方程的解。那么可以对若干的小素数求出可能的余数解,然后CRT合并就可以得到对一个大模数下的方程的解,这样每个(b, c)只需验证少量a的同余类即可。
exp.py:
# SageMath 10.7from tqdm import tqdmfrom itertools import product
def F(a,b,c): f1 = 2744*a**3 + 10648*b**3 + 17984728*c**3 + 3161480*a**2 + 12256398*a*b + 9908932*b**2 + 115107342*a*c + 185540278*b*c + 52709471256*c f2 = 10241*a**2*b + 16093*a*b**2 + 121961*a**2*c + 464002*a*b*c + 301169*b**2*c + 2282413*a*c**2 + 3586649*b*c**2 + 1698355526*c**2 + 1440958176*a + 2321077176*b + 538357358480 return f1 - f2
def Fa_mod_p(b,c,p): R.<x> = GF(p)[] return R( F(x, ZZ(b), ZZ(c)) )
def crt_combine(res_lists, mods): M = 1 for m in mods: M *= m if any(len(S) == 0 for S in res_lists): return [], M cand = set() for tpl in product(*res_lists): r = 0 mod = 1 ok = True for ri, mi in zip(tpl, mods): try: r = crt([r, ri], [mod, mi]) except Exception: ok = False break mod *= mi if ok: cand.add(int(r % M)) return sorted(cand), M
# 太小再加BMAX, CMAX = 400, 400AMAX = 200000# 不够可以加模数PRIMES = [7, 11, 13, 17, 19, 23]for b in tqdm(range(1, BMAX + 1)): for c in range(1, CMAX + 1): root_sets = [] for p in PRIMES: P = Fa_mod_p(b,c,p) rs = [int(r) for r in P.roots(multiplicities=False)] # a ≡ r (mod p) if not rs: root_sets = [] break root_sets.append(rs) if not root_sets: continue residues, M = crt_combine(root_sets, PRIMES) if not residues: continue # 只在 a ≡ residue (mod M) 的序列上检查,步长 = M for r in residues: start = r if r > 0 else r + M for a in range(start, AMAX + 1, M): if a <= 0: continue if F(a,b,c) == 0: print(f"FOUND a={a} b={b} c={c}") raise SystemExit# 30%|█████████████████████▉ | 355/1200 [00:17<00:41, 20.42it/s]# FOUND a=1347 b=356 c=365> ncat challenge.ilovectf.cn 30786a = 1347b = 356c = 365flag{16fe3697-ca92-4635-91ce-76d08c93f189}观察了一下官方 WriteUp,居然是转到椭圆曲线上求解吗hhh,有点意外了,复现一下。(但是这个观察立方项系数发现是立方数然后还要想到放缩求平移常数实在有点emmmm想不到)
当f1 == f2时,可以令:
则有原式等价于:
然后可以使用EllipiticCurve_from_cubic将上述三次齐次式映射到一条有理数域的椭圆曲线,利用gens找到椭圆曲线的生成元,再通过椭圆曲线上的加法和数乘计算出该生成元的n倍点并在这些点中找到一个满足a,b,c都为正整数的(x, y, z)。
exp.py:
# SageMath 10.7F.<a, b, c> = ZZ[]f1 = 2744*a**3 + 10648*b**3 + 17984728*c**3 + 3161480*a**2 + 12256398*a*b + 9908932*b**2 + 115107342*a*c + 185540278*b*c + 52709471256*cf2 = 10241*a**2*b + 16093*a*b**2 + 121961*a**2*c + 464002*a*b*c + 301169*b**2*c + 2282413*a*c**2 + 3586649*b*c**2 + 1698355526*c**2 + 1440958176*a + 2321077176*b + 538357358480f = f1 - f2F2.<x, y, z> = QQ[]g = f((x - 3) / 7, y / 11 - 37, z / 131 + 29)F = EllipticCurve_from_cubic(g, morphism=True)E = F.codomain()G = E.gens()[0]Finv = F.inverse()for k in range(100): ABC = Finv(k * G) AA, BB, CC = ABC d = max(AA.denominator(), BB.denominator(), CC.denominator()) AA, BB, CC = AA * d, BB * d, CC * d AA, BB, CC = ((AA - 3) / 7, BB / 11 - 37, CC / 131 + 29) if all([i.denominator() == 1 and i > 0 for i in [AA, BB, CC]]): print(k) print('a =', AA) print('b =', BB) print('c =', CC) print(f(AA, BB, CC)) breakmy crypto
题目:
from Crypto.Util.number import *from Crypto.Cipher import AESfrom secret import keys, flag
assert all([isPrime(i) for i in keys])
a, b, c, d = keysflag1 = flag[:16]flag2 = flag[16:]
def getrandbits(nbits): global a a = (a * b + c) % d res = a % (1 << (nbits % 32)) for i in range(nbits>>5): a = (a * b + c) % d res = (res << 32) + a return res
def my_getPrime(nbits): while 1: p = getrandbits(nbits) if isPrime(p): return p
k = b''.join([long_to_bytes(i) for i in keys])cipher = AES.new(k,AES.MODE_ECB)c1 = cipher.encrypt(flag1)print(c1.hex())# b7a6aa2e92065c6ca0f641774daa6be7
p = my_getPrime(512)q = my_getPrime(512)n = p * qd = my_getPrime(32)e = pow(d, -1, (p-1)*(q-1))m = bytes_to_long(flag2)c2 = pow(m, e, n)print(f'{n = }')print(f'{e = }')print(f'{c2 = }')
'''n = ...e = ...c2 = ...'''分两个部分,前半是个 AES-ECB,关键是求key,key由a,b,c,d四个参数组成;后半部分是 RSA,用了自己写的 LCG 函数生成p,q,d。AES的key中的四个参数同时以 LCG 的参数参与了后半部分的 RSA,后半部分的 RSA 可以直接 Wiener 板子打出d,就能求解后半部分 flag。然后有了d和k可以分解n,求出p和q,再把p和q从高位往低位每 32 比特提取,可以得到 LCG 的状态组(但是p和q的状态组之间不连续),然后通过 LCG 可以求得a,b,c,d四个参数就可以解前半部分 flag 了。
exp.py:
from Crypto.Util.number import *from Crypto.Cipher import AESfrom sympy import *
class ContinuedFraction(): def __init__(self, numerator, denominator): self.numberlist = [] self.fractionlist = [] self.GenerateNumberList(numerator, denominator) self.GenerateFractionList()
def GenerateNumberList(self, numerator, denominator): # 生成简单连分数 while numerator != 1: quotient = numerator // denominator remainder = numerator % denominator self.numberlist.append(quotient) numerator = denominator denominator = remainder
def GenerateFractionList(self): self.fractionlist.append([self.numberlist[0], 1]) for i in range(1, len(self.numberlist)): numerator = self.numberlist[i] denominator = 1 for j in range(i): temp = numerator numerator = denominator + numerator * self.numberlist[i - j - 1] denominator = temp self.fractionlist.append([numerator, denominator])
n = ...e = ...c2 = ...res = ContinuedFraction(e, n)for k, d in res.fractionlist: # 判断哪一个是我们所需的d if k == 0 or k == 1: continue elif (e * d - 1) % k == 0: breakm = pow(c2, d, n)flag2 = long_to_bytes(m).decode()phi = (e * d - 1) // kp, q = var("p q")eq1 = Eq(p * q, n)eq2 = Eq((p - 1) * (q - 1), phi)p, q = solve([eq1, eq2], [p, q])[0]assert p * q == nstate = [(p >> (32 * (15 - i))) & 0xffffffff for i in range(16)]diff = []for i in range(len(state) - 1): diff.append(state[i + 1] - state[i])d = diff[-1] * diff[-3] - diff[-2] ** 2for i in range(2, len(diff) - 1): d = gcd(d, diff[i] * diff[i - 2] - diff[i - 1] ** 2)assert isPrime(int(d))for i in range(2, len(diff) - 1): b = (diff[i] * invert(diff[i - 1], d)) % db += dbinv = invert(b, d)c = (state[1] - state[0] * b) % dfor i in range(len(state) - 1): assert state[i + 1] == (b * state[i] + c) % da = (state[0] - c) * binv % dc1 = bytes.fromhex("b7a6aa2e92065c6ca0f641774daa6be7")while 1: a = (a - c) * binv % d cnt += 1 if cnt % 100000 == 0: print(cnt) if isPrime(int(a)): k = b''.join([long_to_bytes(i, 4) for i in [a, b, c, d]]) cipher = AES.new(k, AES.MODE_ECB) flag1 = cipher.decrypt(c1) if b'flag{' in flag1: print(flag1.decode() + flag2) break# a = 3597317387# b = 4126288123# c = 3523486777# d = 3952641229# flag{LCG_is_n0t_a_cryp7o_5ecure_PRNG}期末考试(!)
题目:
from secret import B, Cimport numpy as np
p = 251A = np.diag([np.random.randint(0,p) for i in range(6)])
B = np.matrix(B)C = np.matrix(C)print(B * C %p)
'''[...]'''
D = B * A * C %pprint('D =',D.tolist())
'''D = [...]'''
flag = bytes(B.reshape((1,36)).tolist()[0])assert flag[:4] == b'flag'print(flag)
# b'flag{you_need_to_solve_by_yourself!}' ## fakeflag加密流程是在模 251 的域上取随机对角阵 A,选择可逆矩阵 B 以及 B 的逆矩阵 C。输出:
flag就是 B 矩阵按行展平。可以验证一下 D 在域上是可对角化且特征值互异:
# SageMath 10.7print(D.is_diagonalizable())print(D.eigenvalues())# True# [182, 150, 132, 109, 68, 16]首先这题要说一下自由度的问题:
设 的列是 的右特征向量,满足
其中有两类等价变换不会改变 :
-
**列缩放:**任取可逆对角矩阵
含义:每列特征向量可乘任意非零标量,自由度 ,规模为 250^6。
-
**列置换:**任取置换矩阵 ,
含义:列顺序可任意换,同时对 以相同置换重排对角元,自由度 6!。
也就是说 唯一确定的是“每个特征空间的方向”,而不是具体的列向量的顺序。然后再看回这道题就会发现,是个有点巧妙的题了,flag{}刚好限制了 6 个列缩放自由度,只需要再爆破一下列置换自由度就行了。
exp.py:
# SageMath 10.7from itertools import permutations
# 取一条向量并规范化def canon(v): v = vector(F, v) for x in v: if x != 0: return (1 / x) * v return v
p = 251D = Matrix(GF(p), [...])spec = D.eigenvectors_right()pairs = [(lam, canon(vs[0])) for lam, vs, mult in spec]# 全排列列顺序target_head = list(b"flag{")for perm in permutations(range(6)): # 组 B, A cols = [pairs[i][1] for i in perm] lams = [pairs[i][0] for i in perm] B = Matrix(F, cols).transpose() # 必须可逆 if B.rank() < 6: continue try: C = B.inverse() except ZeroDivisionError: continue A = diagonal_matrix(F, lams) if B * A * C != D: continue # 固定右下角为 '}' if B[5, 5] == 0: continue s5 = F(125) * (B[5, 5]) ** -1 B[:, 5] *= s5 # 固定首行为 b"flag{" ok = True for j in range(5): if B[0, j] == 0: ok = False break sj = F(target_head[j]) * (B[0, j]) ** -1 B[:, j] *= sj if not ok: continue # 重新验证可逆与相似关系 try: C = B.inverse() except ZeroDivisionError: continue if B * A * C != D: continue if all(32 <= int(x) <= 126 for x in B.list()): flag = bytes(int(x) for x in B.list()) print(flag.decode()) break# flag{3Njoy_My_eZ_m4tr1x_5imi1aRi7y!}参照官方exp码一个,主要是学习记录一下 SageMath 的diagonalization函数的使用。
exp.py:
# SageMath 10.7from itertools import permutations
D = Matrix(GF(251), [...])A, E = D.diagonalization()eye = [[1 if i == j else 0 for j in range(6)] for i in range(6)]for m in permutations(range(6)): P = [] for i in m: P.append(eye[i]) P = Matrix(Zmod(251), P) for n in range(32, 127): B = E / P * diagonal_matrix([ord("f"), ord("l"), ord("a"), ord("g"), ord("{"), n]) flag = B.list() if all([32 <= i <= 127 for i in flag]) and flag[-1] == 125: print(bytes(flag).decode())# flag{3Njoy_My_eZ_m4tr1x_5imi1aRi7y!}粗心的刀客塔
题目:
作战中,粗心的刀客塔在给干员们下达机密指令时漏发了部分内容,为了省事,他直接用原来的密钥重发了完整指令。可他万万没想到,这个看似简单的操作,却导致战术被普瑞赛斯全盘破解。
from Crypto.Util.number import *from secret import flagimport string
assert all([32 <= i < 127 for i in flag])
p = getPrime(256)q = getPrime(256)n = p * qe = 101m = bytes_to_long(flag)leak = pow(m >> 24, e, n)c = pow(m, e, n)
print(f"{n = }")print(f"{c = }")print(f"{leak = }")
'''n = ...c = ...leak = ...'''好简洁的题目,好舒服啊哈哈哈哈哈。根据描述以及这个代码内容,相关信息攻击,上板子!但是会有个问题,这个右移 24 位怎么解决呢,肯定不能爆破,有点大了。这个时候观察一下题目给的一个提示:assert all([32 <= i < 127 for i in flag]),那么我们爆破直接爆破两位字符就可以了(最后一位一定是})。
exp.py:
# SageMath 10.7from Crypto.Util.number import *from tqdm import trangefrom sys import exit
def franklinReiter(n, e, c1, c2, r): PR.<x> = PolynomialRing(Zmod(n)) g1 = x ** e - c1 g2 = (x * 2 ** 24 + r) ** e - c2
def gcd(g1, g2): while g2: g1, g2 = g2, g1 % g2 return g1.monic() return -gcd(g1, g2)[0]
e = ...n = ...c = ...leak = ...for i in trange(32, 127): for j in range(32, 127): r = i * 65536 + j * 256 + ord("}") x = franklinReiter(n, e, leak, c, r) m = int(x * 2 ** 24 + r) try: flag = long_to_bytes(m) if b'flag{' in flag: print(flag.decode()) raise SystemExit except SystemExit: print("Over!") raise SystemExit except: continue# 1%|▉ | 1/95 [00:00<00:14, 6.38it/s]# flag{Franklin-Reiter_Re1at3d_Mes5age_4t7ack!!}# Over!Week 4
Crazy List(-)
题目:
import hashlibfrom random import randintfrom secret import flag
def crash(b): assert type(b) == list def T(b): a = [abs(i) for i in b] n, l, r = max(a) + 1, len(b), [] p = list(range(n)) p1 = p.copy() for i in a: p[i-1], p[i] = p[i], p[i-1] F = set(i+1 for i in range(n-1) if p[i]>p[i+1]) p = [p.index(i) for i in range(n)] S = set(i+1 for i in range(n-1) if p[i]>p[i+1]) while p != p1: for i in range(n-1): if p[i]>p[i+1]: p[i], p[i+1] = p[i+1], p[i] r.append(i+1) return r, S, F n, l = max(b) + 1, len(b) p, r, B, S, F = list(range(n)), [], [[]], [set()], [] for i in b: if {p[i-1],p[i]} in r: F.append(set(j+1 for j in range(n-1) if p[j]>p[j+1])) p, r = list(range(n)), [] B.append([]) S.append(set()) B[-1].append(i) r.append({p[i-1],p[i]}) if abs(p[i-1]-p[i]) == 1: S[-1].add(min(p[i-1],p[i])+1) p[i-1], p[i] = p[i], p[i-1] F.append(set(j+1 for j in range(n-1) if p[j]>p[j+1])) check = True while check: check = False for i in range(len(B)-1): d = S[i+1].difference(F[i]) if len(d) != 0: d1 = d.pop() B[i], S[i], F[i] = T(B[i]+[d1]) B[i+1], S[i+1], F[i+1] = T([-d1]+B[i+1]) check = True nB = [] for i in B: nB.extend(i) return nB
def hash_list(b): b = crash(b) h = hashlib.sha512(bytes(b)).digest() return h
n = 10nLen = 70
x1, x2 = [randint(1,n-1) for i in range(nLen)], [randint(1,n-1) for i in range(nLen)]
a = [randint(1,2*n-1) for i in range(1000)]b = x1 + a + x2
y1, y2 = [randint(n+1,2*n-1) for i in range(nLen)], [randint(n+1,2*n-1) for i in range(nLen)]
xor = lambda a,b:bytes([i^j for i,j in zip(a,b)])c1 = crash(y1 + a + y2)c2 = xor(flag, hash_list(y1 + b + y2))
a = crash(a)b = crash(b)
with open('output.txt','w') as f: f.write(f'{a = }\n') f.write(f'{b = }\n') f.write(f'{c1 = }\n') f.write(f'{c2 = }\n')群论没学好笑的,这个有点太难了,先鸽一鸽害。
Myneighbors
题目:
from secret import magical_num, flagfrom Crypto.Util.Padding import padfrom Crypto.Util.number import *from Crypto.Cipher import AESimport hashlib
p = 431F.<i> = GF(p^2, modulus = x^2 + 1)E = EllipticCurve(j=F(magical_num))assert E.is_supersingular()
P = E(0).division_points(2)[1:]neighbors = []for idx in range(len((P))): phi = E.isogeny(P[idx]) EE = phi.codomain() neighbors.append(EE.j_invariant())
assert E.j_invariant() in neighbors
P = E(0).division_points(3)[1:]shuffle(P)
phi = E.isogeny(P[0])E = phi.codomain()H = hashlib.md5(str(E.j_invariant()).encode()).hexdigest().encode()key, iv = H[:16], H[16:]
aes = AES.new(key, AES.MODE_CBC, iv)cipher = aes.encrypt(pad(flag, 16))print(f'cipher: {cipher.hex()}')#cipher: 49e90a91357fef12c54234b3cb553bb2fdd61f2af8c7e78b3d5ffdeac7022af0本周签到题,真就只做出来这一道,哭了qaq。同源真难啊,先简单学习一下,后面系统学习总结一下。
加密代码首先选定了一条特定的超奇异曲线,从这条超奇异椭圆曲线出发,通过一个3-isogeny得到新曲线,并用新曲线的j-invariant的md5值切割为 AES 的key和iv来加密flag。解密枚举出满足约束条件的候选起点曲线E,并对每个候选枚举所有非平凡3-torsion子群生成的3-isogeny,找到能解出flag的那条就行。
exp.py:
# SageMath 10.7from sage.all import *from hashlib import md5from tqdm import trangefrom Crypto.Cipher import AESfrom Crypto.Util.Padding import unpad
p = 431F.<i> = GF(p^2, modulus=x^2 + 1)cipher = bytes.fromhex("49e90a91357fef12c54234b3cb553bb2fdd61f2af8c7e78b3d5ffdeac7022af0")found = Falsefor magical_num in trange(p): E = EllipticCurve(j=F(magical_num)) if E.is_supersingular() and found == False: P = E(0).division_points(3)[1:] for p in P: try: phi = E.isogeny(p) E1 = phi.codomain() H = md5(str(E1.j_invariant()).encode()).hexdigest().encode() key, iv = H[:16], H[16:] aes = AES.new(key, AES.MODE_CBC, iv) flag = aes.decrypt(cipher) if b'flag{' in flag: print(unpad(flag, 16).decode()) found = True break except: continue elif found: break else: continue# 14%|███████████▌ | 62/431 [00:00<00:00, 1200.25it/s]# flag{I_@m_4_n31gh80r_0f_my53lf}RSA紧缩术(*)
题目:
from Crypto.Util.number import *from secret import flag
p = getPrime(1024)q = getPrime(1024)
n = p * qphi = (p - 1) * (q - 1)
e = getPrime(128)d = pow(e, -1, phi)leak = (1145 * d + 1419 * p + 19810 * q) >> 200
m = bytes_to_long(flag)c = pow(m, e, n)
print(f'{n = }')print(f'{e = }')print(f'{c = }')print(f'{leak = }')
'''n = ...e = ...c = ...leak = ...'''变式 RSA 的私钥d高位泄露的 CopperSmith 攻击。
主要还是考虑将已知d的高位泄露想办法转化为已知p的高位泄露。leak = (1145 * d + 1419 * p + 19810 * q) >> 200,我们可以记:
由已知
左右同乘e,移项可得
而第二项根据位数分析一下可知应该很小,在0 - 1之间,我们可以考虑k = (e * L_high) // (1145 * n)。从上述式子还能找出一个等式:
由于e * p * L_low很小,我们省略这一项然后在实数域对p求解可以得到p的高位(但是不是我们常见意义的高位,并非说是p >> 200 << 200这种高位,只是p减去了一个小值p_low而已),然后 CopperSmith 打出p的低位就能分解n了。
exp.py:
# SageMath 10.7from tqdm import trangefrom Crypto.Util.number import long_to_bytes
n = ...e = ...c = ...leak = ...L_high = leak << 200k = (e * L_high) // (1145 * n)RF.<ph> = RealField(2000)[]f1 = e * ph * L_high - 1419 * e * ph ** 2 - 19810 * e * n - 1145 * ph - 1145 * k * (n * ph - ph ** 2 - n + ph)for res, _ in f1.roots(): p_high = int(res) PR.<pl> = PolynomialRing(Zmod(n)) f2 = pl + p_high p_low = f2.small_roots(X=2**200, beta=0.4, epsilon=0.02) if p_low: p = int(p_low[0] + p_high) q = n // p assert p * q == n phi = (p - 1) * (q - 1) d = inverse_mod(e, phi) m = pow(c, d, n) print(long_to_bytes(int(m)).decode()) break# flag{c0pp3r5mith_Shrinkage_Technique}Xaleid◆scopiX(*)
题目:
from Crypto.Util.number import *import randomimport os
flag = open('/home/ctf/flag').read()
basis = random.randint(2,(1<<128)-2)prime = getPrime(128)
def kaleidoscope(msg): r = basis for i in msg: r = r * prime % (1 << 128) r = r ^ i return r
def main(i): key = os.urandom(random.randint(64,128)) print('Hello! Your gift:', kaleidoscope(key)) s1 = input('Tell me your name: ').encode() print('Hello,', kaleidoscope(s1), '! Let\'s sign in!') s2 = input('Your account: ').encode() print('Your account:', kaleidoscope(s2)) s3 = int(input('Your password: ')) if s3: if s3 == kaleidoscope(s1 + key + s2): print('Sign in successfully!') s4 = input('Your operation: ') if s4 == 'flag': if s2 == b'admin': print(flag) else: print('Permission deny') elif s4 == 'logout' and i: main(i-1) else: print('Unknown operation') else: print('Password incorrect!')
if __name__ == '__main__': main(1)也算是第一次了解 FNV 了,上一次看到 FNV 还是在 R3 上,根本看不懂那个hhh。
本题服务器会在我们每一次发送字符串后返回哈希值。而basis和prime是固定的,因此我们可以想办法把basis和prime求出来。题目有一个较大的泄漏点就是没有检查s1是否为空字符串,所以我们可以发送空字符串s1直接得到basis值,然后发送任意字节比如"1",就可以得到((basis * prime) % (2 ** 128)) ^ ord("1"),这样就可以求出prime的值,考虑到basis可能为偶数,题解使用了gcdext去逆basis求解prime,当basis为偶数时穷举可能的prime。
然后求出这两个已知信息后,易知s1 + key的哈希值就是key的哈希值,而且key + s2的哈希值是在key的基础上以key的哈希值作为basis算出的s2的哈希值。因此只要我们有key的哈希值和prime就可以在不知道key的实际值的情况下伪造key + s2的哈希值。题目给了两轮的机会,可以在第一轮验证时求出basis和prime的值然后logout,在第二轮伪造出key + "admin"的哈希值获取flag。
exp.py:
from pwn import *from gmpy2 import gcdext, is_prime
def kaleidoscope(basis, prime, msg): r = basis for i in msg: r = r * prime % (1 << 128) r = r ^ i return r
r = remote("ctf.a1natas.com", 24524)r.recvuntil(b'Hello! Your gift: ')key = int(r.recvline().strip().decode())r.sendafter(b'Tell me your name: ', b'\n')basis = int(r.recvline()[7:-18].decode())r.sendafter(b'Your account: ', b'1\n')r.recvuntil(b'Your account: ')s2 = int(r.recvline().strip().decode())g = gcdext(basis, 1 << 128)p_r= ((s2 ^ ord("1")) * g[1] % (1 << 128)) // g[0]for i in range(g[0]): prime = p_r + i * (1 << 128) // g[0] if is_prime(prime) and prime.bit_length() == 128: pswd = (key * prime % (1 << 128)) ^ ord("1") r.sendline(str(pswd).encode()) r.sendline(b'logout') r.recvuntil(b'Hello! Your gift: ') key = int(r.recvline().strip().decode()) r.sendafter(b'Tell me your name: ', b'\n') r.sendafter(b'Your account: ', b'admin\n') r.recvuntil(b'Your password: ') r.sendline(str(kaleidoscope(key, prime, b'admin')).encode()) r.sendafter(b'Your operation: ', b'flag\n') print(r.recvline().decode())# flag{ea93b1b3-8a01-4145-9b4d-2fde98400fae}ezECC(-)
题目:
from Crypto.Util.Padding import padfrom Crypto.Cipher import AESfrom secrets import flagimport randomimport hashlib
class Niederreiter_Cryptosystem: def __init__(self, n, k): self.F = GF(2^8,repr='int') alpha = self.F.list()[1:] self.C = codes.GeneralizedReedSolomonCode(alpha, k) self.t = (n - k) // 2 self.pub, self.priv = self.gen(n, k)
def get_recover_ability(self): return self.t
def get_key(self): return self.priv
def gen(self, n, k): H = self.C.parity_check_matrix() P = Permutations(n).random_element().to_matrix() S = random_matrix(self.F, n-k, n-k) while S.det() == 0: S = random_matrix(self.F, n-k, n-k) Pub = S * H * P return Pub, (S, H, P)
def encrypt(self, msg): e = vector(self.F, [self.F._cache.fetch_int(x) for x in msg]) return self.pub * e
n, k = 255, 223encryptor = Niederreiter_Cryptosystem(n, k)key = [random.getrandbits(8) for _ in range(encryptor.get_recover_ability())] + [0] * (n - encryptor.get_recover_ability())random.shuffle(key)
cipher = encryptor.encrypt(key)S, H, P = encryptor.get_key()
key = hashlib.sha256(str(key).encode()).digest()aes = AES.new(key, AES.MODE_ECB)c = aes.encrypt(pad(flag, 16))
with open('output.txt', 'w') as file: file.write('encrypted key: ' + str(cipher) + '\n') file.write('S: ' + str(S.list()) + '\n') file.write('P: ' + str(P.list()) + '\n') file.write('encrypted flag: ' + str(c))初看这题应该就要单纯的根据这个私钥解出原来的密文,题目也没有藏着掖着很直接的就给出了Niederreiter密码系统。
复现好像有点问题一直不对hhh,先鸽了。
linear function?(*)
题目:
import randomfrom Crypto.Util.number import *P = ...
def pairing(a, b): return (a * b) % P
def modexp(base, exp): return pow(base, exp, P)
def fake_hash(msg: bytes) -> int: return (bytes_to_long(msg)+q*114514114514)% P
q = 1# ?? #small number
g1 = pow(random.randint(2, P-1), (P-1)//q, P)g2 = pow(random.randint(2, P-1), (P-1)//q, P)x = random.randint(2, q-1)y = random.randint(2, q-1)
pk1 = modexp(g1, x)pk2 = modexp(g2, y)
flag = b""
m = fake_hash(flag)c1 = modexp(m, y)c2 = pow(pairing(m, g2), x, P)
print("g1 =", g1)print("g2 =", g2)print("pk1 =", pk1)print("pk2 =", pk2)print("c1 =", c1)print("c2 =", c2)'''g1 = ...g2 = ...pk1 = ...pk2 = ...c1 = ...c2 = ...'''考点是双线性配对,这是一类满足 的映射,它允许将两个群元素中的指数线性地组合。本题中的pairing(a, b) = ab mod p是一个不安全的伪双线性配对,满足双线性性质:,但是暴露了指数结构,使得密文中的指数可以被代数拆解从而恢复明文。
题目给出了密文:
其中m = fake_hash(flag) = (bytes_to_long(flag) + q * 114514114514) % P。
由于pairing是线性的:
如果我们能得到x,就可以直接消去g2项:
而且计算阶发现g1和g2的阶足够小且较为平滑(且很巧ord(g1) = 2 * ord(g2)),可以直接DLP打出x和y,这样就能恢复出:
然后通过计算发现d = gcd(x, y) = 3,我们可以通过扩展欧几里得找到u,v使
这样我们便可以找到m ** 3,然后在模P下开三次方找到三个根,分别逆fake_hash筛出flag。
exp.py:
# SageMath 10.7from Crypto.Util.number import long_to_bytes
P = ...g1 = ...g2 = ...pk1 = ...pk2 = ...c1 = ...c2 = ...ZP = Zmod(P)g1, g2, pk1, pk2, c1, c2 = ZP(g1), ZP(g2), ZP(pk1), ZP(pk2), ZP(c1), ZP(c2)ord_g1 = g1.multiplicative_order()ord_g2 = g2.multiplicative_order()# ord(g1) == 2 * ord(g2)q_sub = Integer(ord_g2)x = discrete_log(pk1 % P, g1 % P, ord=q_sub*2)y = discrete_log(pk2 % P, g2 % P, ord=q_sub)QMAX = max(x, y) * 4mx = c2 / (g2 ** x)my = c1d, u, v = xgcd(x, y)md = (mx ** u) * (my ** v) # md = m^dF = GF(P)mdF = F(int(md))roots = list(mdF.nth_root(3, all=True))candidates_m = [ZP(int(r)) for r in roots]for ridx, m in enumerate(candidates_m): m_int = Integer(m) for q_try in range(2, Q_MAX + 1): val = (m_int - q_try * C) % P msg = int_to_bytes(val) if msg.startswith(b"flag{") and msg.endswith(b"}"): print(msg.decode()) raise SystemExit# flag{pair1n9_13_s000_fun}官方的 WriteUp 就更暴力一些了,直接通过爆破去找x,y,然后在模P下恢复到m的gcd(y, P - 1) = 3次方,再进行开方计算flag = long_to_bytes((r - q * 114514114514) % P)筛出真正的flag。
exp.py:
from sys import exitfrom gmpy2 import gcd, invertfrom Crypto.Util.number import long_to_bytesfrom sympy.ntheory.residue_ntheory import nthroot_mod
def modexp(base, exp): return pow(base, exp, P)
def force(base, value, p, q): cur = 1 for e in range(q): if cur == value: return e cur = (cur * base) % p return None
P = ...g2 = ...pk2 = ...c1 = ...y = Nonefor q in range(100, 150): rec_y = force(g2, pk2, P, q) y = rec_y if y != None: M = (P - 1) // gcd(y, P - 1) e = invert(y // gcd(y, P - 1), M) m3 = pow(c1, e, P) roots = nthroot_mod(m3, 3, P, all_roots=True) for r in roots: flag = long_to_bytes((r - q * 114514114514) % P) if flag.startswith(b"flag{") and flag.endswith(b"}"): print(flag.decode()) exit(1)# flag{pair1n9_13_s000_fun}异域症(*)
题目:
from Crypto.Util.number import *import osfrom secret import flagimport hashlib
def mul(a, b, n): return ((a[0]*b[0]+2*a[1]*b[2]+2*a[2]*b[1]) % n, (a[0]*b[1]+a[1]*b[0]+2*a[2]*b[2]) % n, (a[0]*b[2]+a[2]*b[0]+a[1]*b[1]) % n)def powmod(a, b, n): res = (1, 0, 0) while b > 0: if b & 1 == 1: res = mul(res, a, n) a = mul(a, a, n) b >>= 1 return res
p = getPrime(512)q = getPrime(512)N = p * q
e = 13
mask = [os.urandom(64) for i in range(3)]m = tuple(bytes_to_long(i) for i in mask)hint = mask[2]
c = powmod(m, e, N)
xor = lambda a,b:[i^j for i,j in zip(a,b)]cipher = bytes(xor(hashlib.sha512(b''.join(mask)).digest(),flag))
print(f'{N = }')print(f'{e = }')print(f'{c = }')print(f'{hint = }')print(f'{cipher = }')
'''N = ...e = 13c = (..., ..., ...)hint = b'...'cipher = b'...''''前两天 ISCTF 上碰到的下架的题目也是结式题,在这再仔细学习学习(不过这题好难,是环上的结式并非是域上的)。
首先本题得了解一下商环,在此暂时不展开说明。了解完仔细看看这个mul函数就会发现这是一个商环 上的乘法。而m是R中的一个随机元素并且泄露了其a2分量,根据m和c的各分量相等我们可以给出三个方程,未知数为a0和a1。
为了进一步简化问题,可以利用结式(Resultant)来消去一个未知数。结式是通过消去多项式中的一个变量,得到另一个关于剩余变量的方程。在本题中,环上无法使用resultant函数,可以利用 Sylvester 矩阵 来计算结式,并通过计算行列式来判断多项式是否有公共根。具体来说,通过计算多项式的 Sylvester 矩阵的行列式来得到结式,从而消去未知数,并通过得到的方程求解出 (a_0) 和 (a_1)。再结合hint就可以计算出m了。
exp.py:
# SageMath 10.7from Crypto.Util.number import *from hashlib import sha512
def poly_gcd(f, g): if f.degree() < g.degree(): f, g = g, f while not g.is_zero(): f, g = g, f % g return f
xor = lambda a, b: [i ^^ j for i, j in zip(a, b)]
N = ...e = 13c = (..., ..., ...)hint = b'...'cipher = b'...'h = bytes_to_long(hint)R.<s, t, x> = Zmod(N)[]C = c[0] + c[1] * x + c[2] * x ** 2m = s + t * x + h * x ** 2c1 = m ** e - C# 这里 c2 = c1 % (x ** 3 - 2) 有点小问题,还没弄明白,手搓了一个c2 = c1while c2.degree(x) >= 3: for i in range(3, c2.degree(x) + 1): c2 += 2 * c2.coefficient({x:i}) * (x ** (i - 3)) c2 -= c2.coefficient({x:i}) * (x ** i)cc1 = c2.coefficient({x:0})cc2 = c2.coefficient({x:1})cc3 = c2.coefficient({x:2})ccc1 = cc1.sylvester_matrix(cc2, R.gen(1)).det()ccc2 = cc1.sylvester_matrix(cc3, R.gen(1)).det()F2.<q> = Zmod(N)[]ccc1 = ccc1(q, 0, 0)ccc2 = ccc2(q, 0, 0)mask1 = N - poly_gcd(ccc1, ccc2).monic()[0]ccc3 = cc1(mask1, q, 0)ccc4 = cc2(mask1, q, 0)mask2 = N - poly_gcd(ccc3, ccc4).monic()[0]flag = bytes(xor(sha512(long_to_bytes(int(mask1)) + long_to_bytes(int(mask2)) + hint).digest(), cipher))print(flag.decode())# flag{D0N'T_B3_Depr3s5i0n_bE_HOpefu1_4nd_HappY}Some information may be outdated









