2025古剑山aesstudy详细解析
本文分析了一道密码学CTF题目,涉及AES CBC加密模式的安全性缺陷。题目分为工作量证明(PoW)和多轮AES CBC挑战两部分。通过暴力枚举解决PoW后,利用字节翻转攻击构造密文以满足特定明文条件。最终完成9轮挑战获取flag,并探讨了CBC模式的安全缺陷及防御措施。 2025-11-30 07:43:7 Author: www.freebuf.com(查看原文) 阅读量:0 收藏

题目概述

本文将深入分析一道经典的密码学CTF题目,该题考察了AES CBC加密模式的安全性缺陷。题目分为两个阶段:工作量证明(Proof of Work)和多轮AES CBC挑战。通过完成9轮挑战后,可以获取最终的flag。

题目服务器地址:47.107.168.16:40605

第一部分:工作量证明挑战

连接服务器后,首先遇到的是工作量证明(PoW)挑战。服务器要求找到一个特定的字节序列x,使其SHA256哈希值的前8位符合给定值。

挑战要求

x = chr(random.randint(0,0xff)) + chr(random.randint(0,0xff)) + chr(random.randint(0,0x1f))
hashlib.sha256(x).hexdigest()[0:8] == '指定的8位十六进制值'

这意味着x由3个字节组成:

  • 第一个字节:范围0-255 (256种可能)

  • 第二个字节:范围0-255 (256种可能)

  • 第三个字节:范围0-31 (32种可能)

总搜索空间为:256 × 256 × 32 = 2,097,152

破解方法

由于搜索空间相对较小,我们采用暴力枚举的方法:

def solve_pow(target_hash):
    """
    暴力枚举求解工作量证明
    """
    for a in range(256):
        for b in range(256):
            for c in range(32):
                x = bytes([a, b, c])
                hash_result = hashlib.sha256(x).hexdigest()

                if hash_result[:8] == target_hash:
                    return x.hex()

    return None

这个过程通常需要1-2分钟完成。虽然可以通过多线程优化,但单线程已经足够快。

第二部分:AES CBC挑战核心原理

通过PoW验证后,进入真正的密码学挑战环节。理解这个挑战需要深入了解AES CBC模式的工作原理。

CBC模式加密过程

CBC(Cipher Block Chaining)是一种分组密码工作模式。在CBC模式中,每个明文块在加密前都要与前一个密文块进行XOR运算。

加密流程:

C[0] = AES_Encrypt(P[0] XOR IV, key)
C[i] = AES_Encrypt(P[i] XOR C[i-1], key)  (i > 0)

其中:

  • P[i] 是第i个明文块

  • C[i] 是第i个密文块

  • IV 是初始化向量

  • AES块大小为16字节

CBC模式解密过程

解密过程是加密的逆运算:

P[0] = AES_Decrypt(C[0], key) XOR IV
P[i] = AES_Decrypt(C[i], key) XOR C[i-1]  (i > 0)

这个公式是理解后续攻击的关键。注意到每个明文块的解密结果依赖于前一个密文块。

挑战内容

每轮挑战,服务器提供:

  1. c1:已知密文(base64编码,256字节)

  2. m1:已知明文(base64编码,256字节)

  3. 8个目标要求,格式为:m2[pos] = char

要求:构造新的密文c2,使得解密后的明文m2在指定的8个位置上具有目标字符。

示例:

m2[23]=Z
m2[63]=R
m2[86]=l
m2[119]=x
m2[147]=Q
m2[182]=W
m2[213]=r
m2[254]=c

第三部分:字节翻转攻击原理

CBC模式的安全弱点在于:修改前一个密文块的某个字节,可以精确控制当前明文块对应位置的字节。这就是所谓的字节翻转攻击(Bit-Flipping Attack)。

数学推导

假设我们想要修改位置pos的明文字符,从原始值m1[pos]变为目标值target[pos]。

首先,我们需要理解位置pos在哪个块中:

block_num = pos // 16      # 块编号
block_offset = pos % 16    # 块内偏移

