缓冲区溢出问题

<转>机器级代码

 

计算机系统使用了多种不同形式的抽象,利用更简单的抽象模型来隐藏实现的细节。

对于机器级编程来说,其中两种抽象尤为重要:

1、指令集体系结构(Instruction set architecture ISA)

它定义了处理器状态、指令的格式,以及每条指令对状态的影响。

IA32将程序的行为描述成好像每条指令时按顺序执行的,一条指令结束后,下一条再开始。(实际上处理器并发地执行许多指令,但是可以采取措施保证整体行为与ISA指定的顺序执行完全一致)

2、机器级程序使用的存储器地址是虚拟地址

提供的存储器模型看上去是一个非常大的字节数组。存储器系统的实际实现是将多个硬件存储器和操作系统软件组合起来。

程序存储器(program memory)包含:程序的可执行机器代码、操作系统需要的一些信息、栈、堆。程序存储器用虚拟地址来寻址(此虚拟地址不是机器级虚拟地址)。操作系统负责管理虚拟地址空间(程序级虚拟地址),将虚拟地址翻译成实际处理器存储器中的物理地址(机器级虚拟地址)。

函数调用过程

进程的虚拟地址空间

函数调用时的用户栈

 

IA32程序用程序栈来支持过程调用。机器用栈来传递过程参数、存储返回信息、保存寄存器用于以后恢复,以及本地存储。

栈向低地址方向增长,而栈指针%esp指向栈顶元素。

寄存器使用惯例

 

程序寄存器组是唯一能被所有函数共享的资源。

虽然在给定时刻只能有一个函数是活动的,但是我们必须保证当一个函数调用另一个函数时,被调用者不会覆盖某个调用者稍后会用到的值。为此,IA32采用了一组统一的寄存器使用规则,所有的函数都必须遵守,包括程序库中的函数。

根据惯例:寄存器%eax、%edx、%ecx被划分为调用者保存寄存器。当过程P调用Q时,Q可以覆盖这些寄存器,不会破坏任何P所需要的数据。

另一方面,寄存器%ebx、%esi、%edi被划分为被调用者寄存器。这意味着Q必须在覆盖这些寄存器的值之前,先把它们保存到栈中,并在返回前恢复它们。此外还必须保持寄存器%ebp和%esp。

转移控制

 

call Label     过程调用

call *Operand 过程调用

leave          为返回准备栈

ret            从过程调用中返回

call指令的效果是将返回地址入栈,并跳转到被调用过程的起始处。(返回地址是在程序正文中紧跟在call后面的那条指令的地址,这样当被调用过程返回时,执行流会从此处继续。)

ret指令从栈中弹出地址,并跳转到这个位置。(使用这条指令前,要使栈做好准备,栈顶指针要指向前面call指令存储返回地址的位置)

leave指令 使栈做好返回的准备。它等价于:

movl %ebp, %esp ; 把寄存器%ebp中的值复制到寄存器%esp中(回收本函数的栈空间)

popl %ebp

leave指令的使用在返回前,既重置了栈指针,也重置了基址指针。

【示例】

  1. int
    swap_add(int* xp, int yp)
    {
        int x = *xp ;
        int y = *yp ;  
    
        *xp = y ;
        *yp = x ;  
    
        return x+y ;
    }  
    
    int
    caller()
    {
        int arg1 = 534 ;
        int arg2 = 1057 ;
        int sum = swap_add(&arg1, &arg2) ;
        int diff = arg1 - arg2 ;  
    
        return sum*diff ;
    }  

函数caller的汇编代码:
_caller:
    pushl    %ebp                        ;保存基址指针
    movl    %esp, %ebp             ;把基址指针设置为当前的栈顶(意味着call函数栈的开始)
    subl    $24, %esp                 ;分配24个字节作为栈空间
    movl    $534, -12(%ebp)     ;在距离基址12字节处 作为参数arg1的空间(此间有8字节的空档)
    movl    $1057, -16(%ebp)   ;在距离基址16字节处 作为参数arg2的空间
    leal    -16(%ebp), %eax  ;
    movl    %eax, 4(%esp)        ;把参数2的地址值放到离栈顶指针的4字节处
    leal    -12(%ebp), %eax
    movl    %eax, (%esp)          ;把参数1的地址值放到栈顶指针处
    call    _swap_add               ;调用函数swap_add(把当前程序正文地址压栈,跳转到函数swap_add地址处)
    movl    %eax, -8(%ebp)     ;把返回值%eax放到距离基址8字节处 作为参数sum的空间
    movl    -12(%ebp), %edx
    movl    -16(%ebp), %eax
    movl    %edx, %ecx
    subl    %eax, %ecx              ;arg1 - arg2
    movl    %ecx, %eax
    movl    %eax, -4(%ebp)    ;把arg1 - arg2的结果值放到距离基址4字节处 作为参数diff的空间
    movl    -8(%ebp), %eax
    imull    -4(%ebp), %eax   ;sum*diff
    leave
    ret

//从以上代码中可看出,函数的返回值是放在寄存器%eax中的。

//gcc分配了从不使用的空间

GCC为caller参数的代码在栈上分配了24个字节,但是只使用了其中的16个。因为,GCC坚持一个X86编程指导方针,也就是一个函数使用的所有栈空间必须是16字节的整数倍。

包括保存%ebp值的4字节和返回值的4字节,caller一共使用了32字节。采用这个规则是为了保证访问数据的严格对齐(alignment)。

在返回前,函数必须将栈恢复到原始条件,可以恢复所有的被调用者保存寄存器(手动) 和%ebp,并且重置%esp使其指向返回地址。(由leave指令完成)

然后,ret指令把返回地址弹出到pc寄存器,CPU继续从原调用处开始执行。

缓冲区溢出

通常,在栈中分配某个字节数组来保存一个字符串,但是字符串的长度超出了为数组分配的空间。C对于数组引用不进行任何边界检查,而且局部变量和状态信息,都存在栈中。这样,对越界的数组元素的写操作会破坏存储在栈中的状态信息。当程序使用这个被破坏的状态,试图重新加载寄存器或执行ret指令时,就会出现很严重的错误。

【示例】

void echo()

{

char buf[8] ;

gets(buf) ;

puts(buf) ;

}

由于栈是向地地址增长的,数组缓冲区是向高地址增长的。故,长一些的字符串会导致gets覆盖栈上存储的某些信息。

随着字符串变长,下面的信息会被破坏:

输入的字符数量   被破坏的状态

0---7         无

8---11        保存的%ebx的值

12---15       保存的%ebp的值

16---19      返回地址

20+         caller中保存的状态

如果破坏了存储%ebp的值,那么基址寄存器就不能正确地恢复,因此调用者就不能正确地引用它的局部变量或参数。

如果破坏了存储的返回地址,那么ret指令会使程序跳转到完全意想不到的地方。

缓冲区溢出的一个更加致命的使用就是让程序执行它本来不愿意执行的函数。这是一种最常见的通过计算机网络攻击系统安全的方法。通常,输入给程序一个字符串,这个字符串包含一些可执行代码的字节编码,称为攻击代码,另外还有一些字节会用一个指向攻击代码的指针覆盖返回地址。那么,执行ret指令的效果就是跳转到攻击代码。

通常,使用gets或其他任何能导致存储溢出的函数,都不是好的编程习惯。不幸的是,很多常用库函数,包括strcpy、strcat、sprintf,都有一个属性——不需要告诉它们目标缓冲区的大小,就产生一个字节序列。

对抗缓冲区溢出攻击

1、栈随机化

为了在系统中插入攻击代码,攻击者不但要插入代码,还要插入指向这段代码的指针,这个指针也是攻击字符串的一部分。产生这个指针需要知道这个字符串放置的栈地址。在过去,程序的栈地址非常容易预测,在不同的机器之间,栈的位置是相当固定的。

栈随机化的思想使得栈的位置在程序每次运行时都有变化。因此,即使许多机器都运行相同的代码。它们的栈地址都是不同的。

实现的方式是:程序开始时,在栈上分配一段0--n字节之间的随机大小空间。程序不使用这段空间,但是它会导致程序每次执行时后续的栈位置发生了变化。

在Linux系统中,栈随机化已经变成了标准行为。(在linux上每次运行相同的程序,其同一局部变量的地址都不相同)

2、栈破坏检测

在C语言中,没有可靠的方法来防止对数组的越界写,但是,我们能够在发生了越界写的时候,在没有造成任何有害结果之前,尝试检测到它。

最近的GCC版本在产生的代码中加入了一种栈保护者机制,用来检测缓冲区越界,其思想是在栈中任何局部缓冲区与栈状态之间存储一个特殊的金丝雀值。这个金丝雀值是在程序每次运行时随机产生的,因此,攻击者没有简单的办法知道它是什么。

在恢复寄存器状态和从函数返回之前,程序检查这个金丝雀值是否被该函数的某个操作或者函数调用的某个操作改变了。如果是,那么程序异常终止。

3、限制可执行代码区域

限制那些能够存放可执行代码的存储器区域。在典型的程序中,只有保存编译器产生的代码的那部分存储器才需要是可执行的,其他部分可以被限制为只允许读和写。

现在的64位处理器的内存保护引入了”NX”(不执行)位。有了这个特性,栈可以被标记为可读和可写,但是不可执行,检查页是否可执行由硬件来完成,效率上没有损失。

转载自:http://blog.csdn.net/yang_yulei/article/details/21407461

时间: 2024-10-11 05:58:15

缓冲区溢出问题的相关文章

堆栈 Cookie 检测代码检测到基于堆栈的缓冲区溢出

 报错:0x000CC3C9 处有未经处理的异常(在 image_opencv2.exe 中):  堆栈 Cookie 检测代码检测到基于堆栈的缓冲区溢出. 主要检查代码中有没有对数组的越界操作,就解决了这个bug. 其它的相关知识查后再补充.

关于对抗缓冲区溢出攻击

