我们将文件拖入虚拟机中运行看到这样的效果
其中上方的数字是不停变化的,下面的次数也在不断的增长。我们猜测这两者是有关联的。
接下来我们进行反编译程序的分析。最上面的字符输出肯定是与printf函数有关,所以我们检索printf在main函数中的调用
time(&timer);v13 = 1;v24 = 0LL;v23 = 0;v22 = 0;v12 = off_FA88;while ( v13 ){if ( dword_E104 )printf("\x1B[H");elseprintf("\x1B[u");for ( k = dword_E1EC; k < dword_E1F0; ++k ){for ( m = dword_E1F4; m < dword_E1F8; ++m ){if ( k <= 23 || k > 42 || m >= 0 ){if ( m >= 0 && (unsigned int)k <= 0x3F && m <= 63 ){v19 = off_FA20[v24][k][m];off_FA88 = sub_6314((unsigned int)v24, k, m, (__int64)v12);}else{v19 = 44;}}else{v18 = (2 - m) % 16 / 8;if ( ((v24 >> 1) & 1) != 0 )v18 = 1 - v18;s[128] = (__int64)",,>>&&&+++###==;;;,,";v19 = asc_BFE3[v18 - 23 + k];if ( !v19 )v19 = 44;}if ( v25 ){printf("%s", *((const char **)&unk_FCC0 + v19));}else if ( v19 == v22 || !*((_QWORD *)&unk_FCC0 + v19) ){printf("%s", off_FA88);}else{v22 = v19;printf("%s%s", *((const char **)&unk_FCC0 + v19), off_FA88);}}sub_65E2(1);}if ( dword_E100 ){time(&time1);v11 = difftime(time1, timer);v10 = sub_63FF((unsigned int)(int)v11);for ( n = (dword_E1FC - 29 - v10) / 2; n > 0; --n )putchar(32);dword_E1E8 += printf("\x1B[1;37mYou have nyaned for %d times!\x1B[J\x1B[0m", (unsigned int)++dword_108E0);}v22 = 0;++v23;if ( dword_104C4 && v23 == dword_104C4 )sub_6471();if ( !off_FA20[++v24] )v24 = 0LL;usleep(1000 * v27);}return 0LL;
}
我们注意到这一部分,printf("%s", off_FA88);
这里我们进一步跟进 off_FA88
看到了对它的赋值操作 off_FA88 = sub_6314((unsigned int)v24, k, m, (__int64)v12);
此时我们查看sub_6314
的源代码
char *__fastcall sub_6314(__int64 a1, int a2, int a3, __int64 a4)
{if ( a2 != 18 )return (char *)a4;if ( a3 <= 4 || a3 > 54 )return (char *)a4;byte_104C9 = 32;dword_E120[a3 - 5] ^= sub_62B5();if ( (unsigned __int8)sub_62E3(dword_E120[a3 - 5]) )byte_104C8 = dword_E120[a3 - 5] & 0x7F;elsebyte_104C8 = 32;return &byte_104C8;
}
这里的a4实际上就是off_FA88的值,我们通过分析可以把这个函数理解为以下的函数
for (int i = 0; i < 50; ++i) {dword_E120[i] ^= sub_62B5(); // 对数组的每个元素进行异或操作if ((unsigned __int8)sub_62E3(dword_E120[i])) // 调用 sub_62E3 进行判断flag[i] = dword_E120[i] & 0x7F; // 如果条件成立,设置 flag[i] 为 dword_E120[i] & 0x7Felseflag[i] = 32; // 否则,设置 flag[i] 为 32(空格字符)
}
也就是说接下来我们需要去进一步跟进 sub_62B5
和sub_62E3
的内容
__int64 sub_62B5()
{dword_E1E8 = 1103515245 * dword_E1E8 + 12345;return (dword_E1E8 >> 10) & 0x7FFF;
}_BOOL8 __fastcall sub_62E3(char a1)
{return (a1 & 0x7Fu) <= 0x7E && (a1 & 0x7Fu) > 0x20;
}
这些都是做判断和计算用的函数,不用逆向,只需要直接调用即可
现在我们对flag的逆向加密逻辑完成了,我们需要去注意使用的参数,比如dword_E1E8
和dword_E120
,其中dword_E120
可以直接提取出来,但是dword_E1E8
则需要计算得到,而且他在程序中还有自增操作
dword_E1E8 += printf("\x1B[1;37mYou have nyaned for %d times!\x1B[J\x1B[0m", (unsigned int)++dword_108E0);
每次他都会加上一个printf的返回值,由于调用printf返回是一件很麻烦的事情,所以我们手动计算它的返回值
综上我们可以写出以下解密脚本
#include<stdio.h>
#include<string.h>
#include<stdbool.h>int flag[50];
//可以在IDA中得到
int dword_E1E8 = 0x1106;
int dword_E120[50] = { 0x27fb, 0x27a4, 0x464e, 0x0e36, 0x7b70, 0x5e7a, 0x1a4a, 0x45c1, 0x2bdf, 0x23bd, 0x3a15, 0x5b83, 0x1e15, 0x5367, 0x50b8, 0x20ca, 0x41f5, 0x57d1, 0x7750, 0x2adf, 0x11f8, 0x09bb, 0x5724, 0x7374, 0x3ce6, 0x646e, 0x010c, 0x6e10, 0x64f4, 0x3263, 0x3137, 0x00b8, 0x229c, 0x7bcd, 0x73bd, 0x480c, 0x14db, 0x68b9, 0x5c8a, 0x1b61, 0x6c59, 0x5707, 0x09e6, 0x1fb9, 0x2ad3, 0x76d4, 0x3113, 0x7c7e, 0x11e0, 0x6c70 };bool __fastcall sub_62E3(char a1)
{return (a1 & 0x7Fu) <= 0x7E && (a1 & 0x7Fu) > 0x20;
}
__int64 sub_62B5()
{dword_E1E8 = 1103515245 * dword_E1E8 + 12345;return (dword_E1E8 >> 10) & 0x7FFF;
}
int charnum(unsigned int a){int c = 0;while(a != 0){a/=10;c+=1;}return c;
}int main(){unsigned int dword_108E0 = 0;while (1) {char flag[50];for (int i = 0; (int)i < 50; ++i) {dword_E120[i] ^= sub_62B5();if ((unsigned __int8)sub_62E3(dword_E120[i]))flag[i] = dword_E120[i] & 0x7F;elseflag[i] = 32;}if (!strncmp(flag, "CatCTF", 6)) {printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");puts(flag);printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n %d" ,dword_108E0);break;}dword_108E0 += 1;dword_E1E8 += 41;dword_E1E8 += charnum(dword_108E0);if(dword_108E0%1000000==0)printf("%d\n",dword_108E0);}return 0;
}
太抽象了,这真的是难度一吗?
总结:
明确flag是哪个参数,不断跟进,逆向加密逻辑,拿到解密数据,不要放弃,保持耐心与冷静