IDA Pro实践

发布于 2018-07-22  791 次阅读


ISCC 2018 leftleftrightright题目下载

Download

分析

解压后文件夹里只有一个leftleftrightright.exe文件,先运行一下发现什么都没有显示,随便输入一些字符串后会显示"try again!"。然后,拖到PEID里查壳

IDA Pro实践

可以看到有一个UPX的壳,这个UPX的壳可以用UPX Unpacker脱壳

IDA Pro实践

脱完壳后加载到IDA中分析,直接Shift+F12查找字符串,可以找到"try again!"和一些与flag有关的字符串,双击"try again!"这个字符串跳转到rdata段,然后在跳转到相对应的汇编代码段。
F5查看伪代码

int sub_401170()
{
  int v0; // edx@1
  int v1; // ebx@1
  int v2; // edi@1
  void **v3; // esi@1
  int v4; // ecx@1
  void **v5; // eax@2
  void **v6; // edx@3
  void *v7; // eax@5
  char *v8; // ecx@6
  unsigned int v9; // eax@8
  void *v10; // eax@16
  unsigned int v11; // eax@19
  unsigned int v12; // edi@26
  signed int v13; // eax@26
  const char *v14; // edx@30
  int v15; // eax@32
  int v16; // ecx@32
  unsigned int v17; // eax@36
  unsigned int v18; // ecx@38
  unsigned int v19; // eax@48
  unsigned int v20; // esi@50
  int v22; // [sp+10h] [bp-5Ch]@1
  void *Memory; // [sp+14h] [bp-58h]@5
  unsigned int v24; // [sp+28h] [bp-44h]@5
  void *v25; // [sp+2Ch] [bp-40h]@1
  int v26; // [sp+3Ch] [bp-30h]@1
  unsigned int v27; // [sp+40h] [bp-2Ch]@1
  void *Dst; // [sp+44h] [bp-28h]@1
  unsigned int v29; // [sp+54h] [bp-18h]@1
  unsigned int v30; // [sp+58h] [bp-14h]@1
  int v31; // [sp+68h] [bp-4h]@1

  v27 = 15;
  v26 = 0;
  LOBYTE(v25) = 0;
  v31 = 0;
  v30 = 15;
  v29 = 0;
  LOBYTE(Dst) = 0;
  LOBYTE(v31) = 1;
  sub_401F60(std::cin, &v25);
  v1 = v26;
  v2 = 0;
  v3 = (void **)v25;
  v4 = v26 - 1;
  v22 = v26 - 1;
  while ( v1 > 0 )
  {
    --v1;
    v5 = &v25;
    if ( v1 & 2 )
    {
      v6 = &Dst;
      if ( v27 >= 0x10 )
        v5 = v3;
      LOBYTE(v6) = *((_BYTE *)v5 + v4);
      v7 = (void *)sub_401AD0((int)&Memory, (int)v6, &Dst);
      sub_401480(&Dst, v7);
      if ( v24 >= 0x10 )
      {
        v8 = (char *)Memory;
        if ( v24 + 1 >= 0x1000 )
        {
          if ( (unsigned __int8)Memory & 0x1F )
            goto LABEL_35;
          v9 = *((_DWORD *)Memory - 1);
          if ( v9 >= (unsigned int)Memory )
            goto LABEL_35;
          v8 = (char *)Memory - v9;
          if ( (char *)Memory - v9 < (char *)4 || (unsigned int)v8 > 0x23 )
            goto LABEL_35;
          v8 = (char *)*((_DWORD *)Memory - 1);
        }
        j_free(v8);
      }
      v4 = v22-- - 1;
    }
    else
    {
      if ( v27 >= 0x10 )
        v5 = v3;
      LOBYTE(v0) = *((_BYTE *)v5 + v2);
      v10 = (void *)sub_401AD0((int)&Memory, v0, &Dst);
      sub_401480(&Dst, v10);
      if ( v24 >= 0x10 )
      {
        v8 = (char *)Memory;
        if ( v24 + 1 >= 0x1000 )
        {
          if ( (unsigned __int8)Memory & 0x1F )
            goto LABEL_35;
          v11 = *((_DWORD *)Memory - 1);
          if ( v11 >= (unsigned int)Memory )
            goto LABEL_35;
          v8 = (char *)Memory - v11;
          if ( (char *)Memory - v11 < (char *)4 || (unsigned int)v8 > 0x23 )
            goto LABEL_35;
          v8 = (char *)*((_DWORD *)Memory - 1);
        }
        j_free(v8);
      }
      v4 = v22;
      ++v2;
    }
  }
  v12 = v29;
  v13 = 29;
  if ( v29 < 0x1D )
    v13 = v29;
  if ( sub_401090(v13) || v12 < 0x1D || (v14 = "flag is right!", v12 > 0x1D) )
    v14 = "try again!";
  v15 = sub_401BF0(std::cout, v14);
  std::basic_ostream<char,std::char_traits<char>>::operator<<(v15, sub_401E30);
  system("pause");
  if ( v30 >= 0x10 )
  {
    v8 = (char *)Dst;
    if ( v30 + 1 >= 0x1000 )
    {
      if ( (unsigned __int8)Dst & 0x1F )
LABEL_35:
        invalid_parameter_noinfo_noreturn(v8);
      v17 = *((_DWORD *)v8 - 1);
      if ( v17 >= (unsigned int)v8 )
        v17 = invalid_parameter_noinfo_noreturn(v8);
      v18 = (unsigned int)&v8[-v17];
      if ( v18 < 4 )
        v17 = invalid_parameter_noinfo_noreturn(v18);
      if ( v18 > 0x23 )
        v17 = invalid_parameter_noinfo_noreturn(v18);
      v8 = (char *)v17;
    }
    j_free(v8);
  }
  v30 = 15;
  v29 = 0;
  LOBYTE(Dst) = 0;
  if ( v27 >= 0x10 )
  {
    if ( v27 + 1 >= 0x1000 )
    {
      if ( (unsigned __int8)v25 & 0x1F )
        invalid_parameter_noinfo_noreturn(v16);
      v19 = (unsigned int)*(v3 - 1);
      if ( v19 >= (unsigned int)v3 )
        v19 = invalid_parameter_noinfo_noreturn(v16);
      v20 = (unsigned int)v3 - v19;
      if ( v20 < 4 )
        v19 = invalid_parameter_noinfo_noreturn(v16);
      if ( v20 > 0x23 )
        v19 = invalid_parameter_noinfo_noreturn(v16);
      v3 = (void **)v19;
    }
    j_free(v3);
  }
  return 0;
}

