首页
社区
课程
招聘
[原创]看雪CTF2017第二题解析
2017-6-6 05:31 5996

[原创]看雪CTF2017第二题解析

2017-6-6 05:31
5996

第一次写分析文章,第一是分享自己的分析过程,还有一个就是对自己做这道题的一个总结吧。。。

首先打开main函数,发现fgets获取输入,FILE*结构体就是stdin,OD动态跟发现也是我输入的字符串,所以确认这就是输入点

fgets(input, 260, &stru_4090E0);//这里获取输入
  inputLen = strlen(input) - 1;//-1是因为fgets会把'\n'也包括进来,所以-1刚好就是输入的长度
  if ( inputLen < 8 || inputLen > 20 )// 8<=len<=20
  {
    printf((int)aKeyLenErrorD__, inputLen);
    return 0;
  }
  v6 = 0;
  i = 0;
  input[inputLen] = 0;                          // '\n' -> '\0'
  if ( inputLen > 0 )                           // i < inputLen
  {
    do
    {
      v8 = input[i];
      if ( v8 <= '0' || v8 > '9' )
        ++v6;
      ++i;
    }
    while ( i < inputLen );                     // 必须是digit 1-9
    if ( v6 )
    {
      printf((int)aKeyFormatError, v22);
      return 0;
    }
  }

这是最基本的判断,接下来调用了一个thiscall的函数,传了局部变量的一个指针,这里猜测这个局部变量是一个类

push    esi
mov     esi, ecx
mov     dword ptr [esi], offset vt1
call    ds:GetTickCount
mov     ecx, esi
mov     [esi+200Ch], eax
mov     [esi+2008h], eax
call    init_class

进去之后使用了指针后面偏移0x200C和0x2008的DWORD数据,把它赋值为GetTickCount的返回值,然后发现又调用了一个子函数

跟进那个子函数,还有第一个dword是虚表


// about initialization of class CriticalClass:
//     0 vt
//     1 something, 
//     2 to 1025 reverse order of arr2
//     1026 to 2049 saves shaffled 0 to 1023
//     2051 to 2050 saves getTickCount
//     unit dword
_DWORD *__thiscall init_class(CriticalClass *this)
{
  CriticalClass *this_1; // esi@1
  signed int i; // eax@1
  _DWORD *i_this_p1026_1; // edi@1
  _DWORD *i_this_p1026; // ecx@1
  int v5; // edx@2
  signed int j; // ebp@3
  int v7; // ebx@4
  int v8; // eax@4
  _DWORD *arr1; // eax@5
  int *p; // ecx@5
  signed int i_; // edx@5
  int v12; // esi@6
  this_1 = this;
  i = 0;
  i_this_p1026_1 = this->arr2;
  i_this_p1026 = this->arr2;
  do
  {
    ++i_this_p1026;
    v5 = i++ & 0x3FF;                           // 0011 1111 1111
    *(i_this_p1026 - 1) = v5;                   // assign from this[1026] to this[1026+1023]
  }
  while ( i < 1024 );                           // traverse from 0 to 0x3FF(1023)
                                                // 
                                                // assign 0-1023 to this[1026] to this[1026+1023]
  j = 1024;
  do
  {
    v7 = *i_this_p1026_1;
    v8 = bit_proc_toTickCount((char *)this_1);  // find a random number from 0 to 1023
                                                // and regard it as index, 
                                                // exchange thing in it with i_this__
    *i_this_p1026_1 = this_1->arr2[v8];
    ++i_this_p1026_1;
    --j;
    this_1->arr2[v8] = v7;                      // exchange the number
  }
  while ( j );                                  // j from 1024 to 1
                                                // from [1026] to [1026+1023]
                                                // 
                                                // shuffle the array
  arr1 = this_1->arr1;
  p = &this_1->arr2[1023];
  i_ = 1024;
  do
  {
    v12 = *p;
    --p;
    *arr1 = v12;
    ++arr1;
    --i_;
  }
  while ( i_ );                                 // _i from 1024 to 1
                                                // p from [2049] to [1026]
                                                // arr1 from [2] to [1025]
  return arr1;                                  // cpy arr1 as the reverse order of arr2
}

第一步,这个函数把[1026](把this看成dword*)往后1024个DWORD赋值为了0-1023

然后bit_proc_toTickCount根据之前用GetTickCount存储在这个class里面的值做了一些位处理,姑且算他是伪随机生成器,然后进行一个数组打乱的算法,用OD看一下也的确是乱着的0-1023

最后,倒序复制[1026]后1024个dword到[2]后的1024个dword

初步分析这个类的结构如下

00000000 CriticalClass   struc ; (sizeof=0x2010, mappedto_22) ; XREF: _main/r
00000000                                         ; _main/r ...
00000000 vt              dd ?                    ; char
00000004 unknown         dd ?
00000008 arr1            dd 1024 dup(?)
00001008 arr2            dd 1024 dup(?)
00002008 tickCount1      dd ?
0000200C tickCount2      dd ?
00002010 CriticalClass   ends
constructor(&critical_class);
  v26 = 0;
  init_cc_accToInput(&critical_class, input);
  nullsub_1();

