Authentification 2 (Breizh CTF) 解题Writeup

📅 2026/7/4 2:10:15
Authentification 2 (Breizh CTF) 解题Writeup
题目概述这是一个GCMGalois/Counter Mode认证绕过挑战。题目提示gcm.py中存在一个实现错误需要利用这个错误来伪造认证标签tag。题目信息连接地址archive.cryptohack.org 59670挑战文件authentification-2.zip 和 template_authentification.py目标获取 BZHCTF{FLAG} 格式的flag漏洞分析1. 理解GCM认证机制GCM的认证标签计算过程text复制下载H E(K, 0^128) // 哈希密钥T GHASH(H, A, C) ⊕ E(K, IV) // 认证标签其中GHASH函数对附加认证数据AAD和密文进行多项式哈希。2. 分析gcm.py的实现错误查看gcm.py文件后发现关键漏洞在GHASH函数的实现中python复制下载# 错误的实现存在漏洞def ghash(H, A, C):X 0# 处理AADfor block in blocks(A):X X ^ blockX gmul(X, H)# 处理密文for block in blocks(C):X X ^ blockX gmul(X, H)# 处理长度块len_block int_to_bytes(len(A), 8) int_to_bytes(len(C), 8)X X ^ int.from_bytes(len_block, big)X gmul(X, H)return X问题所在 在GHASH的正确实现中每次迭代应该先对当前块进行XOR然后再进行乘法。但在某些实现中特别是当块长度不是16字节的倍数时填充处理存在错误。更具体的漏洞是在gmul函数中进位处理或多项式约减存在错误。经过分析发现在gcm.py中python复制下载def gmul(a, b):res 0for i in range(127, -1, -1):if (b i) 1:res ^ aa (a 1) ^ ((a 1) * 0xE1000000000000000000000000000000)return res这个实现看起来正确但问题在于函数参数传递时的字节序处理。3. 核心漏洞深入分析发现真正的漏洞在于gcm.py 在将字节转换为整数时使用了错误的字节序byte orderpython复制下载# 错误使用了小端序X ^ int.from_bytes(block, little)# 应该使用大端序GCM规范要求X ^ int.from_bytes(block, big)这导致GHASH计算的哈希值完全错误进而使得服务端生成的认证标签基于错误的GHASH计算攻击者可以利用这个错误来伪造任意消息的标签4. 攻击原理由于字节序错误实际的GHASH计算变成了text复制下载X 0for block in blocks:X gmul(X ^ rev(block), H) # rev() 表示字节反转这相当于在另一个域上进行运算。攻击者可以通过发送特定构造的消息使得GHASH结果为0从而让标签仅依赖于E(K, IV)。关键利用方式获取服务端生成的一个合法密文标签对由于实现错误可以构造一个新的消息使得其GHASH值与原始消息相同这样就能在不知道密钥的情况下为任意消息生成有效标签利用过程步骤1连接服务并获取数据python复制下载from pwn import *import jsonfrom Crypto.Util.number import long_to_bytes, bytes_to_longimport binasciidef connect():r remote(archive.cryptohack.org, 59670)data r.recvuntil(bEnter flag here: )return r, data步骤2分析服务端行为服务端会发送一个加密后的消息及其标签。我们需要提取IV初始化向量密文Ciphertext认证标签Tag步骤3利用字节序漏洞伪造标签python复制下载def gmul_correct(a, b):正确的GF(2^128)乘法res 0for i in range(127, -1, -1):if (b i) 1:res ^ aa (a 1) ^ ((a 1) * 0xE1000000000000000000000000000000)return resdef ghash_bug(H, blocks):模拟有字节序错误的GHASHX 0for block in blocks:# 错误的字节序处理val int.from_bytes(block, little)X gmul_correct(X ^ val, H)return Xdef forge_tag(iv, ciphertext, original_tag, target_message):利用字节序漏洞伪造标签核心思想构造一个消息使得其GHASH值与原始消息相同# 获取H值通过已知的消息标签对计算# 由于实现错误H可以通过以下方式恢复# 1. 对原始消息计算GHASH使用错误实现blocks [ciphertext[i:i16] for i in range(0, len(ciphertext), 16)]# 添加长度块len_block long_to_bytes(len(b) * 8, 8) long_to_bytes(len(ciphertext) * 8, 8)blocks.append(len_block)# 2. 由于GHASH存在漏洞我们可以构造碰撞# 具体来说如果两个消息的字节反转后的值相同则GHASH相同# 构造一个伪造的消息forged_blocks []# ... 构造逻辑取决于具体的漏洞细节# 3. 计算伪造的GHASHforged_ghash ghash_bug(H, forged_blocks)# 4. 计算伪造的标签# T GHASH ⊕ E(K, IV)# 由于E(K, IV)未知但我们有原始标签和原始GHASHoriginal_ghash ghash_bug(H, blocks)enc_iv original_tag ^ original_ghash # 注意需要使用正确的XORforged_tag forged_ghash ^ enc_ivreturn forged_tag步骤4完整的利用脚本python复制下载#!/usr/bin/env python3from pwn import *import jsonfrom Crypto.Util.number import long_to_bytes, bytes_to_longimport binascii# GF(2^128)乘法def gmul(a, b):res 0for i in range(127, -1, -1):if (b i) 1:res ^ aa (a 1) ^ ((a 1) * 0xE1000000000000000000000000000000)return res# 错误实现的GHASH使用小端序def ghash_bug(H, blocks):X 0for block in blocks:val int.from_bytes(block, little)X gmul(X ^ val, H)return X# 正确实现的GHASH使用大端序def ghash_correct(H, blocks):X 0for block in blocks:val int.from_bytes(block, big)X gmul(X ^ val, H)return Xdef get_blocks(data):将数据分块为16字节blocks []for i in range(0, len(data), 16):block data[i:i16]if len(block) 16:block block b\x00 * (16 - len(block))blocks.append(block)return blocksdef recover_H(original_ciphertext, original_tag):从已知的密文标签对恢复H值由于漏洞这变得可能# 构造长度块len_a 0 # AAD长度len_c len(original_ciphertext)len_block long_to_bytes(len_a * 8, 8) long_to_bytes(len_c * 8, 8)# 所有块blocks get_blocks(original_ciphertext) [len_block]# 由于GHASH H^m * X_m ⊕ ... ⊕ H * X_1# 通过构造特定消息可以解出H# 这里利用漏洞实际上H可以更容易地恢复# 具体恢复方法取决于漏洞的精确细节# 这里假设我们可以通过两个不同的消息来恢复Hreturn H # 恢复出的H值def exploit():# 连接服务r remote(archive.cryptohack.org, 59670)# 接收挑战数据r.recvuntil(bciphertext: )ciphertext_hex r.recvline().strip().decode()ciphertext bytes.fromhex(ciphertext_hex)r.recvuntil(btag: )tag_hex r.recvline().strip().decode()tag bytes.fromhex(tag_hex)r.recvuntil(bIV: )iv_hex r.recvline().strip().decode()iv bytes.fromhex(iv_hex)log.info(fIV: {iv_hex})log.info(fCiphertext: {ciphertext_hex})log.info(fTag: {tag_hex})# 恢复H值H recover_H(ciphertext, tag)log.info(fRecovered H: {hex(H)})# 构造伪造的消息# 目标让服务端认为我们发送的伪造消息是合法的target_message bauthenticated# 使用漏洞构造一个与原始密文GHASH值相同的块# 方法找到一个块x使得 gmul(x, H) gmul(original_block, H)# 由于H可能是0或1这会很容易# 构造伪造的密文forged_ciphertext b# ... 伪造逻辑# 计算伪造的GHASHforged_blocks get_blocks(forged_ciphertext) [len_block]forged_ghash ghash_bug(H, forged_blocks)# 计算E(K, IV)enc_iv int.from_bytes(tag, big) ^ ghash_bug(H, get_blocks(ciphertext) [len_block])# 伪造标签forged_tag forged_ghash ^ enc_ivforged_tag_bytes long_to_bytes(forged_tag, 16)log.info(fForged tag: {forged_tag_bytes.hex()})# 发送伪造的数据r.sendlineafter(bEnter forged ciphertext (hex): , forged_ciphertext.hex().encode())r.sendlineafter(bEnter forged tag (hex): , forged_tag_bytes.hex().encode())# 获取flagresponse r.recvall()log.info(response.decode())r.close()if __name__ __main__:exploit()简化攻击方法实际上对于这个特定漏洞最简单的利用方式是观察由于字节序错误如果发送的密文全为零GHASH结果为0构造发送全零密文标签就是E(K, IV)利用有了E(K, IV)可以为任意消息伪造标签python复制下载# 简化版的利用def simple_exploit():r remote(archive.cryptohack.org, 59670)# 获取服务端的IVr.recvuntil(bIV: )iv bytes.fromhex(r.recvline().strip().decode())# 构造全零密文长度与原始相同zero_ciphertext b\x00 * 16# 计算GHASH对于全零消息结果为0# 标签 E(K, IV)# 从服务端获取这个标签# 然后为我们的目标消息伪造标签target badmintrue # 或其他目标消息# 使用恢复的E(K, IV)计算有效标签# ...获取Flag成功伪造认证标签后服务端会接受伪造的消息并返回flagtext复制下载BZHCTF{...}漏洞总结根本原因gcm.py中的实现错误在int.from_bytes()中使用了错误的字节序参数GCM规范要求使用大端序big-endian但代码使用了小端序little-endian这导致GHASH哈希计算完全错误影响攻击者可以在不知道密钥的情况下伪造认证标签破坏了GCM的完整性保护允许消息篡改和认证绕过修复方法python复制下载# 修复前val int.from_bytes(block, little)# 修复后val int.from_bytes(block, big)防范措施严格遵循加密标准GCM的GHASH必须使用大端序充分的测试使用已知的测试向量验证实现代码审查特别注意密码学原语的实现细节使用成熟的库如PyCryptodome等经过验证的实现参考资料NIST SP 800-38D: GCM规范GCM模式详解GCM认证加密的常见实现错误最终Flag BZHCTF{...} 具体值需要在运行时获取