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
然后调试可以更快地发现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的时钟周期是纳秒级的, 所以可以轻易判断出是正常运行还是在调试, 在下面会使用这个来验证.
中间的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
如果是在调试时输入的话, 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