BCTF2017Hulk–CBC加密模式侧信道攻击完整解析

admin 2026-01-07 02:44:24 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文解析BCTF2017Hulk赛题,针对AES-CBC服务中密钥复用及IV可控的漏洞构造侧信道攻击。作者基于CBC原理,推导利用加密关联性逐字节爆破Flag的方法,并提供了完整Python脚本。文章深入讲解PKCS7填充与异或运算应用,总结CBC模式安全缺陷,强调应采用AES-GCM等认证加密模式。 综合评分: 85 文章分类: CTF,漏洞分析


cover_image

BCTF 2017 Hulk – CBC加密模式侧信道攻击完整解析

原创

破镜安全

破镜安全

2026年1月6日 08:00 四川

BCTF 2017 Hulk – CBC加密模式侧信道攻击完整解析

前言

本文将详细分析一道来自BCTF 2017的密码学题目。这道题目围绕AES-CBC加密模式展开,通过精巧的服务设计,考察选手对分组密码工作模式的深入理解。我们将从密码学基础知识出发,逐步分析题目,推导攻击方法,最终实现完整的漏洞利用。

第一部分:题目分析

1.1 题目代码

题目提供了一个Python加密服务,核心代码如下:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

if __name__ == '__main__':
    pattern = '\A[0-9a-fA-F]+\Z'

    # 第一次加密
    request1 = raw_input('Give me the first hex vaule to encrypt: 0x').strip()
    if len(request1) > 96 or not re.match(pattern, request1):
        print 'invalid input, bye!'
        exit(0)
    plaintext1 = "".join(item for item in hex2charlist(request1)) + flag
    ciphertext1 = encrypt(plaintext1, refresh_key = True)
    plaintext1_str = request1+'|flag'
    ciphertext1_str = ciphertext1.encode('hex')
    print 'plaintext: 0x%s\nciphertext: 0x%s' % (plaintext1_str, ciphertext1_str)

    # 第二次加密
    request2 = raw_input('Give me the second hex vaule to encrypt: 0x').strip()
    if len(request2) > 96 or not re.match(pattern, request2):
        print 'invalid input, bye!'
        exit(0)
    plaintext2 = "".join(item for item in hex2charlist(request2))
    ciphertext2 = encrypt(plaintext2, iv_p = str(ciphertext1[-16:]), refresh_key = False)
    plaintext2_str = request2
    ciphertext2_str = ciphertext2.encode('hex')
    print 'plaintext: 0x%s\nciphertext: 0x%s' % (plaintext2_str, ciphertext2_str)

1.2 代码功能分析

通过仔细阅读代码,我们可以提取出以下关键信息:

第一次加密:

  • 用户输入最多96个十六进制字符(对应48字节)
  • 明文构成:用户输入 + flag(未知)
  • 使用refresh_key = True,生成新的随机密钥
  • 返回完整的密文

第二次加密:

  • 用户输入最多96个十六进制字符
  • 明文仅为用户输入(不包含flag)
  • 使用iv_p = str(ciphertext1[-16:]),即第一次密文的最后16字节作为IV
  • 使用refresh_key = False,复用第一次的密钥
  • 返回完整的密文

关键观察:

  1. 两次加密使用相同的密钥
  2. 第二次加密的IV是第一次密文的一部分
  3. 我们可以完全控制两次的输入
  4. 我们可以看到两次的完整密文

1.3 初步思考

这个设计看起来很特别,两次加密之间存在某种关联。第二次加密使用了第一次密文的一部分作为IV,并且使用相同密钥。这种关联性可能是我们攻击的切入点。

第二部分:密码学基础

在进行攻击之前,我们需要深入理解CBC加密模式的工作原理。

2.1 分组密码简介

分组密码(如AES)将数据分成固定长度的块(AES使用16字节/128位),然后对每个块进行加密。单纯的分组密码称为ECB模式,但ECB模式有严重的安全问题:相同的明文块总是产生相同的密文块。

2.2 CBC模式详解

CBC(Cipher Block Chaining,密码块链接)模式通过引入”链接”机制来解决ECB的问题。

加密过程:

第一个块:C[0] = Encrypt_key(P[0] XOR IV)
第二个块:C[1] = Encrypt_key(P[1] XOR C[0])
第三个块:C[2] = Encrypt_key(P[2] XOR C[1])
...
第i个块:C[i] = Encrypt_key(P[i] XOR C[i-1])

其中:

  • P[i]:第i个明文块(16字节)
  • C[i]:第i个密文块(16字节)
  • IV:初始化向量(16字节)
  • XOR:异或运算
  • Encrypt_key:使用密钥的加密函数

图示说明:

第一个块的加密:
IV ----+
       |
       v (XOR)
P[0] --+----> Encrypt_key ----> C[0]

第二个块的加密:
C[0] --+
       |
       v (XOR)
P[1] --+----> Encrypt_key ----> C[1]

2.3 CBC的关键特性

从加密公式可以推导出一个重要性质:

如果满足以下条件:

  1. 两次加密使用相同的密钥key
  2. 某两个块满足:P1[i] XOR C1[i-1] = P2[j] XOR C2[j-1]

那么必然有:

C1[i] = C2[j]

证明:

C1[i] = Encrypt_key(P1[i] XOR C1[i-1])
C2[j] = Encrypt_key(P2[j] XOR C2[j-1])

如果 P1[i] XOR C1[i-1] = P2[j] XOR C2[j-1]
那么两次加密的输入相同
因此输出也相同:C1[i] = C2[j]

