前言:
如您所见的是,图片似乎带有水印,但这并不意味着它是转载的,因为某站上的这篇帖子也是我写的......好不容易终于注册上这个大佬如云的地方的账号了,于是整理一下也发在这里了。另外一边已经完全弃用了。
自己的水平还不是很高,欢迎大佬们交流。如果您发现我的叙述存在任何问题,也请务必纠正!感谢!
正文:
无壳,IDA打开可以直接进入main函数:
第12行调用VirtualProtect函数更改了offset encrypt处的访问保护权限
BOOL VirtualProtect( LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
参见:Memory Protection Constants (WinNT.h) - Win32 apps | Microsoft Docs
该处数据为0x4:PAGE_READWRITE
Enables read-only or read/write access to the committed region of pages. If Data Execution Prevention is enabled, attempting to execute code in the committed region results in an access violation.
简单来说就是让这块数据能够被读写了(通常text段中只能拥有读/写中的一种)
继续往下读,发现输入值应该符合 24字节 的长度,然后遇到wrong 和 omg这两个函数
[C] 纯文本查看 复制代码 char *__cdecl wrong(char *a1)
{
char *result; // eax
int i; // [esp+Ch] [ebp-4h]
for ( i = 0; i <= 23; ++i )
{
result = &a1[i];
if ( (i & 1) != 0 )
a1[i] -= i;
else
a1[i] ^= i;
}
return result;
}
[C] 纯文本查看 复制代码 int __cdecl omg(char *a1)
{
int result; // eax
int v2[24]; // [esp+18h] [ebp-80h] BYREF
int i; // [esp+78h] [ebp-20h]
int v4; // [esp+7Ch] [ebp-1Ch]
v4 = 1;
qmemcpy(v2, &unk_4030C0, sizeof(v2));
for ( i = 0; i <= 23; ++i )
{
if ( a1[i] != v2[i] )
v4 = 0;
}
if ( v4 == 1 )
result = puts("hahahaha_do_you_find_me?");
else
result = puts("wrong ~~ But seems a little program");
return result;
}
wrong对输入值进行了一些加减或异或处理,然后将结果在omg中同unk_4030C0处数据进行对比;wrong的逆算法容易实现,照抄就行了
(现在才知道能够通过导出窗口快捷的提取出数据,一直以来的手抄实在是太笨了)
[] 纯文本查看 复制代码 unsigned int k[24] = { 0x66,0x6b,0x63,0x64,0x7f,0x61,0x67,0x64,0x3b,0x56,0x6b,0x61,0x7b,0x26,0x3b,0x50,0x63,0x5f,0x4d,0x5a,0x71,0xc,0x37,0x66 };
for (int i = 0;i < 24; i++)
{
if ((i & 1) != 0)
{
k[i] += i;
}
else
{
k[i] ^= i;
}
cout << (char)k[i];
}
cout << endl;
得到结果flag{fak3_alw35_sp_me!!},提交发现错误;由于往下还有关键的encrypt段没分析,所以不用太怀疑flag是否算错,可以大胆的将它当作一个假的flag
再往下读for循环,发现它对offset encrypt进行了异或,判断其为代码段解密,可以用动调转到这个地方
IDA没能及时更新,需要我们手动修正为函数
选中00401500~0040152F,将其标为代码(Force)
然后在00401502处创建函数,即可得到合适的结果
[C++] 纯文本查看 复制代码 // positive sp value has been detected, the output may be wrong!
void __usercall __noreturn sub_401502(int a1@<ebp>)
{
unsigned __int32 v1; // eax
v1 = __indword(0x57u);
*(_DWORD *)(a1 - 32) = 1;
qmemcpy((void *)(a1 - 108), &unk_403040, 0x4Cu);
for ( *(_DWORD *)(a1 - 28) = 0; *(int *)(a1 - 28) <= 18; ++*(_DWORD *)(a1 - 28) )
{
if ( (char)(*(_BYTE *)(*(_DWORD *)(a1 - 28) + *(_DWORD *)(a1 + 8)) ^ Buffer[*(_DWORD *)(a1 - 28)]) != *(_DWORD *)(a1 + 4 * *(_DWORD *)(a1 - 28) - 108) )
{
puts("wrong ~");
*(_DWORD *)(a1 - 32) = 0;
exit(0);
}
}
if ( *(_DWORD *)(a1 - 32) == 1 )
puts("come here");
}
IDA分析得到的代码并不是那么易读,显然,它将一些索引给翻译错了,但并非无法理解的程度
首先,提取unk_403040处的数据放入(a1-108)处,以及循环中用到的Buffer
[C++] 纯文本查看 复制代码 char Buffer[] = "hahahaha_do_you_find_me?";
unsigned int unk_403040[19] = {0x0E,0x0D ,0x09 ,0x06 ,0x13 ,0x05 ,0x58 ,0x56 ,0x3E ,0x06 ,0x0C ,0x3C ,0x1F ,0x57 ,0x14 ,0x6B ,0x57 ,0x59 ,0x0D };
*(a1-28)实际上是一个索引,指示了这个循环会执行19次;而(*(a1 - 28) + *(a1 + 8))相当于输入值指针加上一个偏移,其内容就是我们的输入值
这个输入值和Buffer异或后的结果应该等于(a1 - 108)的内容,也就是unk_403040处的数据,同样也容易写出解密代码
[C++] 纯文本查看 复制代码 char key1[] = "hahahaha_do_you_find_me?";
unsigned int f[19] = {0x0E,0x0D ,0x09 ,0x06 ,0x13 ,0x05 ,0x58 ,0x56 ,0x3E ,0x06 ,0x0C ,0x3C ,0x1F ,0x57 ,0x14 ,0x6B ,0x57 ,0x59 ,0x0D };
for (int i = 0; i < 19; i++)
{
f[i] ^= key1[i];
cout << (char)f[i];
}
cout << endl;
得到flag{d07abccf8a410c
我们知道,flag应有24字节,但for循环只有19次,也就是缺少了5个字符;由于encrypt函数已经读完了,所以我们需要的结果应该在最后一个函数中,也就是finally函数
将40159A~40159D处的数据全都转为代码,并将函数改为Undefine
重新在40159A处创建函数,得到新函数finally:
[C++] 纯文本查看 复制代码 int __cdecl finally(char *a1)
{
unsigned int v1; // eax
int result; // eax
char v3[9]; // [esp+13h] [ebp-15h] BYREF
int v4; // [esp+1Ch] [ebp-Ch]
strcpy(v3, "%tp&:");
v1 = time(0);
srand(v1);
v4 = rand() % 100;
if ( (v3[*&v3[5]] != a1[*&v3[5]]) == v4 )
result = puts("Really??? Did you find it?OMG!!!");
else
result = puts("I hide the last part, you will not succeed!!!");
return result;
}
time(0)用以获取当前时间,第10行将其作为种子,第11行获取随机数;大概率我们是难以获取到出题人得到的种子的,因此,这个随机数若是必要的,应该只能通过预测得出
以及下面的if判断条件过于难以理解,不妨试着用OD去动调一下吧(个人觉得OD的动调会更好用一些,也好在这个函数没有被加密,OD还是能分析出来的,否则只能用IDA动调了,虽然没什么差别......)
即便用OD动调也仍然不是很容易能够读懂其意义
关键的比较在401617处,如果相等的话,就说明flag输对了
大致就是取flag的第几位同“%tp&:"几位,相等即可;并且这正好是5个字节,很可能就是剩下的flag
但汇编代码中似乎也同样没有相应的加密过程,只能靠猜测它没有被复杂的加密
通过前半段的flag猜测最后一个字符应该为‘}’,将其与“%tp&:”的最后一个异或后得到 71,并由此得到最后结果
[C++] 纯文本查看 复制代码 char key2[] = "%tp&:";
int v5 = '}' ^ key2[4];
for (int i = 0; i < 5; i++)
{
cout << (char)(key2[i] ^ v5);
}
//flag{d07abccf8a410cb37a}
我也试着将这个提交成功的flag输入进去,但它仍然不会输出成功的标识,可能是出题人的一点“恶意”吧......最后要靠猜测来得到结果,说实在的,有点难以释然,总觉得是不是自己看漏了什么重要内容...... |