下载、查壳、64位、IDA打开
tea算法的特征非常明显
TEA(Tiny Encryption Algorithm,微型加密算法)是由剑桥大学的 David Wheeler 和 Roger Needham 于 1994 年提出的一种轻量级分组对称加密算法。它的设计目标是代码极简、易于实现、占用资源极少,非常适合嵌入式设备、逆向 CTF 题目等场景。
核心参数
表格
| 项 | 标准 TEA 默认值 | 本题对应实现 |
|---|---|---|
| 分组长度 | 64 bit(8 字节,拆为 2 个 32 位整数v0, v1) | 一致,每组 2 个 int(8 字节) |
| 密钥长度 | 128 bit(16 字节,拆为 4 个 32 位整数k[0]~k[3]) | 一致,16 字节密钥 |
| 经典轮数 | 32 轮 | 一致,循环0x1F即 32 轮 |
| 魔数 delta | 0x9E3779B9(源于黄金分割比例,保证混淆性) | 等价补码写法:0x61C88647 |
标准 TEA 加密原理
TEA 基于迭代混淆 - 扩散的思想,对两个 32 位明文块v0、v1进行多轮运算,每轮通过位移、异或、加法和密钥混合,逐步打乱明文特征。
加密流程(32 轮)
- 初始化累加值
sum = 0,delta 为固定魔数 - 每轮执行:
sum = sum + delta; v0 = v0 + ( ((v1 << 4) + k[0]) ^ (v1 + sum) ^ ((v1 >> 5) + k[1]) ); v1 = v1 + ( ((v0 << 4) + k[2]) ^ (v0 + sum) ^ ((v0 >> 5) + k[3]) );- 32 轮结束后,
v0、v1就是最终的密文分组
核心设计:
(v << 4) ^ (v >> 5)是位移混淆,通过左移 4 位和右移 5 位的异或,打散比特位分布- 加法和异或交替使用,实现非线性混淆,增加破解难度
sum累加值每轮递增,保证每轮的密钥混合因子都不同,避免相同轮函数重复
本题中的 TEA 代码对应
你 IDA 反汇编里的tea_encrypt函数,本质就是标准 TEA 的轻微改写,对应关系如下:
- 变量映射
v6= 左 32 位明文 / 密文v0v5= 右 32 位明文 / 密文v1v4= 轮累加值suma2= 128 位密钥数组首地址k
- delta 的写法差异
代码里写的是v4 -= 1640531527(即sum -= 0x61C88647)
它和标准 TEA 的sum += 0x9E3779B9运算结果完全等价,因为 32 位无符号整数运算下:
-0x61C88647 mod 2^32 = 0x9E3779B9只是编译器把正数加法优化成了负数减法的补码形式。
3.密钥索引的差异
标准 TEA 固定用k[0]/k[1]计算 v0、k[2]/k[3]计算 v1;
本题用sum & 3和(sum >> 11) & 3动态取密钥下标,属于 CTF 中很常见的 TEA 变种写法,本质还是 4 个密钥字循环参与运算。
TEA 解密逻辑
解密是加密的严格逆过程:
- 初始
sum设为加密结束时的最终值(32 轮后 sum = 32 × delta) - 每轮倒序执行加密操作:先还原 v1,再回退 sum,最后还原 v0
- 循环 32 轮后得到原始明文
这也是为什么之前的解密脚本里,sum 初始值是负数、运算顺序和加密反过来的原因。
代码解析:
这是一个flag 验证程序的主函数,核心逻辑是:读取用户输入 → 按 TEA 算法加密 → 和内置密文对比,判断输入是否正确。我们按执行顺序拆解:
1. 变量声明
int v4[16]; // 整数数组,共16个int,用于存放「内置密文」和「加密后的输入」 char v5[44]; // 字符缓冲区,存放用户输入的flag字符串 int k, j, i; // 循环计数器 int *v9; // int型指针,用于把字符串按4字节整数读取v4是核心数组:前半部分存正确答案(密文),后半部分存用户输入加密后的结果,最后逐位对比。v5是输入缓冲区,最大 44 字节,但实际只会用到前 24 字节(6 个 int)。
2. 输入读取
puts("please input your flag"); scanf("%s", v5); v9 = (int *)v5;- 提示用户输入 flag,读取字符串存入
v5。 v9 = (int *)v5:把字符数组的地址强制转为 int 指针。
作用是:把输入的字符串,按4 字节一个 32 位整数的方式来读取(x86 平台默认小端序)。
3. 输入拆分:字符串 → 整数数组
for ( i = 0; i <= 5; ++i ) v4[i + 8] = *v9++;- 循环 6 次,把输入的 flag 按 4 字节切分成6 个 int 整数,依次存入
v4[8] ~ v4[13]。 - 6 个 int = 24 字节,说明 flag 的有效长度最多 24 字节。
4. 核心:分组 TEA 加密
for ( j = 0; j <= 2; ++j ) tea_encrypt(&v4[2 * j + 8], &key);- 循环 3 次,每次取**2 个 int(8 字节)**作为一组,调用
tea_encrypt进行加密。- j=0:加密
v4[8], v4[9](第 1 组) - j=1:加密
v4[10], v4[11](第 2 组) - j=2:加密
v4[12], v4[13](第 3 组)
- j=0:加密
tea_encrypt是原地加密:直接修改传入地址里的数值,加密完成后v4[8]~v4[13]就从明文变成了密文。- 这符合 TEA 算法的特征:64 位(8 字节)分组加密,每次处理 2 个 32 位整数。
5. 内置正确密文
v4[0] = -1054939302; v4[1] = -1532163725; v4[2] = -165900264; v4[3] = 853769165; v4[4] = 768352038; v4[5] = 876839116;v4[0] ~ v4[5]是程序写死的正确密文(标准答案)。- 这里是有符号 int 显示,实际参与运算时是 32 位无符号整数。
6. 校验对比
for ( k = 0; k <= 5; ++k ) { if ( v4[k] != v4[k + 8] ) { printf("ERROR!"); exit(9); } } printf("you are right!");- 循环 6 次,逐位对比:
- 左边
v4[k]:内置的正确密文 - 右边
v4[k+8]:用户输入加密后的结果
- 左边
- 只要有一位不相等,就打印
ERROR!并退出; - 全部相等则打印
you are right!,说明输入的 flag 正确。
整体逻辑总结
用户输入flag字符串 ↓ 按4字节拆成6个int整数(存入v4[8]~v4[13]) ↓ 分成3组,每组2个int,分别调用TEA加密 ↓ 加密结果 和 程序内置的6个正确密文 逐位对比 ↓ 全部相等 → 正确;否则 → 错误然后解析加密算法:
这是一个32 轮迭代的 64 位分组对称加密算法(变种 TEA),输入是 8 字节明文(拆为 2 个 32 位整数)和 16 字节密钥(拆为 4 个 32 位整数),经过 32 轮混淆运算后输出 8 字节密文,属于原地加密(直接修改输入的内存数据)。
二、变量映射与初始化
1. 输入参数
unsigned int *a1:明文指针,指向连续 2 个unsigned int(共 8 字节),分别是明文的左 32 位、右 32 位__int64 a2:密钥指针,指向连续 4 个unsigned int(共 16 字节),即 128 位密钥
2. 内部变量对应
表格
| 变量 | 对应 TEA 核心概念 | 作用 |
|---|---|---|
v6 | 左半部分v0 | 存储加密过程中的左 32 位数据 |
v5 | 右半部分v1 | 存储加密过程中的右 32 位数据 |
v4 | 轮累加值sum | 每轮递变的常量,保证每轮运算因子不同 |
i | 轮计数器 | 控制 32 轮循环 |
3. 初始化步骤
v6 = *a1; // 读取明文左32位 v5 = a1[1]; // 读取明文右32位 v4 = 0; // 轮累加值 sum 初始化为 0循环for ( i = 0i64; i <= 0x1F; ++i ):0x1F= 31,循环执行32 轮(0~31 共 32 次),和标准 TEA 的轮数一致。
三、单轮加密核心运算(逐行拆解)
每一轮循环严格分为三步:更新左半 v6 → 更新 sum 值 v4 → 更新右半 v5。
第 1 步:更新左半部分 v6
v6 += (((v5 >> 5) ^ (16 * v5)) + v5) ^ (*(_DWORD *)(4i64 * (v4 & 3) + a2) + v4);从内到外逐层拆解运算逻辑:
- 比特位移混淆:
(v5 >> 5) ^ (16 * v5)v5 >> 5:v5 逻辑右移 5 位,高位补 016 * v5:等价于v5 << 4,v5 左移 4 位,低位补 0- 两者异或:把 v5 的高低比特位交叉打散,是 TEA 算法的核心混淆单元,目的是破坏明文的统计规律
- 非线性叠加:
((位移异或结果) + v5)- 把混淆后的结果再加上 v5 本身,通过加法引入非线性,进一步扩散比特位
- 密钥 + 轮值混合:
(*(_DWORD *)(4i64 * (v4 & 3) + a2) + v4)v4 & 3:取 sum 的低 2 位,得到 0/1/2/3 的下标,动态选择 4 个密钥字中的一个4i64 * 下标:每个密钥字占 4 字节,计算对应的字节偏移量- 加上密钥基地址
a2,取出对应的 32 位密钥字 - 再加上当前轮的 sum 值
v4:让密钥和轮值绑定,相同密钥字在不同轮的作用不同
- 最终合并:
(非线性叠加结果) ^ (密钥轮值混合结果),结果累加到 v6 上- 用异或把数据和密钥混合,再通过加法更新左半部分,完成一轮左半部分的扩散
第 2 步:更新轮累加值 sum(v4)
v4 -= 1640531527;- 常量
1640531527的十六进制为0x61C88647 - 在 32 位无符号整数运算中,
减去0x61C88647等价于加上0x9E3779B9(标准 TEA 的经典 delta 常量,源于黄金分割数) - 作用:每轮让 sum 变化一个固定值,保证每一轮的密钥混合因子都不相同,避免轮函数重复
第 3 步:更新右半部分 v5
v5 += (((v6 >> 5) ^ (16 * v6)) + v6) ^ (*(_DWORD *)(4i64 * ((v4 >> 11) & 3) + a2) + v4);和 v6 的更新结构对称,但有两个关键区别:
- 输入是更新后的 v6:用刚更新完的左半部分 v6 来计算右半部分,形成迭代依赖,这是分组密码实现扩散的核心
- 密钥索引方式不同:
(v4 >> 11) & 3- 把 sum 右移 11 位后再取低 2 位,和 v6 更新时的
v4 & 3索引不同 - 目的是让左右两半使用不同的密钥字调度,增加算法的非线性和破解难度
- 把 sum 右移 11 位后再取低 2 位,和 v6 更新时的
四、结果写回
32 轮循环结束后:
*a1 = v6; // 加密后的左32位写回原地址 a1[1] = v5; // 加密后的右32位写回原地址 return v5; // 返回右半部分(主函数未使用该返回值)这是原地加密:输入的明文内存直接被覆盖为密文,不需要额外的输出缓冲区。
五、算法核心特性总结
- 64 位分组:一次处理 8 字节数据,符合 TEA 家族的基本设计
- 32 轮迭代:通过多轮重复的简单运算,累积实现足够的混淆和扩散
- 位移混淆单元:
左移4 ^ 右移5是 TEA 的标志性设计,用极简单的运算实现比特打散 - 动态密钥调度:通过 sum 的不同位选择密钥字,相比标准 TEA 的固定密钥搭配,变种性更强
- 轮值 sum:每轮变化的累加值,让相同密钥在不同轮产生不同的加密效果
这个算法本质是标准 TEA 的轻量变种,保留了 TEA 代码极简、易于实现的特点,修改了密钥调度和运算顺序,是 CTF 逆向题的高频出题模板。
提取key:
最后写解密脚本:
importstructdeftea_decrypt(v0,v1,key):""" 变种TEA解密函数,严格逆向反汇编中的加密逻辑 v0: 密文左32位(对应加密中的v6) v1: 密文右32位(对应加密中的v5) key: 128位密钥,4个32位无符号整数 """delta=0x61C88647# 加密中的递减常量 1640531527# 加密32轮结束后,sum的最终值:初始0,每轮减delta,共减32次sum_val=(0-32*delta)&0xFFFFFFFFfor_inrange(32):# --------------------------# 第1步:逆加密的最后一步(v5更新)# 加密:v5 += ( ((v6>>5) ^ (v6<<4)) + v6 ) ^ ( key[(sum>>11)&3] + sum )# 解密:v5 减去该增量# --------------------------# 位移混淆部分(左移4位必须截断32位,模拟C语言溢出)mix=((v0>>5)^((v0<<4)&0xFFFFFFFF))&0xFFFFFFFF# 密钥 + 轮累加值k_sum=(key[(sum_val>>11)&3]+sum_val)&0xFFFFFFFF# 总增量inc=(mix+v0)&0xFFFFFFFF^k_sum# 逆加法v1=(v1-inc)&0xFFFFFFFF# --------------------------# 第2步:逆sum的更新# 加密:sum -= delta → 解密:sum += delta# --------------------------sum_val=(sum_val+delta)&0xFFFFFFFF# --------------------------# 第3步:逆加密的第一步(v6更新)# 加密:v6 += ( ((v5>>5) ^ (v5<<4)) + v5 ) ^ ( key[sum&3] + sum )# 解密:v6 减去该增量# --------------------------mix=((v1>>5)^((v1<<4)&0xFFFFFFFF))&0xFFFFFFFFk_sum=(key[sum_val&3]+sum_val)&0xFFFFFFFFinc=(mix+v1)&0xFFFFFFFF^k_sum# 逆加法v0=(v0-inc)&0xFFFFFFFFreturnv0,v1# --------------------------# 1. 提取密钥:数据段字节 → 小端解析为4个32位无符号整数# --------------------------key_bytes=bytes([0x03,0x02,0x01,0x00,0x07,0x06,0x05,0x04,0x0B,0x0A,0x09,0x08,0x0F,0x0E,0x0D,0x0C])key=list(struct.unpack('<4I',key_bytes))# < 表示小端序# --------------------------# 2. 提取密文:有符号int → 32位无符号整数# --------------------------cipher=[-1054939302,-1532163725,-165900264,853769165,768352038,876839116]cipher_uint=[x&0xFFFFFFFFforxincipher]# --------------------------# 3. 分3组解密(每组8字节/2个int)# --------------------------groups=[(cipher_uint[0],cipher_uint[1]),(cipher_uint[2],cipher_uint[3]),(cipher_uint[4],cipher_uint[5])]# --------------------------# 4. 逐组解密,拼接为flag字符串# --------------------------flag_bytes=b''forv0,v1ingroups:plain0,plain1=tea_decrypt(v0,v1,key)# 小端打包回字节序列flag_bytes+=struct.pack('<2I',plain0,plain1)# 输出结果print("解密得到的flag:",flag_bytes.decode('ascii'))运行得到: