巅峰极客线上第一场部分ctf

Posted quxiaowqu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了巅峰极客线上第一场部分ctf相关的知识,希望对你有一定的参考价值。

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"[email protected][email protected][email protected][email protected]_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:[email protected][email protected][email protected][email protected]_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)还有就是求解零解。思路按着预期解的思路走。

 关于求零解

 技术分享图片 

技术分享图片       技术分享图片

这两个结果倒是一样。

 

 

 


 

信息安全菜为原罪、




以上是关于巅峰极客线上第一场部分ctf的主要内容,如果未能解决你的问题,请参考以下文章

2014百度之星初赛第一场部分题解

历史上第一个网页长这样 | 极客分享第 29 期

第十二届蓝桥杯省赛C_C++ 大学 B 组第一场部分题解

CTF [极客大挑战 2019]Secret File 复现

云计算 「半步巅峰」

ciscn2021 ctf线上赛baby.bc wp(#超详细,带逆向新手走过一个又一个小坑)