调用完刚才的函数之后(八成就是构造函数了),又调用了一个函数,传入了输入作为参数

void __thiscall init_cc_accToInput(CriticalClass *this, const char *input)
{
  CriticalClass *this_1; // ebx@1
  int i; // esi@1
  unsigned int inputLen_plus1; // kr04_4@1
  char c; // al@2
  signed int v; // eax@4
  int v7; // ecx@6
  this_1 = this;
  i = 0;
  inputLen_plus1 = strlen(input) + 1;
  this_1->rec = 0;
  if ( (signed int)(inputLen_plus1 - 1) > 0 )   // i < len
  {
    do
    {
      c = input[i];
      if ( c >= '0' && c <= '9' )               // always true
      {
        v = c - '0';
        if ( v )
        {
          if ( i )
          {
            v7 = i;
            do
            {
              v *= 10;
              --v7;
            }
            while ( v7 );                       // convert to number according to position
                                                // 123
                                                // convert to 
                                                // 1
                                                // 20
                                                // 300 in each loop
          }
          critical_401580(this_1, v);
        }
      }
      ++i;
    }
    while ( i < (signed int)(inputLen_plus1 - 1) );// i < len
  }
  make_e_smaller_than_10(this_1);
}

这个就是遍历输入字符串,然后根据下标转换成数字,比方说输入“123...”,第一次循环得到的就是1,第二次20,第三次300...

然后把这个v跟着this传到另外一个函数里面去


接下来看critical_401580的内容,顺便看了看xref发现这个函数只有一处引用

void __thiscall critical_401580(CriticalClass *arr, signed int v)
{
  signed int v_; // eax@1
  int i; // ebx@1
  DWORD *i_arr2; // edi@2
  int rec; // edx@3
  DWORD arr2_1st; // edx@5
  signed __int64 v_now; // rtt@5
  v_ = v;
  i = 0;
  if ( v )
  {
    i_arr2 = arr->arr2;
    do
    {
      rec = arr->rec;
      if ( i >= rec )                           // 所以i这里最高为9,rec最多被加到10,因为只有32位
      {
        arr->arr1[arr->arr2[rec]] = 0;          // when v is 300, rec is 2 here
        ++arr->rec;                             // finnally rec is equal to the length of input
                                                // (not completely true for overflow case)
      }
      arr2_1st = *i_arr2;
      ++i_arr2;
      v_now = v_;
      v_ /= 10;
      ++i;
      arr->arr1[arr2_1st] += v_now % 10;        // eg 300, arr1[arr2[2]] will add 3
                                                // the cell that is added to 3 is always the cell that is cleared to zero in the beginning
                                                // 
                                                // case is different for overflow
                                                // for overflow, it is possible fro v to not be multiple of 10
    }
    while ( v_ );                               // 循环的次数等于v的位数
                                                // 但是最多循环十次,因为只有32位
  }
  make_e_smaller_than_10(arr);
}                                               // arr1[arr2[i]]是input的第i个数

这个循环呢仔细看看其实也不难分析,比方说进来一个1234,实际会是4,30,200,1000这样进来(注意这样的话实际在控制台的输入是4321,当然不够八位这里不考虑就举个栗子),他会把4放在[0],3放在[1],2放在[2],1放在[3],而且下标永远不能超过结构体偏移为4的那个整形,如果位数大于了那个数,就会把它++。比方说rec是2,输入1234,循环到2的时候,i>=rec(我们姑且先把+4命名为rec),会把[2]清0,然后rec++。

这里要注意的是,这个存储的方式不是用单个数组。比方说[0],真正用到的是arr1[arr2[0]]来存放,因为arr2里面的值永远在0-1023之间,所以不会越过下标,可以把它理解为两次映射,这可能是作者的一种混淆方式。

实际调用这个函数的时候,传进来的v会是x x0 x00 x000 x0000。。。x是1-9的数字

所以如果不溢出的话,如果我输入192837465

这个类就会按如下方式保存

arr1[arr2[0]] 1

arr1[arr2[1]] 9

arr1[arr2[2]] 2

arr1[arr2[3]] 8

......

溢出的话就不一定是x00...的形式了,因为那样的话要对0x100000000取模,现在暂时不考虑溢出的情况,但不排除溢出的可能性,因为最多能有20位。不过就算溢出,也会使得这个类保存某个数,只不过这个数不直接是输入的数了


接下来再看看这个make_e_smaller_than_10

