第一层变换后,驱动把结果传入另一个校验函数。这个函数分两轮处理输入。第一轮使用初始值 0x34 做链式异或:
prev 0x34; for (i 0; i len; i) { tmp[i] input[i] ^ prev; prev input[i]; }第二轮继续链式异或并把结果与.data段中的 32 字节常量比较for (i 0; i len; i) { old tmp[i]; tmp[i] ^ prev; prev old; }目标常量是66 0a 09 e0 e2 e3 cb 09 14 15 0c 38 01 1f 05 42 71 6e 56 7a 00 20 e4 bf e6 cd 28 30 2c 75 a0 3a如果比较成功驱动不会返回真正的新字符串而是返回flag is you input这句话很关键。它说明 flag 不是驱动输出内容而是用户输入本身。也就是说攻击目标变成了构造一个 32 字节输入使它经过两层变换后等于.data段常量。先逆第二层校验。由于第二层是链式异或异或本身可逆所以可以从目标常量倒推出第一层变换后的中间值。逆推得到的 32 字节中间态是2f 3e 26 de c4 3d 0f 34 1b 21 17 19 16 06 13 44 62 2a 34 50 34 70 d0 cf 36 02 1e 32 32 47 92 7d接下来逆第一层变换。第一层有这个结构buf[i - 1] original[i - 1] ^ f(original[i - 1], original[i])因为它依赖右侧相邻字节所以从最后一个字节开始向前回溯最自然。末尾buf[31]没被第一层循环修改因此可以直接确定原始输入最后一字节。然后逐位向前枚举original[i - 1]要求它变换后等于已知中间态。回溯后得到多个数学上可行的候选但大多数包含不可打印字符或不符合 flag 格式。唯一符合常见 CTF flag 形态、且完整可打印的是flag{wnNCZJbBOqL3QA1C1cypiKYII4}为了避免只靠格式猜测还需要做正向验证。把这个候选作为原始输入先执行驱动第一层变换得到2f3e26dec43d0f341b21171916061344622a34503470d0cf36021e323247927d再执行第二层校验变换得到660a09e0e2e3cb0914150c38011f0542716e567a0020e4bfe6cd28302c75a03a它与.data段目标常量完全一致因此该输入一定会命中成功分支。最终驱动成功分支提示flag is you input说明正确输入就是 flag。经过逆向两层变换并正向验证最终 flag 为flag{wnNCZJbBOqL3QA1C1cypiKYII4}SimpleSocket一个轻量级客户端与服务端程序通过自定义通信流程保护返回信息。请逆向分析交换逻辑还原最终内容。题目目录中有四个文件py、packet1、packet2、packet3。先看pyfrom Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP, AES from Crypto.Util.Padding import pad, unpad import os import socket import time def generate_rsa_keys(): key RSA.generate(1024) #生成一对RSA密钥1024位 private_key key.export_key() #调用.export_key()把key转换为文本格式 public_key key.publickey().export_key() #只取出公钥部分 return private_key, public_key def client_logic(public_key): current_time int(time.time()) aes_key os.urandom(16) #生成16个字节128位的完全随机的数据作为AES加密的密钥 cipher_rsa PKCS1_OAEP.new(RSA.import_key(public_key)) encrypted_aes_key cipher_rsa.encrypt(aes_key) #加密 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect((localhost, 9999)) s.sendall(encrypted_aes_key) encrypted_data s.recv(1024) def server_logic(private_key): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.bind((localhost, 9999)) s.listen() conn, addr s.accept() with conn: encrypted_aes_key conn.recv(256) cipher_rsa PKCS1_OAEP.new(RSA.import_key(private_key)) aes_key cipher_rsa.decrypt(encrypted_aes_key) #解密 flag bthis is flag cipher_aes AES.new(aes_key, AES.MODE_ECB) encrypted_flag cipher_aes.encrypt(pad(flag, AES.block_size)) conn.sendall(encrypted_flag) private_key, public_key generate_rsa_keys() import threading server_thread threading.Thread(targetserver_logic, args(private_key,)) server_thread.start() client_logic(public_key)整体逻辑是用RSA加密AES密钥再用AES加密数据packet2: PEM 格式 RSA PRIVATE KEY但换行被写成了字面量 \n packet3: 256 个十六进制字符即 128 字节 packet1: 96 个十六进制字符即 48 字节packet3的 128 字节长度正好对应 1024-bit RSA 密文packet1的 48 字节长度是 16 的倍数符合 AES 分组密文特征packet2则是泄露的 RSA 私钥。packet3 --RSA私钥解密-- AES key AES key packet1 --AES-ECB解密-- flag注意packet1和packet3都是十六进制文本不是原始二进制所以使用前要先bytes.fromhex()。packet2里的私钥不是标准 PEM 换行而是包含字面量\n所以导入 RSA 私钥前要替换成真实换行private_key packet2_text.replace(\\n, \n).encode()