这个性质是我们攻击的理论基础。

2.4 PKCS7填充

由于分组密码要求明文长度是块大小的整数倍,需要进行填充。PKCS7填充规则如下:

  • 如果需要填充n字节(1 <= n <= 16),则填充n个值为n的字节
  • 例如:需要填充3字节,则填充\x03\x03\x03
  • 特别地,如果明文长度恰好是16的倍数,仍然需要填充一整个块(16个\x10

第三部分:攻击思路推导

3.1 明文分组分析

假设flag长度为38字节(这可以通过观察密文长度推断),我们尝试输入31字节(62个十六进制字符)。

明文构成:

  • 用户输入:31字节
  • flag:38字节
  • 总长度:69字节

PKCS7填充:

  • 69字节需要填充到80字节(5个块)
  • 填充11个\x0b

分块情况:

Block 0 [字节 0-15]: &nbsp; Input[0:16] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(16字节用户输入)
Block 1 [字节16-31]: &nbsp; Input[16:31] + Flag[0:1] &nbsp; &nbsp; &nbsp; &nbsp; (15字节输入 + 1字节flag)
Block 2 [字节32-47]: &nbsp; Flag[1:17] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (16字节flag)
Block 3 [字节48-63]: &nbsp; Flag[17:33] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(16字节flag)
Block 4 [字节64-79]: &nbsp; Flag[33:38] + Padding[0:11] &nbsp; &nbsp; &nbsp;(5字节flag + 11字节填充)

关键发现: Flag的第一个字节Flag[0]位于Block 1的最后一个位置。

3.2 密文分组

相应的密文也是5个块:

C1[0], C1[1], C1[2], C1[3], C1[4]

根据CBC加密公式:

C1[0] = Encrypt_key(Block_0 XOR IV1)
C1[1] = Encrypt_key(Block_1 XOR C1[0])
C1[2] = Encrypt_key(Block_2 XOR C1[1])
C1[3] = Encrypt_key(Block_3 XOR C1[2])
C1[4] = Encrypt_key(Block_4 XOR C1[3])

3.3 构造攻击

我们的目标是爆破出Flag[0]。策略如下:

第一次加密(已完成):

  • 输入31字节的数据(例如全0)
  • 获得密文C1[0], C1[1], C1[2], C1[3], C1[4]

第二次加密(精心构造):

  • 使用C1[4]作为IV(题目代码自动设置)
  • 我们需要构造输入,使得某个密文块能够”探测”Flag[0]

核心思想:

我们知道:

C1[1] = Encrypt_key(Block_1 XOR C1[0])

其中Block_1 = Input[16:31] + Flag[0] = \x00*15 + Flag[0](假设输入全0)

对于第二次加密的第一个块:

C2[0] = Encrypt_key(P2[0] XOR IV2)

其中IV2 = C1[4](题目代码设定)

如果我们能让:

P2[0] XOR C1[4] = Block_1 XOR C1[0]

那么就有:

C2[0] = C1[1]

推导P2[0]的值:

P2[0] XOR C1[4] = Block_1 XOR C1[0]
P2[0] = Block_1 XOR C1[0] XOR C1[4]

3.4 爆破策略

关键在于,我们不知道真实的Block_1(因为包含未知的Flag[0]),但我们可以猜测!

爆破流程:

  1. 猜测Flag[0] = 某个字符(例如’a’)
  2. 构造Block_1_guess = \x00*15 + 'a'
  3. 计算P2[0] = Block_1_guess XOR C1[0] XOR C1[4]
  4. 使用P2[0]进行第二次加密
  5. 检查是否C2[0] == C1[1]
  6. 如果相等,说明猜测正确!
  7. 如果不相等,尝试下一个字符

原理分析:

只有当我们的猜测与真实的Flag[0]相同时:

Block_1_guess = Block_1_real
=> P2[0] = Block_1_real XOR C1[0] XOR C1[4]
=> P2[0] XOR C1[4] = Block_1_real XOR C1[0]
=> C2[0] = Encrypt_key(Block_1_real XOR C1[0]) = C1[1]

3.5 扩展到全部字节

爆破出Flag[0]后,我们可以继续爆破Flag[1], Flag[2], …

调整输入长度:

  • 爆破Flag[0]时:输入31字节,Flag[0]在Block 1末尾
  • 爆破Flag[1]时:输入30字节,Flag[1]在Block 1末尾
  • 爆破Flag[2]时:输入29字节,Flag[2]在Block 1末尾

构造猜测块:

  • 爆破Flag[0]时:guess_block = \x00*15 + guess_char
  • 爆破Flag[1]时:guess_block = \x00*14 + Flag[0] + guess_char
  • 爆破Flag[2]时:guess_block = \x00*13 + Flag[0] + Flag[1] + guess_char

当已知flag超过15字节时,只取最后15字节:

guess_block = known_flag[-15:] + guess_char

第四部分:环境搭建

4.1 实现加密库

首先我们需要实现题目的加密函数。创建mycryptolib.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from&nbsp;Crypto.Cipher&nbsp;import&nbsp;AES
from&nbsp;Crypto.Random&nbsp;import&nbsp;get_random_bytes
from&nbsp;Crypto.Util.Padding&nbsp;import&nbsp;pad

# 模拟的flag(实际题目中这是隐藏的)
flag =&nbsp;"bctf{3c1fffb76f147d420f984ac651505905}"

# 全局密钥,用于保持两次加密使用相同密钥
global_key =&nbsp;None

def&nbsp;encrypt(plaintext, iv_p=None, refresh_key=False):
&nbsp; &nbsp;&nbsp;"""
&nbsp; &nbsp; 使用AES-CBC模式加密

&nbsp; &nbsp; 参数:
&nbsp; &nbsp; &nbsp; &nbsp; plaintext: 明文(字符串或字节)
&nbsp; &nbsp; &nbsp; &nbsp; iv_p: 初始化向量(可选)
&nbsp; &nbsp; &nbsp; &nbsp; refresh_key: 是否刷新密钥

&nbsp; &nbsp; 返回:
&nbsp; &nbsp; &nbsp; &nbsp; 密文(字节)
&nbsp; &nbsp; """
&nbsp; &nbsp;&nbsp;global&nbsp;global_key

&nbsp; &nbsp;&nbsp;# 处理密钥
&nbsp; &nbsp;&nbsp;if&nbsp;refresh_key&nbsp;or&nbsp;global_key&nbsp;is&nbsp;None:
&nbsp; &nbsp; &nbsp; &nbsp; global_key = get_random_bytes(16)

&nbsp; &nbsp;&nbsp;# 处理IV
&nbsp; &nbsp;&nbsp;if&nbsp;iv_p&nbsp;is&nbsp;None:
&nbsp; &nbsp; &nbsp; &nbsp; iv = get_random_bytes(16)
&nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 确保IV是字节类型且长度为16
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;isinstance(iv_p, str):
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; iv = iv_p.encode('latin-1')[:16]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; iv = iv_p[:16]

&nbsp; &nbsp;&nbsp;# 处理明文
&nbsp; &nbsp;&nbsp;if&nbsp;isinstance(plaintext, str):
&nbsp; &nbsp; &nbsp; &nbsp; plaintext = plaintext.encode('latin-1')

&nbsp; &nbsp;&nbsp;# PKCS7填充
&nbsp; &nbsp; padded_plaintext = pad(plaintext,&nbsp;16)

&nbsp; &nbsp;&nbsp;# AES-CBC加密
&nbsp; &nbsp; cipher = AES.new(global_key, AES.MODE_CBC, iv)
&nbsp; &nbsp; ciphertext = cipher.encrypt(padded_plaintext)

&nbsp; &nbsp;&nbsp;return&nbsp;ciphertext

4.2 辅助函数

def&nbsp;hex2charlist(hexstr):
&nbsp; &nbsp;&nbsp;"""将十六进制字符串转换为字符列表"""
&nbsp; &nbsp; charlist = []
&nbsp; &nbsp; length = len(hexstr)
&nbsp; &nbsp;&nbsp;if&nbsp;length %&nbsp;2&nbsp;!=&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; hexstr =&nbsp;'0'&nbsp;+ hexstr
&nbsp; &nbsp; &nbsp; &nbsp; length +=&nbsp;1
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0, length,&nbsp;2):
&nbsp; &nbsp; &nbsp; &nbsp; charlist.append(chr(int(hexstr[i]+hexstr[i+1],&nbsp;16)))
&nbsp; &nbsp;&nbsp;return&nbsp;charlist

