AliCTF 2026 Crypto - Griffin

SUBGROUPX Repo: https://github.com/n-WN/SubgroupX Griffin 这题的关键是 Phase1 恢复的 $x$ 存在仿射不确定性,导致 Phase2 求根得到的是 $x = a m + b$ 而不是 $m$,需要对 $(a,b)$ 做 affine 枚举。 / 结论 flag: alictf{**} 关键坑点: Phase1 恢复的 $x$ 存在仿射不确定性,导致 Phase2 求根得到的是 $x = a m + b$ 而非 $m$,必须做 affine 枚举,最终命中 $(a,b)=(-1,256)$ Full solve code (gist) 复现参数 以下数值来自一次成功复现的检查点和附件内容:
Read more

N1CTF Junior 2026 1/2

SUBGROUPX Repo: https://github.com/n-WN/SubgroupX 解了几道分数较高的题目,部署后继续修复公式渲染(theme/模板问题)。 / AstralCat 服务端每轮随机 99 字节 message,用 Luo Shu 做 CBC。关键点是同一个对象连续调用两次 Encrypt(),第二次加密的输入其实是第一次的密文。
Read more

XCTF Lilac 2026 Writeup

SUBGROUPX Repo: https://github.com/n-WN/SubgroupX 整理版汇总:按仓库内文档与脚本串主线,每题只保留关键思路和 exp 核心片段(截图已脱敏)。 / 整理说明 本文按仓库内文档与脚本整理,每题只保留解题主线与 exp 核心片段。 文中用 HOST:PORT 代替真实地址,flag 统一写为 LilacCTF{...},不包含外链 URL 与真实 IP。
Read more
July 9, 2024

2024.07.09 某内部赛

