首页
社区
课程
招聘
[原创] 第四题 英雄救美题解
发表于: 2021-5-15 01:44 6831

[原创] 第四题 英雄救美题解

2021-5-15 01:44
6831

使用ida打开CrackMe.exe, 跳转到主函数F5, 先做初步分析:

image-1

可以看到输入首先经过check_1, 再经过check_2, 然后进行 md5 -> sub_401ed0 -> 申请内存 -> 从4181A0拷贝再做某些处理 > 执行该内存.

后面的部分先放一下, 首先分析check_1:

image-2

通过从地址0x416260乱序拷贝数据来初始化somekey, 长度是5*0x10+1共81字节. 这里选择在动态调试 somekey赋值完成后读取somekey的值.

使用frida代码:

输出结果是

image-20210515004447099

共81字节.

然后:

image-4

主体逻辑是在循环中从somekey中找到输入字符的位置, 模9加1后填入input2中. 同时在输入是数字字符时会判断 该字符等于9减去临时已输入的字符数量, 符合时会将临时已输入的字符数量置0, 然后将 从somekey中找输入字符时的起始位置 加9.

然后看check_2:

在循环中将input2填入地址0x4187C4到0x418908之间, 如果该地址内的值非0就会跳过. 注意到是9个为一轮且数据大小为9*9*4.使用frida按照9*9打印该地址的数据:

在onEnter时的输出为:

九乘九, 1到9填空, 后面还有一坨验证逻辑, DNA告诉我它是数独. 于是找了个在线解数独的网站:

image-5

数独数据为546719238921834657837625419718463925453291786692587143284956371365172894179348562, 要填入的数据是5619238183457621978469254539786692871328563617281793452.

然而填入数据来源是输入字符在81字节长的somekey中位置的模9加1, 此时每个字符仍有9种可能, 分析到这时的我还没搞懂check_1中输入是数字字符时的判断逻辑是要我输入什么, 先继续分析接下来的流程.

回到主函数:

image-1

注意sub_401ed0的定义, ida认为它是int __cdecl sub_401ED0(int a1, unsigned __int8 *a2)其中a1和a2都来自栈, 但是在函数中其实使用的是ecx和栈上一个值作为参数, 需要更改该函数的定义为int __fastcall sub_401ED0(int a1, unsigned __int8 *a2, char *a3, char *a4), a1, a2来自ecx和edx, 其它来自栈, 函数没有用到a2和a3.

改完后再看sub_401ed0:

image-6

查看byte_415960处的数据, 搜索可发现该数据是AES算法的s_box, 可以判断该函数功能是初始化AES密钥. 结合上面的对输入进行md5hash的操作来看,很可能是将输入的md5值作为密钥, 解密接下来的数据. 使用frida调试配合本地nodejs脚本测试解密可以验证算法正确:

image-7

image-8

可以看出解密后的数据是匹配的.

在没有找到更多对输入的限制的情况下, 似乎只能爆破? 然而输入的可能性仍比9^55还要多, 爆破是不可能爆破的.

这时想到对输入包含0到9字符时的处理逻辑, 看来要猜一猜出题人想要我们输入什么.

当输入了i <= 9个字符后输入数字9-i, 它会将somekey前面截短9个字符. 只输入数独的话用55个字符, 如果再输入9个字符, 长度就会达到64, 刚好满足输入长度限制64. 可以猜测是不是想要选手分9行输入数独, 每行输入完成后输入该行已有的的数字数量, 同时somekey进入下一节.

于是构造输入的逻辑就要变成:

验证发现该输入就是正确的输入, 也是最终的flag.

 
 
 
 
 
 
 
Interceptor.attach(PE.base.add(0x12A8), function() {
    console.log((this.context as Ia32CpuContext).ebp.sub(0x58).readCString(81));
});
Interceptor.attach(PE.base.add(0x12A8), function() {
    console.log((this.context as Ia32CpuContext).ebp.sub(0x58).readCString(81));
});
 
 
 
 
 
 
int __fastcall check_2(int *input2, int inplen)
{
  ipos = 0;
  data_p = (int *)&unk_4187C4;
  do
  {
    if ( !*(data_p - 1) )
    {
      v4 = input2[ipos++];
      *(data_p - 1) = v4;
    }
    if ( !*data_p )
    {
      v5 = input2[ipos++];
      *data_p = v5;
    }
    if ( !data_p[1] )
    {
      v6 = input2[ipos++];
      data_p[1] = v6;
    }
    if ( !data_p[2] )
    {
      v7 = input2[ipos++];
      data_p[2] = v7;
    }
    if ( !data_p[3] )
    {
      v8 = input2[ipos++];
      data_p[3] = v8;
    }
    if ( !data_p[4] )
    {
      v9 = input2[ipos++];
      data_p[4] = v9;
    }
    if ( !data_p[5] )
    {
      v10 = input2[ipos++];
      data_p[5] = v10;
    }
    if ( !data_p[6] )
    {
      v11 = input2[ipos++];
      data_p[6] = v11;
    }
    if ( !data_p[7] )
    {
      v12 = input2[ipos++];
      data_p[7] = v12;
    }
    if ( ipos >= inplen )
      break;
    data_p += 9;
  }
  while ( (int)data_p < (int)&unk_418908 );
int __fastcall check_2(int *input2, int inplen)
{
  ipos = 0;
  data_p = (int *)&unk_4187C4;
  do
  {
    if ( !*(data_p - 1) )
    {
      v4 = input2[ipos++];
      *(data_p - 1) = v4;
    }
    if ( !*data_p )
    {
      v5 = input2[ipos++];
      *data_p = v5;
    }
    if ( !data_p[1] )
    {
      v6 = input2[ipos++];
      data_p[1] = v6;
    }
    if ( !data_p[2] )
    {
      v7 = input2[ipos++];
      data_p[2] = v7;
    }
    if ( !data_p[3] )
    {
      v8 = input2[ipos++];
      data_p[3] = v8;
    }
    if ( !data_p[4] )
    {
      v9 = input2[ipos++];
      data_p[4] = v9;
    }
    if ( !data_p[5] )
    {
      v10 = input2[ipos++];
      data_p[5] = v10;
    }
    if ( !data_p[6] )
    {
      v11 = input2[ipos++];
      data_p[6] = v11;
    }
    if ( !data_p[7] )
    {
      v12 = input2[ipos++];
      data_p[7] = v12;
    }
    if ( ipos >= inplen )
      break;

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//