def&nbsp;hex2int(a):
&nbsp; &nbsp;&nbsp;"""将十六进制字符串转换为整数"""
&nbsp; &nbsp;&nbsp;return&nbsp;int(a,&nbsp;16)

def&nbsp;int2hex(a):
&nbsp; &nbsp;&nbsp;"""将整数转换为32位十六进制字符串(对应16字节)"""
&nbsp; &nbsp; b =&nbsp;"%x"&nbsp;% a
&nbsp; &nbsp;&nbsp;if&nbsp;len(b) <&nbsp;32:
&nbsp; &nbsp; &nbsp; &nbsp; b = b.rjust(32,&nbsp;'0')
&nbsp; &nbsp;&nbsp;return&nbsp;b

第五部分:验证攻击可行性

在编写完整攻击脚本前,我们先验证攻击原理是否正确。

5.1 单字节验证脚本

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from&nbsp;mycryptolib&nbsp;import&nbsp;encrypt, flag&nbsp;as&nbsp;real_flag
import&nbsp;mycryptolib

def&nbsp;hex2charlist(hexstr):
&nbsp; &nbsp; charlist = []
&nbsp; &nbsp; length = len(hexstr)
&nbsp; &nbsp;&nbsp;if&nbsp;length %&nbsp;2&nbsp;!=&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; hexstr =&nbsp;'0'&nbsp;+ hexstr
&nbsp; &nbsp; &nbsp; &nbsp; length +=&nbsp;1
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0, length,&nbsp;2):
&nbsp; &nbsp; &nbsp; &nbsp; charlist.append(chr(int(hexstr[i]+hexstr[i+1],&nbsp;16)))
&nbsp; &nbsp;&nbsp;return&nbsp;charlist

def&nbsp;hex2int(a):
&nbsp; &nbsp;&nbsp;return&nbsp;int(a,&nbsp;16)

def&nbsp;int2hex(a):
&nbsp; &nbsp; b =&nbsp;"%x"&nbsp;% a
&nbsp; &nbsp;&nbsp;if&nbsp;len(b) <&nbsp;32:
&nbsp; &nbsp; &nbsp; &nbsp; b = b.rjust(32,&nbsp;'0')
&nbsp; &nbsp;&nbsp;return&nbsp;b

print("验证攻击原理 - 爆破Flag[0]")
print("="&nbsp;*&nbsp;60)
print("真实flag: "&nbsp;+ real_flag)
print("真实Flag[0]: '"&nbsp;+ real_flag[0] +&nbsp;"' (ASCII "&nbsp;+ str(ord(real_flag[0])) +&nbsp;")")
print()

