stack2--攻防世界
这是一道数组越界的题目,记录一下。
常规操作。
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v5; // [esp+18h] [ebp-90h]
unsigned int v6; // [esp+1Ch] [ebp-8Ch]
int v7; // [esp+20h] [ebp-88h]
unsigned int j; // [esp+24h] [ebp-84h]
int v9; // [esp+28h] [ebp-80h]
unsigned int i; // [esp+2Ch] [ebp-7Ch]
unsigned int k; // [esp+30h] [ebp-78h]
unsigned int l; // [esp+34h] [ebp-74h]
char v13[100]; // [esp+38h] [ebp-70h]
unsigned int v14; // [esp+9Ch] [ebp-Ch]
v14 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
v9 = 0;
puts("***********************************************************");
puts("* An easy calc *");
puts("*Give me your numbers and I will return to you an average *");
puts("*(0 <= x < 256) *");
puts("***********************************************************");
puts("How many numbers you have:");
__isoc99_scanf("%d", &v5);
puts("Give me your numbers");
for ( i = 0; i < v5 && (signed int)i <= 99; ++i )
{
__isoc99_scanf("%d", &v7);
v13[i] = v7;
}
for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
__isoc99_scanf("%d", &v6);
if ( v6 != 2 )
break;
puts("Give me your number");
__isoc99_scanf("%d", &v7);
if ( j <= 0x63 )
{
v3 = j++;
v13[v3] = v7;
}
}
if ( v6 > 2 )
break;
if ( v6 != 1 )
return 0;
puts("id\t\tnumber");
for ( k = 0; k < j; ++k )
printf("%d\t\t%d\n", k, v13[k]);
}
if ( v6 != 3 )
break;
puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7;
}
if ( v6 != 4 )
break;
v9 = 0;
for ( l = 0; l < j; ++l )
v9 += v13[l];
}
return 0;
这里是程序的主函数。程序的思路是:创建一个数组然后对数组进行一些操作。先来介绍一下数组越界吧。
因为栈是向下增长的,在进入一个函数之前,会先把参数和下一步要执行的指令地址(通过call实现)压栈,在函数的入口会把ebp压栈,并把esp赋值给ebp,在函数返回的时候,将ebp值赋给esp,pop先前栈内的上级函数栈的基地址给ebp,恢复原栈基址,然后把调用函数之前的压入栈的指令地址pop出来(通过ret实现)。
栈是由高往低增长的,而数组的存储是由低位往高位存的,如果越界的话,会把当前函数的ebp和下一个跳转的指令地址覆盖掉,如果覆盖了当前函数的ebp,那么在恢复的时候esp就不能指向正确的地方,从而导致未可知的情况,如果下一跳的地址也被覆盖掉,那么肯定会导致crash。
以下是我所画的数组元素在栈中的布局:
我们再来看一下反汇编代码。在实现添加数据,查看数据的操作中都没有发现,溢出点。但是在更改数据的时候:
我们需要输入数组的下表,这时下表我们可以随意输入,因为没有对下表进行检查。在IDA中的函数栏中我们看到了 hackhere
思路是将某个函数的返回地址修改成 hackhere这个函数的返回地址。我们需要知道数组的首地址和返回地址的偏移量。这样才能溢出到返回地址并修改返回地址
因为程序在一开始会给数组赋初值,并且这个过程是我们可以参与的。 考虑到一般的数组赋值会从首位开始,我们可以猜想,如果我们知道我们写入程序的第一个数据的存储位置是否我们就知道了数组的首地址?
那么首先,我们来求这个地址
程序一开始会给数组赋值,我们来看一下这一块的伪代码和汇编
从汇编中可以看到程序通过scanf将数据存储到栈中,然后通过eax和ecx将数据存储到eax中存放的地址中去(cl是ecx的低位)
那意味着在程序运行到0x080486D5的位置时,此时eax中存放的即时数组的首地址 linux下我们用gdb调试的看一下
我们在0x080486D5的位置下个断点,输入点全部输入1(如下)
我们来看看此时的各寄存器
可以看到此时寄存器ecx中就是我们输入的1,而eax中的地址是0xffffd5b8
我们来验证一下看看我们做的对不对,分别看看这步前后栈中数据的变化
可以看到d5b8的位置数据由0x000000e0变成了0x00000001,说明我们找的没问题,ok,数组首地址找到了。 接下来的问题就是如何去找函数的返回地址了,这个就简单的多了,我们知道当函数运行到return语句的时候,栈顶一定是返回地址。 继续用gdb调试
通过esp我们知道这个值是0xffffd63c,和首地址做差是0x84原文章:https://zhuanlan.zhihu.com/p/104028967
这里需要注意是hackhere 中的system函数 是开启、bin/bash这种shell,所以不一定能打通,我们可以换成bin/sh 这种shell。