手把手教你用Python复现BUUCTF signin题:从密文signin.txt到flag的完整逆向

在CTF竞赛中,密码学题目往往考验选手对加密算法的理解与逆向能力。今天我们将以BUUCTF平台上的"signin"题目为例,通过Python代码完整还原解题过程。这道题来自2020年羊城杯赛事,采用了典型的 Toy Cipher 设计思路,非常适合初学者理解多层加密的逆向分析方法。

1. 题目分析与准备工作

首先我们需要明确题目提供的两个关键要素:

  1. 密文文件 signin.txt 内容为: BCEHACEIBDEIBDEHBDEHADEIACEGACFIBDFHACEGBCEHBCFIBDEGBDEGADFGBDEHBDEGBDFHBCEGACFIBCFGADEIADEIADFH
  2. 参考论文中提供的替换规则表(字典映射关系)

常见踩坑点

  • 密文读取时注意文件编码(建议使用UTF-8)
  • 替换表需要完整准确,任何一个字符映射错误都会导致最终结果偏差
  • 分组处理时注意字符串长度是否为4的倍数

准备基础代码框架:

# 初始化环境
cipher_dict = {
    'M':'ACEG', 'R':'ADEG', 'K':'BCEG', 'S':'BDEG',
    'A':'ACEH', 'B':'ADEH', 'L':'BCEH', 'U':'BDEH',
    'D':'ACEI', 'C':'ADEI', 'N':'BCEI', 'V':'BDEI',
    'H':'ACFG', 'F':'ADFG', 'O':'BCFG', 'W':'BDFG',
    'T':'ACFH', 'G':'ADFH', 'P':'BCFH', 'X':'BDFH',
    'E':'ACFI', 'I':'ADFI', 'Q':'BCFI', 'Y':'BDFI'
}

def read_ciphertext(file_path):
    with open(file_path, 'r', encoding='utf-8') as f:
        return f.read().strip()

2. 第一阶段解密:四字母分组替换

原始密文由多个4字母组成的片段构成,我们需要先将其拆解并对照字典进行替换:

def first_stage_decrypt(ciphertext, cipher_dict):
    result = []
    # 创建反向字典便于查询
    reverse_dict = {v: k for k, v in cipher_dict.items()}
    
    for i in range(0, len(ciphertext), 4):
        block = ciphertext[i:i+4]
        if block in reverse_dict:
            result.append(reverse_dict[block])
        else:
            raise ValueError(f"未知的密文块: {block}")
    
    return ''.join(result)

关键改进点

  1. 使用字典推导式创建反向映射表,提升查询效率
  2. 添加异常处理机制,遇到无法识别的密文块会立即报错
  3. 采用列表拼接字符串,性能优于直接字符串相加

执行第一阶段解密:

ciphertext = read_ciphertext('signin.txt')
stage1_result = first_stage_decrypt(ciphertext, cipher_dict)
print("第一阶段解密结果:", stage1_result)  # 输出: LDVUUCMEXMLQSSFUSXKEOCCG

3. 第二阶段解密:列表反转替换

根据题目提示,我们需要将原始字母表反转后进行二次替换:

def second_stage_decrypt(ciphertext):
    original_list = ['M','R','K','S','A','B','L','U','D','C','N','V',
                    'H','F','O','W','T','G','P','X','E','I','Q','Y']
    reversed_list = original_list[::-1]
    
    result = []
    for char in ciphertext:
        if char in original_list:
            index = original_list.index(char)
            result.append(reversed_list[index])
        else:
            raise ValueError(f"未知的密文字符: {char}")
    
    return ''.join(result)

优化技巧

  • 使用列表切片 [::-1] 快速实现反转
  • 再次添加字符验证机制,确保输入合法性
  • 提前定义好原始字母表,避免硬编码

执行第二阶段解密:

stage2_result = second_stage_decrypt(stage1_result)
print("第二阶段解密结果:", stage2_result)  # 输出: TOYSAYGREENTEAISGWHTCOOL

4. Flag格式处理与最终输出

CTF题目的flag通常有特定格式要求,我们需要进行最后修饰:

def format_flag(raw_flag):
    flag = raw_flag.replace('GWHT', 'GWHT{').replace('COOL', 'COOL}')
    if not flag.startswith('GWHT{') or not flag.endswith('COOL}'):
        raise ValueError("Flag格式异常,请检查解密过程")
    return flag

final_flag = format_flag(stage2_result)
print("最终Flag:", final_flag)  # 输出: GWHT{TOYSAYGREENTEAISCOOL}

注意事项

  • 实际比赛中flag格式可能不同,需要根据题目描述调整
  • 添加格式验证可以避免因解密错误导致的无效提交
  • 建议将GWHT和COOL作为可配置参数

5. 构建通用Toy Cipher解密工具

为了提升代码复用性,我们可以将上述步骤封装成一个完整类:

class ToyCipherDecoder:
    def __init__(self):
        self.cipher_dict = {
            'M':'ACEG', 'R':'ADEG', 'K':'BCEG', 'S':'BDEG',
            'A':'ACEH', 'B':'ADEH', 'L':'BCEH', 'U':'BDEH',
            'D':'ACEI', 'C':'ADEI', 'N':'BCEI', 'V':'BDEI',
            'H':'ACFG', 'F':'ADFG', 'O':'BCFG', 'W':'BDFG',
            'T':'ACFH', 'G':'ADFH', 'P':'BCFH', 'X':'BDFH',
            'E':'ACFI', 'I':'ADFI', 'Q':'BCFI', 'Y':'BDFI'
        }
        self.original_list = [
            'M','R','K','S','A','B','L','U','D','C','N','V',
            'H','F','O','W','T','G','P','X','E','I','Q','Y'
        ]
    
    def decrypt_file(self, file_path):
        ciphertext = self.read_ciphertext(file_path)
        stage1 = self.first_stage_decrypt(ciphertext)
        stage2 = self.second_stage_decrypt(stage1)
        return self.format_flag(stage2)
    
    # 其他方法保持不变,只需改为实例方法
    # ...

工具类优势

  1. 封装所有解密逻辑,提供统一接口
  2. 可轻松扩展支持更多加密变种
  3. 便于集成到自动化解题脚本中

使用示例:

decoder = ToyCipherDecoder()
flag = decoder.decrypt_file('signin.txt')
print("使用工具类解密结果:", flag)

6. 常见问题与调试技巧

在实际操作中可能会遇到以下问题及解决方案:

问题1:密文长度不是4的倍数

# 解决方案:添加长度检查
if len(ciphertext) % 4 != 0:
    print("警告:密文长度异常,可能存在截断或读取错误")

问题2:替换后结果不符合预期

# 调试建议:打印中间结果
print("当前处理的密文块:", block)
print("对应的明文字符:", reverse_dict.get(block, '未知'))

问题3:最终flag格式不正确

# 应对策略:灵活处理格式
prefix = 'GWHT{'
suffix = 'COOL}'
if prefix in raw_flag and suffix in raw_flag:
    flag = raw_flag.replace(prefix, '').replace(suffix, '')
    flag = f"{prefix}{flag}{suffix}"

性能优化建议

  • 对于超长密文,可以考虑使用生成器而非列表
  • 使用字符串的 translate 方法可能比循环替换更高效
  • 对反向字典查询添加缓存机制

7. 扩展应用与变种思路

掌握了基础解密方法后,我们可以进一步思考:

变种1:动态替换表

def generate_cipher_dict(seed):
    # 基于种子生成动态替换表
    random.seed(seed)
    chars = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
    random.shuffle(chars)
    return {c: ''.join(random.sample('ABCDEFGH',4)) for c in chars}

变种2:多层嵌套加密

def multi_layer_encrypt(text, layers=3):
    for _ in range(layers):
        text = first_stage_encrypt(text)
        text = second_stage_encrypt(text)
    return text

实战技巧

  • 遇到类似题目时,先分析加密步骤的组成部分
  • 使用中间结果验证每个阶段的正确性
  • 保留完整的测试用例便于回归验证

在CTF比赛中,密码学题目往往会在基础算法上增加各种变化。通过这个案例,我们不仅学会了如何逆向分析Toy Cipher,更重要的是掌握了 分层解密 的通用思路。下次遇到类似题目时,可以尝试先将复杂问题拆解为多个简单步骤,然后逐个击破。

更多推荐