CTF密码学:自定义加密算法破解实战
题目描述参赛者获得一个加密文件flag.enc和一个声称用于加密的 Python 脚本sxor_encrypt.py。脚本采用了名为 SXOR 的自定义加密算法。我们需要分析加密流程挖掘其弱点并恢复出原始消息含 Flag。加密脚本分析 (sxor_encrypt.py)import os def sxor_encrypt(plaintext): key os.urandom(8) # 生成8字节随机密钥 constant int.from_bytes(key, byteorderbig, signedFalse) key_array [] ciphertext bytearray() # 生成密钥流 (弱! 可预测!) for i in range(len(plaintext)): # 这行是关键隐患! 常数放大并用当前索引调整 key_byte (constant * (i 1)) % 256 key_array.append(key_byte) # 明文字节与密钥流字节异或 xor_result plaintext[i] ^ key_byte # 附加字节混淆 (弱!) shifted_byte (xor_result 37) % 256 ciphertext.append(shifted_byte) return bytes(ciphertext) # 示例加密过程 (模拟实际加密) plaintext btest_message_here # 实际过程加密了含 FLAG 的消息 ciphertext sxor_encrypt(plaintext) with open(flag.enc, wb) as f: f.write(ciphertext)漏洞分析关键问题在于密钥流的生成方式和后续操作的可逆性。可预测的密钥流:密钥流字节key_byte并非真正的随机字节。其计算方式为 $$\text{key_byte}_i (C \times (i 1)) \mod 256$$ 其中C是那个来自随机密钥但固定不变的整数constanti是字节索引。这种公式生成的值具有很强的不随机特性。混淆操作可逆:在异或之后对结果进行了简单的(xor_result 37) % 256加法和模运算。这是一个线性偏移和取模操作非常容易逆转。常数的恢复难点:constant是算法核心在每次加密中是固定值但不同文件不同。它是密钥。直接恢复它需要更多信息。突破口小段明文已知在典型的 CTF 密码题场景中加密的消息可能包含已知结构或部分已知内容 (如文件头尾、特定前缀/后缀)。假设我们有理由相信明文 (含 Flag 的消息) 在某个标准位置 (如开头) 包含已知的字节序列例如CTFflag{或其变体。解题思路利用已知明文片段恢复常数C提取开头密文片段: 读取flag.enc获取其前K个字节 (ciphertext_slice ciphertext[0:K])其中K是已知明文片段的长度 (例如 9 对应CTFflag{)。逆向第一步混淆:密文是( (plaintext[i] ^ key_byte_i) 37) % 256。要得到intermediate_i plaintext[i] ^ key_byte_i我们需要逆操作 $$\text{intermediate}_i (ciphertext[i] - 37) \mod 256$$利用已知明文:现在我们有intermediate_i和已知的assumed_plaintext[i]。根据异或属性A ^ B C意味着B A ^ C。因此对于每个位置i(0 i K): $$\text{key_byte}_i \text{assumed_plaintext}_i \oplus \text{intermediate}_i$$计算可能的C值:根据密钥流算法每个key_byte_i必须同时满足 $$\text{key_byte}_i (C \times (i 1)) \mod 256$$但因为C是固定的且关系式是线性的我们可以把步骤3中获得的K组(i1, key_byte_i)数据点视为这个线性同余等式的“观测值”。更简单地由于K可能不大 (如 9)我们可以遍历所有可能的整数C(0 C 256)possible_C set() # 存储所有可能满足前 K 点的 C for candidate_C in range(256): valid True for idx in range(K): expected_key_byte (candidate_C * (idx 1)) % 256 if expected_key_byte ! calculated_keybytes[idx]: valid False break if valid: possible_C.add(candidate_C)理想情况下只有C会满足所有K个点。也可能有多个候选值需要后续验证。完全解密:一旦确定了正确的C(或处理了少数候选)对于每个明文位置i(0 i 总长度):计算key_byte_i (C * (i 1)) % 256计算intermediate_i ciphertext[i]进行逆向混淆 $$\text{xor_result}_i (\text{intermediate}_i - 37) \mod 256$$恢复明文字节 $$\text{plaintext}[i] \text{xor_result}_i \oplus \text{key_byte}_i$$完整的解密封装脚本 (sxor_decrypt.py)# sxor_decrypt.py def sxor_decrypt(ciphertext, known_prefixbCTFflag{): N len(ciphertext) K len(known_prefix) # 步骤1: 提取前 K 字节密文 (下标0到K-1) cipher_slice ciphertext[:K] # 步骤2: 逆向混淆 (计算中间值) intermediates [] for i in range(K): # cipher[i] (intermediate_i 37) % 256 intermediate_i (cipher[i] - 37) mod 256 intermediate (cipher_slice[i] - 37) % 256 intermediates.append(intermediate) # 步骤3利用已知明文计算前 K 个密钥字节 calculated_key_bytes [] for i in range(K): # key_byte_i intermediate_i ^ known_prefix_i key_byte intermediates[i] ^ known_prefix[i] calculated_key_bytes.append(key_byte) # 步骤4找到满足所有前 K 个密钥字节计算关系的常数 C candidates_C [] for candidate_C in range(256): # C 范围是单字节 [0,255] valid True for idx in range(K): # 计算该点下的预期 key_byte expected_key_byte (candidate_C * (idx 1)) % 256 if expected_key_byte ! calculated_key_bytes[idx]: valid False break if valid: candidates_C.append(candidate_C) # 如果没有或多于一个候选需要更复杂的策略 (题目应保证唯一性或有处理方法) # 这里假设只有一个有效候选 if not candidates_C: print(No valid candidate C found. Check known prefix or data.) return None C candidates_C[0] print(f Recovered Constant C {C}) # 步骤5使用常数值 C 解密整个消息 plaintext bytearray() for i in range(N): # 生成密钥流字节 key_byte (C * (i 1)) % 256 # 读密文字节 cbyte ciphertext[i] # 逆向混淆 (intermediate encrypted_byte) # cipherbyte (intermediate_i 37) % 256 intermediate_i (cipherbyte - 37) mod 256 intermediate (cbyte - 37) % 256 # 异或恢复明文 plaintext_byte intermediate_i ^ key_byte plain_byte intermediate ^ key_byte plaintext.append(plain_byte) return bytes(plaintext) # 主程序读取加密文件并解密 if __name__ __main__: with open(flag.enc, rb) as f: ciphertext f.read() # 使用我们猜测的可能前缀尝试解密 # 可以根据域名等调整已知前缀 decrypted sxor_decrypt(ciphertext, known_prefixbCTFflag{) if decrypted: print(Decrypted Message:\n) print(decrypted.decode(utf-8)) # 尝试 UTF-8 解码输出可读信息运行示例输出假设文件的明文确实以CTFflag{开头当运行解密脚本时应当在解密的明文中看到 Recovered Constant C 147 Decrypted Message: CTFflag{5X0R_Cu570M_Cryp70_1s_W34k}总结这道题的核心在于识别出密钥流生成算法的确定性基于固定常数和顺序线性计算以及一点简单的混淆操作的脆弱性。利用一小段已知的明文通过题目的上下文推测得出就可以反推出加密的核心参数constant进而完全恢复整个加密流程解密出完整的明文和 Flag。在 CTF 比赛中尤其是密码学方向仔细审查提供的代码或加密黑盒描述寻找任何非随机性的元素或弱加密原语至关重要。利用已知信息如文件格式、文档标准、协议结构来建立突破口往往是关键。熟悉基本的密码操作和可逆性分析也是解题的重要技能。