8abyRSA 题面 from Crypto.Util.number import getPrime, bytes_to_long from LCGRandom import lcg flag = b"DASCTF{xxxx}" m = bytes_to_long(flag) q = getPrime(512) p = getPrime(512) Q = pow(q, 2, p) # Q = q^2 mod p n = p*q e = getPrime(512) c = pow(m, e, n) print('c = ' + str(c)) mul = p n = e c = Q lcg1 = lcg(2023, mul, n, c) print(lcg1.generate(6)) # Data # c = 14968774972802568447907734980942381885170753466134942777553237930032293557412276601708303806296932877792965437305452274767013479132983066980557420348521340748653518789837988349171582558831336243036976332252071289409948879158346086243451785505819420501498240344839999096449458990633841326838003742773196414924 # [1829291767649461103161355195365566822501625317566021355553611375584303624765389047237072963403651896280059309840080549881138083110457632256619677785411889, 8614784647131959905489143385414473366220060118109917221659356152996027731619256154868253763867569947876938642677951734943013867186530789820165520197525548, 7845112879564311528346861967994968539141359532994486000551038947867807602664345244223622318955917969589790931596150225393028516278795565957075910272882168, 464696663556289552651401659156226806419638205935729756668755007052945250273596319671267391086858010496056118762904740103810918505838370732992982394379753, 6368769952056267497431070536699202267447921981615807369090285698513090854334005214066202915932322373348204466447234330868540529876844912401907159350901200, 4401214958628797991805307100256403706658940216552816808894942142670769563264366171079551655390635613048161714797548328158857275108940238519111063046341755] c = 14968774972802568447907734980942381885170753466134942777553237930032293557412276601708303806296932877792965437305452274767013479132983066980557420348521340748653518789837988349171582558831336243036976332252071289409948879158346086243451785505819420501498240344839999096449458990633841326838003742773196414924 lcg_gen_6 = [1829291767649461103161355195365566822501625317566021355553611375584303624765389047237072963403651896280059309840080549881138083110457632256619677785411889, 8614784647131959905489143385414473366220060118109917221659356152996027731619256154868253763867569947876938642677951734943013867186530789820165520197525548, 7845112879564311528346861967994968539141359532994486000551038947867807602664345244223622318955917969589790931596150225393028516278795565957075910272882168, 464696663556289552651401659156226806419638205935729756668755007052945250273596319671267391086858010496056118762904740103810918505838370732992982394379753, 6368769952056267497431070536699202267447921981615807369090285698513090854334005214066202915932322373348204466447234330868540529876844912401907159350901200, 4401214958628797991805307100256403706658940216552816808894942142670769563264366171079551655390635613048161714797548328158857275108940238519111063046341755] seed = 2023 Exploit SageMath’s Code / from sage.all import * import random from Crypto.Util.number import long_to_bytes lcg_gen_6 = [1829291767649461103161355195365566822501625317566021355553611375584303624765389047237072963403651896280059309840080549881138083110457632256619677785411889, 8614784647131959905489143385414473366220060118109917221659356152996027731619256154868253763867569947876938642677951734943013867186530789820165520197525548, 7845112879564311528346861967994968539141359532994486000551038947867807602664345244223622318955917969589790931596150225393028516278795565957075910272882168, 464696663556289552651401659156226806419638205935729756668755007052945250273596319671267391086858010496056118762904740103810918505838370732992982394379753, 6368769952056267497431070536699202267447921981615807369090285698513090854334005214066202915932322373348204466447234330868540529876844912401907159350901200, 4401214958628797991805307100256403706658940216552816808894942142670769563264366171079551655390635613048161714797548328158857275108940238519111063046341755] """ seed = 2023 x = [seed] x = x + lcg_gen_6 """ x = lcg_gen_6 x1 = x[0] x2 = x[1] x3 = x[2] t = [] for i in range(1, len(x)): t.append(x[i] - x[i-1]) # 恢复 Modulus m = 0 for i in range(1, len(t)-1): m = GCD(t[i+1]*t[i-1] - t[i]*t[i], m) print(f'[+] modulus: {m}') n = m # 恢复 Multiplier, Increment (a, b) R = Zmod(n) a = R(x[2] - x[1]) / (x[1] - x[0]) b = R(x[1] - a * x[0]) print(f'[+] a = {a}\n[+] b = {b}') # 也许参数位置不对 p = ZZ(a) e = ZZ(b) Q = ZZ(n) # Q \equiv q^2 \pmod{p} # 求二次剩余, 返回空, 大概率位置错了 # [] # check location print(f'[*] Parameters location check:\t{is_prime(p)} {is_prime(e)} {is_prime(Q)}') # 1 0 1 # p, q, e 均为 512-bits 素数, 而上面第二个返回值为 False, 则说明位置错了 Q, e = e, Q # 判二次剩余 if not legendre_symbol(Q, p) == 1: print('[-] No solution') else: print('[+] Found solution') # Solution quadratic residue # Modulus is prime, So # Cipolla Algorithm class CipollaAlgorithm: def __init__(self, p): self.p = p # 模数p def mul(self, a, b, w): # 复数乘法 x = (a[0] * b[0] + a[1] * b[1] * w) % self.p y = (a[0] * b[1] + a[1] * b[0]) % self.p return (x, y) def qpow_r(self, a, b): # 实数快速幂 res = 1 while b: if b & 1: res = res * a % self.p a = a * a % self.p b >>= 1 return res def qpow_i(self, a, b, w): # 复数快速幂 res = (1, 0) while b: if b & 1: res = self.mul(res, a, w) a = self.mul(a, a, w) b >>= 1 return res[0] def cipolla(self, n): n %= self.p if self.qpow_r(n, (self.p - 1) // 2) == self.p - 1: return -1 # 没有解 while True: a = random.randint(0, self.p-1) w = (a*a - n) % self.p if self.qpow_r(w, (self.p - 1) // 2) == self.p - 1: break x = (a, 1) return self.qpow_i(x, (self.p + 1) // 2, w) # Q = q^2 mod p p = p n = Q cipolla = CipollaAlgorithm(p) ans1 = cipolla.cipolla(n) ans2 = (-ans1) % p ans = [] if ans1 == -1: print("[-] No solution") else: if ans1 > ans2: ans1, ans2 = ans2, ans1 if ans1 == ans2: ans.append(ans1) else: ans.append(ans1) ans.append(ans2) # check prime for i in ans: if not is_prime(i): # print(f'[-] {i} is not prime') pass else: q = i print(f'[+] {i} is prime') print('[*] DONE Quadratic Residue') phi = (p - 1) * (q - 1) d = inverse_mod(e, phi) c = 14968774972802568447907734980942381885170753466134942777553237930032293557412276601708303806296932877792965437305452274767013479132983066980557420348521340748653518789837988349171582558831336243036976332252071289409948879158346086243451785505819420501498240344839999096449458990633841326838003742773196414924 m = pow(c, d, p * q) print(f'[*] 全因子解密: \n{long_to_bytes(m)}') # uuid 长 42 bytes # DASCTF{} 长 8 bytes # 50 * 8 = 400 bits < 512 bits # 单因子解密 phi = p - 1 d = inverse_mod(e, phi) m = power_mod(c, d, p) print(f'[*] 单因子解密: \n{long_to_bytes(m)}') """ inva = inverse(a, n) x1 = x[0] for i in range(300): x1 = (x1 - b) * inva % n try: flag = long_to_bytes(x1) if b'flag{' in flag: print(flag) except Exception as err: print(err) continue """
Read more
June 29, 2024

2024.6.29 某行业赛 一道 Crypto+Misc 希尔密码

Hill Cipher(不知道题目叫什么名字) 下载题目附件后打开看到 / 拖到 010 Editor 提示报错,注意到文件末尾存在字符串,且为三的倍数 / 同时结合图片名为 hill.png 以及图片中数字的排列,推导可能是 Hill Cipher
Read more
June 28, 2024