根据CBC解密公式:

原始解密:m1[pos] = D[pos] XOR c1[pos-16]
目标解密:m2[pos] = D[pos] XOR c2[pos-16]

这里D[pos]代表AES解密函数的中间输出,它只依赖于密文块和密钥。由于我们不改变当前密文块,也不知道密钥,所以D[pos]保持不变。

从第一个等式可以得出:

D[pos] = m1[pos] XOR c1[pos-16]

代入第二个等式:

m2[pos] = (m1[pos] XOR c1[pos-16]) XOR c2[pos-16]

要使m2[pos]等于target[pos],需要:

target[pos] = m1[pos] XOR c1[pos-16] XOR c2[pos-16]

整理得到攻击公式:

c2[pos-16] = c1[pos-16] XOR m1[pos] XOR target[pos]

位置计算

对于目标位置pos,我们需要修改的密文位置是:

modify_pos = (block_num - 1) * 16 + block_offset

这是因为在CBC模式中,要影响块i的解密结果,需要修改块i-1的密文。

特殊情况:第一个块

需要注意的是,第一个块(block 0)的明文是由IV控制的:

P[0] = AES_Decrypt(C[0], key) XOR IV

要修改第一个块的明文,需要修改IV,但通常我们无法访问IV。因此,这个攻击只能影响第二个块(block 1)及之后的块。

第四部分:攻击实现

核心攻击函数

基于上述原理,我们实现CBC字节翻转攻击函数:

def cbc_bitflip_attack(c1_b64, m1_b64, targets):
    """
    执行CBC字节翻转攻击

    参数:
        c1_b64: Base64编码的原始密文
        m1_b64: Base64编码的原始明文
        targets: 字典,键为位置,值为目标字符的ASCII码

    返回:
        Base64编码的修改后密文c2
    """
    # 解码base64
    c1 = base64.b64decode(c1_b64)
    m1 = base64.b64decode(m1_b64)

    # 从原始密文开始构造c2
    c2 = bytearray(c1)

    # 对每个目标位置应用字节翻转
    for pos, target_char in targets.items():
        # 计算块信息
        block_num = pos // 16
        block_offset = pos % 16

        if block_num == 0:
            # 第一块需要修改IV,通常无法访问
            continue

        # 计算需要修改的密文位置
        modify_pos = (block_num - 1) * 16 + block_offset

        # 应用XOR翻转公式
        c2[modify_pos] = c1[modify_pos] ^ m1[pos] ^ target_char

    # 编码为base64返回
    return base64.b64encode(bytes(c2)).decode()

数据解析

从服务器响应中提取挑战数据需要使用正则表达式:

def parse_aes_challenge(text):
    """
    解析AES挑战数据
    """
    # 提取c1 (密文)
    c1_match = re.search(
        r"c1\.encode\('base64'\)=([A-Za-z0-9+/=\s]+?)(?=\n#|\n@|$)",
        text,
        re.DOTALL
    )
    c1_b64 = c1_match.group(1).replace('\n', '').replace(' ', '').strip()

    # 提取m1 (明文)
    m1_match = re.search(
        r"m1\.encode\('base64'\)=([A-Za-z0-9+/=\s]+?)(?=\n#|\n@|$)",
        text,
        re.DOTALL
    )
    m1_b64 = m1_match.group(1).replace('\n', '').replace(' ', '').strip()

    # 提取目标位置和字符
    targets = {}
    for match in re.finditer(r"m2\[(\d+)\]=(\w)", text):
        pos = int(match.group(1))
        char = match.group(2)
        targets[pos] = ord(char)

    return {
        'c1_b64': c1_b64,
        'm1_b64': m1_b64,
        'targets': targets
    }

第五部分:完整攻击流程

系统架构

我们采用面向对象的设计,创建一个AES挑战求解器类:

class AESChallengeSolver:
    """AES CBC字节翻转攻击求解器"""

    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.sock = None

    def connect(self):
        """连接到挑战服务器"""
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.connect((self.host, self.port))

    def disconnect(self):
        """关闭连接"""
        if self.sock:
            self.sock.close()

    def recv_data(self, timeout=2.0):
        """接收数据,设置超时"""
        all_data = ""
        self.sock.settimeout(timeout)
        while True:
            try:
                data = self.sock.recv(4096).decode('utf-8', errors='ignore')
                if data:
                    all_data += data
                else:
                    break
            except socket.timeout:
                break
        return all_data

    def send_data(self, data):
        """发送数据"""
        self.sock.send((data + "\n").encode())

主执行流程

def run(self):
    """主执行流程"""
    # 连接服务器
    self.connect()

    # 第一步:解决PoW挑战
    pow_data = self.recv_data(timeout=3)
    target_hash = self.parse_pow_challenge(pow_data)
    pow_answer = self.solve_pow(target_hash)
    self.send_data(pow_answer)

    # 第二步:多轮AES挑战
    round_num = 0
    while round_num < 20:
        round_num += 1

        # 接收挑战数据
        challenge_data = self.recv_data(timeout=3)

        # 检查是否获得flag
        if 'flag{' in challenge_data:
            flag_match = re.search(r'(flag\{[^}]+\})', challenge_data)
            if flag_match:
                print(f"成功获取FLAG: {flag_match.group(1)}")
            break

        # 解析AES挑战
        aes_challenge = self.parse_aes_challenge(challenge_data)
        if not aes_challenge:
            break

        # 执行CBC字节翻转攻击
        c2_answer = self.cbc_bitflip_attack(
            aes_challenge['c1_b64'],
            aes_challenge['m1_b64'],
            aes_challenge['targets']
        )

        # 提交答案
        self.send_data(c2_answer)

    self.disconnect()

第六部分:实战案例分析

让我们通过一个真实的例子来理解攻击过程。

案例:修改位置23的字符

假设在第一轮挑战中,我们需要将位置23的字符从'X'修改为'Z'。

已知信息:

  • 位置23的原始明文:m1[23] = 'X' (ASCII码0x58)

  • 目标字符:target = 'Z' (ASCII码0x5A)

  • 原始密文字节:c1[7] = 0xCD (假设值)

位置计算:

pos = 23
block_num = 23 // 16 = 1        # 位置23在第1块
block_offset = 23 % 16 = 7      # 块内偏移为7
modify_pos = (1 - 1) * 16 + 7 = 7  # 需要修改密文的位置7

应用攻击公式:

c2[7] = c1[7] XOR m1[23] XOR target
      = 0xCD XOR 0x58 XOR 0x5A

二进制验证:

0xCD = 11001101
0x58 = 01011000
       -------- XOR
       10010101
0x5A = 01011010
       -------- XOR
0xCF = 11001111

因此,c2[7] = 0xCF

攻击效果:

当服务器使用修改后的密文c2进行解密时:

解密中间值 D = AES_Decrypt(C1, key)  (保持不变)
m2[23] = D[7] XOR c2[7]
       = D[7] XOR 0xCF

由于原始解密时:

m1[23] = D[7] XOR c1[7]
0x58 = D[7] XOR 0xCD
D[7] = 0x58 XOR 0xCD = 0x95

所以:

m2[23] = 0x95 XOR 0xCF = 0x5A = 'Z'

成功将字符从'X'修改为'Z',而其他位置的字符保持不变。

完整的第一轮攻击

在实际的第一轮挑战中,我们需要同时修改8个位置:

位置 | 块号 | 偏移 | 修改位置 | 原字符 | 目标字符
-----|------|------|----------|--------|----------
 23  |  1   |  7   | c2[7]    |  'X'   |  'Z'
 63  |  3   | 15   | c2[47]   |  'G'   |  'R'
 86  |  5   |  6   | c2[70]   |  'M'   |  'l'
119  |  7   |  7   | c2[103]  |  'J'   |  'x'
147  |  9   |  3   | c2[131]  |  'P'   |  'Q'
182  | 11   |  6   | c2[166]  |  'Y'   |  'W'
213  | 13   |  5   | c2[197]  |  'O'   |  'r'
254  | 15   | 14   | c2[238]  |  'Z'   |  'c'