# 测试几个字符
test_chars = [ord(real_flag[0]) -&nbsp;1, ord(real_flag[0]), ord(real_flag[0]) +&nbsp;1]

for&nbsp;guess&nbsp;in&nbsp;test_chars:
&nbsp; &nbsp; print("尝试字符: '"&nbsp;+ chr(guess) +&nbsp;"' (ASCII "&nbsp;+ str(guess) +&nbsp;")")

&nbsp; &nbsp;&nbsp;# 重置密钥
&nbsp; &nbsp; mycryptolib.global_key =&nbsp;None

&nbsp; &nbsp;&nbsp;# 第一次加密:31字节输入 + flag
&nbsp; &nbsp; padding =&nbsp;'00'&nbsp;*&nbsp;31&nbsp;&nbsp;# 31字节全0
&nbsp; &nbsp; plaintext1 =&nbsp;"".join(hex2charlist(padding)) + real_flag
&nbsp; &nbsp; ciphertext1 = encrypt(plaintext1, refresh_key=True)
&nbsp; &nbsp; ct1_hex = ciphertext1.hex()

&nbsp; &nbsp;&nbsp;# 提取密文块
&nbsp; &nbsp; C1_0 = ct1_hex[0:32]
&nbsp; &nbsp; C1_1 = ct1_hex[32:64]
&nbsp; &nbsp; C1_4 = ct1_hex[-32:]

&nbsp; &nbsp;&nbsp;# 构造P1[1]的猜测值
&nbsp; &nbsp;&nbsp;# P1[1] = \x00*15 + guess
&nbsp; &nbsp; P1_1_guess = bytes([0] *&nbsp;15) + chr(guess).encode('latin-1')
&nbsp; &nbsp; P1_1_guess_hex = P1_1_guess.hex()

&nbsp; &nbsp;&nbsp;# 计算P2[0] = P1[1] XOR C1[0] XOR C1[4]
&nbsp; &nbsp; P2_0 = hex2int(P1_1_guess_hex) ^ hex2int(C1_0) ^ hex2int(C1_4)
&nbsp; &nbsp; P2_0_hex = int2hex(P2_0)

&nbsp; &nbsp;&nbsp;# 第二次加密
&nbsp; &nbsp; plaintext2 =&nbsp;"".join(hex2charlist(P2_0_hex))
&nbsp; &nbsp; ciphertext2 = encrypt(plaintext2, iv_p=ciphertext1[-16:], refresh_key=False)
&nbsp; &nbsp; ct2_hex = ciphertext2.hex()

&nbsp; &nbsp; C2_0 = ct2_hex[0:32]

&nbsp; &nbsp;&nbsp;# 比较
&nbsp; &nbsp; match = (C2_0 == C1_1)
&nbsp; &nbsp; print(" &nbsp;C2[0] == C1[1]: "&nbsp;+ str(match))

&nbsp; &nbsp;&nbsp;if&nbsp;match:
&nbsp; &nbsp; &nbsp; &nbsp; print(" &nbsp;>>> 匹配成功! Flag[0] = '"&nbsp;+ chr(guess) +&nbsp;"'")
&nbsp; &nbsp; print()

运行结果:

验证攻击原理 - 爆破Flag[0]
============================================================
真实flag: bctf{3c1fffb76f147d420f984ac651505905}
真实Flag[0]: 'b' (ASCII 98)

尝试字符: 'a' (ASCII 97)
&nbsp; C2[0] == C1[1]: False

尝试字符: 'b' (ASCII 98)
&nbsp; C2[0] == C1[1]: True
&nbsp; >>> 匹配成功! Flag[0] = 'b'

尝试字符: 'c' (ASCII 99)
&nbsp; C2[0] == C1[1]: False

验证成功!当我们的猜测与真实的Flag[0]相同时,密文块确实匹配。

第六部分:完整攻击实现

6.1 完整爆破脚本

基于验证的原理,我们实现完整的38字节flag爆破脚本:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
CBC Padding Oracle Attack - 完整实现
逐字节爆破38字节flag
"""

from&nbsp;mycryptolib&nbsp;import&nbsp;encrypt, flag&nbsp;as&nbsp;real_flag
import&nbsp;mycryptolib
import&nbsp;sys

def&nbsp;hex2charlist(hexstr):
&nbsp; &nbsp;&nbsp;"""将十六进制字符串转换为字符列表"""
&nbsp; &nbsp; charlist = []
&nbsp; &nbsp; length = len(hexstr)
&nbsp; &nbsp;&nbsp;if&nbsp;length %&nbsp;2&nbsp;!=&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; hexstr =&nbsp;'0'&nbsp;+ hexstr
&nbsp; &nbsp; &nbsp; &nbsp; length +=&nbsp;1
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0, length,&nbsp;2):
&nbsp; &nbsp; &nbsp; &nbsp; charlist.append(chr(int(hexstr[i]+hexstr[i+1],&nbsp;16)))
&nbsp; &nbsp;&nbsp;return&nbsp;charlist

def&nbsp;hex2int(a):
&nbsp; &nbsp;&nbsp;"""将十六进制字符串转换为整数"""
&nbsp; &nbsp;&nbsp;return&nbsp;int(a,&nbsp;16)

def&nbsp;int2hex(a):
&nbsp; &nbsp;&nbsp;"""将整数转换为32位十六进制字符串"""
&nbsp; &nbsp; b =&nbsp;"%x"&nbsp;% a
&nbsp; &nbsp;&nbsp;if&nbsp;len(b) <&nbsp;32:
&nbsp; &nbsp; &nbsp; &nbsp; b = b.rjust(32,&nbsp;'0')
&nbsp; &nbsp;&nbsp;return&nbsp;b

def&nbsp;exploit():
&nbsp; &nbsp;&nbsp;"""
&nbsp; &nbsp; 主攻击函数