void __thiscall make_e_smaller_than_10(CriticalClass *this)
{
  int rec; // edx@1
  int i; // ebx@1
  DWORD *p; // esi@2
  signed int arr1_arr2_i; // eax@3
  int v5; // edx@6
  rec = this->rec;
  i = 0;
  if ( rec > 0 )                                // i < rec
  {
    p = &this->arr2[1];
    do
    {
      arr1_arr2_i = this->arr1[*(p - 1)];       // arr1[arr2[i]]
      if ( arr1_arr2_i >= 10 )
      {
        if ( i == rec - 1 )                     // only do this for the last time of loop
        {
          if ( rec >= 1024 )
            return;                             // probable unlikely to occur
          this->rec = rec + 1;
          v5 = arr1_arr2_i % 10;
          this->arr1[*p] = arr1_arr2_i / 10;
        }
        else
        {
          v5 = arr1_arr2_i % 10;
          this->arr1[*p] += arr1_arr2_i / 10;
        }
        this->arr1[*(p - 1)] = v5;
      }
      rec = this->rec;
      ++i;
      ++p;
    }
    while ( i < rec );                          // 使[0..rec)都小于10
                                                // 若大于10,则把多的部分/10放在后面
  }
}

如注释所写,他会把所有0到rec-1的数都弄得小于10,如果大了,就把多出来的部分/10放在后面,然后自己%10,如果rec要爆了,就把它++

现在就可以有灵感了,这个类很有可能是一个表示大数的类,表示方法如下

arr1[arr2[i]]存储第i位数字

如数字4567,7就存在[0]处,6在[1],以此类推,其中,rec是数字的位数


那么前面的那个函数,

critical_401580

就是把那个大数加上参数v,因为这样有可能会使个别数字大于等于10,所以调用make_e_smaller_than_10


好的回到main继续分析

multiply2(&critical_class, 9);                // v9没用
  nullsub_1();

看下multiplty2这个函数(当然现在分析到这里的时候还不知道是multiply。。因为我这个已经分析好了,所以就重命名了,,算剧透一波了。。。。)

signed int __userpurge multiply2@<eax>(CriticalClass *this@<ecx>, signed int a3)
{
  int v2; // eax@0
  void *v3; // esp@1
  CriticalClass *_this; // ebp@1
  int i; // edi@1
  int v6; // edx@1
  signed int num; // eax@1
  int num_mod10; // esi@2
  int num_div10; // ebx@2
  signed int result; // eax@5
  CriticalClass v11; // [sp+0h] [bp-402Ch]@1
  CriticalClass sum; // [sp+2010h] [bp-201Ch]@1
  int v13; // [sp+4020h] [bp-Ch]@1
  int (*v14)(); // [sp+4024h] [bp-8h]@1
  int v15; // [sp+4028h] [bp-4h]@1
  v15 = -1;
  v14 = sub_4075D6;
  v13 = v2;
  v3 = alloca(0x4020);                          // 两个CriticalClass
  _this = this;
  constructor(&v11);
  i = 0;
  v15 = 0;
  constructor3(&sum, 0);
  num = a3;
  LOBYTE(v15) = 1;
  if ( a3 )
  {
    while ( 1 )                                 // 循环次数是Num的位数
    {
      v6 = num % 10;
      num_mod10 = num % 10;
      num_div10 = num / 10;
      if ( num % 10 )
      {
        cc_cpy(&v11, v6, _this);
        lsr_critical_class(&v11, i);
        multiply_byConst(&v11, num_mod10);
        add(&sum, &v11);
        if ( getRec(&sum) >= 1024 )
          break;
      }
      num = num_div10;                          // num /= 10
      ++i;
      if ( !num_div10 )                         // if (num == 0)
        goto LABEL_5;
    }
    setVt(&sum);
    setVt(&v11);
    result = 1;
  }
  else
  {
LABEL_5:
    cc_cpy(_this, v6, &sum);
    setVt(&sum);
    setVt(&v11);
    result = 0;
  }
  return result;
}

这里又出现了一个constructor3,其实里面内容和constructor基本一样,只不过这个函数会直接把rec清0

CriticalClass *__thiscall constructor3(CriticalClass *this, int zero)//cref看,第二个参数永远是0
{
  CriticalClass *_this; // esi@1
  DWORD v3; // eax@1
  _this = this;
  this->vt = (int)&vt1;
  v3 = GetTickCount();
  _this->tickCount2 = v3;
  _this->tickCount1 = v3;
  init_class(_this);
  setRecZero(_this, zero);//进去这个函数,发现如果第二个参数是0的话,做的事情也就是把rec=0而已
  return _this;
}

接着分析这4个函数,在循环里面的

        cc_cpy(&v11, v6, _this);
        lsr_critical_class(&v11, i);
        multiply_byConst(&v11, num_mod10);
        add(&sum, &v11);

一个一个来,第一个发现这个hexray瞎**给我反编译,所以自己看asm。。。

.text:00401540 ; this->arr1[arr2[i]] = cc3->arr1[arr2[i]]
.text:00401540 ; for i [0..cc3.rec)
.text:00401540 ; this->rec=cc3->rec
.text:00401540 ;
.text:00401540 ; 2nd arg not useful
.text:00401540
.text:00401540 ; int __fastcall cc_cpy(CriticalClass *this, int a2, CriticalClass *cc3)
.text:00401540 cc_cpy          proc near               ; CODE XREF: multiply2+6Cp
.text:00401540                                         ; multiply2+BAp ...
.text:00401540
.text:00401540 cc3             = dword ptr  4
.text:00401540
.text:00401540                 push    esi
.text:00401541                 mov     esi, [esp+4+cc3] ; esi is cc3
.text:00401545                 xor     edx, edx
.text:00401547                 mov     eax, [esi+4]    ; eax=cc3->rec
.text:0040154A                 test    eax, eax
.text:0040154C                 mov     [ecx+4], eax    ; this->rec=cc3->rec
                                                       ;还有这个,使this的rec等于cc3的rec
