收获

  • tea 算法的加密解密

(2023年4月16日)【GDOUCTF 2023】Tea


思路

解压得到一个 teaaaa.exe 程序,试运行:

2023GDOUCTF-tea1.png

给出了提示,让我们输入十六进制数据来获得 flag

用 64 位 IDA 打开,由于没有 main() 函数,查看字符串:

2023GDOUCTF-tea2.png

可以看到上面是程序的输出

注意到下面有一句提示:fault!\nYou can go online and learn the tea algorithm!
定位过去,在 sub_140016230() 函数中:

2023GDOUCTF-tea3.png

观察形式,v6 的值决定了用户的输入是否正确,跟进一下 sub_140011352(v8) 函数,发现 sub_140011352(v8) 会执行 sub_140011B60(a1) 函数:

2023GDOUCTF-tea4.png

后面的一个 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 ) 判断之前的这部分:

2023GDOUCTF-tea5.png

① 根据形式和程序的输出,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 中的值:

2023GDOUCTF-tea6.png

④ 修改后:
v7[0] = 2233
v7[1] = 4455
v7[2] = 6677
v7[3] = 8899

函数 sub_140011145(v8, v9) 会调用 sub_140012030(a1, a2) 实现 v8v9 复制的操作
但是注意到后面并没有用到 v9,于是不管

跟进 sub_1400112B7(v8, v7) 函数,会执行 sub_140011900(a1, a2)

2023GDOUCTF-tea7.png

根据前面的了解,这个应该就是 tea 算法的实现了

接下来重点就是要找出 tea 算法中 δ(Delta) 的值和 128 位的 key,以及密文了
① 前面通过 check() 函数可知,check 是将用户输入与这一段数据进行校验:

2023GDOUCTF-tea8.png

那么这些数据肯定就是 tea 算法加密后的密文了

② 注意实现 tea 算法的函数 sub_1400112B7(v8, v7) 的传参是 v7v8
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)

2023GDOUCTF-tea9.png