缓冲区溢出说白了就是因为内存中的非法访问而导致的一些状态的破坏. 这一点,C语言中的一些和数组有关的库函数最容易导致这类情况. 缓冲区溢出攻击,有很大一部分是通过数据的溢出,内存的非法访问来执行一些攻击代码,因为数据在栈中一旦溢出,那么他就会覆盖一些重要的状态,从而被攻击者操控. 那么关于对抗缓冲区溢出的攻击,有这样的几种方法 1.栈的随机化 在植入缓冲区溢出攻击的代码的时候,还要植入指向这段代码的指针,指针指向这段代码的栈的位置.因为执行这段代码是考指针进行跳转过去的.由于以前的操作系统程序的

kali实战-缓冲区溢出

我们之前可能都了解过自动化的程序,学习自动化程序的使用方法,但是在计算机领域有一个词语叫做"零日漏洞",这样的漏洞是如何发现的呢?有些漏洞通过远程就可以控制你的计算机,安装木马,达到这样的目的,他们是如何通过远程控制你的系统的呢?今天我们学习此类攻击中最主要一种:缓冲区溢出,通过缓冲区溢出我们就可以控制目标机器.基于本章的内容从原理上我们已经具备这样的方法可以自己去发现"零日漏洞",发现的原理和方法将在本章注意展开讲解. 加Q群交流kali Q群:108186516

缓冲区溢出笔记

首先得会内存.寄存器还有程序运行的规则. 存储知识: 文件地址(File Offset):数据在PE文件中的地址,文件在磁盘上存放时相对于文件开头的偏移: 虚拟内存地址:每个进程都有的4G虚拟空间: 物理内存地址: 这三个地址要层层映射 内存: 代码区:存放二进制代码 数据区:存储全局变量 堆区:动态内存空间(还没太明白用途) 栈区:存放函数调用关系(缓冲区溢出就在这里发生) 重点研究栈的结构: 栈帧:每个函数有自己的栈帧,只有被调用的函数才会在系统栈开辟栈帧,调用完毕将弹出栈帧 两个寄存器(用

缓冲区溢出漏洞实验 20125108 冯相国

练习1 一.实验描述 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况.这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段.这一漏洞的出现是由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写. 二.实验准备 系统用户名shiyanlou,密码shiyanlou 实验楼提供的是64位Ubuntu linux,而本次实验为了方便观察汇编语句,我们需要在32位环境下作操作,因此实验之前需要做一些准备. 此过程消耗了不少时间,学校的网速实在太渣. 三.实验步骤

实验一——缓冲区溢出漏洞实验

Linux实验一 —— 缓冲区溢出漏洞实验 20122137 一.实验描述 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况.这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段.这一漏洞的出现是由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写. 二.实验准备 系统用户名shiyanlou,密码shiyanlou 实验楼提供的是64位Ubuntu linux,而本次实验为了方便观察汇编语句,我们需要在32位环境下作操作,因此实验之前需要做一些准备. 1.

缓冲区溢出漏洞实验

一.实验过程及截图 输入命令安装一些用于编译32位C程序的东西: 进入32位linux环境.此时你会发现,命令行用起来没那么爽了,比如不能tab补全了,所以输入“/bin/bash”使用bash.使用地址空间随机化来随机堆(heap)和栈(stack)的初始地址,这使得猜测准确的内存地址变得十分困难,而猜测内存地址是缓冲区溢出攻击的关键. 把以下代码保存为“stack.c”文件,保存到 /tmp 目录下. 把以下代码保存为“exploit.c”文件,保存到 /tmp 目录下. 结果: 上面的代码

实验一缓冲区溢出漏洞实验

一.实验描述 缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况.这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段.这一漏洞的出现是由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写 二.实验准备 系统用户名shiyanlou,密码shiyanlou 实验楼提供的是64位Ubuntu linux,而本次实验为了方便观察汇编语句,我们需要在32位环境下作操作,因此实验之前需要做一些准备. 1.输入命令安装一些用于编译32位C程序的东西: sudo apt-

缓冲区溢出基础知识

缓冲区理论学习PE文件(进程)装入内存:PE文件(进程)在内存中按照功能大致划分4个部分(1)代码区(程序段) .text 主要存储被装入执行的二进制代码,ALU会到这个取指令并执行,这个段通常是只读,对它的写操作是非法的.(2)数据区 .data 主要是存储的全局变量,主要存储静态数据.(3)堆区 程序在堆区可以动态的请求分配一定大小的内存,并在用完后归还给堆区,动态分配和回收是堆区的主要特点,主要存储动态数据.(4)栈区 用于动态的存储函数之间的调用关系,以保证被调用函数在返回时恢复到母函数

缓冲区溢出漏洞实战(1)

目标软件:BlazeDVD Pro 版本号:7.0.0.0 系统:Windows xp,Win7,Win8 ------ 主要是Immunity Debugger mona.py插件的使用,此插件是Corelan Team的精品.设置mona.py工作路径: !mona config -set workingfolder c:\logs\%p 生成测试数据: #!/usr/bin/python from struct import pack   buffer = '\x41'*1000 try: