TEA加密, XTEA加密, 去符号

查壳

无壳

运行

D:\TRY\holiday\recurrent\1_havetea\copy\havetea>havetea
Input the secret key:aaaaa
Wrong!

分析

拖入IDA, F5反编译

v3 = &unk_42C1C0;
  do
  {
    *(v3 - 256) = &unk_42AAA0;
    *v3++ = &unk_42AA70;
  }
  while ( (int)v3 < (int)&dword_42C5C0 );
  sub_4026C0();
  sub_402DB0(&dword_42B150, &dword_42AAC0);
  if ( dword_42AAD0 == 16 )
  {
    v4 = (_OWORD *)unknown_libname_15(16);
    v5 = &dword_42AAC0;
    v6 = v4;
    if ( (unsigned int)dword_42AAD4 >= 0x10 )
      v5 = (void **)dword_42AAC0;
    *v4 = *(_OWORD *)v5;
    v7 = __rdtsc();
    dword_42C5C0 = v7;
    sub_4020F0(v6, HIDWORD(v7));
    sub_4020F0((char *)v6 + 8, v8);
    v9 = __rdtsc();
    dword_42BDBC = v9;
    v10 = v6;
    v11 = &unk_42AA60;
    v12 = 12;
    while ( *v10 == *v11 )
    {
      ++v10;
      ++v11;
      v13 = v12 < 4;
      v12 -= 4;
      if ( v13 )
      {
        sub_4026C0();
        sub_402DB0(&dword_42B150, &dword_42AAD8);
        if ( dword_42AAE8 == 32 )
        {
          v14 = __rdtsc();
          dword_42C5C4 = v14;
          v15 = &dword_42AAC0;
          if ( (unsigned int)dword_42AAD4 >= 0x10 )
            v15 = (void **)dword_42AAC0;
          *v6 = *(_OWORD *)v15;
          v16 = (_OWORD *)unknown_libname_15(32);
          v17 = &dword_42AAD8;
          v18 = v16;
          if ( (unsigned int)dword_42AAEC >= 0x10 )
            v17 = (void **)dword_42AAD8;
          *v16 = *(_OWORD *)v17;
          v16[1] = *((_OWORD *)v17 + 1);
          sub_402170(v16, v6);
          sub_402170(v18 + 2, v6);
          sub_402170(v18 + 4, v6);
          sub_402170(v18 + 6, v6);
          v19 = __rdtsc();
          dword_42BDB8 = v19;
          v20 = 28;
          v21 = (unsigned int)(dword_42BDBC + v19 - dword_42C5C4 - dword_42C5C0) >> 5;
          if ( v21 > 0x100 )
            v21 = 256;
          v22 = (_DWORD *)dword_42BDC0[v21];
          while ( *v18 == *v22 )
          {
            ++v18;
            ++v22;
            v13 = v20 < 4;
            v20 -= 4;
            if ( v13 )
            {
              sub_4026C0();
              sub_403050(dword_42AAE8);
              sub_4026C0();
              return 0;
            }
          }
        }
        break;
      }
    }
  }
  sub_4026C0();
  return 0;
}

去符号, 需要先优化一下代码, 分析起来容易一些.

查看汇编, 里面的压入在运行中看到的字符串,可以判断是printf

image.png

然后调试可以更快地发现scanf, 加密函数, malloc函数

