前言

最近正值暑假,闲着也是闲着,于是来玩玩吧。我发现自己真的很弱小,这些赛题的分值与解出的队伍数量成反比,将所有的题目按照最终分值排序,我恰好完成了分值最少的四道。加上事后补的两道,共完成了分值最少的六道。

——就是说难的一道都不会(躺)

正文

既然都这样了,那干脆就按照最终题目分数升序写题解吧,按理来说,越前面的越简单。

1.Vinad

观察代码,我们得到了pubkey的第1和第2项,即Rp*q,和加密后的c
若要解出原文,需要得到p或者q,以及e。在genkey()函数中,q是随机生成的512位质数,pe都与函数vinad()有关,我们自然想知道,vinad()在做什么。
对于给定的列表Rvinad()取出每一个元素r,将其与x异或,返回异或后的'1'的个数的奇偶,奇数为1,偶数为0。将所有的r如此操作,结果拼成的字符串就是返回值的二进制形式。
假设其中某两次取出的元素为$r_1$和$r_2$,那么,记$x \oplus r_1 = k_1$,$x \oplus r_2 = k_2$,则$k_1 \oplus k_2 = \text{XOR}(x,r_1,x,r_2)$。而$x \oplus x = 0$,对结果没有影响,所以$k_1 \oplus k_2 = r_1 \oplus r_2$。其中$\oplus$符号和$\text{XOR}$均表示异或。
这样一来,虽然x是一个很大的未知数,但任意两个结果的异或值都已经能从R中知晓,换言之,只要知道了其中一个结果(0或者1),就知道了全部的结果。x实际上和只能取0/1没有区别。
所以,我们取x=0,得到Q = int(''.join(str(parinad(R[i])) for i in range(512)), 2),得到p的一个候选值;如果n%Q!=0,即Q不正确,那么,将每一位取反,就是另一个候选值Q_alt
经过验证,可以得到pq

对于e,我们已经分析得到vinad()函数在R给定的情况下只有两种可能的结果,用QQ_alt试两次就可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#Crypto CTF https://cr.yp.toc.tf/ vinad题解

from Crypto.Util.number import *

R = [] #R太占版面了,就删掉了。题目附件里已经给出
n = 58113574203067314600162910771848744432179168354040678920098167335472534222998261639291145191568159464990603689062679467360303185717662426122140998218656632568172511390111887830539687208220100574329903748617343193392646019854280519859403817579746765861359633174218846216669659258251676438195667516224684805919
c = 56754194307199340085459028397027924827853574000671575387226403396873568994756738512141122143372650573201079937375922460851170745485734799044781029943783218210457587599666501326645229924138230588050782907693019958930006807017898115655426823272342984109999519420817119999272583495848119171867835187241510764427

def parinad(n):
return bin(n)[2:].count('1') % 2

# 1. 求出p和q
Q = int(''.join(str(parinad(R[i])) for i in range(512)), 2)
Q_alt = (1<<512) - 1 - Q
if n % Q == 0 and isPrime(Q):
p = Q
else:
p = Q_alt
#print(p)
assert isPrime(p) and n%p==0

#print(sum(R))

e = Q_alt
q = n//p

# 2. 计算欧拉函数 φ(n)
phi_n = (q - 1)*(p - 1)
#print("phi = ",phi_n)

# 3. 计算 d (e 的模 φ(n) 的乘法逆元)
d = inverse(e, phi_n)
#print("d = ",d)

# 4. 解密消息 m
m = pow(c, d, n) # 快速模幂运算

print("解密后的消息 m 为:", long_to_bytes(m - sum(R)))
#CCTF{s0lV1n9_4_Syst3m_0f_L1n3Ar_3qUaTi0n5_0vEr_7H3_F!3lD_F(2)!}
#本题由ChatGPT提供思路与代码

2.Interpol

观察代码,randpos()函数在0和1之间选一个数。如果选1,返回True和一个有两个数的元组,这两个数和flag相关;如果选0,返回False和另外一个元组,这个元组的两个数是一个随机整数和一个随机的有理数。
接下来是while(True)部分:先得到一个randpos()的结果,如果返回True,那么n自增1,否则n不增加。但无论如何,DATA都会将新生成的元组加入,例外情况是_d[0][0]H中,即元组的第一个数在H中。
接下来使用拉格朗日插值,将以上元组表示的点变为一个有理数域的函数,我们最后得到了这个函数。
因此,函数上的点就包含了flag上的点,根据randpos()函数我们可以发现,由flag得到的点,其横坐标一定是负数,纵坐标一定是整数,而随机点的横坐标大于等于0,纵坐标也是两个质数之比,不会是整数。我们据此得到所有满足x<0的整点,就是由flag得到的点。
对于点(-x,y),我们有x = 1 + (19*n - 14) % len(flag)y = ord(flag[(63 * n - 40) % len(flag)])。(n<len(flag))即:
$n = (x + 13) \text{inverse}(19)\space mod\space \text{len(flag)}$,$\text{chr}(y) = \text{flag}[(63 n - 40)\space mod\space \text{len(flag)}]$。于是可以根据每个x得到对应的n,进而得到flag中每个字符的位置。这里假设len(flag)和19,和63均互质,否则,flag不唯一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#Crypto CTF https://cr.yp.toc.tf/ Interpol题解

#!/usr/bin/env sage

# 从 output.raw 加载序列化多项式
with open('output.raw', 'rb') as f:
poly_str = f.read()

# 反序列化多项式
P = loads(poly_str)

# 定义多项式函数 p(x)
p = P

# 步骤1: 收集候选真点 (x, y),其中 x 为负整数,p(x) 为整数
points = []
x_val = -1
consecutive_non_integer = 0
threshold = 20 # 连续非整数阈值,用于停止迭代

while consecutive_non_integer < threshold:
try:
y_val = p(x_val) # 计算 p(x)
# 检查是否为整数(有理数且分母为1)
if y_val in ZZ or (y_val in QQ and y_val.denominator() == 1):
points.append((x_val, ZZ(y_val))) # 记录点
consecutive_non_integer = 0 # 重置计数器
else:
consecutive_non_integer += 1
except Exception as e: # 处理可能的求值错误(如 x 过大)
consecutive_non_integer += 1
x_val -= 1 # 移至下一个负整数

if not points:
raise ValueError("未找到候选点。检查文件路径或多项式。")

# 按 x 排序(降序:-1, -2, -3, ...)
points.sort(key=lambda pt: pt[0], reverse=True)

# 步骤2: 推导 L = len(flag)
min_x = min(x for x, y in points) # 最小 x(最负)
L_candidate = -min_x # L = -min_x
num_points = len(points)

# 验证点数是否合理(应接近 L_candidate)
if num_points < L_candidate:
print(f"警告: 只找到 {num_points} 个点,但 L_candidate = {L_candidate}。可能有遗漏。")
elif num_points > L_candidate:
print(f"警告: 找到 {num_points} 个点,但 L_candidate = {L_candidate}。可能有误报。")
# 截断至前 L_candidate 个点(假设 x 最小的点可能为误报)
points = points[:L_candidate]

L = L_candidate # 使用 L_candidate 作为 flag 长度

# 步骤3: 计算 19 模 L 的逆元(用于索引映射)
try:
inv19 = inverse_mod(19, L) # 19 在模 L 下的逆元
except Exception as e:
raise ValueError(f"19 和 L={L} 不互质,无法计算逆元。错误: {e}")

# 步骤4: 映射每个点到 flag 字符
flag_array = [None] * L # 初始化 flag 数组

for x, y in points:
# 计算 a = (19n - 14) % L(来自真点 x 公式)
a = -1 - x # 因为 x = -(1 + a)
if a < 0 or a >= L:
print(f"警告: x={x} 的 a={a} 超出 [0, L-1]。跳过。")
continue

# 计算 n(真点索引)
c = a + 14 # 来自 a = (19n - 14) % L
n_val = (c * inv19) % L # n = (c * inv19) mod L

# 计算 b(flag 字符位置)
b_val = (63 * n_val - 40) % L # b = (63n - 40) % L

# 检查 y 是否为可打印 ASCII
if y < 32 or y > 126:
print(f"警告: x={x} 的 y={y} 不是可打印 ASCII。跳过。")
continue

# 存储字符(位置 b_val)
flag_array[b_val] = chr(y)

# 检查是否所有位置都已填充
if None in flag_array:
missing = [i for i, char in enumerate(flag_array) if char is None]
print(f"警告: 位置 {missing} 未填充。尝试调整点集合或 L。")
else:
print("所有位置填充成功。")

# 构建 flag 字符串
flag_str = ''.join(flag_array)
print(f"恢复的 flag: {flag_str}")
#CCTF{7h3_!nTeRn4t10naL_Cr!Min41_pOlIc3_0r9An!Zati0n!}
#本题由ChatGPT和Deepseek提供思路,Deepseek提供代码

3.Mechanic

代码很短,但是遇见了不认识的库,多半是在调用库函数加密了,上网找找如何解密。
搜索KryptonKEM找到了https://github-wiki-see.page/m/aabmets/quantcrypt/wiki/Code-Examples,看到“KryptonKEM for Asymmetric File Encryption”一节,找到了相关的示例代码。
加密的流程是,读取flag.png,生成40位随机数,根据该随机数的比特,决定是对明文/上一轮密文进行一次加密,并将skey(secret_key)写入文件中,还是伪造一组skey写入文件中。
因此,一共生成了40次密钥,其中有部分是真的,也有一些是假的。根据最后的文件名flag_22.enc,一共进行了23轮加密。同时我们可以知道,先加密的密钥一定在后加密的密钥之前。所以,我们可以从最后一个密钥开始,不断向前试探,得到最初的文件。
如果遇到假私钥,解密会报错,根据能否解密判断是否为真的私钥。为了防止解密中间文件被占用而无法写入,每一个文件都使用不同的文件名。运行一遍程序,就可以得到最初的flag.png
以及,kem.param_sizes.sk_size = 3168(自己偷偷装这个库,在python跑一遍就知道了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#Crypto CTF https://cr.yp.toc.tf/ mechanic题解

#!/usr/bin/env python3
from quantcrypt.kem import MLKEM_1024
from quantcrypt.cipher import KryptonKEM
from pathlib import Path

kem = MLKEM_1024()
kry = KryptonKEM(MLKEM_1024)
SK_SIZE = 3168
TOTAL_LAYERS = 23

# --- 1. 读取并切分 output.raw ---
raw = Path('output.raw').read_bytes()
assert len(raw) % SK_SIZE == 0, "output.raw 长度必须是 SK_SIZE 的整数倍"
chunks = [ raw[i:i+SK_SIZE]
for i in range(0, len(raw), SK_SIZE) ]

# --- 2. 准备初始密文路径 ---
current_ct = Path('flag_22.enc')
found_sks = []

# 从后往前遍历每一段私钥
for idx, sk in enumerate(reversed(chunks)):
if len(found_sks) >= TOTAL_LAYERS:
break

# 生成唯一的临时输出文件名
tmp_path = Path(f'tmp_{len(found_sks)}.out')

try:
# 解密到唯一文件
kry.decrypt_to_file(sk, current_ct, tmp_path)

# 解密成功,记录私钥,并更新 current_ct
found_sks.append(sk)
current_ct = tmp_path
print(f"✔ 用 chunks[{len(chunks)-1-idx}] 解出了第 {len(found_sks)} 层 → {tmp_path.name}")

except Exception as e:
print("ERROR: ",e)
continue

assert len(found_sks) == TOTAL_LAYERS, "没找到所有私钥段!"

# 最终把 current_ct 重命名为 flag.png
current_ct.rename('flag.png')
print("🎉 已成功还原出 flag.png")
#CCTF{k3y_3NcAp5uL4t!0n_M3cH4n1Sms!}
#本题由ChatGPT提供思路和代码,略做修正

4.Mancity

气急败坏的典型示例——不过还是先分析一下代码吧
keygen()生成了两个质数,这两个质数都由同一个256bit的质数p变化而来,质数rp的每一个bit后都增加一个'1',质数qp后面增加256个'1',这样qr都是512位质数,且其中一半的位我们已经知道了
假设bin(p)=10010…01,则r = 1101011101…0111q = 10010…011111…11,则q的低256位已知。我们将qr划分为high和low,其中高256位为high,低256位为low,则$q_{low} = 2^{256}-1$。对$n = q*r$两边同时模$2^{256}$,则可知$n$的低256位完全由$q_{low}$和$r_{low}$决定,于是可以求$q_{low}$的逆元,乘上$n$的低256位,再模$2^{256}$,得到$r_{low}$。
$r_{low}$的二进制形式里,有一半是'1‘,另一半是p的低128位。由此我们得到了p的低128位,也就是q的256-383位,于是可以重复上述过程,每次只需要调整模数,就可以不断向p的高位推进。
理论上这个方法可以推进到只剩最后1位未知,实际上运行到240位的时候就报错了,不过剩下16位并不算多,暴力破解一轮即可。得到p后推出qr,进而还原消息m

事后发现,如果$r_{low}$解出来的最高位是0,那么填充1的位置不会被正确检测到,因而不能得到正确的p。我增加了对于$r_{low}$比特长度的检测,如果是奇数,就补上最前面的'0',这样就可以继续解码了。枉我气急败坏,甚至写了5个同样的函数去做同一件事……还是不够老练
只需要使用最后的recover()函数,循环代入每一轮的p,加上最后256个'1'作为$q_{low}$,即可解出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#Crypto CTF https://cr.yp.toc.tf/ Mancity题解

def decode(p_man:str):
orig_bits = ''
for i in range(0, len(p_man), 2):
two_bits = p_man[i:i + 2]
if two_bits == '01':
orig_bits += '0'
elif two_bits == '11':
orig_bits += '1'
else:
raise ValueError(f"Invalid bit pair: {two_bits}")
return orig_bits

def recover_256(n:int):
q = 2**256-1
modulus = 2 ** (32 + 64 + 128 + 256)
inv = pow(q, -1, modulus)
p = bin((n % modulus) * inv % modulus)[2:]
if len(p) % 2:
p = '0' + p
assert all(p[i] == '1' for i in range(1, len(p), 2))
return p

def recover_128(n:int):
q = int('11011101001110011100110101111001000101001101011110110111001100100001011111001100001100001010001110110001100001000100110111111111'+'1'*256,2)
modulus = 2 ** (32 + 64 + 128 + 256)
inv = pow(q, -1, modulus)
p = bin((n % modulus) * inv % modulus)[2:]
if len(p) % 2:
p = '0' + p
assert all(p[i] == '1' for i in range(1, len(p), 2))
return p

def recover_64(n:int):
q = int('111101111110100010100000100010100110101111000001100000011010011011011101001110011100110101111001000101001101011110110111001100100001011111001100001100001010001110110001100001000100110111111111'+'1'*256,2)
modulus = 2 ** (32 + 64 + 128 + 256)
inv = pow(q, -1, modulus)
p = bin((n % modulus) * inv % modulus)[2:]
if len(p) % 2:
p = '0' + p
assert all(p[i] == '1' for i in range(1, len(p), 2))
return p

def recover_32(n:int):
q = int('11110111000111010010000101100001111101111110100010100000100010100110101111000001100000011010011011011101001110011100110101111001000101001101011110110111001100100001011111001100001100001010001110110001100001000100110111111111'+'1'*256,2)
modulus = 2 ** (32 + 64 + 128 + 256)
inv = pow(q, -1, modulus)
p = bin((n % modulus) * inv % modulus)[2:]
if len(p) % 2:
p = '0' + p
assert all(p[i] == '1' for i in range(1, len(p), 2))
return p

def recover_16(n:int):
q = int('110101000111000011110111000111010010000101100001111101111110100010100000100010100110101111000001100000011010011011011101001110011100110101111001000101001101011110110111001100100001011111001100001100001010001110110001100001000100110111111111'+'1'*256,2)
modulus = 2 ** (32 + 64 + 128 + 256)
inv = pow(q, -1, modulus)
p = bin((n % modulus) * inv % modulus)[2:]
if len(p) % 2:
p = '0' + p
assert all(p[i] == '1' for i in range(1, len(p), 2))
return p

def recover(n:int,q_bit:str):
q = int(q_bit+'1'*256, 2)
modulus = 2 ** (len(q_bit) + 256)
inv = pow(q, -1, modulus)
p = bin((n % modulus) * inv % modulus)[2:]
if len(p) % 2:
p = '0' + p
assert all(p[i] == '1' for i in range(1, len(p), 2))
return p


# 给定参数
n = 147170819334030469053514652921356515888015711942553338463409772437981228515273287953989706666936875524451626901247038180594875568558137526484665015890594045767912340169965961750130156341999306808017498374501001042628249176543370525803456692022546235595791111819909503496986338431136130272043196908119165239297

# 恢复因子
#p = recover_256(n)
#print(decode(p))
#第一轮的p:11011101001110011100110101111001000101001101011110110111001100100001011111001100001100001010001110110001100001000100110111111111
#这是最低的128位
#p2 = recover_128(n)
#print(decode(p2))
#第二轮的p:111101111110100010100000100010100110101111000001100000011010011011011101001110011100110101111001000101001101011110110111001100100001011111001100001100001010001110110001100001000100110111111111
#这是128+64=192位
#p3 = recover_64(n)
#print(decode(p3))
#第三轮的p:11110111000111010010000101100001111101111110100010100000100010100110101111000001100000011010011011011101001110011100110101111001000101001101011110110111001100100001011111001100001100001010001110110001100001000100110111111111
#128+64+32=224位
#p4 = recover_32(n)
#print(decode(p4))
#110101000111000011110111000111010010000101100001111101111110100010100000100010100110101111000001100000011010011011011101001110011100110101111001000101001101011110110111001100100001011111001100001100001010001110110001100001000100110111111111
#...+16=240位
for i in range(10000,2**18):
s = bin(i)[2:] + '110101000111000011110111000111010010000101100001111101111110100010100000100010100110101111000001100000011010011011011101001110011100110101111001000101001101011110110111001100100001011111001100001100001010001110110001100001000100110111111111' + '1'*256
q = int(s, 2)
if not (n%q):
print(q)
break
#b'CCTF{M4nch3sReR_c0D!ng_wI7H_RSA}'
#本题由自己提供思路和代码

5.Vainrat

这题……我都想原封不动放到我们学校的校赛或者类似的比赛上了,如果我有机会的话。
太阴了,阴得没边。
服务器创建精度为440位的实数域,每次交互时,输入c获取最新的y值,输入q退出。但是,只有尝试的次数足够多,才能抓到老鼠,得到位置,至少需要20次尝试以后,才能稳定获得y
我们的目标是得到x0,也就是flag转为整数后前面加小数点。由于每次求平均数和开方都会有精度损失,最后可能不能一次解出完整的flag,需要修正。
因此我们需要从可以得到的yi中,获得上一步的信息,直到最终的x0
研究一下rat()函数:第一行将xy求平均数,第二行将上一步的xy求几何平均数,最后返回新的x和新的y。如果直接把代码丢给AI,AI们很容易误以为这段代码是求xy的算数平均数(AM)和几何平均数(GM),并将AM赋给x,GM赋给y
因为被AI阴了导致没做出来,其实不难的。以及,多次试验会发现,y会越来越小。如果当成AMGM,那每个y都应该小于对应的x,这样y应该越来越大。
我先得到两组连续的y。这里运气不错得到了$y_{19}$和$y_{20}$,如果没得到$y_{19}$,拿$y_{21}$也一样。我们有公式$x_i = \frac{(x_{i-1} + y_{i-1})}{2}$,$y_i = \sqrt{x_i y_{i-1}}$。
因此,根据连续的$y_i$,$y_{i-1}$,可以得到$x_i = \frac{y_i^2}{y_{i-1}}$。这样我们就有了一组$x_i$和$y_i$。
有了$x_i$和$y_i$,就有$y_{i-1} = \frac{y_i^2}{x_i}$,$x_{i-1} = 2x_i - y_{i-1} = 2x_i - \frac{y_i^2}{x_i}$,写出函数。
因为懒得打理变量所以即使x回到$x_0$了变量名还是x19()总之这样就能得到x0了。但是我们不知道flag有多少位,所以需要一位一位尝试,转成比特后全都是可打印字符的/以CCTF开头的就是可能的解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#Crypto CTF https://cr.yp.toc.tf/ vainrat题解


from decimal import *
from Crypto.Util.number import long_to_bytes

'''
def rat(x, y):
x = R(x + y) * R(0.5)
y = R((x * y) ** 0.5) #这个x是上一步的x!也就是算数平均数,而不是参数x
return x, y
'''
getcontext().prec = 460

def return_previous(x:Decimal,y:Decimal)->(Decimal, Decimal):
return (Decimal(2)*x - (y**2/x),y**2/x)

def is_bytes_printable(data):
"""
判断bytes对象是否可打印。

Args:
data: bytes对象。

Returns:
如果bytes对象可打印,返回True,否则返回False。
"""
for byte in data:
if not (32 <= byte <= 126): # 检查是否在ASCII可打印字符范围
return False
return True


y20 = Decimal('0.850721739388853613891153549431010808552676731821999538996176033215422247824841783375972792896987047341958120004524577777036176502090')
y19 = Decimal('0.850721739389079303159246072119468136821544049380443888847082554769733382902933214834218375079868150945588972107307455168793172804985')
y0 = Decimal('0.939435784300590373652615235586222521209371224933347916892430414723880727978737194445756901098227178356345123621981413057062575844130')

x19 = Decimal(2)*y20**Decimal(2)/y19 - y19
for i in range(19):
x19,y19 = return_previous(x19,y19)

print("x = ", x19, "y = ", y19)
assert abs(y0 - y19) <Decimal("0.0000000000000000000000000000000000000000000000000000001")


for i in range(1,461):
res = int(x19*(10**i))
if(is_bytes_printable(long_to_bytes(res))): print(long_to_bytes(res))
#CCTF{h3Ur1s7!c5_anD_iNv4rIanTs_iN_CryptoCTF_2025!}
#本题有ChatGPT和Deepseek提供错误思路,自行调试得到思路和代码

6.Matemith

flag分成了长为14的段,但是不知道有多少段,由于后面使用了M[0]M[5],我们就假设它有6段吧。
fk共6个函数,次数都是2~3次,变量也很多,所有的系数也是小于某个313位质数p的随机数。
然后,将每个函数转换到有理数域R上,代入uzM[0]M[5],计算出结果存入CNST
例:f(M[0], M[1], M[2], M[3], M[4], M[5]) = COEFS[0] * M[0] * M[1] + COEFS[1] * M[0] + COEFS[2] * M[1]
之后对于每一个函数都加上一个常数项,常数项是p减去上面的计算结果,再模p,也就是说,在模p的意义下,每个函数都满足function(M[0], M[1], M[2], M[3], M[4], M[5])-CNST[function] ≡ 0 (mod p)
因此,我们需要求出满足六个同余方程的解,并选出长度均为14字节的整数结果。由于方程较多,变量较多,次数也较高,可以分批求解。
观察发现,函数f, h, j只有u,v,w三个变量,可以解出它们。利用Sagemath的求解器进行求解,有一个明显短的结果,再用u,v,w代入另外三个方程,这样就只剩三个变量,同样可以解出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#Crypto CTF https://cr.yp.toc.tf/ matemith题解


from Crypto.Util.number import *

# 定义模数 p
p = 9892984422801315119260311427714389408772405421306235794826917610128461644036928139298330716261
F = GF(p) # 创建有限域

# 将系数转换为有限域元素
a1 = F(8593371583346286129538282168765198524220954884352992069219549555526097253129502925759872761483)
b1 = F(8192555264287905175212103898575474256555217842060435386769432116145712989123062847161390929397)
c1 = F(9598573789403814092125115160545174167539204328557118715540593719644188998531033259685435430387)
d1 = F(5738603225260621554442220996093767502015758942320213371600986432070445300427944977409453429117)

a2 = F(6107224904478508858527197508483774405356161856691777460732363192128980355274418091837270668258)
b2 = F(3584245173493717638976874408629921683995390608944250077841702023698807664457252845973088744491)
c2 = F(5646173287331462026544218972062953582608380797148923127395811758145598594972832047259631339566)
d2 = F(1994681139685786114971936867358158466232859433926848067961874687630342141141862187589124089741)

a3 = F(1912186465211454827473018892315659311053527670028135595953520151335825509122313783795561869379)
b3 = F(6246883466276200389231653597272295993565421216541002743075041326054203024921176043191679609212)
c3 = F(4002308425802254921531592700910138281674785127934610897914017993007060136199147207365547047048)
d3 = F(973159800079995512996976852328990077106942094656694887771601292254542762394381629810393447820)

# 声明多项式环和变量
R.<u,v,w> = PolynomialRing(F)

# 定义方程
f = a1*u*v + b1*u + c1*v + d1
h = a2*u*w + b2*u + c2*w + d2
j = a3*v*w + b3*v + c3*w + d3

# 创建理想
I = R.ideal([f, h, j])

# 计算解集
solutions = I.variety()

# 输出结果
if solutions:
print(f"找到 {len(solutions)} 个解:")
for i, sol in enumerate(solutions, 1):
# 验证解
f_val = f.subs(sol)
h_val = h.subs(sol)
j_val = j.subs(sol)
print(f"\n验证: f={f_val}, h={h_val}, j={j_val}")

print(f"解 {i}:")
print(f"u = {sol[u]}, {long_to_bytes(int(sol[u]))}")
print(f"v = {sol[v]}, {long_to_bytes(int(sol[v]))}")
print(f"w = {sol[w]}, {long_to_bytes(int(sol[w]))}")

else:
print("未找到解")

# 已知的 u, v, w 值
u = F(1078804227986401794161149736863793)
v = F(2033644392583863279506423899386719)
w = F(1631639702310041336611888741434165)

# 将系数转换为有限域元素
# g 的系数
a1 = F(7737077144206080155196706693824644356475708615710271404071364943161652008584970269394416250641)
a2 = F(6282097687310252658473848438985225466620614743750918909885172321224925965646628839166491648752)
a3 = F(7737077144206080155196706693824644356475708615710271404071364943161652008584970269394416250641)
a4 = F(3354788147890488743832873565215769634619909759459203496980671578348799162553954862104978291860)
a5 = F(2560270290674636359252235177920929027441112715609783111306743340637878970846852799006820932563)

# i 的系数
b1 = F(7622670835797214156123791992548663880284352234566921286637648219243086701251627093499322050472)
b2 = F(6026769215097777844835562389865313764490318485655789123763637718591748620654875700763740623760)
b3 = F(8145050175261359549200629067766090532616263522561328878195831921153188650784907223634130346224)
b4 = F(3622105614070476540808786980829452605696331317022729645355376801209444137548670550164418237117)
b5 = F(4800360746061605999597274870855047707130861888252519642520437605796496240599924899885487900040)

# k 的系数
c1 = F(1423338294606985951732736428034353751447528399559929388138157330118213387990891693204997290038)
c2 = F(784018806462384388182217012266169299116410899849461442885543245867941419322406775218178098109)
c3 = F(7684681843989505989596042520590550892565982707534588920361260899638313817214040416765327284778)
c4 = F(4982848574842913858489870338816729222210785430242027484672099513487039514577513464674726403409)
c5 = F(7781690757622738625626304200561818137843970209349935834539461705684625161407233281360563620790)

# 定义多项式环
R.<x, y, z> = PolynomialRing(F)

# 代入已知的 u, v, w 后,g, i, k 成为关于 x, y, z 的方程
g = a1 * u * x * y + a2 * v + a3 * x + a4 * y + a5
i = b1 * v * y * z + b2 * w + b3 * y + b4 * z + b5
k = c1 * w * x * z + c2 * u + c3 * x + c4 * z + c5

# 创建理想
I = R.ideal([g, i, k])

# 求解方程组
solutions = I.variety()

# 输出结果
if solutions:
print(f"找到 {len(solutions)} 个解:")
for idx, sol in enumerate(solutions):
print(f"\n解 {idx + 1}:")
print(f"x = {sol[x]}")
print(f"y = {sol[y]}")
print(f"z = {sol[z]}")

# 验证解
g_val = g.subs(sol)
i_val = i.subs(sol)
k_val = k.subs(sol)
print(f"验证: g={g_val}, i={i_val}, k={k_val}")
else:
print("未找到解")

#CCTF{50lv!n6_7H3_H1dD3n__num8Ers_Pr08l3m_f0r_C51dH_4nd_C5uRf_v14_4uT0m473d_C0pp3r5m17h!!?}