2024.6.28 某(农信?)行业赛 Crypto 与 Misc

Crypto easyLCG 题目代码 from Crypto.Util.number import * from random import randint FLAG = ? ROUND = randint(200, 300) class LCG: lcg_a = getPrime(498) lcg_b = getPrime(498) lcg_n = getPrime(512) def __init__(self, lcg_seed): self.state = lcg_seed def next(self): self.state = (self.state * self.lcg_a + self.lcg_b) % self.lcg_n return self.state seed = bytes_to_long(FLAG) print(seed.bit_length()) lcg = LCG(seed) for _ in range(ROUND-6): lcg.next() print([lcg.next() for _ in range(6)]) # 351 # [2485483242304449696161151168576736302336140244327446722677621064961717587947642623655706309294371876714311165214262071924932913930440142186509325733360885, 6174672247406972581092780254648964828729162316422924118545162215261641830776919624579500836375440017861960302702691972683448546062254126073514097080361044, 4584872703321313263026316988830140564935997972340636369234263637913498924218112980629201945528119162637762726584003527994733552009906632734086040880127542, 4829175497283310360340169484343201154685159906303099960915060755507745973302279262836696523131741537828200567942990339422885197644614494869008027862313162, 6669041483112643196441450289748743294802963984583343809912658359929976814854869038886397237458253328867571805574485994275429182399171275201561798677581761, 2732859498560958306654933055106793656103744294844703560692860713169132266734427400301681246251133210447387861776419850678194913478737337289544516324708390] 思路 比较基础的参数恢复后逆推 seed / flag 是初始 seed 的 bytes 形式, 且 seed 的 bit 长度为 $351$ / 同时分析代码中 class LCG 的 next() 有
Read more
June 24, 2024

熊猫杯初赛与决赛 Crypto WriteUp

初赛密码题只有一道,还沾了点猜谜… / 绝密文件-代号P 题目代码 import cv2 import numpy as np from Crypto.Util.number import * from secret import p, q, c def arnold(img, shuffle_times, a, b): r, c, d = img.shape # d: dimension p = np.zeros(img.shape, np.uint8) # new image for s in range(shuffle_times): # shuffle times for i in range(r): # height for j in range(c): # width x = (i + b * j) % r # new position y = ((a * i) + (a * b + 1) * j) % c # new position p[x, y, :] = img[i, j, :] img = np.copy(p) return p Img_path = "flag_test.png" Img = cv2.imread(Img_path) assert isPrime(p) and isPrime(q) and p**2 + \ q**2 == c and c == 179093209181929149953346613617854206675976823277412565868079070299728290913658 Img_arnold = arnold(Img, c, p, q) cv2.imwrite("flag_enc.png", Img_arnold) 思路 分析得知 arnold()管理图片像素置换,而参数 $a,b$ 未知
Read more
June 21, 2024

一些入门的常见的 CTF Crypto 题型[2]

部分位已知 通过分析题目附件中的代码发现:素数总是隐藏了 50%bit,反过来就是 50%bit 已知 / DFS + 枚举状态 + 通过 $N$ 的低部分位来 Check 枚举的 $p,q$ 低位是否准确即可解出 $p,q$ / 随后即为常规 RSA
Read more
June 20, 2024

趣题分享[5] -- 古典密码 相关题目

可通过优化代码的方式打表,但这里只给出以古典方式分析题目 确认 c 与 secrets 的对应关系(使得可以通过 c 恢复 secrets) / 确认 Time_Machine() 的单调性(使得可以根据大小关系推断) / 词频分析(找出空格、e的对应值) / 特判(Case) / 枚举状态、NLP 处理小技巧、使用等宽字体、使用辅助函数恢复(磨刀不误砍柴工)、生成 ASCII 码表、生成多种排序方式、生成 Key Value Pair,且使用 Tuple 来实现更多展示信息,方便分析
Read more
June 20, 2024

一些入门的常见的 CTF Crypto 题型[1]

RSA是什么 特征:给出 p、q,考察 RSA 解密原理 SageMath’s Code / from Crypto.Util.number import long_to_bytes p = q = phi = (p - 1) * (q - 1) n = p * q e = d = inverse_mod(e, phi) m = power_mod(c, d, n) print(long_to_bytes(int(m))) # 强制类型转换 RSA $e=3$ 特征:$e$ (公钥指数) 很小 from gmpy2 import iroot import itertools n = e = 3 c = """ m = iroot(c, e) if m[1]: print(long_to_bytes(m[0])) """ for i in itertools.count(0): m = iroot(c + i * n, e) if m[1]: print(long_to_bytes(m[0])) break RSA 因数分解 特征:$N$ 的因子很小 这里为 ($80\text{bit} * 80\text{bit}$)
Read more