缓冲区溢出漏洞入门介绍

缓冲区溢出漏洞入门介绍
文/hokersome

一、引言      
    不管你是否相信,几十年来,缓冲区溢出一直引起许多严重的安全性问题。甚至毫不夸张的说,当前网络种种安全问题至少有50%源自缓冲区溢出的问题。远的不说,一个冲击波病毒已经令人谈溢出色变了。而作为一名黑客,了解缓冲区溢出漏洞则是一门必修课。网上关于溢出的漏洞的文章有很多,但是大多太深或者集中在一个主题,不适合初学者做一般性了解。为此,我写了这篇文章,主要是针对初学者,对缓冲区溢出漏洞进行一般性的介绍。
    缓冲区溢出漏洞是之所以这么多,是在于它的产生是如此的简单。只要C/C++程序员稍微放松警惕,他的代码里面可能就出现了一个缓冲区溢出漏洞,甚至即使经过仔细检查的代码,也会存在缓冲区溢出漏洞。

二、溢出
    听我说了这些废话,你一定很想知道究竟什么缓冲区溢出漏洞,溢出究竟是怎么发生的。好,现在我们来先弄清楚什么是溢出。以下的我将假设你对C语言编程有一点了解,一点点就够了,当然,越多越好。
    尽管缓冲区溢出也会发生在非C/C++语言上,但考虑到各种语言的运用程度,我们可以在某种程度上说,缓冲区溢出是C/C++的专利。相信我,如果你在一个用VB写的程序里面找溢出漏洞,你将会很出名。回到说C/C++,在这两种使用非常广泛的语言里面,并没有边界来检查数组和指针的引用,这样做的目的是为了提高效率,而不幸的是,这也留下了严重的安全问题。先看下面一段简单的代码:
#include<stdio.h>
void main()
{
    char buf[8];
    gets(buf);
}
程序运行的时候,如果你输入“Hello”,或者“Kitty”,那么一切正常,但是如果输入“Today is a good day”,那么我得通知你,程序发生溢出了。很显然,buf这个数组只申请到8个字节的内存空间,而输入的字符却超过了这个数目,于是,多余的字符将会占领程序中不属于自己的内存。因为C/C++语言并不检查边界,于是,程序将看似正常继续运行。如果被溢出部分占领的内存并不重要,或者是一块没有使用的内存,那么,程序将会继续看似正常的运行到结束。但是,如果溢出部分占领的正好的是存放了程序重要数据的内存,那么一切将会不堪设想。
    实际上,缓冲区溢出通常有两种,堆溢出和堆栈溢出。尽管两者实质都是一样,但由于利用的方式不同,我将在下面分开介绍。不过在介绍之前,还是来做一些必要的知识预备。

三、知识预备
    要理解大多数缓冲区溢出的本质,首先需要理解当程序运行时机器中的内存是如何分配的。在许多系统上,每个进程都有其自己的虚拟地址空间,它们以某种方式映射到实际内存。我们不必关心描述用来将虚拟地址空间映射成基本体系结构的确切机制,而只关心理论上允许寻址大块连续内存的进程。
    程序运行时,其内存里面一般都包含这些部分:1)程序参数和程序环境;2)程序堆栈,它通常在程序执行时增长,一般情况下,它向下朝堆增长。3)堆,它也在程序执行时增长,相反,它向上朝堆栈增长;4)BSS 段,它包含未初始化的全局可用的数据(例如,全局变量); 5)数据段,它包含初始化的全局可用的数据(通常是全局变量);6)文本段,它包含只读程序代码。BSS、数据和文本段组成静态内存:在程序运行之前这些段的大小已经固定。程序运行时虽然可以更改个别变量,但不能将数据分配到这些段中。下面以一个简单的例子来说明以上的看起来让人头晕的东西:
#include<stdio.h>
char buf[3]="abc";
int i; 
void main()
{
  i=1
  return;    
}
其中,i属于BBS段,而buf属于数据段。两者都属于静态内存,因为他们在程序中虽然可以改变值,但是其分配的内存大小是固定的,如buf的数据大于三个字符,将会覆盖其他数据。
    与静态内存形成对比,堆和堆栈是动态的,可以在程序运行的时候改变大小。堆的程序员接口因语言而异。在C语言中,堆是经由 malloc() 和其它相关函数来访问的,而C++中的new运算符则是堆的程序员接口。堆栈则比较特殊,主要是在调用函数时来保存现场,以便函数返回之后能继续运行。

四、堆溢出
    堆溢出的思路很简单,覆盖重要的变量以达到自己的目的。而在实际操作的时候,这显得比较困难,尤其是源代码不可见的时候。第一,你必须确定哪个变量是重要的变量;第二,你必须找到一个内存地址比目标变量低的溢出点;第三,在特定目的下,你还必须让在为了覆盖目标变量而在中途覆盖了其他变量之后,程序依然能运行下去。下面以一个源代码看见的程序来举例演示一次简单的堆溢出是如何发生的:
#include "malloc.h"
#include "string.h"
#include "stdio.h"
void main()
{
  
   char *large_str = (char *)malloc(sizeof(char)*1024);
   char *important = (char *)malloc(sizeof(char)*6);
   char *str = (char *)malloc(sizeof(char)*4);
   strcpy(important,"abcdef");//给important赋初值 
  
   //下面两行代码是为了看str和important的地址
   printf("%d/n",str);
   printf("%d/n",important);

   gets(large_str);//输入一个字符串
   strcpy(str, large_str);//代码本意是将输入的字符串拷贝到str

   printf("%s/n",important);
}
在实际应用中,这样的代码当然是不存在的,这只是一个最简单的实验程序。现在我们的目标是important这个字符串变成"hacker"。str和important的地址在不同的环境中并不是一定的,我这里是7868032和7868080。很好,important的地址比str大,这就为溢出创造了可能。计算一下可以知道,两者中间隔了48个字节,因此在输入溢出字符串时候,可以先输入48个任意字符,然后再输入hakcer回车,哈哈,出来了,important成了"hacker"。
    
   
五、堆栈溢出
    堆溢出的一个关键问题是很难找到所谓的重要变量,而堆栈溢出则不存在这个问题,因为它将覆盖一个非常重要的东西----函数的返回地址。在进行函数调用的时候,断点或者说返回地址将保存到堆栈里面,以便函数结束之后继续运行。而堆栈溢出的思路就是在函数里面找到一个溢出点,把堆栈里面的返回地址覆盖,替换成一个自己指定的地方,而在那个地方,我们将把一些精心设计了的攻击代码。由于攻击代码的编写需要一些汇编知识,这里我将不打算涉及。我们这里的目标是写出一个通过覆盖堆栈返回地址而让程序执行到另一个函数的堆栈溢出演示程序。
    因为堆栈是往下增加的,因此,先进入堆栈的地址反而要大,这为在函数中找到溢出点提供了可能。试想,而堆栈是往上增加的,我们将永远无法在函数里面找到一个溢出点去覆盖返回地址。还是先从一个最简单的例子开始:
void test(int i)
{
char buf[12];
}
void main()
{
  test(1);
}
test 函数具有一个局部参数和一个静态分配的缓冲区。为了查看这两个变量所在的内存地址(彼此相对的地址),我们将对代码略作修改:
void test(int i)
{
  char buf[12];
  printf("&i = %d/n", &i);
  printf("&buf[0] = %d/n", buf);
}
void main()
{
  test(1);
}
需要说明的是,由于个人习惯的原因,我把地址结果输出成10进制形式,但愿这并不影响文章的叙述。在我这里,产生下列输出:&i = 6684072 &buf[0] = 6684052。这里我补充一下,当调用一个函数的时候,首先是参数入栈,然后是返回地址。并且,这些数据都是倒着表示的,因为返回地址是4个字节,所以可以知道,返回地址应该是保存在从6684068到6684071。因为数据是倒着表示的,所以实际上返回地址就是:buf[19]*256*256*256+buf[18]*256*256+buf[17]*256+buf[16]。
    我们的目标还没有达到,下面我们继续。在上面程序的基础,修改成:
#include <stdio.h>
void main()
{
  void test(int i);
  test(1);
}

void test(int i)
{
  void come();
  char buf[12];//用于发生溢出的数组
  int addr[4];
  int k=(int)&i-(int)buf;//计算参数到溢出数组之间的距离
  int go=(int)&come;
  //由于EIP地址是倒着表示的,所以首先把come()函数的地址分离成字节
  addr[0]=(go << 24)>>24;
  addr[1]=(go << 16)>>24;
  addr[2]=(go << 8)>>24;
  addr[3]=go>>24;
  //用come()函数的地址覆盖EIP
   for(int j=0;j<4;j++)
   {
    buf[k-j-1]=addr[3-j];
   }
}
void come()
{
 printf("Success!");
}
    一切搞定!运行之后,"Success!"成功打印出来!不过,由于这个程序破坏了堆栈,所以系统会提示程序遇到问题需要关闭。但这并不要紧,因为至少我们已经迈出了万里长征的第一步。