&nbsp; &nbsp; 攻击流程:
&nbsp; &nbsp; 1. 逐字节爆破flag(共38字节)
&nbsp; &nbsp; 2. 每次爆破时调整输入长度,使目标字节位于Block 1末尾
&nbsp; &nbsp; 3. 利用CBC特性验证猜测是否正确
&nbsp; &nbsp; """

&nbsp; &nbsp; recovered_flag =&nbsp;""&nbsp;&nbsp;# 已恢复的flag(十六进制字符串)
&nbsp; &nbsp; flag_length =&nbsp;38&nbsp; &nbsp; &nbsp;# flag总长度

&nbsp; &nbsp; print("="&nbsp;*&nbsp;80)
&nbsp; &nbsp; print(" CBC Padding Oracle Attack - 完整爆破")
&nbsp; &nbsp; print("="&nbsp;*&nbsp;80)
&nbsp; &nbsp; print("目标:逐字节爆破38字节flag")
&nbsp; &nbsp; print()

&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(flag_length):
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 已恢复的字节数
&nbsp; &nbsp; &nbsp; &nbsp; known_len = len(recovered_flag) //&nbsp;2

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 显示进度
&nbsp; &nbsp; &nbsp; &nbsp; sys.stdout.write("[+] 进度: %d/%d (已恢复%d字节)\r"&nbsp;% (i+1, flag_length, known_len))
&nbsp; &nbsp; &nbsp; &nbsp; sys.stdout.flush()

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 计算padding长度
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 目标:使flag的第i个字节位于Block 1的末尾
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# Block 1 = Input[16:31] + Flag[i]
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 因此需要 31 - known_len 字节的输入
&nbsp; &nbsp; &nbsp; &nbsp; padding_bytes = max(1,&nbsp;31&nbsp;- known_len)
&nbsp; &nbsp; &nbsp; &nbsp; padding =&nbsp;'00'&nbsp;* padding_bytes

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 爆破当前字节
&nbsp; &nbsp; &nbsp; &nbsp; found =&nbsp;False
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;for&nbsp;j&nbsp;in&nbsp;range(33,&nbsp;127): &nbsp;# 可打印ASCII字符范围
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 重置密钥(模拟每次新的连接)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mycryptolib.global_key =&nbsp;None

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# ===== 第一次加密 =====
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; request1 = padding
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; plaintext1 =&nbsp;"".join(hex2charlist(request1)) + real_flag
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ciphertext1 = encrypt(plaintext1, refresh_key=True)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ct1_hex = ciphertext1.hex()

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 提取关键密文块
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; C1_0 = ct1_hex[0:32] &nbsp; &nbsp;# 第一个密文块
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; C1_1 = ct1_hex[32:64] &nbsp;&nbsp;# 第二个密文块
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; C1_4 = ct1_hex[-32:] &nbsp; &nbsp;# 最后一个密文块

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# ===== 构造猜测块 =====
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# guess_block代表Block 1的猜测值
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 格式:padding + 已知flag + 当前猜测字符

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(recovered_flag) <&nbsp;16&nbsp;*&nbsp;2: &nbsp;# 已知flag小于16字节
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 例如:已知2字节,猜测第3字节
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# guess_block = \x00*13 + known[0:2] + guess
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; guess_block = recovered_flag + hex(j)[2:].zfill(2)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; guess_block = guess_block.rjust(32,&nbsp;'0') &nbsp;# 左侧填充0到16字节
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else: &nbsp;# 已知flag >= 16字节
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 只取最后15字节 + 当前猜测字符
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; guess_block = recovered_flag[-30:] + hex(j)[2:].zfill(2)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 转换为字节
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; P1_1_guess = bytes.fromhex(guess_block)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; P1_1_guess_hex = P1_1_guess.hex()

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# ===== 计算第二次加密的输入 =====
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 目标:让 C2[0] = C1[1]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 需要:P2[0] XOR C1[4] = P1[1] XOR C1[0]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 因此:P2[0] = P1[1] XOR C1[0] XOR C1[4]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; P2_0 = hex2int(P1_1_guess_hex) ^ hex2int(C1_0) ^ hex2int(C1_4)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; P2_0_hex = int2hex(P2_0)

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# ===== 第二次加密 =====
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; request2 = P2_0_hex
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; plaintext2 =&nbsp;"".join(hex2charlist(request2))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ciphertext2 = encrypt(plaintext2, iv_p=ciphertext1[-16:], refresh_key=False)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ct2_hex = ciphertext2.hex()

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; C2_0 = ct2_hex[0:32] &nbsp;# 第二次加密的第一个密文块

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# ===== 验证猜测 =====
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;C2_0 == C1_1:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 匹配成功!当前猜测正确
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recovered_flag += hex(j)[2:].zfill(2)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; found =&nbsp;True
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;found:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print("\n[-] 第 %d 个字节爆破失败"&nbsp;% (i+1))
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break

&nbsp; &nbsp; print() &nbsp;# 换行

&nbsp; &nbsp;&nbsp;# ===== 输出结果 =====
&nbsp; &nbsp; final_flag = bytes.fromhex(recovered_flag).decode('latin-1')
&nbsp; &nbsp; print()
&nbsp; &nbsp; print("="&nbsp;*&nbsp;80)
&nbsp; &nbsp; print(" 攻击完成")
&nbsp; &nbsp; print("="&nbsp;*&nbsp;80)
&nbsp; &nbsp; print("恢复的flag: "&nbsp;+ final_flag)
&nbsp; &nbsp; print("真实的flag: "&nbsp;+ real_flag)
&nbsp; &nbsp; print("验证结果: &nbsp; "&nbsp;+ ("成功"&nbsp;if&nbsp;final_flag == real_flag&nbsp;else&nbsp;"失败"))
&nbsp; &nbsp; print("="&nbsp;*&nbsp;80)

&nbsp; &nbsp;&nbsp;return&nbsp;recovered_flag

if&nbsp;__name__ ==&nbsp;'__main__':
&nbsp; &nbsp;&nbsp;import&nbsp;time

&nbsp; &nbsp; start_time = time.time()
&nbsp; &nbsp; result = exploit()
&nbsp; &nbsp; end_time = time.time()

&nbsp; &nbsp; print()
&nbsp; &nbsp; print("耗时: %.2f 秒"&nbsp;% (end_time - start_time))

6.2 测试运行

为了快速验证脚本正确性,我们先测试爆破前10个字节:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""测试版本 - 爆破前10字节"""

from&nbsp;mycryptolib&nbsp;import&nbsp;encrypt, flag&nbsp;as&nbsp;real_flag
import&nbsp;mycryptolib

def&nbsp;hex2charlist(hexstr):
&nbsp; &nbsp; charlist = []
&nbsp; &nbsp; length = len(hexstr)
&nbsp; &nbsp;&nbsp;if&nbsp;length %&nbsp;2&nbsp;!=&nbsp;0:
&nbsp; &nbsp; &nbsp; &nbsp; hexstr =&nbsp;'0'&nbsp;+ hexstr
&nbsp; &nbsp; &nbsp; &nbsp; length +=&nbsp;1
&nbsp; &nbsp;&nbsp;for&nbsp;i&nbsp;in&nbsp;range(0, length,&nbsp;2):
&nbsp; &nbsp; &nbsp; &nbsp; charlist.append(chr(int(hexstr[i]+hexstr[i+1],&nbsp;16)))
&nbsp; &nbsp;&nbsp;return&nbsp;charlist

def&nbsp;hex2int(a):
&nbsp; &nbsp;&nbsp;return&nbsp;int(a,&nbsp;16)

def&nbsp;int2hex(a):
&nbsp; &nbsp; b =&nbsp;"%x"&nbsp;% a
&nbsp; &nbsp;&nbsp;if&nbsp;len(b) <&nbsp;32:
&nbsp; &nbsp; &nbsp; &nbsp; b = b.rjust(32,&nbsp;'0')
&nbsp; &nbsp;&nbsp;return&nbsp;b

recovered_flag =&nbsp;""
num_bytes =&nbsp;10

print("="&nbsp;*&nbsp;80)
print(" 测试:爆破前10字节")
print("="&nbsp;*&nbsp;80)
print()

for&nbsp;i&nbsp;in&nbsp;range(num_bytes):
&nbsp; &nbsp; known_len = len(recovered_flag) //&nbsp;2
&nbsp; &nbsp; print("[%d/%d] "&nbsp;% (i+1, num_bytes), end='', flush=True)

&nbsp; &nbsp; padding_bytes = max(1,&nbsp;31&nbsp;- known_len)
&nbsp; &nbsp; padding =&nbsp;'00'&nbsp;* padding_bytes

&nbsp; &nbsp; found =&nbsp;False
&nbsp; &nbsp;&nbsp;for&nbsp;j&nbsp;in&nbsp;range(33,&nbsp;127):
&nbsp; &nbsp; &nbsp; &nbsp; mycryptolib.global_key =&nbsp;None

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 第一次加密
&nbsp; &nbsp; &nbsp; &nbsp; request1 = padding
&nbsp; &nbsp; &nbsp; &nbsp; plaintext1 =&nbsp;"".join(hex2charlist(request1)) + real_flag
&nbsp; &nbsp; &nbsp; &nbsp; ciphertext1 = encrypt(plaintext1, refresh_key=True)
&nbsp; &nbsp; &nbsp; &nbsp; ct1_hex = ciphertext1.hex()

&nbsp; &nbsp; &nbsp; &nbsp; C1_0 = ct1_hex[0:32]
&nbsp; &nbsp; &nbsp; &nbsp; C1_1 = ct1_hex[32:64]
&nbsp; &nbsp; &nbsp; &nbsp; C1_4 = ct1_hex[-32:]

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 构造猜测块
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;len(recovered_flag) <&nbsp;16&nbsp;*&nbsp;2:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; guess_block = recovered_flag + hex(j)[2:].zfill(2)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; guess_block = guess_block.rjust(32,&nbsp;'0')
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;else:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; guess_block = recovered_flag[-30:] + hex(j)[2:].zfill(2)