.text:0040154F                 jle     short loc_401579 ; if (cc3->rec > 0)
.text:00401551                 push    ebx
.text:00401552                 push    ebp
.text:00401553                 push    edi
.text:00401554                 mov     edi, esi
.text:00401556                 lea     eax, [ecx+4104] ; eax=this->arr2
.text:0040155C                 sub     edi, ecx        ; edi is cc3-this
.text:0040155E
.text:0040155E loc_40155E:                             ; CODE XREF: cc_cpy+34j
.text:0040155E                 mov     ebx, [edi+eax]  ; take element in cc3->arr2
.text:00401561                 mov     ebp, [eax]      ; take element in this->arr2
.text:00401563                 inc     edx             ; i++
.text:00401564                 add     eax, 4          ; eax used to traverse
.text:00401567                 mov     ebx, [esi+ebx*4+8] ; this->arr1[arr2[i]] = cc3->arr1[arr2[i]]
                                                          ;这句是关键,说明是把cc3赋给this
.text:00401567                                         ; for i [0..cc3.rec)
.text:00401567                                         ; this->rec=cc3->rec
.text:0040156B                 mov     [ecx+ebp*4+8], ebx
.text:0040156F                 mov     ebx, [ecx+4]    
.text:00401572                 cmp     edx, ebx        ;循环到this,也就是cc3的rec
.text:00401574                 jl      short loc_40155E ; do_while(edx < this->rec)
.text:00401576                 pop     edi
.text:00401577                 pop     ebp
.text:00401578                 pop     ebx
.text:00401579
.text:00401579 loc_401579:                             ; CODE XREF: cc_cpy+Fj
.text:00401579                 pop     esi
.text:0040157A                 retn    4
.text:0040157A cc_cpy          endp

分析一下发现很简单,他就是使this所存储的数等于cc3所存储的数,相当于一个赋值

// 总结一下,就是把this所存储的数乘上10^a2
signed int __thiscall lsr_critical_class(CriticalClass *this, int a2)
{
  signed int result; // eax@2
  int this_rec_p_a2; // eax@3
  int v4; // eax@5
  int *i_arr2_a2; // esi@6
  int this_rec_minus1; // eax@6
  int *i_arr2; // edx@6
  int i; // eax@6
  int v9; // ebx@7
  int v10; // ebp@7
  int *v11; // eax@9
  int j; // edx@9
  int v13; // edi@10
  if ( a2 > 0 )                                 // 总是 true
  {
    this_rec_p_a2 = a2 + this->rec;
    if ( this_rec_p_a2 <= 1024 )                // 基本上是 true
    {
      this->rec = this_rec_p_a2;
      v4 = this_rec_p_a2 - 1;
      if ( v4 >= a2 )
      {
        i_arr2_a2 = &this->arr2[v4];
        this_rec_minus1 = v4 - a2;
        i_arr2 = &this->arr2[this_rec_minus1];
        i = this_rec_minus1 + 1;
        do
        {
          v9 = *i_arr2;
          v10 = *i_arr2_a2;
          --i_arr2;
          --i_arr2_a2;
          --i;
          this->arr1[v10] = this->arr1[v9];     // 赋值
        }
        while ( i );                            // i 从1到this->rec
                                                // i_arr2 = arr2[rec-1] 到 arr2[0]
                                                // i_arr2_a2 = arr2[this->rec + a2 - 1] 到 arr2[a2]
                                                // 
                                                // 这个循环"右移"CriticalClass里面的数据
      }
      if ( a2 - 1 >= 0 )
      {
        v11 = &this->arr2[a2 - 1];
        j = a2;
        do
        {
          v13 = *v11;
          --v11;
          --j;
          this->arr1[v13] = 0;                  // 将 arr1[arr2[0]] 到 arr1[arr2[a2-1]] 清零
        }
        while ( j );                            // j从1到a2
      }
      result = 0;
    }
    else
    {
      result = 1;
    }
  }
  else
  {
    result = -1;
  }
  return result;
}
signed int __thiscall multiply_byConst(CriticalClass *this, signed int a2)
{
  int i; // edx@3
  int *p_arr2; // esi@4
  int v4; // eax@5
  signed int result; // eax@6
  if ( a2 < 0 || a2 >= 10 )
  {
    result = 1;
  }
  else
  {
    i = 0;
    if ( this->rec > 0 )
    {
      p_arr2 = this->arr2;
      do
      {
        v4 = *p_arr2;
        ++p_arr2;
        this->arr1[v4] *= a2;                   // arr1[arr2[i]] *= a2
        //很明显了,把所有位都乘上a2,相当于把这个数乘上a2
        ++i;
      }
      while ( i < this->rec );
    }
    make_e_smaller_than_10(this);
    //防止出现一位大于10的情况
    result = 0;
  }
  return result;
}
void __thiscall add(CriticalClass *a1, CriticalClass *a2)
{
  int max_rec; // edx@1
  int i; // ebx@3
  int *p_a1_arr2; // eax@4
  signed int diff2_1; // edi@4
  int a1_rec; // edx@5
  int max_rec_; // [sp+8h] [bp-4h]@1
  max_rec = a1->rec;
  max_rec_ = max_rec;
  if ( max_rec < a2->rec )
  {
    max_rec = a2->rec;
    max_rec_ = a2->rec;
  }
  i = 0;
  if ( max_rec > 0 )                            // i < max_Rec
  {
    p_a1_arr2 = a1->arr2;
    diff2_1 = (char *)a2 - (char *)a1;
    do
    {
      a1_rec = a1->rec;
      if ( i < a1_rec )
      {
        if ( i < a2->rec )
          a1->arr1[*p_a1_arr2] += a2->arr1[*(int *)((char *)p_a1_arr2 + diff2_1)];// a1->arr1[arr2[i]] += a2->arr1[arr2[i]]
      }
      else//如果没过a1的rec的话,就直接把对应位加起来,如果过了,就要把a1的rec++,然后直接赋值
      {
        a1->arr1[a1->arr2[a1_rec]] = a2->arr1[*(int *)((char *)p_a1_arr2 + diff2_1)];// 
                                                // a1->arr1[arr2[i]] = a2->arr1[arr2[i]]
                                                // a1->rec++
        ++a1->rec;
      }
      ++i;
      ++p_a1_arr2;
    }
    while ( i < max_rec_ );//本质上呢,就是干的a1+=a2这事
  }
  make_e_smaller_than_10(a1);//同样,防止大于等于10的数字位
}

