攻防世界-RE-CatFly
我们将文件拖入虚拟机中运行看到这样的效果
其中上方的数字是不停变化的,下面的次数也在不断的增长。我们猜测这两者是有关联的。
接下来我们进行反编译程序的分析。最上面的字符输出肯定是与printf函数有关,所以我们检索printf在main函数中的调用
time(&timer);
v13 = 1;
v24 = 0LL;
v23 = 0;
v22 = 0;
v12 = off_FA88;
while ( v13 )
{
if ( dword_E104 )
printf("\x1B[H");
else
printf("\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;
else
byte_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] & 0x7F
else
flag[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;
else
flag[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是哪个参数,不断跟进,逆向加密逻辑,拿到解密数据,不要放弃,保持耐心与冷静
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)