2003.10.6夜--10.7早上

 

    



 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: boofuzz是一款开源的模糊测试框架,用于帮助发现软件和网络服务中的漏洞。使用boofuzz,我们可以通过生成各种输入来测试目标程序,包括随机数据、边界值以及特定的恶意输入。下面是boofuzz实战入门的步骤: 1. 安装boofuzz:首先,我们需要在我们的机器上安装Python和boofuzz库。安装过程大致分为以下几个步骤:下载和安装Python,设置环境变量,使用pip安装boofuzz库。 2. 确定目标:在使用boofuzz之前,我们需要确定我们要测试的目标。可以选择一款自己编写的程序,或者选择已经公开的漏洞软件。 3. 配置boofuzz:在开始模糊测试之前,需要对boofuzz进行配置。这包括指定目标和端口,设置网络协议和通信方式,配置生成器和监视器等。 4. 编写boofuzz脚本:使用Python编写boofuzz脚本是进行模糊测试的关键步骤。脚本主要包括定义测试用例、设置消息发送和接收的规则、确定测试用例的生成方式和执行的顺序等。 5. 运行模糊测试:完成boofuzz脚本的编写后,我们可以执行模糊测试了。Boofuzz将根据我们在脚本中定义的规则和生成器生成各种类型的输入,并发送给目标程序进行测试。 6. 分析测试结果:模糊测试完成后,我们需要对测试结果进行分析。Boofuzz将记录每个测试用例的执行情况和目标程序的响应。我们可以查看执行成功和失败的测试用例,并分析找到的漏洞。 通过以上步骤,我们可以实战入门boofuzz。在实际使用中,还需要不断调试和改进脚本,以提高模糊测试的效果和发现更多的漏洞。 ### 回答2: boofuzz是一个针对网络协议和API的模糊测试工具,可以用于漏洞发现和安全评估。下面是boofuzz实战入门的一些步骤和方法。 1. 安装boofuzz:可以在boofuzz的官方网站或GitHub上找到最新版本的安装包,并按照提供的说明进行安装。boofuzz是用Python编写的,因此需要确保Python解释器已安装并配置正确。 2. 目标选择:选择要测试的目标,通常是一个网络协议或API。选择一个开源的协议可以更容易地查找相关的文档和资源,以帮助我们理解协议的特点和潜在的漏洞。 3. 创建boofuzz脚本:使用boofuzz提供的API编写一个脚本,设置目标的IP地址和端口,并定义测试用例。测试用例可以是针对协议的不同字段和操作的各种输入数据,包括正常的、异常的和边缘的情况。 4. 执行测试:运行boofuzz脚本,它将自动发送测试用例到目标,并监视响应是否异常或触发了漏洞。可以在boofuzz的输出中查看每个测试用例的执行结果和目标的响应。 5. 分析结果:根据boofuzz的输出和目标的响应,分析测试结果。可以检查是否有异常行为、崩溃或内存泄漏等迹象,这些可能表明存在漏洞。如果发现漏洞,应及时报告给开发者或相关团队,并协助修复。 6. 脚本优化:根据实际的测试结果,不断优化脚本,增加更具挑战性和有效性的测试用例。可以尝试不同的测试策略,如随机生成输入或使用模糊测试模块来发现更复杂的漏洞。 7. 学习和扩展:boofuzz的学习曲线可能较陡峭,需要花时间理解网络协议和API的工作原理。可以通过阅读相关的文档、书籍和博客,参与安全社区的讨论,深入学习和扩展boofuzz的功能和应用场景。 总之,boofuzz是一款强大的模糊测试工具,对于入门用户来说,从安装、目标选择、脚本编写、测试执行到结果分析都是必经的步骤。通过不断的实践和学习,可以逐渐掌握boofuzz的使用技巧,并提高漏洞发现和安全评估的能力。 ### 回答3: boofuzz是一种用于模糊测试的开源框架,旨在帮助发现和利用软件中的安全漏洞。下面是关于boofuzz实战入门的一些基本概念和步骤。 首先,要使用boofuzz,我们需要了解模糊测试的基本原理和目标。模糊测试是一种通过向目标软件发送异常或非预期的输入来检测其安全漏洞的方法。通过模糊测试,我们可以发现潜在的缓冲区溢出、格式化字符串漏洞等问题。 接下来,我们需要安装并配置boofuzz。Boofuzz是一个基于Python的框架,可以通过pip进行安装。安装完成后,我们需要创建一个新的boofuzz项目,设置目标软件的IP地址和端口号,并定义发送的测试数据。可以使用boofuzz提供的示例模板来帮助我们快速入门。 在配置好boofuzz项目后,我们需要定义模糊测试的策略,即如何生成测试数据以及如何探测目标软件的响应。boofuzz提供了多种生成测试数据的机制,比如随机生成、字符串替换、模板生成等。我们还可以定义检测目标软件响应的方式,比如检查响应中的关键字或状态码。 一旦配置好策略,我们就可以开始运行模糊测试了。boofuzz会自动按照我们的配置生成并发送测试数据,并记录目标软件的响应。我们可以根据响应来判断是否发现了安全漏洞,比如崩溃或异常行为。如果发现了漏洞,我们可以进一步分析并复现该漏洞。 在使用boofuzz进行实战时,有一些技巧和注意事项。首先,要在进行模糊测试之前,尽可能了解目标软件的协议和漏洞历史,这有助于我们选择合适的测试策略。其次,由于模糊测试可能导致目标软件崩溃或异常,我们应该使用虚拟机或沙盒环境来运行模糊测试,以确保不会对系统造成损害。 总的来说,boofuzz是一个功能强大的模糊测试框架,可以帮助我们发现和利用软件中的安全漏洞。通过了解模糊测试的原理和目标,安装并配置boofuzz,定义适当的测试策略,并进行实际的测试,我们可以开始在boofuzz中进行模糊测试的入门实战。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值