那么这4个函数都分析完了,那么来看看这个multiply2为什么是乘吧。。。

signed int __userpurge multiply2@<eax>(CriticalClass *this@<ecx>, signed int a3)
{
  int v2; // eax@0
  void *v3; // esp@1
  CriticalClass *_this; // ebp@1
  int i; // edi@1
  int v6; // edx@1
  signed int num; // eax@1
  int num_mod10; // esi@2
  int num_div10; // ebx@2
  signed int result; // eax@5
  CriticalClass v11; // [sp+0h] [bp-402Ch]@1
  CriticalClass sum; // [sp+2010h] [bp-201Ch]@1
  int v13; // [sp+4020h] [bp-Ch]@1
  int (*v14)(); // [sp+4024h] [bp-8h]@1
  int v15; // [sp+4028h] [bp-4h]@1
  v15 = -1;
  v14 = sub_4075D6;
  v13 = v2;
  v3 = alloca(0x4020);                          // 两个CriticalClass
  _this = this;
  constructor(&v11);
  i = 0;
  v15 = 0;
  constructor3(&sum, 0);
  num = a3;
  LOBYTE(v15) = 1;
  if ( a3 )
  {
    while ( 1 )                                 // 循环次数是Num的位数
    {
      v6 = num % 10;
      num_mod10 = num % 10;
      num_div10 = num / 10;
      if ( num % 10 )
      {
        cc_cpy(&v11, v6, _this);
        lsr_critical_class(&v11, i);
        multiply_byConst(&v11, num_mod10);
        add(&sum, &v11);
        if ( getRec(&sum) >= 1024 )
          break;
      }
      num = num_div10;                          // num /= 10
      ++i;
      if ( !num_div10 )                         // if (num == 0)
        goto LABEL_5;
    }
    /*
    我直接把解释写注释里了,,,更方便更好看。。。
    首先这个循环是根据num这个数字的位数在循环,比方说a3是1234,那么会分别把4 3 2 1取出来
    (通过%10,然后/10进行下一次循环,直到Num变成0)
    然后我们设this所存储的数是x
    那么第一次循环,x放到v11,然后乘上4*10^0,加到sum
    第二次,乘3*10^1,加到sum
    所以数学式子就是x*4*1 + x*3*10 + x*2*100 + x*1*1000
    提公因式,变成x*(4+30+200+1000)
    就是1234x
    所以以此类推,任何常数都一样
    */
    setVt(&sum);
    setVt(&v11);
    result = 1;
  }
  else
  {
LABEL_5:
    cc_cpy(_this, v6, &sum);//this*=a3
    setVt(&sum);
    setVt(&v11);
    result = 0;
  }
  return result;
}

嗯。。所以这玩意就是乘,至于返回值,就是基本上就会返回0,不正常的情况下(rec>=1024了,明显不会有那么大的数。。。)才会返回1

而且再看看main,0也是我们想要的返回值。。。原因待会说。。。

