【GDOUCTF 2023】Tea
收获
tea
算法的加密解密
思路
解压得到一个 teaaaa.exe
程序,试运行:
给出了提示,让我们输入十六进制数据来获得 flag
用 64 位 IDA 打开,由于没有 main()
函数,查看字符串:
可以看到上面是程序的输出
注意到下面有一句提示:fault!\nYou can go online and learn the tea algorithm!
定位过去,在 sub_140016230()
函数中:
观察形式,v6
的值决定了用户的输入是否正确,跟进一下 sub_140011352(v8)
函数,发现 sub_140011352(v8)
会执行 sub_140011B60(a1)
函数:
后面的一个 for
循环用来校验 *(a1 + 4 * j)
的值是否与 v8[j]
中的值相等
只有当每一个值都相同时,v7
才会一直保持非 0,于是返回一个非 0 值给 v6
,就输入正确
(不过这里的逻辑貌似有点 bug,其实只需要 a1 的最后一个值与 v8 的最后一个值相等即可)
所以这其实是一个 check()
函数
注意到 check
失败的时候会提示我们去了解一下 tea
算法:
TEA 算法最初是由剑桥计算机实验室的 David Wheeler 和 Roger Needham 在 1994 年设计的。 TEA 算法使用 64 位的明文分组和 128 位的密钥,它使用 Feistel 分组加密框架,需要进行 64 轮迭代,尽管作者认为 32 轮已经足够了
该算法使用了一个神秘常数
δ(Delta)
作为倍数,它来源于黄金比率,以保证每一轮加密都不相同。但δ(Delta)
的精确值似乎并不重要,这里 TEA 把它定义为δ =「(√5 - 1)231」
(也就是程序中的0x9e3779b9
)
网上找的 tea
算法加解密源码如下:
#include<stdio.h>
#define DELTA 0x9e3779b9
void tea_encrypt(unsigned int* v, unsigned int* key) {
unsigned int l = v[0], r = v[1], sum = 0;
for (size_t i = 0; i < 32; i++) { // 进行32次迭代加密,Tea算法作者的建议迭代次数
// 利用多次双位移和异或将明文与密钥扩散混乱,并将两个明文互相加密
l += (((r << 4) ^ (r >> 5)) + r) ^ (sum + key[sum & 3]);
sum += DELTA; // 累加Delta的值
r += (((l << 4) ^ (l >> 5)) + l) ^ (sum + key[(sum >> 11) & 3]);
}
v[0] = l;
v[1] = r;
}
void tea_decrypt(unsigned int* v, unsigned int* key) {
unsigned int l = v[0], r = v[1], sum = 0;
sum = DELTA * 32; // 32次迭代累加后delta的值
for (size_t i = 0; i < 32; i++) {
r -= (((l << 4) ^ (l >> 5)) + l) ^ (sum + key[(sum >> 11) & 3]);
sum -= DELTA;
l -= (((r << 4) ^ (r >> 5)) + r) ^ (sum + key[sum & 3]);
}
v[0] = l;
v[1] = r;
}
int main(int argc, char const *argv[])
{
unsigned int key[4]={0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f};
unsigned int v1[2] = {0xaabbccdd, 0x01234567};
tea_encrypt(v1, key);
printf("tea_encrypt:%x %x\n", v1[0], v1[1]);
tea_decrypt(v1, key);
printf("tea_decrypt:%x %x\n", v1[0], v1[1]);
return 0;
}
tea
算法最关键的是要找到δ(Delta)
的值和 128 位的key
在逆向程序的时候,可以利用 IDA 的插件
Findcrypt
识别tea
算法(有时可能不成功)
回到 sub_140016230()
函数中,关键在 if ( v6 )
判断之前的这部分:
① 根据形式和程序的输出,sub_1400111FE("%x", &v8[j])
应该是一个 scanf()
函数
让用户输入十六进制的数据,共需要输入 10 个
② 初始时:v7[0] = 1234
v7[1] = 5678
v7[2] = 9012
v7[3] = 3456
③ 后面的 sub_140011339(v7)
函数会调用 sub_1400117D0(a1)
函数,改变了 v7
中的值:
④ 修改后:v7[0] = 2233
v7[1] = 4455
v7[2] = 6677
v7[3] = 8899
函数 sub_140011145(v8, v9)
会调用 sub_140012030(a1, a2)
实现 v8
往 v9
复制的操作
但是注意到后面并没有用到 v9
,于是不管
跟进 sub_1400112B7(v8, v7)
函数,会执行 sub_140011900(a1, a2)
:
根据前面的了解,这个应该就是 tea
算法的实现了
接下来重点就是要找出 tea
算法中 δ(Delta)
的值和 128 位的 key
,以及密文了
① 前面通过 check()
函数可知,check 是将用户输入与这一段数据进行校验:
那么这些数据肯定就是 tea
算法加密后的密文了
② 注意实现 tea
算法的函数 sub_1400112B7(v8, v7)
的传参是 v7
和 v8
而 v8
是用户的输入,也就是明文,那剩下的一个 v7
必然就是加密的 key
了:v7[0] = 2233
v7[1] = 4455
v7[2] = 6677
v7[3] = 8899
每个 v7[]
有 32 位,四个正好 128 位
③ 最后,注意到 tea
算法的加密过程会有一个操作是: sum += DELTA
累加 Delta
的值
结合 IDA 给出的伪代码,Delta = 256256256
剩下的就是根据逻辑写出解密的脚本了
形式好像跟网上介绍的 tea
算法不一样,可能有魔改,直接用原版貌似跑不出来
所以可以直接基于 IDA 的伪代码进行改写
脚本
#include <iostream>
using namespace std;
#define DELTA 256256256
void sub_1400117D0(unsigned int *a1){
int v6 = 2233;
int v7 = 4455;
int v8 = 6677;
int v9 = 8899;
*a1 = 2233;
a1[1] = v7;
a1[2] = v8;
a1[3] = v9;
}
void tea_decrypt(unsigned int *a1, unsigned int *a2) {
int v5, v6, v3;
for (int i = 8; i >= 0; --i)
{
v5 = 0;
v6 = 256256256 * (i + 32);
v3 = i + 1;
do {
++v5;
a1[v3] -= (v6 + a2[(v6 >> 11) & 3]) ^ (a1[i]
+ ((a1[i] >> 5) ^ (16 * a1[i])));
a1[i] -= v6 ^ (a1[v3] + ((a1[v3] >> 5) ^ (16 * a1[v3]))) ^ (v6 + a2[v6 & 3]);
v6 -= 256256256;
}
while ( v5 <= 0x20 );
}
}
int main() {
int v5 = 32;
int v6 = 0;
int v9[50];
v9[15] = 0;
v9[23] = 0;
unsigned int v7[4]; // v7存放密钥key
v7[0] = 1234;
v7[1] = 5678;
v7[2] = 9012;
v7[3] = 3456;
unsigned int v8[10]; // v8存放密文
v8[0] = 444599258;
v8[1] = -140107365;
v8[2] = 1226314200;
v8[3] = -234802392;
v8[4] = 359413339;
v8[5] = 1013885656;
v8[6] = -2066432216;
v8[7] = -249921817;
v8[8] = 856928850;
v8[9] = -576724359;
sub_1400117D0(v7); // 先修改v7的值
tea_decrypt(v8, v7); // tea的解密算法
for (int k = 0; k < 10; ++k ) { // 输出明文
for (int m = 3; m >= 0; --m)
printf("%c", (v8[k] >> (8 * m)));
}
return 0;
}
结果
NSSCTF{hzCtf_94_re666fingcry5641qq}
(最终要求将 HZCTF 改为 NSSCTF)