先检查下文件, 32位的PE,无壳
主函数:
int __cdecl main(int argc, const char **argv, const char **envp) { int result; // eax int v4; // ebx size_t v5; // eax int v6; // ebx char v7[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 *v14)(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(); v14 = func0; v15 = func1; v16 = func2; 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); // v17 = 0 if ( v17 ) { puts("something wrong"); result = 0; } else { for ( i = 0; i < v18; ++i ) // 将data之中的数据写入 v7[20]数组 { v4 = i; v7[v4] = fgetc(v19); } v5 = strlen(v7); // v7之中的有效字符数 if ( v5 <= v18 ) { v18 = v11; i = 0; v17 = v13; while ( i <= 2 ) // 3轮 正常情况下依次调用函数 func0 、 func1 、 func2 { v6 = i + 1; *(&v8 + v6) = (*(&v14 + i))((int)&v8, v12, v13);// 返回值依次填入 v9 v10 v11 v12 = ++i; v13 = i + 1; // 先交换 v8[4*v12] 与 v8[4*v13] 返回值 = 1 } // 返回 abs( v8[4*v13] + v8[4*v12] ) - abs( v8[4*v13] ) - abs( v8[4*v12] ) + 2 if ( v11 ) // 返回 abs(v8[4*v13]) - abs( v8[4*v13] + v8[4*v12] ) + abs( v8[4*v12] ) + 2 { result = -1; // 因为 abs(a)+abs(b) >= abs(a+b) 所以正常情况下第三轮的返回值即 v11 永远大于零 // 所以利用 func0 交换 func1 和 func2 在在栈上的地址 } // // 第二轮开始时 v12 = 1 v13 = 2 else // 第三轮开始时 v12 = 2 v13 = 3 { // 交换之后: get_key(v18, v17); // 第二轮: abs(v9) + abs(v10) - abs( v9 + v10 ) + 2 system("PAUSE"); // 第三轮: abs( v11 + v10 ) - abs( v11 )- abs( v10 ) + 2 result = 0; } } else { result = -1; } } return result; }
通过后面对于 while() 循环的发现可知,正常情况下,3个func走完后 v11是绝对不为 0 的。
在不对程序本身下手的情况下。我们只能把注意打到那个叫 data 的文件上。
刚好程序中 data 写入 v7[20] 这一步因为没有对写入长度进行限制,存在溢出。
而 3 个 func 函数又是通过函数指针进行间接调用,函数的地址也是都写在栈上,还都在 v7[20] 后面:
做过pwn的小伙伴们立刻想到:通过栈溢出改变程序的执行流:
利用 data 的写入覆盖 v12 和 v13
使 v8[4*v12] 和 v8[4*v13] 分别指向 func1 和 func2
在 func0 执行后交换 v15 和 v16,从而改变 func1 和 func2 的执行顺序
于是我们先构造一个 44 字节的data
我试过将v12覆盖为8,v13为7,到最后虽然也能触发 _get_key 但输出的flag值提交上去是错的。
然后是要构造 v9 、v10 、 v11 的值,使 v11在func2 、 func1 执行结束后为 0.
我在注释里也写得挺清楚:
v9 = x v10 = y v11 = z y = |x| + |y| - |x + y| + 2 z = |y + z| - |y| - |z| + 2 z == 0
解不唯一,我算了两个都能用:
v9 = 1 v10 = 0 v11 = -1 v9 = -1 v10 = 1 v11 = -1
最后用 winhex 生成 data :
( 前20字节可以随便写,不用客气的。 )
FLAG:flag{8cda1bdb68a72a392a3968a71bdb8cda}
原文:https://www.cnblogs.com/pmrPMR/p/13796012.html