接着就继续看main

  while ( 1 )
  {
    constructor2(&cc_input, input);//这个其实就是constructor+init_cc_accToInput,很简单不说了。。。
    LOBYTE(v26) = 1;
    v9 = multiply3(&cc_input, &critical_class, &cc_input);//这个很重要,待会分析
    criticalRet2 = multiply2(&critical_class, 9) + v9;
    nullsub_1();
    if ( criticalRet2 || getRec(&critical_class) % 2 != 1 )// 我们想让这个为false,所以这两个都要是0
                                                //想让criticalRet2为0,那么就要multiply2,3都返回0
                                                // 而且critical_class的rec必须要是奇数
      goto LABEL_16;                            // getRec很简单就直接返回的是this->rec
    cc_rec = getRec(&critical_class);
    v12 = getDigit(&critical_class, cc_rec >> 1);
    v13 = getDigit(&cc_input, 0);//这个getDigit也很简单,就是return this->arr1[this->arr2[a2]]
    v14 = &cc_input;
    if ( v12 == v13 )
      break;//我们想让他break,所以v12要等于v13,
LABEL_17:
    LOBYTE(v26) = 0;
    setVt(v14);
    if ( criticalRet2 )                         
    {
      printf((int)aWrongKey___, v22);
      goto LABEL_19;
    }
  }
  v15 = getRec(&cc_input) - 1;
  v16 = 1 - getRec(&cc_input);
  v17 = getRec(&critical_class);
  v18 = critical_4013E0(&critical_class, &cc_input, v17 + v16, 1, v15, 0);
  v19 = getRec(&cc_input);
  if ( critical_4013E0(&critical_class, &cc_input, 0, 1, v19 - 1, 1) + v18 )
  //我们想让他为false,所以这两个critical_4013E0必须要是相反数
  {
    criticalRet2 = 0;
LABEL_16:
    v14 = &cc_input;
    goto LABEL_17;
  }
  printf((int)aWellDone, v22);

来看看multiply3

// need this to return 0
// need to see how this affect the cc1 and cc2
signed int __userpurge multiply3@<eax>(CriticalClass *cc1@<eax>, CriticalClass *cc2@<ecx>, CriticalClass *cc1_)
{
  void *v3; // esp@1
  CriticalClass *cc2_1; // esi@1
  int i; // edi@1
  int cc2Rec; // ecx@1
  int cc1Rec; // eax@1
  DWORD *cc1_arr2; // edx@4
  signed int v10; // ebp@5
  int v11; // edx@7
  DWORD *i_cc1_arr2; // [sp+0h] [bp-4030h]@4
  CriticalClass cc2_tmp; // [sp+4h] [bp-402Ch]@1
  CriticalClass sum; // [sp+2014h] [bp-201Ch]@1
  CriticalClass *cc1_1; // [sp+4024h] [bp-Ch]@1
  int (__cdecl *v16)(EXCEPTION_RECORD *, struct EHRegistrationNode *, struct _CONTEXT *, void *); // [sp+4028h] [bp-8h]@1
  int v17; // [sp+402Ch] [bp-4h]@1
  v17 = -1;
  v16 = sub_4075F6;
  cc1_1 = cc1;
  v3 = alloca(0x4024);
  cc2_1 = cc2;
  constructor(&cc2_tmp);
  i = 0;
  v17 = 0;
  constructor3(&sum, 0);
  cc2Rec = cc2_1->rec;
  LOBYTE(v17) = 1;
  cc1Rec = cc1_->rec;
  if ( cc1Rec + cc2Rec > 1024 )                 // cc1->rec + cc2->rec <= 1024
  {
LABEL_2:
    setVt(&sum);                                // 出问题,就返回1,但基本上不会有那么大的数。。。
    setVt(&cc2_tmp);
    return 1;
  }
  if ( cc1Rec > 0 )                             // i < ccRec
  {
    cc1_arr2 = cc1_->arr2;
    i_cc1_arr2 = cc1_->arr2;
    do
    {
      v10 = cc1_->arr1[*i_cc1_arr2];
                                                // 在循环中,i_cc1_arr2 总是 &cc1->arr2[i]
      cc_cpy(&cc2_tmp, (int)cc1_arr2, cc2_1);//第二个参数没用,IDA抽了
      lsr_critical_class(&cc2_tmp, i);
      multiply2(&cc2_tmp, v10);                 // cc1->arr1[arr2[i]]
      add(&sum, &cc2_tmp);
      if ( cc2_1->rec >= 1024 )
        goto LABEL_2;
      ++i;
      ++i_cc1_arr2;
    }
    while ( i < cc1_->rec );            // i从0到cc1->rec
    /*
    这个跟前面那个multiply2很像,只不过是乘上另外一个CriticalClass
    设this为x
    也是从最低位开始,x*10^i*cc1的第i位,加起来加到sum
    一样的,也是把x提出来,最后发现结果就是cc1*x
    */
  }
  init_class(cc2_1);
  cc_cpy(cc2_1, v11, &sum);//this*=cc1
  setVt(&sum);
  setVt(&cc2_tmp);
  return 0;
}

回到main,看下逻辑

设input输入为x

x先乘上9,然后再乘x,然后再乘9,就是81x^2

设这玩意为y

1.y一定是奇数位数

2.y的(位数>>1)所存的数字一定要跟x的第0位的数字一样

3.如果不一样,还有几次循环的机会,到目前为止就只能分析到这么多