IDA Pro实践

发现这段代码对输入的字符串进行了限制,长度必须为29,然后上面的那个循环就是对输入的字符串打乱。在if里发现了sub_401090(v13)函数,跟进去之后的伪代码

int __cdecl sub_401090(unsigned int a1)
{
  int v1; // ecx@0
  const char *v3; // esi@3
  unsigned int v4; // edx@3
  bool v5; // cf@7
  unsigned __int8 v6; // al@9
  unsigned __int8 v7; // al@11
  unsigned __int8 v8; // al@13

  if ( !a1 )
    return 0;
  v3 = "s_imsaplw_e_siishtnt{g_ialt}F";
  v4 = a1 - 4;
  if ( a1 < 4 )
  {
LABEL_6:
    if ( v4 == -4 )
      return 0;
  }
  else
  {
    while ( *(_DWORD *)v1 == *(_DWORD *)v3 )
    {
      v1 += 4;
      v3 += 4;
      v5 = v4 < 4;
      v4 -= 4;
      if ( v5 )
        goto LABEL_6;
    }
  }
  v5 = *(_BYTE *)v1 < (const unsigned __int8)*v3;
  if ( *(_BYTE *)v1 != *v3 )
    return -v5 | 1;
  if ( v4 != -3 )
  {
    v6 = *(_BYTE *)(v1 + 1);
    v5 = v6 < v3[1];
    if ( v6 != v3[1] )
      return -v5 | 1;
    if ( v4 != -2 )
    {
      v7 = *(_BYTE *)(v1 + 2);
      v5 = v7 < v3[2];
      if ( v7 != v3[2] )
        return -v5 | 1;
      if ( v4 != -1 )
      {
        v8 = *(_BYTE *)(v1 + 3);
        v5 = v8 < v3[3];
        if ( v8 != v3[3] )
          return -v5 | 1;
      }
    }
  }
  return 0;
}

找到了特殊字符串"s_imsaplw_e_siishtnt{g_ialt}F",后面的代码大致是把打乱后的字符串与整个特殊字符串进行对比。
然后开始用IDA进行动态调试,脱完壳的程序调试不了,只能拿原来加了壳的进行调试。
先找到start函数的入口,然后拉到后面可以看到一个红色高亮显示的"start endp ; sp-analysis failed",
在这一行的上面可以看到一个"jmp loc_1924C7"在这条代码的地方下一个断点,然后开始动态调试。
然后在程序窗口里输入29位无重复的字符串:0123456789abcdefghiklmnopqrst,输入完后不会显示,字符串还在缓存里面。
然后按F8进行单步调试。

IDA Pro实践

调试到我们输入的字符串显示出来的时候,这个时候调试会停住,按下回车才会继续。我们在这里下一个断点,按下回车程序会直接结束。

IDA Pro实践

这时候我们重新再调一遍。
运行到我们刚才下段点的地方的时候我们按F7加入这个函数里面。
然后继续F8单步调试,调试到程序窗口出现我们输入的字符串的时候按下回车继续,这时候会发现程序没停止,还是可以继续调试。
之后会进入一个循环,这个循环会把我们输入的字符串一个一个的放到Ecx寄存器里,一直按F8就可以了。
循环结束后继续运行一会后会进入一个新的循环,并且把Ecx中的字符串放到Esi中,这个循环就是打乱输入的那个循环。
这时候可以往下找一下有没有特殊的字符串,有的话可以在那里下一个断点跳出循环。

IDA Pro实践

这时候我们就可以看打乱后的字符串了。把鼠标放在Ecx后面的数据地址上面,然后往下滚鼠标滑轮,可以看到全部的字符串。

IDA Pro实践

然后把刚才的那个特殊字符串"s_imsaplw_e_siishtnt{g_ialt}F"进行位移,规则可以从这两个字符串"edfgcbhia9kl87mn65op43qr21st0","0123456789abcdefghiklmnopqrst"的位置关系发现。

C++脚本

#include <iostream>
#include <string>
#include <map>

using namespace std;

int main() {
    map<char, int> m;
    string str1 = "s_imsaplw_e_siishtnt{g_ialt}F";
    string str2 = "edfgcbhia9kl87mn65op43qr21st0";
    string str3 = "0123456789abcdefghiklmnopqrst";
    string flag = "";
    for (int i = 0; i < 29; i++) 
        m[str2[i]] = i;
    for (int i = 0; i < 29; i++)
        flag += str1[m[str3[i]]];
    cout << flag << endl;
}

运行后的flag:Flag{this_was_simple_isnt_it}