
本文共 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
三个函数指针,分别指向func0
,func1
,func2
,读取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指向的函数)的返回值是赋给v9
;func1
(v15指向的函数)的的返回值是赋给v10
;func2
(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,否则返回其他值。
参数:
- stream:文件指针;
- offset:偏移量,整数表示正向偏移,负数表示负向偏移;
- fromwhere:设定从文件的哪里开始偏移,可能取值为:SEEK_CUR,SEEK_END 或 SEEK_SET
SEEK_SET: 文件开头
SEEK_CUR: 当前位置
SEEK_END: 文件结尾
其中SEEK_SET,SEEK_CUR和SEEK_END和依次为0,1和2;
例子:
- fseek(fp,50L,0);把fp指针移动到离文件开头50字节处;
- fseek(fp,50L,1);把fp指针移动到离文件当前位置50字节处;
- 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()
来决定发生什么情况。
参数:
- buffer :读取的数据存放的内存的指针(可以是数组,也可以是新开辟的空间,buffer就是一个索引);
- size : 每次读取的字节数 ;
- count :读取次数 ;
- 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;}
func1
的return
值化简后为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;}
func2
的return
值化简后为-4a2-a1+abs((4a2 + a1))+2
(注意,这里我把右移31
位直接看成0
了,32
位的数最高位为1被输入的话很难遇到,直接忽略。)
-4a2-a1+abs((4a2 + a1))+2
也就是y=-x+|x|+2的函数图像,如下:
也就是说返回值恒大于0
解决方法分析
- 使func2返回值为0
- 使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
发表评论
最新留言
关于作者