我们的程序会自动对每个位置应用上述公式,一次性修改8个密文字节,从而精确控制8个明文字符。

第七部分:执行结果

运行完整的攻击脚本后,成功完成9轮挑战:

============================================================
AES CBC Bit-Flipping Attack - Complete Solution
============================================================

[+] Connected to 47.107.168.16:40605

[STEP 1] Proof of Work Challenge
[*] Solving PoW: sha256(x)[0:8] == '5767ab2e'
[+] PoW solved: x = a4781f

[STEP 2] Multi-Round AES CBC Challenges

============================================================
ROUND 1
============================================================
[+] Parsed AES challenge
    Targets: 8 positions
[*] Performing CBC bit-flipping attack...
[+] Generated c2 (length: 344 chars)
[*] Sending answer to server...

[继续完成ROUND 2-9...]

============================================================
 SUCCESS! FLAG CAPTURED!
============================================================

flag{2b6f73f87a8350b130577ced2c58c454}

最终FLAG:flag{2b6f73f87a8350b130577ced2c58c454}

第八部分:安全性分析与防御

CBC模式的安全缺陷

这道题目揭示了CBC模式在没有完整性保护时的严重安全问题:

1. 保密性不等于完整性

CBC模式提供了保密性(攻击者无法读取明文),但不提供完整性保护(攻击者可以修改密文)。即使攻击者不知道加密密钥,也能通过修改密文来精确控制解密结果。

2. 可锻造性

在已知明文-密文对的情况下,攻击者可以构造新的密文,使其解密后产生任意想要的明文。这种攻击被称为"选择密文攻击"(Chosen Ciphertext Attack)的一种形式。

3. 实际危害

在真实系统中,这种攻击可能导致:

  • 权限提升(修改用户角色信息)

  • 数据篡改(修改交易金额)

  • 认证绕过(修改登录凭证)

正确的防御措施

方案一:使用认证加密模式

现代密码学推荐使用AEAD(Authenticated Encryption with Associated Data)模式:

from cryptography.hazmat.primitives.ciphers.aead import AESGCM

# 加密
aesgcm = AESGCM(key)
ciphertext = aesgcm.encrypt(nonce, plaintext, associated_data)

# 解密(自动验证完整性)
try:
    plaintext = aesgcm.decrypt(nonce, ciphertext, associated_data)
except InvalidTag:
    print("密文被篡改,拒绝解密")

推荐的AEAD模式:

  • AES-GCM(最流行)

  • ChaCha20-Poly1305(移动设备友好)

  • AES-CCM

方案二:Encrypt-then-MAC

如果必须使用CBC模式,应该配合HMAC使用:

import hmac
from hashlib import sha256

# 加密端
ciphertext = aes_cbc_encrypt(plaintext, key, iv)
mac = hmac.new(mac_key, ciphertext, sha256).digest()
message = ciphertext + mac

# 解密端
received_ciphertext = message[:-32]
received_mac = message[-32:]

# 先验证MAC
computed_mac = hmac.new(mac_key, received_ciphertext, sha256).digest()
if not hmac.compare_digest(computed_mac, received_mac):
    raise ValueError("MAC验证失败,密文已被篡改")

# MAC验证通过后才解密
plaintext = aes_cbc_decrypt(received_ciphertext, key, iv)

关键点:

  • 必须先验证MAC,再解密

  • 使用constant-time比较防止时序攻击

  • 加密密钥和MAC密钥必须独立

方案三:安全参数选择

如果使用上述方案,还需注意:

  1. IV必须随机且不可预测

  2. 每条消息使用唯一的IV

  3. 密钥长度至少128位(推荐256位)

  4. 使用密码学安全的随机数生成器

import os
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

# 生成随机IV
iv = os.urandom(16)  # 16字节 = 128位

# 正确使用
cipher = Cipher(algorithms.AES(key), modes.CBC(iv))
encryptor = cipher.encryptor()
ciphertext = encryptor.update(plaintext) + encryptor.finalize()

# 错误使用(可预测的IV)
# iv = b'\x00' * 16  # 永远不要这样做!

第九部分:技术总结

关键技术点回顾

1. 密码学原理

  • 深入理解CBC模式的加密/解密流程

  • 掌握XOR运算的可逆性

  • 理解保密性与完整性的区别

2. 数学推导

  • 从CBC解密公式推导攻击公式

  • 理解块和字节位置的计算方法

  • 掌握二进制运算验证技巧

3. 编程实现

  • Socket网络编程

  • Base64编解码

  • 正则表达式数据提取

  • 字节级数据操作

4. 自动化技术

  • 多轮交互式挑战处理

  • 异常处理和重试机制

  • 日志记录和调试输出

学习价值

这道题目不仅是一个CTF挑战,更是一个深入学习密码学的绝佳案例:

理论层面:

  • 理解为什么现代密码学强调"认证加密"

  • 认识到加密算法的正确使用比算法本身更重要

  • 学习密码学中"攻击模型"的概念

实践层面:

  • 从理论推导到代码实现的完整流程

  • 实际攻击案例的调试和优化经验

  • 安全防御措施的设计和实施

工程层面:

  • 清晰的代码结构和文档

  • 可维护的自动化脚本

  • 完整的错误处理机制

复杂度分析

时间复杂度:

  • PoW求解:O(256 × 256 × 32) ≈ O(2^21),约200万次哈希运算

  • CBC攻击:O(n),n为目标位置数量(每轮8个)

  • 总体:受PoW阶段主导,约1-2分钟

空间复杂度:

  • O(密文长度),每轮约256字节

  • 内存占用极小,适合任何环境运行

适用场景

这种攻击在以下情况下有效:

  • 使用CBC模式但没有完整性保护

  • 攻击者能够截获和修改密文

  • 攻击者知道部分或全部明文

  • 系统不验证密文的完整性就解密

在以下情况下攻击失效:

  • 使用认证加密模式(AES-GCM等)

  • 正确实施了Encrypt-then-MAC

  • 解密前验证了HMAC

  • 使用了数字签名

结语

本文通过对一道CTF密码学题目的深入分析,展示了AES CBC模式在缺乏完整性保护时的安全漏洞。我们从数学原理推导出攻击公式,实现了完整的自动化攻击脚本,并深入探讨了正确的防御措施。

密码学是一个"细节决定成败"的领域。即使使用了强大的加密算法,错误的使用方式仍然会导致严重的安全问题。这道题目提醒我们:

  1. 加密不等于安全,还需要认证

  2. 理解原理比记住API更重要

  3. 安全是一个系统工程,不能依赖单一措施

  4. 实践是检验理论的最好方式

希望本文能帮助读者深入理解CBC模式的工作原理和安全隐患,在实际开发中选择合适的加密方案,构建真正安全的系统。

参考资料

  1. NIST SP 800-38A: Recommendation for Block Cipher Modes of Operation

  2. RFC 5116: An Interface and Algorithms for Authenticated Encryption

  3. OWASP Cryptographic Failures (原Top 10 A3:2017-Sensitive Data Exposure)

  4. "Cryptography Engineering" by Ferguson, Schneier, and Kohno

  5. Dan Boneh的密码学课程(Coursera)

附录:完整攻击代码

由于篇幅限制,完整的攻击代码已整合到独立的Python脚本中。主要包括:

  • complete_solution.py:完整的自动化攻击脚本

  • attack_visualization.py:攻击原理的可视化演示

  • solve_pow.py:PoW独立求解器

这些代码采用面向对象设计,具有良好的可读性和可维护性,可作为学习密码学编程的参考范例。

最终获取的FLAG:flag{2b6f73f87a8350b130577ced2c58c454}


本文仅用于安全研究和教育目的。请勿将相关技术用于未经授权的系统。


文章来源: https://www.freebuf.com/articles/others-articles/459696.html
如有侵权请联系:admin#unsafe.sh