经过修改后的代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  _DWORD *v3; // eax
  _OWORD *v4; // eax
  void **_p_input; // ecx
  _OWORD *midput_to_input; // edi
  unsigned __int64 v7; // rax
  int v8; // edx
  unsigned __int64 v9; // rax
  _DWORD *_midput; // ecx
  _DWORD *_cmp_data; // edx
  unsigned int v12; // esi
  bool v13; // cf
  unsigned __int64 v14; // rax
  void **__p_input; // eax
  _OWORD *__pp_input1; // eax
  void **_pp_input1; // ecx
  _DWORD *___pp_input1; // esi
  unsigned __int64 _time3; // rax
  unsigned int v20; // edi
  unsigned int v21; // eax
  _DWORD *v22; // edx

  v3 = &unk_D0C1C0;
  do
  {
    *(v3 - 256) = &dword_D0AAA0;
    *v3++ = &data0;
  }
  while ( (int)v3 < (int)&time0 );
  printf();
  scanf(&str0, &p_input);
  if ( len == 16 )
  {
    v4 = (_OWORD *)malloc(16);                  // 返回了一个地址, 里面看到了malloc, 应该是申请内存的函数
    _p_input = &p_input;
    midput_to_input = v4;
    if ( (unsigned int)num_0x1f >= 0x10 )       // 初始值为0x1f(31), 这个值num_0x1f一直没变过, 所以这个if是无用代码
      _p_input = (void **)p_input;
    *v4 = *(_OWORD *)_p_input;                  // 这里是一个间接指针, v4申请的到的内存存储的并不是数据本身, 而是数据的地址, 但是ida会自动跳到输入内容处, 所以会让人有点迷惑
    v7 = __rdtsc();                             // 该指令返回CPU自启动以来的时钟周期数
    time0 = v7;                                 // time0 = 93E86642h
    tea_encrypt(midput_to_input, HIDWORD(v7));
    tea_encrypt((char *)midput_to_input + 8, v8);
    v9 = __rdtsc();
    time1 = v9;                                 // time1 = 421FC4E2h
    _midput = midput_to_input;
    _cmp_data = &cmp_data;
    v12 = 12;
    while ( *_midput == *_cmp_data )            // 这里是密文比较, 注意比较的大小是4个字节进行比较
    {
      ++_midput;                                // 下一个元素
      ++_cmp_data;                              // 下一个元素
      v13 = v12 < 4;                            // 只有4 * 4个字节的内容全部一致才会进入到if中
      v12 -= 4;
      if ( v13 )
      {
        printf();                               // 下一轮的打印提示
        scanf(&str0, &pp_input1);
        if ( len1 == 32 )
        {
          v14 = __rdtsc();
          time2 = v14;                          // time2 = 0F3E04EA2h
          __p_input = &p_input;
          if ( (unsigned int)num_0x1f >= 0x10 )
            __p_input = (void **)p_input;
          *midput_to_input = *(_OWORD *)__p_input;// 注意指针类型是O, 长度为16字节
          __pp_input1 = (_OWORD *)malloc(32);   // 申请内存空间, 单位是8字节
          _pp_input1 = &pp_input1;
          ___pp_input1 = __pp_input1;           // 但是这里的长度是4字节
          if ( (unsigned int)num_0x2f >= 0x10 )
            _pp_input1 = (void **)pp_input1;
          *__pp_input1 = *(_OWORD *)_pp_input1;
          __pp_input1[1] = *((_OWORD *)_pp_input1 + 1);
          xtea_encrypt(__pp_input1, midput_to_input);// 这里加密是以8字节为单位的
          xtea_encrypt(___pp_input1 + 2, midput_to_input);
          xtea_encrypt(___pp_input1 + 4, midput_to_input);
          xtea_encrypt(___pp_input1 + 6, midput_to_input);
          _time3 = __rdtsc();
          time3 = _time3;
          v20 = 28;
          v21 = (unsigned int)(time1 + _time3 - time2 - time0) >> 5;// 相当于t1 + t2
          if ( v21 > 0x100 )
            v21 = 256;
          v22 = (_DWORD *)dword_D0BDC0[v21];
          while ( *___pp_input1 == *v22 )
          {
            ++___pp_input1;
            ++v22;
            v13 = v20 < 4;
            v20 -= 4;
            if ( v13 )
            {
              printf();
              sub_CE3050(len1);
              printf();
              return 0;
            }
          }
        }
        break;
      }
    }
  }
  printf();
  return 0;
}

第一部分: TEA加密

首先检查了字符串长度, 然后使用变量检查

需要注意的是两个time的记录, 因为CPU的时钟周期是纳秒级的, 所以可以轻易判断出是正常运行还是在调试, 在下面会使用这个来验证.

image.png

中间的tea_encrypt()函数就是我们的加密函数, 因为输入长度为16, 所以调用了两次

跟进该函数

unsigned int __thiscall sub_CE20F0(unsigned int *this)
{
  int sum; // edi
  unsigned int x; // esi
  unsigned int y; // eax
  int i; // ebx

  sum = 0;
  x = *this;                                    // 注意这里的类型识别是int的指针, 所以数据是4个字节为整体传递的, 为小端排序
  y = this[1];
  i = 32;
  do                                            // 单纯的tea加密, key就是delta的拆分
  {
    sum -= 0x61C88647;                          // 这里减去了以后, 变成了9E3779B9, 其实就是sum += 9E3779B9, 只不过用的是负数的形式
    x += (sum + y) ^ (key0 + 16 * y) ^ (key1 + (y >> 5));
    y += (sum + x) ^ (key2 + 16 * x) ^ (key3 + (x >> 5));
    --i;
  }
  while ( i );
  *this = x;
  this[1] = y;
  return y;
}

这里有一个很常见的藏delta的方法:

就是sum -= 0x61C88647;, 这个其实相当于sum += 0x9E3779B9, 二者是相反数, 6的最高位为1, 是负数.

