首页 > 一个简单的缓冲区溢出的思考

一个简单的缓冲区溢出的思考

  从大二开始真正接触技术开始,从最早的HTML,PHP,WEB开发。一直以为以后可能会从事开发的工作,碰巧大三上的时候和同专业的郭子,邹豪参加了南京的一个信息安全技能大赛,才真正找到了兴趣的方向,也从懵懵懂懂开始懂了信安怎么学,像海贼王里面一样,感觉到了新世界了(好吧,我又YY了......)

  最近参加了ISCC2013的比赛,历时一个月的过程,虽然过程很辛苦,每天除了吃饭睡觉都在想题目(我不会告诉你我有一题是在睡觉的时候想出来的,做梦梦到单步调试....)。但是感觉收获颇丰,貌似比以前牛逼了一点点。也因此萌生了要开一个技术博客,写一点自己原创的东西,算是总结提高吧。

  在做溢出关第一题的时候,遇到了缓冲区溢出的问题,之前从没接触过这东西,汇编也不懂。于是各种查资料,看小甲鱼的汇编视频,总算是把原理搞明白了,希望在这里分享一些关于缓冲区溢出和汇编的基础知识,小白一个,大神路过不要拍砖啊。

  首先看一段代码:

#include

void why_here(void) /*这个函数没有任何地方调用过*/

{

  printf("why u here ?! ");

  _exit(0);

}

int main(int argc,char * argv[])

{

  int buff[1];

  buff[2]=(int)why_here;

  return 0;

}

用GCC或者VC编译运行一下,会发现why_here函数被运行了,地球人都知道这是缓冲区溢出,但是在底层代码到底发生了什么呢?

原因是是因为错误的越界溢出赋值导致了函数返回地址被错误的覆盖了,程序流执行到这里之后不是正常的返回而是执行了被覆盖的地址。这样说有点抽象,我们用VC进行debug一下,看看汇编代码到底做了什么。

发生溢出的C代码:

6:    int main(int argc,char * argv[])

7:    {

00401070   push        ebp

00401071   mov         ebp,esp

00401073   sub         esp,44h

00401076   push        ebx

00401077   push        esi

00401078   push        edi

00401079   lea         edi,[ebp-44h]

0040107C   mov         ecx,11h

00401081   mov         eax,0CCCCCCCCh

00401086   rep stos    dword ptr [edi]

8:        int buff[1];

9:        buff[2]=(int)why_here;

00401088   mov         dword ptr [ebp+4],offset @ILT+5(why_here) (0040100a)

10:       return 0;

0040108F   xor         eax,eax

11:   }

00401091   pop         edi

00401092   pop         esi

00401093   pop         ebx

00401094   mov         esp,ebp

00401096   pop         ebp

00401097   ret



这两行是函数调用的常用语句,EBP是基址寄存器,目的是在进入函数前将EBP保护起来,一遍出来的时候可以恢复现场(计算机组成原理上的知识)。

下面这句  sub   esp,44h 应该就是为新进入的函数预留栈空间。接下来就是经典的三个push为main函数传参。

............

我们重点看这行代码:

00401088   mov     dword ptr [ebp+4],offset @ILT+5(why_here) (0040100a)

将why_here的函数入口基地址放入[ebp+4]中,这样看也许看不出什么不妥,我们对比一下正常的情况。

下面是正规规范编写的C代码:

#include

void why_here(void) /*这个函数没有任何地方调用过*/

{

  printf("why u here ?! ");

  _exit(0);

}

int main(int argc,char * argv[])

{

  int buff[1];

  buff[0]=(int)why_here;(因为buff只申请了一个int型,也就是4字节的内存空间,所以只能只能保存一个32位的函数RVA地址)

  return 0;

}

对应的汇编

6:    int main(int argc,char * argv[])

7:    {

00401070   push        ebp

00401071   mov         ebp,esp

00401073   sub         esp,44h

00401076   push        ebx

00401077   push        esi

00401078   push        edi

00401079   lea         edi,[ebp-44h]

0040107C   mov         ecx,11h

00401081   mov         eax,0CCCCCCCCh

00401086   rep stos    dword ptr [edi]

8:        int buff[1];

9:        buff[0]=(int)why_here;

00401088   mov         dword ptr [ebp-4],offset @ILT+5(why_here) (0040100a)

10:       return 0;

0040108F   xor         eax,eax

11:   }

00401091   pop         edi

00401092   pop         esi

00401093   pop         ebx

00401094   mov         esp,ebp

00401096   pop         ebp

00401097   ret



  我看到这里的时候就恍然大悟了,嗖嘎斯内。因为溢出赋值覆盖了EIP(也就是指令寄存器,导致了函数返回后,程序的执行流被改变了)。

这里我们详细讨论一下:

进入main 函数后的栈内容下:

[ eip ][ ebp ][ buff[0] ]

高地址<---- 低地址

  因为在栈空间中,栈是向下生长的,而赋值是向上生长的。所以正常情况下[ebp-4]的赋值是正常的系统允许的,因为这个空间本来就系统为你分配好了。但是如果我们越界赋值[ebp+4]就会怎么?

  对!导致了EIP被覆盖,即发生了缓冲区溢出,可能有朋友要问了,你这么说有什么依据呢?我们还是回到汇编代码:

