zoukankan      html  css  js  c++  java
  • 巅峰极客线上第一场ctf——RE

    Input your lucky number

    要求输入一个数字。

    程序有ASLR,可以去掉便于分析。

     F5

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      signed int v3; // edx
      char *v4; // eax
      char *v5; // ecx
      int v6; // edi
      signed int v7; // esi
      signed int v8; // esi
      __m128i *v9; // edi
      int v11; // [esp+0h] [ebp-3Ch]
      int input; // [esp+14h] [ebp-28h]
      char v13; // [esp+18h] [ebp-24h]
      __int64 v14; // [esp+19h] [ebp-23h]
      int v15; // [esp+21h] [ebp-1Bh]
      int *v16; // [esp+2Ch] [ebp-10h]
      int v17; // [esp+38h] [ebp-4h]
    
      v16 = &v11;
      printf(std::cout, "input your lucky number: ");
      std::basic_istream<char,std::char_traits<char>>::operator>>(std::cin, &input);
      v13 = 0;
      v3 = 0;
      v15 = 0;
      v17 = 0;
      v4 = (char *)&loc_401000 + 2;
      v14 = 0i64;
      v5 = (char *)&loc_401000 + 2;
      while ( *(v5 - 2) != 0xC7u || *(v5 - 1) != 5 || *(int **)v5 != &dword_4043A8 || *((_DWORD *)v5 + 1) != 0x89898989 )
      {
        ++v3;                                       // 9
        ++v5;
        if ( v3 >= 1000 )
          return 0;
      }
      if ( v3 != -1 )
      {
        v6 = v3 + 10;
        v7 = 0;
        while ( *(v4 - 2) != 0xC7u || *(v4 - 1) != 5 || *(int **)v4 != &dword_4043A4 || *((_DWORD *)v4 + 1) != 0x98989898 )
        {
          ++v7;                                     // 0x74
          ++v4;
          if ( v7 >= 1000 )
            return 0;
        }
        if ( v7 != -1 )
        {
          v8 = v7 - v6;                             // 0x61
          v9 = (__m128i *)((char *)&loc_401000 + v6);// 401013
          sub_401100(v8, v9, input);
          ((void (__cdecl *)(const char *, char *))loc_401000)("seed_of_flag", &v13);
          sub_401100(v8, v9, input);
        }
      }
      return 0;
    }

    关键函数401100,传了0x61、0x401013、我们输入的值

    反汇编时看到用到了XMM0、XMM1寄存器,用WinDBG调试。

    我输的是189,16进制是0xBD。

    从上面可以看出,401100函数的作用是把0x401013到0x401013+0x61 =  0x401074的每个字节,跟我们输入的这个数据进行异或。

    PS:

    从这儿看出是只取我们输入的数据的低8位的。之前一直在拿1024在测,一直在踩雷orz。

     

    然后会返回401000执行程序。

    一般来说,会有许多的0出现在文件中,401013到401074出现较多的0X5A,试着输一下0x5a = 90。

     

     


     Simple Base-N

     F5

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      const char *v3; // ecx
      char *v4; // eax
      bool v5; // cf
      unsigned __int8 v6; // dl
      int v7; // eax
      int v9; // eax
      const char *v10; // edx
    
      sub_401590(std::cout, "please input your flag:");
      sub_4017D0(std::cin);
      if ( (signed int)strlen(input) >= 10 )
      {
        change_i(input);
        v3 = "guvf_vf_n_snxr_synt";
        v4 = input;
        while ( 1 )      // v7 = strcmp(input, "guvf_vf_n_snxr_synt");
        {
          v5 = (unsigned __int8)*v4 < *v3;
          if ( *v4 != *v3 )
            break;
          if ( !*v4 )
            goto LABEL_7;
          v6 = v4[1];
          v5 = v6 < v3[1];
          if ( v6 != v3[1] )
            break;
          v4 += 2;
          v3 += 2;
          if ( !v6 )
          {
    LABEL_7:
            v7 = 0;
            goto LABEL_9;
          }
        }
        v7 = -v5 | 1;
    LABEL_9:
        if ( !v7 )
        {
          sub_401590(std::cout, "try a little bit harder!
    ");
          return 0;
        }
        chang_table(v3);
        base32(&input[1]);
        v9 = strcmp(a0, "weNTDk5LZsNRHk6cVogqTZmFy2NRP7X4ZHLTBZwg");
        if ( v9 )
          v9 = -(v9 < 0) | 1;
        v10 = "Congratulations!!!
    ";
        if ( v9 )
          v10 = "soooooooooorry
    ";
        sub_401590(std::cout, v10);
        system("pause");
      }
      return 0;
    }

     

    输进来的字符串首先会做一个如下的变化:

    signed int __thiscall sub_401100(const char *this)
    {
      const char *v1; // edi
      unsigned int i; // esi
      char v3; // cl
    
      v1 = this;
      i = 0;
      if ( strlen(this) )
      {
        do
        {
          v3 = v1[i];
          if ( (unsigned __int8)(v3 - 97) <= 0x19u )
            v1[i] = (v3 - 84) % 26 + 97;
          if ( (unsigned __int8)(v3 - 65) <= 0x19u )
            v1[i] = (v3 - 52) % 26 + 65;
          ++i;
        }
        while ( i < strlen(v1) );
      }
      return 1;
    }

    然后main函数中间的while(1)等价于strcmp(input, "guvf_vf_n_snxr_synt");  不能让他们相等,不然就会直接return 0出去了。这里的while(1)是个迷惑作用,真正要分析的在下面。

    chang_table(v3); OD调试时,发现一串字符串。跟base32的table很像,也是26个字母加6个数字。

    base32(&input[1]); IDA进到这个函数里面有个sub_401170函数,sub_401170函数里面是base32基本特征。有&0x1F、填充等号“=”“==”“===”“====”

    而且这个函数里的table跟chang_table里出来的那个字符串一个地址,所以这是一个改变了table表的base32。

    最后再跟“weNTDk5LZsNRHk6cVogqTZmFy2NRP7X4ZHLTBZwg”比较,要相等。

    综上,将“weNTDk5LZsNRHk6cVogqTZmFy2NRP7X4ZHLTBZwg”解变形base32再进行一个变换。

    这里直接把python自带的base64里table改掉了。

    再进行解base32

    再进行变换

    # -*- coding: utf-8 -*-
    s = b"L@h_Xa@J_o@f332_@Aq_e0g13"
    
    flag = ''
    for i in range(len(s)):
        ch = s[i]
        if ((chr(ch) >= 'a' and chr(ch) <= 'z')):
            ch = (ch - 84)%26 + 97
        if ((chr(ch) >= 'A' and chr(ch) <= 'Z')):
            ch = (ch - 52)%26 + 65
        flag += chr(ch)
            
    print(flag)

    flag:Y@u_Kn@W_b@s332_@Nd_r0t13


    Interesting Pointer

    F5

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
      int result; // eax
      int v4; // ebx
      size_t len; // eax
      int v6; // ebx
      char data[20]; // [esp+1Ch] [ebp-48h]
      int v8; // [esp+30h] [ebp-34h]
      int v9; // [esp+34h] [ebp-30h]
      int v10; // [esp+38h] [ebp-2Ch]
      int v11; // [esp+3Ch] [ebp-28h]
      int v12; // [esp+40h] [ebp-24h]
      int v13; // [esp+44h] [ebp-20h]
      signed int (__cdecl *func_ptr)(int, int, int); // [esp+48h] [ebp-1Ch]
      int (__cdecl *v15)(int, int, int); // [esp+4Ch] [ebp-18h]
      int (__cdecl *v16)(int, int, int); // [esp+50h] [ebp-14h]
      int v17; // [esp+54h] [ebp-10h]
      int v18; // [esp+58h] [ebp-Ch]
      FILE *v19; // [esp+5Ch] [ebp-8h]
    
      __main();
      func_ptr = func0;                             // v8+a 跟 v8+b 交换  
      v15 = func1;                                  // abs(m+n) - abs(m) - abs(n) + 2  ==> v10
      v16 = func2;                                  // abs(x) + abs(y) - abs(x+y) + 2  ==> v11  
      v8 = 0;
      v9 = 1;
      v10 = 2;
      v11 = 3;
      v12 = 3;
      v13 = 4;
      v19 = fopen("data", "rb");
      if ( !v19 )
        return -1;
      fseek(v19, 0, 2);                             // 文件尾
      v18 = ftell(v19);                             // 返回读写位置
      fseek(v19, 0, 0);                             // 文件头
      v17 = ftell(v19);                             // 返回读写位置
      if ( v17 )
      {
        puts("something wrong");
        result = 0;
      }
      else
      {
        for ( i = 0; i < v18; ++i )
        {
          v4 = i;
          data[v4] = fgetc(v19);                    // data
        }
        len = strlen(data);
        if ( len <= v18 )
        {
          v18 = v11;
          i = 0;
          v17 = v13;
          while ( i <= 2 )
          {
            v6 = i + 1;
            *(&v8 + v6) = (*(&func_ptr + i))((int)&v8, v12, v13);// 
                                                    // fun0 : 1  ==> v9
                                                    // fun1 : abs(m+n) - abs(m) - abs(n) + 2  ==> v10
                                                    // fun2 : abs(x) + abs(y) - abs(x+y) + 2  ==> v11  
                                                    // 
            v12 = ++i;
            v13 = i + 1;
          }
          if ( v11 )
          {
            result = -1;
          }
          else
          {
            get_key(v18, v17);
            system("PAUSE");
            result = 0;
          }
        }
        else
        {
          result = -1;
        }
      }
      return result;
    }

    由上看出:要想进到get_key函数,要使v11的值为0。由它原本的程序走下来是不可能给v11赋到0的。

    分析下具体的函数功能

    func0(&v8,v12,v13):进行两个整型数据的交换,&v8 + v12 和 &v8 + v13 两个地址处的数据进行交换。且这个函数固定返回1.

    func1(&v8,v12,v13):abs(m+n) - abs(m) - abs(n) + 2。m是地址为 &v8 + v12 处的整型数据,n是地址为  &v8 + v13 处的整型数据。

    func2(&v8,v12,v13):abs(x) + abs(y) - abs(x+y) + 2。x是地址为 &v8 + v12 处的整型数据,y是地址为  &v8 + v13 处的整型数据。

    for ( i = 0; i < v18; ++i )
        {
          v4 = i;
          data[v4] = fgetc(v19);                    // data
        }
      char data[20]; // [esp+1Ch] [ebp-48h]
      int v8; // [esp+30h] [ebp-34h]
      int v9; // [esp+34h] [ebp-30h]
      int v10; // [esp+38h] [ebp-2Ch]
      int v11; // [esp+3Ch] [ebp-28h]
      int v12; // [esp+40h] [ebp-24h]
      int v13; // [esp+44h] [ebp-20h]
      signed int (__cdecl *func_ptr)(int, int, int); // [esp+48h] [ebp-1Ch]
      int (__cdecl *v15)(int, int, int); // [esp+4Ch] [ebp-18h]
      int (__cdecl *v16)(int, int, int); // [esp+50h] [ebp-14h]
      int v17; // [esp+54h] [ebp-10h]
      int v18; // [esp+58h] [ebp-Ch]
      FILE *v19; // [esp+5Ch] [ebp-8h]

    这里存在溢出。并没有限制data数组的大小,能覆盖掉后面的变量,因为这几个变量的初始化都在这个数组赋值之前。

    绝对值不等式:|a| - |b| ≤ |a + b| ≤| a| + |b|    

    |a + b| ≤ |a| + |b|  取"="的条件是ab≥0

    |a -  b| ≤ |a| + |b|  取"="的条件是ab≤0

    |a + b| ≥ |a| -  |b|  取"="的条件是(a+b)b≤0

    |a - b|  ≥ |a| -  |b|  取"="的条件是(a-b)b≥0

    所以func2:abs(x) + abs(y) - abs(x+y) + 2 这个怎么算都i是>=2的,根据正常的流程,中间的那个while循环中,func2的返回值就是给v11,v11怎么也不可能为0,所以这个地方,即while第三次循环时*(&v8 + v6) = (*(&func_ptr + i))((int)&v8, v12, v13);  (ps:第三次时v6 = 3,即 v11 = *(&func_ptr + 2)((int)&v8, v12, v13);)   *(&func_ptr + 2) 即v16不能是func2。

    通过溢出,这里有许多解法。


    1、理想解:

    利用溢出将v12和v13重新覆盖为7和8,这样exchange就会把v15(&8+7)和v16(&v8+8)两个变量里面的值交换。这样就是v9 = 1;  v10 = func2(&v8, 1, 2);  v11 = func1(&v8, 2, 3)

    • 现在func2(&v8, 1, 2)就是把  x = *(&v8 +1); y = *(&v8 + 2) 即 x = v9 = 1,y = v10,来进行  abs(x) + abs(y) - abs(x+y) + 2  运算,这个结果是大于等于2的。这个运算结果再返回给v10。已经确定x为肯定为1,y如果取大于等于0的数,运算结果会总是2。取小于0的数,运算结果是4。

       假设通过溢出覆盖v10的时候是个大于等于0的数,那么这里v10 = func2(&v8, 1 ,2); 返回给v10的值为2。

    • 再往下func1(&v8, 2, 3)就是把  m = *(&v8 +2); n = *(&v8 + 3) 即 m = v10 = 2,m = v11,abs(m+n) - abs(m) - abs(n) + 2  运算,按着上面我们的假设,这里确定v10即m为2,且这里个函数的返回值是要赋给v11的,所以这里要使这个不等式运算结果为0。|2 + n| - 2  - |n| + 2 = 0  ==> 解得 n = -1。(但其实由于计算机的补码、符号位,这里还有一个解 0x7FFFFFFF (这是非预期解)。)即通过溢出覆盖后v11的值要为-1(0xFFFFFFFF)。

    所以综上,这里就是通过data数组的溢出来进行覆盖,要确保v12跟v13为7和8(可以交换,也可以为8和7),还需确保v11为0x7FFFFFFF

     get_key(-1,8)是他的预期解。所以v12跟v13为7跟8这样会产生非预期。

    AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA 
    AA AA AA AA BB BB BB BB BB BB BB BB BB BB BB BB
    FF FF FF FF 07 00 00 00 08 00 00 00

     

     v12跟v13为7跟8的非预期:

    2、非预期解

    大佬们的神奇操作。

    1)直接把v16原来是func2的地址直接换成了0x00401870:xor eax,eax; retn;   真的是妙。

     

    这个就能产生很多的非预期的结果。

    还有不交换,直接把v16的地址覆盖成func1。

    2)还有就是求解零解。思路按着预期解的思路走。

     关于求零解

      

           

    这两个结果倒是一样。


    信息安全菜为原罪、

  • 相关阅读:
    window.onload与$(document).ready()的区别
    性能优化篇
    Redis配置文件参考
    Redis基础介绍&安装部署
    lazyfree 和memory usage源码分析
    Greenplum启动失败Error occurred: non-zero rc: 1的修复
    MongoDB启动文件配置参数详解
    MongoDB添加仲裁节点报错replica set IDs do not match办法
    Greenplum扩容
    MPP架构海量数据分析仓库——Greenplum介绍
  • 原文地址:https://www.cnblogs.com/ha2ha2/p/9634651.html
Copyright © 2011-2022 走看看