接着就是验证, 用了一个变量v12来记录正确的元素个数.

  while ( *_midput == *_cmp_data )            // 这里是密文比较, 注意比较的大小是4个字节进行比较
    {
      ++_midput;                                // 下一个元素
      ++_cmp_data;                              // 下一个元素
      v13 = v12 < 4;                            // 只有4 * 4个字节的内容全部一致才会进入到if中
      v12 -= 4;
      if ( v13 )
      .......

第二部分: XTEA加密

这一部分跟第一部分很像, 也记录了加密所用时间, 也检查了长度, 但是加密的函数是xtea_encrypt(), 密钥是上一轮输入的明文

跟进该函数

unsigned int __fastcall sub_CE2170(unsigned int *a1, int key)
{
  unsigned int sum; // eax
  unsigned int x; // esi
  unsigned int y; // edi
  int mid; // ecx
  int v7; // [esp+14h] [ebp-4h]

  v7 = 32;
  sum = 0;
  x = *a1;
  y = a1[1];
  do                                            // 这个是XTEA算法, 应该是没有魔改过的
  {
    mid = sum + *(_DWORD *)(key + 4 * (sum & 3));
    sum -= 0x61C88647;                          // 这里的目的是为了让sum的变化看起来像是在xy之前, 实际上是在xy变化的中间
    x += mid ^ (y + ((16 * y) ^ (y >> 5)));
    y += (sum + *(_DWORD *)(key + 4 * ((sum >> 11) & 3))) ^ (x + ((16 * x) ^ (x >> 5)));
    --v7;
  }
  while ( v7 );
  a1[1] = y;
  *a1 = x;
  return sum;
}

也是没变过的XTEA加密, 中间用了一个mid来替换了加密过程的一小部分, 但是如果直接根据这里的代码去写正向的代码:

mid = sum + *(_DWORD *)(key + 4 * (sum & 3)); sum -= 0x61C88647;

这里还是有点迷惑人的, 这样看起来sum是在x算之前变化的, 而实际上,mid中用到了sum, x中只有mid用到了sum, 所以其实sum是在x计算之后才变化的. 当然, 这个坑其实很浅, 正向跟题目不对的话可以在网上参考正确的代码.

在加密后, 又验证了运行时间time

image.png

如果是在调试时输入的话, v21(就是时间差)就会大于0x100, 触发if条件后v21赋值为256, 而数组dword_D0BDC0前所有的元素都指向我们加密后的密文, 但是256号元素指向的是错误的数据. 导致最终的验证出错

解密

TEA解密(C语言)

#include <stdio.h>

void Encrypt(long* EntryData, long* Key) {
	unsigned long x = EntryData[0];
	unsigned long y = EntryData[1];
	int i;

	unsigned long sum = 0;
	unsigned long delta = 0x9E3779B9;

	for (i = 0; i < 32; i++) {
		sum += delta;
		x += (y + sum) ^ ((y << 4) + Key[0]) ^ (Key[1] + (y >> 5));
		y += (x + sum) ^ ((x << 4) + Key[2]) ^ (Key[3] + (x >> 5));
	}

	EntryData[0] = x;
	EntryData[1] = y;
}

void Decrypt(long* DetryData, long* Key) {
	unsigned long x = DetryData[0];
	unsigned long y = DetryData[1];
	int i;

	unsigned sum = 0x9E3779B9 << 5;
	unsigned long delta = 0x9E3779B9;

	for (i = 0; i < 32; i++) {
		y -= (x + sum) ^ ((x << 4) + Key[2]) ^ (Key[3] + (x >> 5));
		x -= (y + sum) ^ ((y << 4) + Key[0]) ^ (Key[1] + (y >> 5));
		sum -= delta;
	}

	DetryData[0] = x;
	DetryData[1] = y;
}

int main(void) {
	long Data[5] = { 0x0E66F0E9E, 0x42A17CD6, 0x5BDDE878, 0x0D236F4AD, 0};
	long key[4] = { 0x9E, 0x37, 0x79, 0xB9 };



	Decrypt(Data, key);
	Decrypt(Data + 2, key);

	printf("解密后数据 = %s\n", (char* )Data);

	return 0;
}

XTEA解密(C语言)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

void Encrypt(long* EncryData, long* Key) {
	unsigned long x = EncryData[0];
	unsigned long y = EncryData[1];
	int i;

	unsigned long sum = 0;
	unsigned long delta = 0x9E3779B9;

	for (i = 0; i < 32; i++) {
		x += (sum + Key[sum & 3]) ^ (y + ((y << 4) ^ (y >> 5)));
		sum += delta;
		y += (sum + Key[(sum >> 11) & 3]) ^ (x + ((x << 4) ^ (x >> 5)));
	}

	EncryData[0] = x;
	EncryData[1] = y;
}

void Decrypt(long* EncryData, long* Key) {
	unsigned long x = EncryData[0];
	unsigned long y = EncryData[1];
	int i;

	unsigned long sum = 0x9E3779B9 << 5;
	unsigned long delta = 0x9E3779B9;

	for (i = 0; i < 32; i++) {
		y -= (sum + Key[(sum >> 11) & 3]) ^ (x + ((x << 4) ^ (x >> 5)));
		sum -= delta;
		x -= (sum + Key[sum & 3]) ^ (y + ((y << 4) ^ (y >> 5)));
	}

	EncryData[0] = x;
	EncryData[1] = y;
}

int main(void) {
	long data[9] = { 0x7D758E0A, 0x0EDAFF6AD, 0x9DDE7E86, 0x0E3D36CD5 , 0x0ABD3C82E , 0x6B7B2CFE , 0x2C6608C2 , 0x686A1DA , 0};
	long key[4] = { 0x61656C70, 0x645F6573, 0x6B6E6972, 0x6165745F };
	int i;

	for (i = 0; i < 4; i++) {
		Decrypt(data + i * 2, key);
	}

	printf("%s\n", (char*)data);

	return 0;
}

flag

获得密钥: please_drink_tea

获得密文: c616454f52a6334273b5f455a10ef818