文章总结: 本文详述第三届数信杯CTF简单AES题目的解法。针对AES-CBC加密,作者分析了自定义填充与异或提示机制,利用已知flag格式推算密钥,并通过单字节暴力破解恢复IV。文章提供了完整Python解题代码,演示了如何逆向推导并成功获取flag,适合CTF初学者参考。 综合评分: 91 文章分类: CTF,数据安全,逆向分析
第三届“数信杯”数据安全大赛WP之简单AES
赛查查
2026年1月8日 10:49 北京
以下文章来源于一只岸上的鱼 ,作者弈秋
一只岸上的鱼 .
我是一条站在岸上的鱼,渴望回到幸福的水域。
第三届“数信杯”数据安全大赛WP之简单AES
2025年12月28日,周日,第三届“数信杯”数据安全大赛,这是今年最后一个CTF类的比赛了。
记录一道数安个人赛的简单AES题目。
aes加密题目ctf中出现的频率一般,但是最近俩年,ctf开始分裂细化,出现一个叫数据安全的分支,这个分支中,数据加密就非常常见了。因此有必要梳理一下知识和解题思路。
题干
import osfrom Crypto.Util.number import *from Crypto.Cipher import AESfrom secret import flag, keyfrom Crypto.Util.Padding import padassert(len(flag) == 38)assert flag[:5] == b'flag{' and flag[-1:] == b'}'assert(len(key) == 16)def padding(msg): tmp = 16 - len(msg) % 16 pad = format(tmp, '02x') return bytes.fromhex(pad * tmp) + msgmessage = padding(flag)hint = bytes_to_long(key) ^ bytes_to_long(message[:16])message = pad(message, 16, 'pkcs7')IV = os.urandom(16)encryption = AES.new(key, AES.MODE_CBC, iv=IV)enc = encryption.encrypt(message)print('enc =', enc.hex())print('hint =', hex(hint)[2:])# enc = 1ce1df3812668ce0bccd86c146cc56989681e128edd0676f5d26e01abdee90c860e22a5a491f94ac5ca3ab02242740fb8c35a3b60ea737ca0d2662fba2b0e299# hint = 32393f4e3c3c4f3e323a512a5356437d
题干很简单,一段代码,有2个输出,然后需要根据这个输出和代码,写一段逆向的代码,把未告知的flag计算出来。
题外话: 感觉这类题目,考程序员的知识,远超于网安了。
知识储备
为了解这道题,必须梳理一下基础知识,否则都看不懂这段代码,逆向也就无从谈起了。
AES加密CBC模式: AES(Advanced Encryption Standard):对称加密,分组加密,分组大小128位(16字节)
CBC (Cipher Block Chaining): 密码分组链接,简单说就是引入一个随机变量IV,避免同样明文加密的密文一样,导致容易被破解
IV:就是那个随机数,分组链接的意思是:第一块密文解密后与IV异或得到第一块明文,后续块解密后与前一块密文异或
加密代码分析
flag和key的基本信息:
- flag长度为38字节,格式为
b'flag{...}'(开头b'flag{',结尾b'}')。 - key长度为16字节。
自定义填充(padding函数):
对flag进行前置填充,填充长度为16 - len(flag) % 16(此处len(flag)=38,故填充10字节),填充内容为十六进制表示的填充长度(即0x0a,共10字节)。填充后长度48字节记为M1(明文第一组)
hint的计算:
hint = bytes_to_long(key) ^ bytes_to_long(M1[:16]),即key的整数形式与M1前16字节的整数形式异或。由此可推知:key与M1[:16]的每个字节存在异或关系(key[i] = M1[:16][i] ^ hint[i])。
PKCS#7填充:
对M1进行PKCS#7填充(因M1长度为48字节,是16的倍数,故填充16字节的0x10),结果记为M2(长度64字节)。
AES-CBC加密:
用key和随机IV对M2加密,得到密文enc(64字节)
逐步完成代码
使用notebook来逐步完成逆向代码:
- 导入包
from Crypto.Util.number import *from Crypto.Cipher import AESfrom Crypto.Util.Padding import unpad
- 已知数据
enc_hex = "1ce1df3812668ce0bccd86c146cc56989681e128edd0676f5d26e01abdee90c860e22a5a491f94ac5ca3ab02242740fb8c35a3b60ea737ca0d2662fba2b0e299"enc = bytes.fromhex(enc_hex)# 密文分块(16字节每块)blocks = [enc[i:i+16] for i in range(0, len(enc), 16)]# hinthint_hex = "32393f4e3c3c4f3e323a512a5356437d"hint_bytes = bytes.fromhex(hint_hex)hint_long = bytes_to_long(hint_bytes)
- 分组加密第一块
根据分析,分组加密第一块16字节,前面15个是已知的: 前10是补的固定值,中间5个是”flag{“,只有最后一个未知,所以
m16 = b'\x0a' * 10 + b'flag{' + bytes(0x0) # 先假设第16个字符为0x0# 根据题干的代码,key为key_long = hint_long ^ bytes_to_long(m16)key = long_to_bytes(key_long, 16)
- 计算IV
计算IV,根据前面回顾的基础知识,知道了第一段明文、密文,计算出IV
aes = AES.new(key, AES.MODE_CBC, iv=bytes(16)) # 临时IV不影响解密结果d1 = aes.decrypt(blocks[0])iv = bytes([m16[i] ^ d1[i] for i in range(16)]) # IV = m16 ^ d1
- 解密密文
有密钥key,有iv,对于aes加密来说就是万事俱备啊,解密:
aes_cbc = AES.new(key, AES.MODE_CBC, iv=iv)plaintext_padded = aes_cbc.decrypt(enc)
打印结果如下图:
- 暴力破解
有了逻辑就简单了,一个字符8位256个嘛,暴力都谈不上循环一下
for i in range(256): m16 = b'\x0a' * 10 + b'flag{' + bytes([i]) key_long = hint_long ^ bytes_to_long(m16) key = long_to_bytes(key_long, 16) aes = AES.new(key, AES.MODE_CBC, iv=bytes(16)) d1 = aes.decrypt(blocks[0]) iv = bytes([m16[i] ^ d1[i] for i in range(16)]) aes_cbc = AES.new(key, AES.MODE_CBC, iv=iv) plaintext_padded = aes_cbc.decrypt(enc) print(plaintext_padded)
结果如下图:
眼睛一扫,是不是看到答案了^_^
完整代码
简单优化一下代码,解码代码如下:
from Crypto.Util.number import *from Crypto.Cipher import AESfrom Crypto.Util.Padding import unpadenc_hex = "1ce1df3812668ce0bccd86c146cc56989681e128edd0676f5d26e01abdee90c860e22a5a491f94ac5ca3ab02242740fb8c35a3b60ea737ca0d2662fba2b0e299"enc = bytes.fromhex(enc_hex)# 密文分块(16字节每块)blocks = [enc[i:i+16] for i in range(0, len(enc), 16)]hint_hex = "32393f4e3c3c4f3e323a512a5356437d"hint_bytes = bytes.fromhex(hint_hex)hint_long = bytes_to_long(hint_bytes)for c in range(256): m16 = b'\x0a' * 10 + b'flag{' + bytes([c]) # 计算key key_long = hint_long ^ bytes_to_long(m16) key = long_to_bytes(key_long, 16) try: aes = AES.new(key, AES.MODE_CBC, iv=bytes(16)) # 临时IV不影响解密结果 d1 = aes.decrypt(blocks[0]) iv = bytes([m16[i] ^ d1[i] for i in range(16)]) # IV = m16 ^ d1 # 用计算出的IV解密整个密文 aes_cbc = AES.new(key, AES.MODE_CBC, iv=iv) plaintext_padded = aes_cbc.decrypt(enc) plaintext = unpad(plaintext_padded, 16)[10:] if plaintext[:5] == b'flag{' and plaintext[-1:] == b'}': print(plaintext) except Exception as e: continue
收获
收获个屁!
作为应用程序员,谁没事研究aes加密过程,只知道拿来用!! 作为打ctf的,我为什么要看懂这些代码?
结果就一个: 超过35岁被淘汰的程序员们! 开始打ctf吧!!!
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:赛查查 《第三届“数信杯”数据安全大赛WP之简单AES》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论