BUUCTF Dig the way
发布日期:2021-05-07 12:08:53 浏览次数:22 分类:原创文章

本文共 5755 字,大约阅读时间需要 19 分钟。

文章目录

题目类型

这道题是一道栈溢出的题目,有点意思。

查壳

在这里插入图片描述
无壳

拖进ida

int __cdecl main(int argc, const char **argv, const char **envp){     int result; // eax@2  int v4; // ebx@6  size_t v5; // eax@8  int v6; // ebx@11  int v7; // [sp+1Ch] [bp-48h]@6  int v8; // [sp+30h] [bp-34h]@1  signed int v9; // [sp+34h] [bp-30h]@1  signed int v10; // [sp+38h] [bp-2Ch]@1  signed int v11; // [sp+3Ch] [bp-28h]@1  int v12; // [sp+40h] [bp-24h]@1  int v13; // [sp+44h] [bp-20h]@1  int (__cdecl *v14)(int, int, int); // [sp+48h] [bp-1Ch]@1  int (__cdecl *v15)(int, int, int); // [sp+4Ch] [bp-18h]@1  int (__cdecl *v16)(int, int, int); // [sp+50h] [bp-14h]@1  __int32 v17; // [sp+54h] [bp-10h]@3  __int32 v18; // [sp+58h] [bp-Ch]@3  FILE *v19; // [sp+5Ch] [bp-8h]@1  __main();  v14 = func0;  v15 = func1;  v16 = func2;  v8 = 0;  v9 = 1;  v10 = 2;  v11 = 3;  v12 = 3;  v13 = 4;  v19 = fopen("data", "rb");  if ( v19 )  {       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;        *((_BYTE *)&v7 + v4) = fgetc(v19);      }      v5 = strlen((const char *)&v7);      if ( v5 <= v18 )      {           v18 = v11;        i = 0;        v17 = v13;        while ( i <= 2 )        {             v6 = i + 1;          *(&v8 + v6) = (*(&v14 + i))(&v8, v12, v13);          v12 = ++i;          v13 = i + 1;        }        if ( v11 )        {             result = -1;        }        else        {             get_key(v18, v17);          system("PAUSE");          result = 0;        }      }      else      {           result = -1;      }    }  }  else  {       result = -1;  }  return result;}

如何获取到flag?
需要执行get_key函数,如果要执行,那么需要保证v11等于0。那么接下来我们来理理整体逻辑,

整体逻辑

v14 = func0;  v15 = func1;  v16 = func2;

v14,v15,v16三个函数指针,分别指向func0func1func2,读取data文件到v7,v7数组只有20个字节的大小,如果文件内容超过20字节,那么就会依次向后(也就是说把v8,v9,v10等等)进行覆盖

 while ( i <= 2 )      {           v6 = i + 1;        *(&v8 + v6) = (*(&v14 + i))((int)&v8, v12, v13);        v12 = ++i;        v13 = i + 1;      }

v8的起始值是0,v6是1,所以这里func0(v14指向的函数)的返回值是赋给v9func1(v15指向的函数)的的返回值是赋给v10func2(v16指向的函数)的的返回值是赋给v11(这里是不是找到关键点了???func2的返回值给v11,继续往下观察)

文件读取函数

fseek函数

FILE *fp = fopen(model_path, "rb");fseek(fp, 0, SEEK_END)

用 法: int fseek(FILE *stream, long offset, int fromwhere);
描 述: 函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset个字节的位置。如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置。
返回值: 成功,返回0,否则返回其他值。
参数:

  1. stream:文件指针;
  2. offset:偏移量,整数表示正向偏移,负数表示负向偏移;
  3. fromwhere:设定从文件的哪里开始偏移,可能取值为:SEEK_CUR,SEEK_END 或 SEEK_SET
    SEEK_SET: 文件开头
    SEEK_CUR: 当前位置
    SEEK_END: 文件结尾

其中SEEK_SET,SEEK_CUR和SEEK_END和依次为0,1和2;
例子:

  1. fseek(fp,50L,0);把fp指针移动到离文件开头50字节处;
  2. fseek(fp,50L,1);把fp指针移动到离文件当前位置50字节处;
  3. fseek(fp,50L,2);把fp指针退回到离文件结尾50字节处。

ftell函数

FILE *fp = fopen(model_path, "rb");fseek(fp, 0, SEEK_END); //fp指针移到文件尾部int model_len = ftell(fp);

用 法: long ftell(FILE *fp);
描 述: 返回当前文件指针位置。这个位置是当前文件指针相对于文件开头的位移量。
返回值:返回文件指针的位置,若出错则返回-1L。
参数:文件指针。

fread函数(补充)

fread(buffer,100,1,fp)  

用 法: size_t fread( void *buffer, size_t size, size_t count, FILE *stream ) ;
描 述: fread()用来从文件流中读取数据。参数stream为已打开的文件指针,参数buffer指向欲存放读取进来的数据空间,读取的字节数以参数size * count来决定。
返回值: 返回实际读取到的count数目,如果此值比参数count来得小,则代表可能读到了文件尾了或者有错误发生(前者几率大),这时必须用feof()ferror()来决定发生什么情况。
参数:

  1. buffer :读取的数据存放的内存的指针(可以是数组,也可以是新开辟的空间,buffer就是一个索引);
  2. size : 每次读取的字节数 ;
  3. count :读取次数 ;
  4. strean:要读取的文件的指针;

分析三个func

func0

signed int __cdecl func0(int a1, int a2, int a3){     int v3; // ST0C_4@1  v3 = *(_DWORD *)(4 * a2 + a1);  *(_DWORD *)(a1 + 4 * a2) = *(_DWORD *)(4 * a3 + a1);  *(_DWORD *)(a1 + 4 * a3) = v3;  return 1;}

func0一看就是利用一个temp把两个东西交换一下。

func1

int __cdecl func1(int a1, int a2, int a3){     int v3; // eax@1  v3 = (*(_DWORD *)(4 * a2 + a1) + *(_DWORD *)(4 * a3 + a1)) >> 31;  return (v3 ^ (*(_DWORD *)(4 * a2 + a1) + *(_DWORD *)(4 * a3 + a1)))       - v3       - (((*(_DWORD *)(4 * a3 + a1) >> 31) ^ *(_DWORD *)(4 * a3 + a1))        - (*(_DWORD *)(4 * a3 + a1) >> 31))       - abs(*(_DWORD *)(4 * a2 + a1))       + 2;}

func1return值化简后为4a2+a1-abs(4a2+a1)+2(注意,这里我把右移31位直接看成0了,32位的数最高位为1被输入的话很难遇到,直接忽略。)
4a2+a1-abs(4a2+a1)+2也就是y=x-|x|+2的函数图像,如下:
在这里插入图片描述
这里返回值的分情况,有小于0,有大于0,有等于0

func2

int __cdecl func2(int a1, int a2, int a3){     int v3; // ecx@1  v3 = (*(_DWORD *)(4 * a3 + a1) + *(_DWORD *)(4 * a2 + a1)) >> 31;  return ((*(_DWORD *)(4 * a3 + a1) >> 31) ^ *(_DWORD *)(4 * a3 + a1))       - (*(_DWORD *)(4 * a3 + a1) >> 31)       - ((v3 ^ (*(_DWORD *)(4 * a3 + a1) + *(_DWORD *)(4 * a2 + a1)))        - v3)       + abs(*(_DWORD *)(4 * a2 + a1))       + 2;}

func2return值化简后为-4a2-a1+abs((4a2 + a1))+2(注意,这里我把右移31位直接看成0了,32位的数最高位为1被输入的话很难遇到,直接忽略。)
-4a2-a1+abs((4a2 + a1))+2也就是y=-x+|x|+2的函数图像,如下:
在这里插入图片描述
也就是说返回值恒大于0

解决方法分析

  1. 使func2返回值为0
  2. 使v16函数指针指向的函数改变(指向v15)

第一种方法,我们所画出的函数恒为正,直接pass
第二种方法,如何改变呢?这里func0本来就是用来交换的,直接在调用func0函数时传func1函数的函数指针v15和func2函数的函数指针v16进行交换即可。交换之后,v16函数指针指向的函数就是func1,func1的返回值可正可负可为零,返回值赋给了v11,然后就可以对flag进行打印输出。

分析

signed int __cdecl func0(int a1, int a2, int a3){     int v3; // ST0C_4  v3 = *(_DWORD *)(4 * a2 + a1);  *(_DWORD *)(a1 + 4 * a2) = *(_DWORD *)(4 * a3 + a1);  *(_DWORD *)(a1 + 4 * a3) = v3;  return 1;}
 *(&v8 + v6) = (*(&v14 + i))((int)&v8, v12, v13);

观察func0,此时func0函数中参与运算的起始地址是v8,偏移分别是v12(3)和v13(4),也就是说,此时func0交换的参数是v11和v12。要使参数变为v15和v16,就需要把偏移改为7和8,也就是把v12的值改为7,v13 的值改为8(其实颠倒过来也行)
在这里插入图片描述
v7有20个字节,v8~v11是4个int,也就是4x4=16个字节,于是data文件需要从第36个字节开始,将v12和v13覆盖为7和8。

此时仅仅只是交换了两个函数指针,如何保证func1的返回值为0呢?
v15执行func2函数,v16执行func1函数,而在循环中v12和v13由i赋值,当执行v16(func1)函数时,v12和v13的值分别为2和3

 while ( i <= 2 )      {           v6 = i + 1;        *(&v8 + v6) = (*(&v14 + i))((int)&v8, v12, v13);        v12 = ++i;        v13 = i + 1;      }

4a2+a1-abs(4a2+a1)+2,v10等于2,要使返回的值为0,则需v11为-1,于是同样利用程序data读文件的漏洞将v11覆盖为-1
在这里插入图片描述
在这里插入图片描述

flag: 8cda1bdb68a72a392a3968a71bdb8cda
上一篇:[NPUCTF2020]芜湖(Base64隐写)
下一篇:BUUCTF 特殊的BASE64

发表评论

最新留言

网站不错 人气很旺了 加油
[***.192.178.218]2025年04月18日 04时58分00秒