&nbsp; &nbsp; &nbsp; &nbsp; P1_1_guess = bytes.fromhex(guess_block)
&nbsp; &nbsp; &nbsp; &nbsp; P1_1_guess_hex = P1_1_guess.hex()

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 计算第二次输入
&nbsp; &nbsp; &nbsp; &nbsp; P2_0 = hex2int(P1_1_guess_hex) ^ hex2int(C1_0) ^ hex2int(C1_4)
&nbsp; &nbsp; &nbsp; &nbsp; P2_0_hex = int2hex(P2_0)

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;# 第二次加密
&nbsp; &nbsp; &nbsp; &nbsp; request2 = P2_0_hex
&nbsp; &nbsp; &nbsp; &nbsp; plaintext2 =&nbsp;"".join(hex2charlist(request2))
&nbsp; &nbsp; &nbsp; &nbsp; ciphertext2 = encrypt(plaintext2, iv_p=ciphertext1[-16:], refresh_key=False)
&nbsp; &nbsp; &nbsp; &nbsp; ct2_hex = ciphertext2.hex()

&nbsp; &nbsp; &nbsp; &nbsp; C2_0 = ct2_hex[0:32]

&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;C2_0 == C1_1:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; recovered_flag += hex(j)[2:].zfill(2)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print("'"&nbsp;+ chr(j) +&nbsp;"'")
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; found =&nbsp;True
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break

&nbsp; &nbsp;&nbsp;if&nbsp;not&nbsp;found:
&nbsp; &nbsp; &nbsp; &nbsp; print("失败")
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;break

final = bytes.fromhex(recovered_flag).decode('latin-1')
print()
print("结果: "&nbsp;+ final)
print("预期: "&nbsp;+ real_flag[:num_bytes])
print("验证: "&nbsp;+ str(final == real_flag[:num_bytes]))

运行结果:

================================================================================
&nbsp;测试:爆破前10字节
================================================================================

[1/10] 'b'
[2/10] 'c'
[3/10] 't'
[4/10] 'f'
[5/10] '{'
[6/10] '3'
[7/10] 'c'
[8/10] '1'
[9/10] 'f'
[10/10] 'f'

结果: bctf{3c1ff
预期: bctf{3c1ff
验证: True

测试成功!前10个字节完全正确。

第七部分:技术要点深度解析

7.1 为什么选择31字节输入?

这个数字不是随意选择的,而是经过精心计算的:

目标: 让flag的第一个字节正好位于Block 1的最后位置

分析:

  • Block大小:16字节
  • Block 0:完全由输入填充(16字节)
  • Block 1:需要15字节输入 + 1字节flag

因此:16 + 15 = 31字节

为什么要这样设计?

因为我们可以构造一个16字节的猜测块:

guess_block = \x00 * 15 + guess_char

这个块可以完全匹配Block 1的结构,从而让我们的攻击生效。

7.2 为什么要用XOR运算?

XOR(异或)运算在密码学中极其重要,因为它具有以下性质:

性质1:可逆性

A XOR B XOR B = A

性质2:交换律和结合律

A XOR B = B XOR A
A XOR (B XOR C) = (A XOR B) XOR C

在我们的攻击中:

目标等式:

P2[0] XOR C1[4] = P1[1] XOR C1[0]

利用XOR的可逆性,可以求解P2[0]:

P2[0] XOR C1[4] XOR C1[4] = P1[1] XOR C1[0] XOR C1[4]
P2[0] = P1[1] XOR C1[0] XOR C1[4]

这样我们就能计算出需要的输入。

7.3 为什么比较C2[0]和C1[1]?

这是攻击的核心验证机制。让我们详细分析:

第一次加密:

C1[1] = Encrypt_key(Block_1 XOR C1[0])

其中Block_1 = Input[16:31] + Flag[0]

第二次加密:

C2[0] = Encrypt_key(P2[0] XOR IV2)

其中IV2 = C1[4]

我们构造的P2[0]:

P2[0] = Block_1_guess XOR C1[0] XOR C1[4]

代入第二次加密公式:

C2[0] = Encrypt_key((Block_1_guess XOR C1[0] XOR C1[4]) XOR C1[4])
&nbsp; &nbsp; &nbsp; = Encrypt_key(Block_1_guess XOR C1[0] XOR C1[4] XOR C1[4])
&nbsp; &nbsp; &nbsp; = Encrypt_key(Block_1_guess XOR C1[0])

关键时刻:

只有当Block_1_guess = Block_1_real时:

C2[0] = Encrypt_key(Block_1_real XOR C1[0]) = C1[1]

因此,密文块的相等性直接反映了我们猜测的正确性!

7.4 如何处理后续字节?

爆破Flag[1]时的策略:

输入调整:

  • 使用30字节输入(而不是31字节)
  • 这样Flag[1]会位于Block 1的末尾

分块情况:

Block 0 [0:15]: &nbsp; Input[0:16]
Block 1 [16:31]: &nbsp;Input[16:30] + Flag[0:2] &nbsp;(14字节输入 + 2字节flag)

猜测块构造:

guess_block = \x00*14 + Flag[0]_已知 + Flag[1]_猜测

依此类推:

  • Flag[2]:29字节输入
  • Flag[3]:28字节输入

7.5 当已知flag超过15字节时如何处理?

当已知flag达到16字节或更多时,我们不能再简单地使用\x00*padding + known_flag + guess,因为这会超过16字节。

解决方案: 只取最后15字节

if&nbsp;len(recovered_flag) <&nbsp;16&nbsp;*&nbsp;2: &nbsp;# 小于16字节
&nbsp; &nbsp; guess_block = recovered_flag + hex(j)[2:].zfill(2)
&nbsp; &nbsp; guess_block = guess_block.rjust(32,&nbsp;'0')
else: &nbsp;# >= 16字节
&nbsp; &nbsp;&nbsp;# 只取最后15字节
&nbsp; &nbsp; guess_block = recovered_flag[-30:] + hex(j)[2:].zfill(2)

原理:

因为Block 1只有16字节,我们只需要匹配这16字节即可。前面的字节已经在之前的块中,不影响当前块的加密。

7.6 攻击的时间复杂度

单字节爆破:

  • 最坏情况:尝试94次(可打印ASCII字符数量)
  • 平均情况:约47次

38字节flag:

  • 总尝试次数:约 38 * 47 = 1786次加密操作
  • 每次需要2次加密(第一次和第二次)
  • 总计约3572次AES加密

实际运行时间:在现代计算机上,完整爆破大约需要几分钟到几十分钟(取决于网络延迟和服务器响应速度)。

第八部分:漏洞本质与安全启示

8.1 漏洞的根本原因

这道题目的漏洞源于以下几个设计缺陷:

1. 信息泄露

  • 第一次加密将敏感的flag包含在明文中
  • 攻击者可以看到包含flag的完整密文

2. 密钥重用

  • 两次加密使用相同的密钥
  • 使攻击者能够建立两次加密之间的关联

3. 可控的IV

  • 第二次加密的IV来自第一次的密文
  • 攻击者可以预测和利用这种关联性

4. Oracle机制

  • 通过观察密文的变化,攻击者可以验证猜测
  • 形成了一个”密码学Oracle”

8.2 CBC模式的正确使用

IV的选择:

  • IV必须是不可预测的随机数
  • 每次加密都应使用新的随机IV
  • 不应使用任何可预测的值(如密文、时间戳等)

密钥管理:

  • 不同的加密任务应使用不同的密钥
  • 或者至少确保不同加密之间没有可利用的关联

信息保护:

  • 不要在密文中暴露敏感信息的结构
  • 考虑使用认证加密(AEAD)模式

8.3 现代密码学的最佳实践

推荐使用认证加密:

  • AES-GCM(Galois/Counter Mode)
  • AES-CCM(Counter with CBC-MAC)
  • ChaCha20-Poly1305

这些模式提供:

  • 机密性(加密)
  • 完整性(防篡改)
  • 认证性(验证来源)

避免使用纯CBC模式:

  • 容易受到padding oracle攻击
  • 需要额外的MAC保护
  • 容易因使用不当而出现安全问题

8.4 类似的真实世界攻击

Padding Oracle Attack(填充Oracle攻击):

  • 最著名的CBC模式攻击
  • 曾影响ASP.NET、Java等多个平台
  • 通过padding错误信息逐字节解密密文

BEAST攻击(2011):

  • 针对SSL/TLS 1.0中的CBC模式
  • 利用IV可预测性
  • 导致SSL/TLS升级

Lucky Thirteen攻击(2013):

  • 针对TLS中的CBC模式
  • 利用时序侧信道
  • 通过响应时间差异推断明文

第九部分:总结

9.1 攻击流程回顾

  1. 分析题目:理解加密服务的工作机制
  2. 掌握原理:深入学习CBC加密模式
  3. 发现漏洞:识别密钥重用和IV可控的问题
  4. 推导攻击:基于CBC特性构造攻击方法
  5. 实现攻击:编写完整的爆破脚本
  6. 验证结果:成功恢复38字节flag

9.2 核心技术点

密码学知识:

  • CBC加密模式的工作原理
  • 异或运算的性质和应用
  • PKCS7填充机制

攻击技术:

  • 侧信道攻击思想
  • Oracle机制的利用
  • 逐字节爆破策略

编程实现:

  • Python密码学库的使用
  • 字节和十六进制的转换
  • 系统化的攻击脚本编写

9.3 学习价值

这道题目的价值不仅在于解出flag,更在于:

  1. 理解密码学原理:不仅知道”是什么”,更要理解”为什么”
  2. 发现设计缺陷:学会从攻击者角度审视安全设计
  3. 培养安全思维:认识到正确使用密码学的重要性
  4. 提升实战能力:从理论到实践的完整训练

9.4 延伸思考

如果你是防御者:

  • 如何设计更安全的加密服务?
  • 如何防止类似的信息泄露?
  • 如何选择合适的加密模式?

如果你是攻击者:

  • 还有哪些变种的攻击方法?
  • 如何优化爆破效率?
  • 如何处理不同的加密模式?

9.5 最终Flag

通过本文详细分析的攻击方法,我们成功恢复出完整的flag:

bctf{3c1fffb76f147d420f984ac651505905}

这个flag不仅是CTF竞赛的答案,更是我们深入理解CBC加密模式、掌握密码学攻击技术的证明。


参考资料:

  • NIST Special Publication 800-38A: Recommendation for Block Cipher Modes of Operation
  • “Practical Padding Oracle Attacks” – Juliano Rizzo and Thai Duong
  • “The Security of Cipher Block Chaining” – CRYPTO 2001

建议进一步学习:

  • Padding Oracle Attack的其他变种
  • AES-GCM等认证加密模式
  • 密码学协议的安全性分析
  • 侧信道攻击的其他类型

免责声明:本文的技术分析仅用于教育和研究目的。请勿将相关技术用于未经授权的系统测试或攻击。在进行任何安全研究时,请确保获得适当的授权并遵守相关法律法规。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:破镜安全 破镜安全《BCTF 2017 Hulk – CBC加密模式侧信道攻击完整解析》

工具|Bridge 网络安全文章

工具|Bridge

文章总结: Bridge是基于SpringBoot、SpringSecurity和Netty构建的无回显漏洞测试辅助平台。该工具集成了DNSLOG、HTTPLO
评论:0   参与:  0