最后看这个critical_4013E0,这个耗了我最久的时间。。。首先反编译效果很差,必须要用asm自己看,而且他分两种情况第一种是第一次调用第二种是第二次调用,我们要一次一次地看。其中this是81x^2,input是x

; unsigned int __thiscall critical_4013E0(CriticalClass *critical_class, CriticalClass *cc_input, int a3, int one, int j_, char a6)
critical_4013E0 proc near
var_4= dword ptr -4
cc_input= dword ptr  4
a3= dword ptr  8
one= dword ptr  0Ch
j_= dword ptr  10h
a6= byte ptr  14h
i = esi
j = edx
this = edi
input = ecx
push    input
mov     j, [esp+4+j_]   ; edx=input_rec
push    i
push    this
mov     this, input     ; edi=this
mov     input, [esp+0Ch+cc_input] ; ecx=input
mov     [esp+0Ch+var_4], 0 ; v4=0
test    j, j
jle     short loc_401477 ; if (rec > 0),小于等于直接飞走return,这个不可能<=0
mov     eax, [esp+0Ch+one] ; eax=1
push    ebx
push    ebp
mov     ebp, [esp+14h+a3] ; ebp=a4
lea     ebx, [eax+j-1]  ; ebx=input_rec
mov     i, eax          ; esi = 1
mov     [esp+14h+j_], ebx ; 编译器抽风没用系列
sub     ebp, eax        ; ebp = a4 - 1

进dowhile循环

先看看dowhile循环的变量范围

loc_401468:
mov     eax, [esp+14h+j_]
dec     eax
inc     i               ; i 从 1 到 input->rec - 1
dec     j               ; j 从 input->rec - 1 到 1
                        ; j == input->rec - i
mov     [esp+14h+j_], eax ; j_--
jnz     short DOWHILE   ; j == j_ always
                        ; 循环次数为 input->rec - 1 次


DOWHILE:                
mov     ebx, [this+4]   ; ebx=this->rec
lea     eax, [i+ebp]    ; eax=a4 - 1 + i
cmp     eax, ebx
jl      short loc_401426
mov     eax, [esp+14h+var_4] ; a4-1+i >= this->rec
                             ; 不可能到这里
                             ;因为i最大是input->rec-1,a4有两种情况0(第二次调用)或者this->rec-input->rec + 1(第一次)
                             ;所以a4-1+i最大是trec-irec+1-1+irec-1,等于trec-1,所以不可能大于等于trec
                             ;PS:待会为了方便起见this->Rec就是trec,input->rec就是irec
inc     eax
xor     ebx, ebx
mov     [esp+14h+var_4], eax ; ebx=0
                        ; v4++
jmp     short loc_401428
loc_401426:             ; 会永远到这里
                        ; a4-1+i < this->rec
mov     ebx, eax        ; ebx=a4-1+i
                        ; 第1次: ebx=cc-input+i
                        ; 第2次: ebx=i-1
                        ;ebx待会会用来作为下标,待会说
loc_401428:
call    getRec
cmp     i, eax
jl      short loc_40143E
mov     eax, [esp+14h+var_4] ; if (i >= input->rec)
                             ; 同样不可能到达,因为i最大为irec-1
inc     eax
mov     [esp+14h+var_4], eax ; ++v4
xor     eax, eax
jmp     short loc_40144C
loc_40143E:             ; if (i < input->rec),永远到这
mov     al, [esp+14h+a6];a6第一次是0,第二次是1
test    al, al
mov     eax, [esp+14h+j_] ; 2nd time第二次,eax会是j
jnz     short loc_40144C
mov     eax, i          ; 1st time第一次,eax会是i
loc_40144C:
mov     ebx, [this+ebx*4+1008h]
mov     eax, [input+eax*4+1008h]
mov     ebx, [this+ebx*4+8] ; this->arr1[arr2[ebx]]
cmp     ebx, [input+eax*4+8] ; input->arr1[arr2[eax]]
                        ; 现在分析这个就差不多了,之前说我想让两次调用返回相反数
                        ;因为这里只有加,而且不大可能溢出,所以只有让他们都返回0
                        ;所以我们不能让v4++,(v4是返回值的一部分,待会说)
                        ;所以一定要让他们总是相等
                        ; 第一次  :input->arr1[arr2[i]]
                        ;          this-> arr1[arr2[cc_Rec-input_rec + i]]
                        ;          i = [1..input->rec-1]
                        ;所以x的1位到irec-1位要 和 y(81x^2)的trec-irec+1位到trec-1位一致
                        ;
                        ; 第二次  :input->arr1[arr2[input_rec-i]]
                        ;          this-> arr1[arr2[i - 1]]
                        ;          i = [1..input->rec-1]
                        ;所以x的irec-1位到1位要 和 y的0位到irec-2位一致,注意这里是倒序一致
jz      short loc_401468
inc     [esp+14h+var_4] ; 爆破点,nop掉直接过,当然要满足最前面的那两条规则

最后看看返回值

