LilCTF 2025 Writeup(复现)

当时跟 sekai CTF 时间有点近,所以没打,这几天没什么事做一下题看看(绝对不是被 sekai CTF 鞭打了回来找找自信٩(ŏ﹏ŏ、)۶

Crypto

ez_math

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from sage.all import *
from Crypto.Util.number import *

flag = b'LILCTF{test_flag}'[7:-1]
lambda1 = bytes_to_long(flag[:len(flag)//2])
lambda2 = bytes_to_long(flag[len(flag)//2:])
p = getPrime(512)
def mul(vector, c):
return [vector[0]*c, vector[1]*c]

v1 = [getPrime(128), getPrime(128)]
v2 = [getPrime(128), getPrime(128)]

A = matrix(GF(p), [v1, v2])
B = matrix(GF(p), [mul(v1,lambda1), mul(v2,lambda2)])
C = A.inverse() * B

print(f'p = {p}')
print(f'C = {str(C).replace(" ", ",").replace("\n", ",").replace("[,", "[")}')

# p = 9620154777088870694266521670168986508003314866222315790126552504304846236696183733266828489404860276326158191906907396234236947215466295418632056113826161
# C = [7062910478232783138765983170626687981202937184255408287607971780139482616525215270216675887321965798418829038273232695370210503086491228434856538620699645,7096268905956462643320137667780334763649635657732499491108171622164208662688609295607684620630301031789132814209784948222802930089030287484015336757787801],[7341430053606172329602911405905754386729224669425325419124733847060694853483825396200841609125574923525535532184467150746385826443392039086079562905059808,2557244298856087555500538499542298526800377681966907502518580724165363620170968463050152602083665991230143669519866828587671059318627542153367879596260872]

分析:
我们已知 A 向量,为了后续方便运算,我们写成列向量的形式:
$$
A =
\begin{bmatrix}
v_1 \newline
v_2
\end{bmatrix}
$$
我们根据 B 的生成方式,可以将其进行分解成两个矩阵相乘的形式:

$$
B =
\begin{bmatrix}
v_1\lambda_1 \newline
v_2\lambda_2
\end{bmatrix}
=
\begin{bmatrix}
\lambda_1 & 0 \newline
0 & \lambda_2
\end{bmatrix}
\begin{bmatrix}
v_1 \newline
v_2
\end{bmatrix}
=
\begin{bmatrix}
\lambda_1 & 0 \newline
0 & \lambda_2
\end{bmatrix}
A
$$

然后再观察生成的 C,可以发现貌似找到了思路:
$$
C = A^{-1}B=A^{-1}
\begin{bmatrix}
\lambda_1 & 0 \newline
0 & \lambda_2
\end{bmatrix}
A
$$
我们设
$$
D=
\begin{bmatrix}
\lambda_1 & 0 \newline
0 & \lambda_2
\end{bmatrix}
$$
上述的分析说明,C 和 D 矩阵相似,由相似矩阵的特性可知其特征值一样,而 D 的特征值很容易得到为 $\lambda_1,\lambda_2$,那么说明 C 的特征值也是 $\lambda_1,\lambda_2$。
那么直接求C的特征值便可以得到 $\lambda_1,\lambda_2$,再转换为 ASCII 字符便能获取到 flag。
解答:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Util.number import long_to_bytes
p = 9620154777088870694266521670168986508003314866222315790126552504304846236696183733266828489404860276326158191906907396234236947215466295418632056113826161
C = [7062910478232783138765983170626687981202937184255408287607971780139482616525215270216675887321965798418829038273232695370210503086491228434856538620699645,7096268905956462643320137667780334763649635657732499491108171622164208662688609295607684620630301031789132814209784948222802930089030287484015336757787801],[7341430053606172329602911405905754386729224669425325419124733847060694853483825396200841609125574923525535532184467150746385826443392039086079562905059808,2557244298856087555500538499542298526800377681966907502518580724165363620170968463050152602083665991230143669519866828587671059318627542153367879596260872]

C = matrix(GF(p), 2, 2, C)

evalue = C.eigenvalues()

lambda1 = int(evalue[0])
lambda2 = int(evalue[1])

flag1 = long_to_bytes(lambda1)
flag2 = long_to_bytes(lambda2)

flag = flag1 + flag2
print(f"LilCTF{{{flag.decode()}}}")

mid_math

题目:

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
from sage.all import *
from Crypto.Util.number import *
from tqdm import tqdm
from random import randint
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

flag = b'LILCTF{test_flag}'

p = getPrime(64)
P = GF(p)

key = randint(2**62, p)

def mul(vector, c):
return [vector[0]*c, vector[1]*c, vector[2]*c, vector[3]*c, vector[4]*c]

v1 = [getPrime(64), getPrime(64), getPrime(64), getPrime(64), getPrime(64)]
v2 = [getPrime(64), getPrime(64), getPrime(64), getPrime(64), getPrime(64)]
v3 = [getPrime(64), getPrime(64), getPrime(64), getPrime(64), getPrime(64)]
v4 = [getPrime(64), getPrime(64), getPrime(64), getPrime(64), getPrime(64)]
v5 = [getPrime(64), getPrime(64), getPrime(64), getPrime(64), getPrime(64)]
a, b, c, d, e = getPrime(64), getPrime(64), getPrime(64), getPrime(64), 0

A = matrix(P, [v1, v2, v3, v4, v5])
B = matrix(P, [mul(v1,a), mul(v2,b), mul(v3, c), mul(v4, d), mul(v5, e)])
C = A.inverse() * B
D = C**key

key = pad(long_to_bytes(key), 16)
aes = AES.new(key,AES.MODE_ECB)
msg = aes.encrypt(pad(flag, 64))

print(f"p = {p}")
print(f'C = {[i for i in C]}'.replace('(', '[').replace(')', ']'))
print(f'D = {[i for i in D]}'.replace('(', '[').replace(')', ']'))
print(f"msg = {msg}")

#p = 14668080038311483271
#C = [[11315841881544731102, 2283439871732792326, 6800685968958241983, 6426158106328779372, 9681186993951502212], [4729583429936371197, 9934441408437898498, 12454838789798706101, 1137624354220162514, 8961427323294527914], [12212265161975165517, 8264257544674837561, 10531819068765930248, 4088354401871232602, 14653951889442072670], [6045978019175462652, 11202714988272207073, 13562937263226951112, 6648446245634067896, 13902820281072641413], [1046075193917103481, 3617988773170202613, 3590111338369894405, 2646640112163975771, 5966864698750134707]]
#D = [[1785348659555163021, 3612773974290420260, 8587341808081935796, 4393730037042586815, 10490463205723658044], [10457678631610076741, 1645527195687648140, 13013316081830726847, 12925223531522879912, 5478687620744215372], [9878636900393157276, 13274969755872629366, 3231582918568068174, 7045188483430589163, 5126509884591016427], [4914941908205759200, 7480989013464904670, 5860406622199128154, 8016615177615097542, 13266674393818320551], [3005316032591310201, 6624508725257625760, 7972954954270186094, 5331046349070112118, 6127026494304272395]]
#msg = b"\xcc]B:\xe8\xbc\x91\xe2\x93\xaa\x88\x17\xc4\xe5\x97\x87@\x0fd\xb5p\x81\x1e\x98,Z\xe1n`\xaf\xe0%:\xb7\x8aD\x03\xd2Wu5\xcd\xc4#m'\xa7\xa4\x80\x0b\xf7\xda8\x1b\x82k#\xc1gP\xbd/\xb5j"

分析:
本题和上边题目还是比较相似的,只是多了一步求解离散对数问题。
根据分析,可以发现矩阵 C 相似于一个对角矩阵:
$$
\begin{bmatrix}
a & 0 & 0 & 0 & 0 \newline
0 & b & 0 & 0 & 0 \newline
0 & 0 & c & 0 & 0 \newline
0 & 0 & 0 & d & 0 \newline
0 & 0 & 0 & 0 & 0
\end{bmatrix}
$$
其特征值为 $a,b,c,d,0$ 那么由$D = C^{key}$,则 D 对应的特征值为 $a^{key},b^{key},c^{key},d^{key},0 \mod p$
在求出特征值后,我们便可以通过求解离散对数问题获取 key。
之后便可以获取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
#sage
from Crypto.Util.number import *
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad, pad

p = 14668080038311483271
C_list = [[11315841881544731102, 2283439871732792326, 6800685968958241983, 6426158106328779372, 9681186993951502212],
[4729583429936371197, 9934441408437898498, 12454838789798706101, 1137624354220162514, 8961427323294527914],
[12212265161975165517, 8264257544674837561, 10531819068765930248, 4088354401871232602, 14653951889442072670],
[6045978019175462652, 11202714988272207073, 13562937263226951112, 6648446245634067896, 13902820281072641413],
[1046075193917103481, 3617988773170202613, 3590111338369894405, 2646640112163975771, 5966864698750134707]]
D_list = [[1785348659555163021, 3612773974290420260, 8587341808081935796, 4393730037042586815, 10490463205723658044],
[10457678631610076741, 1645527195687648140, 13013316081830726847, 12925223531522879912, 5478687620744215372],
[9878636900393157276, 13274969755872629366, 3231582918568068174, 7045188483430589163, 5126509884591016427],
[4914941908205759200, 7480989013464904670, 5860406622199128154, 8016615177615097542, 13266674393818320551],
[3005316032591310201, 6624508725257625760, 7972954954270186094, 5331046349070112118, 6127026494304272395]]
msg = b"\xcc]B:\xe8\xbc\x91\xe2\x93\xaa\x88\x17\xc4\xe5\x97\x87@\x0fd\xb5p\x81\x1e\x98,Z\xe1n`\xaf\xe0%:\xb7\x8aD\x03\xd2Wu5\xcd\xc4#m'\xa7\xa4\x80\x0b\xf7\xda8\x1b\x82k#\xc1gP\xbd/\xb5j"

Fp = GF(p)
C_mat = matrix(Fp, C_list)
D_mat = matrix(Fp, D_list)

#计算 C 和 D 的特征值
eval_C = C_mat.eigenvalues()
eval_D = D_mat.eigenvalues()

#去掉0特征值
eval_C_nonzero = [x for x in eval_C if x != 0]
eval_D_nonzero = [x for x in eval_D if x != 0]

#输出特征值
print("Nonzero eigenvalues of C:", eval_C_nonzero)
print("Nonzero eigenvalues of D:", eval_D_nonzero)

#尝试计算离散对数以找到key
key = None
for mu in eval_D_nonzero:
try:
#尝试计算第一个非零特征值对应的k值
k = discrete_log(mu, eval_C_nonzero[0])
#检查k是否满足条件
valid = True
for lam in eval_C_nonzero:
if lam^k not in eval_D_nonzero:
valid = False
break
if valid:
key = k
print(f"Found key: {key}")
break
except Exception as e:
print(f"Error: {e}")
continue

if key is None:
raise ValueError("Key not found")

key_bytes = pad(long_to_bytes(key), 16)
aes = AES.new(key_bytes, AES.MODE_ECB)
flag = unpad(aes.decrypt(msg), 64)
print("Flag:", flag)

Linear

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import os
import random
import signal

signal.alarm(10)

flag = os.getenv("LILCTF_FLAG", "LILCTF{default}")

nrows = 16
ncols = 32

A = [[random.randint(1, 1919810) for _ in range(ncols)] for _ in range(nrows)]
x = [random.randint(1, 114514) for _ in range(ncols)]

b = [sum(A[i][j] * x[j] for j in range(ncols)) for i in range(nrows)]
print(A)
print(b)

xx = list(map(int, input("Enter your solution: ").strip().split()))
if xx != x:
print("Oh, your linear algebra needs to be practiced.")
else:
print("Bravo! Here is your flag:")
print(flag)

分析:
已知有
$$
A\vec{x} = \vec{b}
$$
设 $y=[x_1,x_2,,,x_n,1]$ 可以转换为
$$
\begin{bmatrix}
A \newline
-\vec{b}
\end{bmatrix}
*y=0
$$
说明 y 存在于该增广矩阵的右核空间中,由于 $\vec{x}$ 中都是整数,而且都是比较小的数,因此可以使用 LLL 在候选的 y 中规约出最短向量
解答:

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
from sage.all import *
from pwn import *
import ast

HOST,PORT = "gz.imxbt.cn" , 20786
io = remote(HOST, PORT)

nrows = 16
ncols = 32

AA = io.recvline()
bb = io.recvline()

#将接收的字符串转换为列表
A = Matrix(ZZ, eval(AA))
b = vector(ZZ, eval(bb))

#创建增广矩阵
M = A.augment(-b)
#计算右核
y_cands = M.right_kernel().basis_matrix()
L = y_cands.LLL()
y = tuple(abs(x) for x in L[0][:-1])

x = " ".join(map(str, y))

io.sendlineafter(b"Enter your solution: ", x.encode())
print(io.recvall().decode())
io.close()

Space Travel

题目:

1
2
3
4
5
6
7
8
from Crypto.Cipher import AES
from hashlib import md5
from params import vecs
from os import urandom
key = int("".join([vecs[int.from_bytes(urandom(2)) & 0xfff] for _ in range(50)]), 2)

print("🎁 :", [[nonce := int(urandom(50*2).hex(), 16), (bin(nonce & key).count("1")) % 2] for _ in range(600)])
print("🚩 :", AES.new(key=md5(str(key).encode()).digest(), nonce=b"Tiffany", mode=AES.MODE_CTR).encrypt(open("flag.txt", "rb").read()))

考点:

1
仿射子空间

分析:
这题是鸡块师傅出的题,虽然简短,但十分有意思,由于我最近事情有点多,为了快点把前边的缺的题目看完,因此这里也是懒得写脚本了,直接将想法告诉 ai 就能直接出(太罪恶了,以后一定会好好写脚本的呜呜呜。。
那么就进入正题吧,这题看得出来是在考察线性代数,整个加密逻辑可以理解为在 $GF(2)$下:

$$
A_{600\times 800}key_{800\times 1} = b_{800\times 1}
$$

可以看出,如果把上式当作线性方程组求解的话,key 中未知的位数有 800 bits,而我们总共有600个方程组,因此该方程组是欠定的,我们直接求解的话,会存在 200 bits 的解空间,通常在没有优化方法的情况下高于 30 bits 我们就不考虑爆破了,因此解题的关键不在于此。
再观察给出的附件,我们会发现有个附件我们似乎还没用上,那就是 params.py,它给出了用来生成 key 的列表 vecs,我们查看一下列表长度会发现,列表长度并不是我们下意识以为的 65536($2^{16}$),而是 4096($2^{12}$),貌似发现了关键所在。
询问 ai 后,ai 猜测存在仿射子空间,仔细一想,确实如此啊(因为 vecs 中不存在子向量,说明它不是线性子空间),key 并不是在 16 维向量空间中生成的,而是在 12 维的子空间生成的,也就是说,其实我们可以将 key 映射到其仿射子空间中,这样的话 key 的未知位数就能从 50×16=800 bits 变为 50×12=600bits 刚好能够求解,说明我们的方向是正确的。
想要将其映射到仿射子空间,我们要知道变换过程中的线性子空间的一组基向量偏移量

  • $v(偏移量):$ 由于偏移量就是线性子空间向仿射子空间的偏移,且线性子空间中存在零向量,因此我们只要随意取仿射子空间中的一个向量作为偏移量即可,通常为了方便,我们会取第一个向量作为偏移量,即 vecs[0]
  • $L(基向量):$ 我们可以通过得到一组线性子空间的向量(如本题的 4096 个向量),然后通过GF(2)下高斯消元即可得到一组基向量(12 个)。
    在得到这些值之后,我们便可以将 key 映射到 仿射子空间上:
    $$
    key_{1 \times 16} = x_{1 \times 12} L_{12\times 16} + v_{1 \times 16}
    $$
    注意,我这里只是单独列举了其中一个组,共有 50 组,每个 key 都要进行这样的变换。
    代码:
    ohh,由于我比较懒,ai 写的代码也比较乱,太难看了(不想拿出来丢人了,如果有需要请直接看鸡块师傅的官方exp(2025-8-wp-crypto | 糖醋小鸡块的blog),非常的简洁明了

[WARM UP] 对称!Just Decrypt

题目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"""Just Decrypt - A simple LilCTF warmup challenge."""

from random import Random
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad


FLAG = b""
key = Random(2025).randbytes(16)
print(key)

print(AES.new(key, AES.MODE_CBC, iv=FLAG[9:25]).encrypt(pad(FLAG, 16)).hex())

#ae39cfab1ba8d38fc3761216c393caf16e3c3f13fe57e2dedd52f1b13072fa93df405c7e731a193cabe5fd88ee3241f79aded62d139faba8c767a3b8efc5a855

分析:
确实是签到题,也是非常的 easy,我们首先能够明显看出来 key 的值是固定的(因为用来生成的随机数种子固定
然后我们发现 iv 的值也非常的可疑,因为他是 FLAG 的 10~25 字节的值,而我们指导 FLAG 的开头是 LILCTF{,也就是说我们已经知道了前 7 个字节,而我们结合 CBC 加密模式,能够得到 FLAG 前 16 个字节和 iv 的异或值,因此首先我们能够直接得到 iv 的前 7 个字节的值,也就是 FLAG 中10~16 字节的值,虽然 8,9 位置的 FLAG 字符还未知,但仅有两个字符,我们可以直接进行爆破,便能得到完整的 iv ,之后就能直接对 AES 进行 CBC 模式的解密了。
脚本:

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
from random import Random
from Crypto.Cipher import AES

key = Random(2025).randbytes(16)
cipher = "ae39cfab1ba8d38fc3761216c393caf16e3c3f13fe57e2dedd52f1b13072fa93df405c7e731a193cabe5fd88ee3241f79aded62d139faba8c767a3b8efc5a855"
cipher_bytes = bytes.fromhex(cipher)

cipher1 = cipher_bytes[:16]
encoder = AES.new(key, AES.MODE_ECB)

table = []
for i in range(32,127):
table.append(chr(i).encode())
print(table)

for i in table:
for j in table:
plain_pre = b"LILCTF{" + i + j
iv1 = bytes(a ^ b for a, b in zip(encoder.decrypt(cipher1), plain_pre + b"\x00" * 7))
iv = bytes(a ^ b for a, b in zip(encoder.decrypt(cipher1), plain_pre + iv1[:7]))
#print(iv)
plaintext = AES.new(key, AES.MODE_CBC, iv=iv).decrypt(cipher_bytes)
if all(k in set(range(32, 127)) for k in iv) and iv == plaintext[9:25]:
print(iv)
print(plaintext)

baaaaaag

题目:

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
from Crypto.Util.number import *
import random
from Crypto.Cipher import AES
import hashlib
from Crypto.Util.Padding import pad
from secret import flag

p = random.getrandbits(72)
assert len(bin(p)[2:]) == 72

a = [getPrime(90) for _ in range(72)]
b = 0
t = p
for i in a:
temp = t % 2
b += temp * i
t = t >> 1

print(p)
key = hashlib.sha256(str(p).encode()).digest()
cipher = AES.new(key, AES.MODE_ECB)
flag = pad(flag,16)
ciphertext = cipher.encrypt(flag)

print(f'a = {a}')
print(f'b = {b}')
print(f"ciphertext = {ciphertext}")

'''
a = [965032030645819473226880279, 699680391768891665598556373, 1022177754214744901247677527, 680767714574395595448529297, 1051144590442830830160656147, 1168660688736302219798380151, 796387349856554292443995049, 740579849809188939723024937, 940772121362440582976978071, 787438752754751885229607747, 1057710371763143522769262019, 792170184324681833710987771, 912844392679297386754386581, 906787506373115208506221831, 1073356067972226734803331711, 1230248891920689478236428803, 713426848479513005774497331, 979527247256538239116435051, 979496765566798546828265437, 836939515442243300252499479, 1185281999050646451167583269, 673490198827213717568519179, 776378201435505605316348517, 809920773352200236442451667, 1032450692535471534282750757, 1116346000400545215913754039, 1147788846283552769049123803, 994439464049503065517009393, 825645323767262265006257537, 1076742721724413264636318241, 731782018659142904179016783, 656162889354758353371699131, 1045520414263498704019552571, 1213714972395170583781976983, 949950729999198576080781001, 1150032993579134750099465519, 975992662970919388672800773, 1129148699796142943831843099, 898871798141537568624106939, 997718314505250470787513281, 631543452089232890507925619, 831335899173370929279633943, 1186748765521175593031174791, 884252194903912680865071301, 1016020417916761281986717467, 896205582917201847609656147, 959440423632738884107086307, 993368100536690520995612807, 702602277993849887546504851, 1102807438605649402749034481, 629539427333081638691538089, 887663258680338594196147387, 1001965883259152684661493409, 1043811683483962480162133633, 938713759383186904819771339, 1023699641268310599371568653, 784025822858960757703945309, 986182634512707587971047731, 1064739425741411525721437119, 1209428051066908071290286953, 667510673843333963641751177, 642828919542760339851273551, 1086628537309368288204342599, 1084848944960506663668298859, 667827295200373631038775959, 752634137348312783761723507, 707994297795744761368888949, 747998982630688589828284363, 710184791175333909291593189, 651183930154725716807946709, 724836607223400074343868079, 1118993538091590299721647899]
b = 34962396275078207988771864327
ciphertext = b'Lo~G\xf46>\xd609\x8e\x8e\xf5\xf83\xb5\xf0\x8f\x9f6&\xea\x02\xfa\xb1_L\x85\x93\x93\xf7,`|\xc6\xbe\x05&\x85\x8bC\xcd\xe6?TV4q'
'''

分析:
看完题目,可以发现是非常原始的背包加密系统,直接造格打 LLL 会发现出不了结果,但是使用 BKZ,然后 block_size 设置的稍微大一点就能出。
脚本:

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
from Crypto.Util.number import *
from Crypto.Cipher import AES
import hashlib

a = [965032030645819473226880279, 699680391768891665598556373, 1022177754214744901247677527, 680767714574395595448529297, 1051144590442830830160656147, 1168660688736302219798380151, 796387349856554292443995049, 740579849809188939723024937, 940772121362440582976978071, 787438752754751885229607747, 1057710371763143522769262019, 792170184324681833710987771, 912844392679297386754386581, 906787506373115208506221831, 1073356067972226734803331711, 1230248891920689478236428803, 713426848479513005774497331, 979527247256538239116435051, 979496765566798546828265437, 836939515442243300252499479, 1185281999050646451167583269, 673490198827213717568519179, 776378201435505605316348517, 809920773352200236442451667, 1032450692535471534282750757, 1116346000400545215913754039, 1147788846283552769049123803, 994439464049503065517009393, 825645323767262265006257537, 1076742721724413264636318241, 731782018659142904179016783, 656162889354758353371699131, 1045520414263498704019552571, 1213714972395170583781976983, 949950729999198576080781001, 1150032993579134750099465519, 975992662970919388672800773, 1129148699796142943831843099, 898871798141537568624106939, 997718314505250470787513281, 631543452089232890507925619, 831335899173370929279633943, 1186748765521175593031174791, 884252194903912680865071301, 1016020417916761281986717467, 896205582917201847609656147, 959440423632738884107086307, 993368100536690520995612807, 702602277993849887546504851, 1102807438605649402749034481, 629539427333081638691538089, 887663258680338594196147387, 1001965883259152684661493409, 1043811683483962480162133633, 938713759383186904819771339, 1023699641268310599371568653, 784025822858960757703945309, 986182634512707587971047731, 1064739425741411525721437119, 1209428051066908071290286953, 667510673843333963641751177, 642828919542760339851273551, 1086628537309368288204342599, 1084848944960506663668298859, 667827295200373631038775959, 752634137348312783761723507, 707994297795744761368888949, 747998982630688589828284363, 710184791175333909291593189, 651183930154725716807946709, 724836607223400074343868079, 1118993538091590299721647899]
b = 34962396275078207988771864327
ciphertext = b'Lo~G\xf46>\xd609\x8e\x8e\xf5\xf83\xb5\xf0\x8f\x9f6&\xea\x02\xfa\xb1_L\x85\x93\x93\xf7,`|\xc6\xbe\x05&\x85\x8bC\xcd\xe6?TV4q'

n = len(a)
d = n / log(max(a),2) #<0.9408
print(RealField(20)(d))
M = matrix(ZZ,n+1,n+1)
for i in range(n):
M[i, i] = 2
M[i,-1] = a[i]
M[-1,i] = 1
M[-1,-1] = b
#print(M)
M_det = det(M)
B = sqrt(n+1)*M_det
print("B=",RealField(20)(B))

L = M.BKZ(block_size = 26)
#L = M.LLL()
for i in L:
p = ""
if i[-1] == 0:
for j in range(n):
if i[j] > 0:
p = "1" + p
else:
p = "0" + p
#print(p)
p = int(p,2)
#print(i)
key = hashlib.sha256(str(p).encode()).digest()
cipher = AES.new(key, AES.MODE_ECB)
flag = cipher.decrypt(ciphertext)
if b"CTF" in flag:
print(flag)

小结

至此,复现已完,虽然整体难度不大,但是还是有许多收获的,比如相似矩阵特征值相等、仿射子空间等题目之前并没有怎么接触过,补一补线性代数知识也是好事,不至于成为彻头彻底的脚本小子(虽然打板子也重要
也深刻认识到了如今 ai 对密码造成了非常大的影响,许多题目都能被 ai 一把梭,看到许多其他方向选手也能使用 ai 来 ak 密码题目,认真分析写脚本的纯粹密码手也越来越少(虽然我有时也偷懒,但我并不认可这种行为。。。
往后,密码手将何去何从啊


LilCTF 2025 Writeup(复现)
http://ramoor.github.io/2025/08/31/LilCTF 2025 Writeup(复现)/
作者
Ramoor
发布于
2025年8月31日
许可协议