00401070   push        ebp

00401071   mov         ebp,esp

00401073   sub         esp,44h

00401076   push        ebx

00401077   push        esi

00401078   push        edi

在开头的4个push入栈操作对应结尾了4个pop出栈操作。

00401091   pop         edi

00401092   pop         esi

00401093   pop         ebx

00401094   mov         esp,ebp

00401096   pop         ebp

00401097   ret

EBP被完好的保存了,注意到这里有个汇编指令ret,这是返回的意思,它的原理实质上等于2条汇编指令的组合:

pop EIP

jmp

将栈上的最底下的4字节空间弹栈赋值给EIP,并跳转到EIP对应的那条指令执行。而这个所谓的栈底的4个空间的内容就是在进入main函数前入栈的,目的是为了等main函数执行完后可以继续执行main之后的指令,这也就是一般的函数调用的原理(因为这里只有一个main函数,所有有些特殊,不过本质上一样的)。

而所谓的溢出本质上是我们覆盖了这段EIP对应的栈空间,然后操作系统就傻乎乎的以为这就是真实的返回地址,pop并执行了,然后就.........所以说童话里都是骗人的

这个程序很简单,但是第一次分析的时候也花了我不少力气,以后希望能更多的研究一些原理性的东西,也继续和大家分享!

最后,希望这次的ISCC能玩的开心

  

转载于:https://www.cnblogs.com/LittleHann/archive/2013/05/26/3100165.html

更多相关:

  •         Apache POI是一个开源的利用Java读写Excel,WORD等微软OLE2组件文档的项目。        我的需求是对Excel的数据进行导入或将数据以Excel的形式导出。先上简单的测试代码:package com.xing.studyTest.poi;import java.io.FileInputSt...

  • 要取得[a,b)的随机整数,使用(rand() % (b-a))+ a; 要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a; 要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1; 通用公式:a + rand() % n;其中的a是起始值,n是整数的范围。 要取得a到b之间的...

  • 利用本征图像分解(Intrinsic Image Decomposition)算法,将图像分解为shading(illumination) image 和 reflectance(albedo) image,计算图像的reflectance image。 Reflectance Image 是指在变化的光照条件下能够维持不变的图像部分...

  • 题目:面试题39. 数组中出现次数超过一半的数字 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。 示例 1: 输入: [1, 2, 3, 2, 2, 2, 5, 4, 2] 输出: 2 限制: 1 <= 数组长度 <= 50000 解题: cl...

  • 题目:二叉搜索树的后序遍历序列 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。 参考以下这颗二叉搜索树:      5     /    2   6   /  1   3示例 1: 输入: [1,6,3,2,5] 输出...

  • 草色新雨中, 松声晚窗里。之前我们学习 Power Query 都是用鼠标就完成了很多复杂的操作。虽然 PowerQuery 已经将大部分常用功能内置成到功能区。基本能完成我们大部分的报表自动化功能。但是总有些复杂的或者个性化的问题是开发团队没有预先想到的,这时我们就需要学习 M 语言。一、M 语言在哪里?M语言的函数公式有三个地...

  • 前言从2020年3月份开始,计划写一系列文档--《小白从零开始学编程》,记录自己从0开始学习的一些东西。第一个系列:python,计划从安装、环境搭建、基本语法、到利用Django和Flask两个当前最热的web框架完成一个小的项目第二个系列:可能会选择Go语言,也可能会选择Vue.js。具体情况待定,拭目以待吧。。。基本概念表达式表...

  • 1.1函数1.1.1什么是函数函数就是程序实现模块化的基本单元,一般实现某一功能的集合。函数名:就相当于是程序代码集合的名称参数:就是函数运算时需要参与运算的值被称作为参数函数体:程序的某个功能,进行一系列的逻辑运算return 返回值:函数的返回值能表示函数的运行结果或运行状态。1.1.2函数的作用函数是组织好的,可重复使用的,用来...

  • 原标题:基于Python建立深度神经网络!你学会了嘛?图1 神经网络构造的例子(符号说明:上标[l]表示与第l层;上标(i)表示第i个例子;下标i表示矢量第i项)单层神经网络图2 单层神经网络示例神经元模型是先计算一个线性函数(z=Wx+b),接着再计算一个激活函数。一般来说,神经元模型的输出值是a=g(Wx+b),其中g是激活函数(...

  • 在学习MySQL的时候你会发现,它有非常多的函数,在学习的时候没有侧重。小编刚开始学习的时候也会有这个感觉。不过,经过一段时间的学习之后,小编发现尽管函数有很多,但是常用的却只有那几个。今天小编就把常用的函数汇总一下,为大家能够能好的学习MySQL中的函数。MySQL常使用的函数大概有四类。时间函数、数学函数、字符函数、控制函数。让我...

  • 输入两个整数序列。其中一个序列表示栈的push顺序,判断另一个序列有没有可能是对应的pop顺序。为了简单起见,我们假设push序列的任意两个整数都是不相等的。   比如输入的push序列是1、2、3、4、5,那么4、5、3、2、1就有可能是一个pop系列。因为可以有如下的push和pop序列:push 1,push 2,push 3,...