loc_401477:
mov     eax, [input+200Ch];还记得那个GetTickCount不
mov     i, [this+200Ch]
mov     input, [esp+0Ch+var_4]
xor     eax, i    ;取出来xor右移7位
shr     eax, 7    ;基本上这个会是0,因为电脑很快的,但是main里面的while循环跑太多次就不一定了。。。所以上面我分析的要求最好一次达标,就是呢,在y=81x^2的时候达标
pop     this
add     eax, input;是0的话,就返回v4的值
pop     i
pop     input
retn    14h

好的,结合前面的两条标准,加上最后分析出的那条标准,我们写出Python爆破器。。。

def takeReverseOrder(num, x):
	ret = 0
	i = 0
	while (i <= x):
		ret = ret * 10
		ret = ret + (num % 10)
		i = i + 1
		num = num / 10
	return ret
def getNumDigit(x):
	ret = 0
	while (x != 0):
		ret = ret + 1
		x = x / 10
	return ret
def getDigit(x, n):
	return (x / pow(10,n)) % 10
def ifMatch(number, nextNum):
	irec = getNumDigit(number)
	trec = getNumDigit(nextNum)
	tmp = number / 10
	nnumD = getNumDigit(nextNum)
	if (tmp == nextNum / pow(10, trec - irec + 1)
		and tmp == takeReverseOrder(nextNum, irec - 2)
		and nnumD % 2 == 1
		and number % 10 == getDigit(nextNum, nnumD / 2)):
		return True
	return False
number = 10000000
while True:
	nextNum = number * number * 81
	if (ifMatch(number, nextNum)):
		print ("correct: %d" % number)
		break
	else:
		if (number % 100000 == 0):
			print ("wrong: %d" % number)
	number = number + 1

最后输出为

wrong: 11800000
wrong: 11900000
wrong: 12000000
wrong: 12100000
wrong: 12200000
wrong: 12300000
correct: 12345679

PS:汇编太菜,F5用的多,略脚本小子
而且这第三题一出我一看就知道不在我的能力范围内。。。所以直接give up。。。就写了这篇分析。。。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞2
打赏
分享
最新回复 (5)
雪    币: 5676
活跃值: (1303)
能力值: ( LV17,RANK:1185 )
在线值:
发帖
回帖
粉丝
holing 15 2017-6-6 05:37
2
0

太长装不下。。剩下的放这。。。doc是全部。。。


进dowhile循环

先看看dowhile循环的变量范围

loc_401468:
mov     eax, [esp+14h+j_]
dec     eax
inc     i               ; i 从 1 到 input->rec - 1
dec     j               ; j 从 input->rec - 1 到 1
                        ; j == input->rec - i
mov     [esp+14h+j_], eax ; j_--
jnz     short DOWHILE   ; j == j_ always
                        ; 循环次数为 input->rec - 1 次


DOWHILE:                
mov     ebx, [this+4]   ; ebx=this->rec
lea     eax, [i+ebp]    ; eax=a4 - 1 + i
cmp     eax, ebx
jl      short loc_401426
mov     eax, [esp+14h+var_4] ; a4-1+i >= this->rec
                             ; 不可能到这里
                             ;因为i最大是input->rec-1,a4有两种情况0(第二次调用)或者this->rec-input->rec + 1(第一次)
                             ;所以a4-1+i最大是trec-irec1+irc-								
																																
								
							
雪    币: 5676
活跃值: (1303)
能力值: ( LV17,RANK:1185 )
在线值:
发帖
回帖
粉丝
holing 15 2017-6-6 05:41
3
0

(1楼怎么只有一半。。。bug吗。。。)

太长装不下。。剩下的放这。。。doc是全部。。。


进dowhile循环

先看看dowhile循环的变量范围

loc_401468:
mov     eax, [esp+14h+j_]
dec     eax
inc     i               ; i 从 1 到 input->rec - 1
dec     j               ; j 从 input->rec - 1 到 1
                        ; j == input->rec - i
mov     [esp+14h+j_], eax ; j_--
jnz     short DOWHILE   ; j == j_ always
                        ; 循环次数为 input->rec - 1 次


DOWHILE:                
mov     ebx, [this+4]   ; ebx=this->rec
lea     eax, [i+ebp]    ; eax=a4 - 1 + i
cmp     eax, ebx
jl      short loc_401426
mov     eax, [esp+14h+var_4] ; a4-1+i >= this->rec
                             ; 不可能到这里
                             ;因为i最大是input->rec-1,a4有两种情况0(第二次调用)或者this->rec-input->rec + 1(第一次)
                             ;所以a4-1+i最大是trec-irec1+irc-								
																																
								
							
雪    币: 5676
活跃值: (1303)
能力值: ( LV17,RANK:1185 )
在线值:
发帖
回帖
粉丝
holing 15 2017-6-6 05:44
4
0

靠。。。bug了。。。直接doc把。。。。

上传的附件:
雪    币: 598
活跃值: (282)
能力值: ( LV13,RANK:330 )
在线值:
发帖
回帖
粉丝
Fpc 4 2017-6-8 09:16
5
0
详细
雪    币: 255
活跃值: (138)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
livewonder 2017-6-22 10:31
6
0
不错。
游客
登录 | 注册 方可回帖
返回