【计算机系统】缓冲区溢出攻击实验

本文介绍通过缓冲区溢出技术实现对特定函数的调用及参数传递的方法,包括利用不同长度的输入字符串覆盖栈帧中的返回地址和局部变量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

github地址

一、 实验目标:

  1. 理解程序函数调用中参数传递机制;
  2. 掌握缓冲区溢出攻击方法;
  3. 进一步熟练掌握GDB调试工具和objdump反汇编工具。

二、实验环境:

  1. 计算机(Intel CPU)
  2. Linux 64位操作系统
  3. GDB调试工具
  4. objdump反汇编工具

三、实验内容

本实验设计为一个黑客利用缓冲区溢出技术进行攻击的游戏。我们仅给黑客(同学)提供一个二进制可执行文件bufbomb和部分函数的C代码,不提供每个关卡的源代码。程序运行中有3个关卡,每个关卡需要用户输入正确的缓冲区内容,否则无法通过管卡!
要求同学查看各关卡的要求,运用GDB调试工具和objdump反汇编工具,通过分析汇编代码和相应的栈帧结构,通过缓冲区溢出办法在执行了getbuf()函数返回时作攻击,使之返回到各关卡要求的指定函数中。第一关只需要返回到指定函数,第二关不仅返回到指定函数还需要为该指定函数准备好参数,最后一关要求在返回到指定函数之前执行一段汇编代码完成全局变量的修改。

实验代码bufbomb和相关工具(sendstring/makecookie)的更详细内容请参考“实验四 缓冲区溢出攻击实验.pptx”。

本实验要求解决关卡1、2、3,给出实验思路,通过截图把实验过程和结果写在实验报告上。

四、实验步骤和结果

因为本次实验用到的可执行文件是32位,而实验环境是64位的,需要先安装一个32位的库,在root权限下安装如下所示:
在这里插入图片描述
还需要安装sendmail
在这里插入图片描述
首先利用反汇编命令查看getbuf函数的汇编代码,以便分析getbuf在调用时的栈帧结构,汇编代码如下:
在这里插入图片描述

步骤1 返回到smoke()

1.1 解题思路

本实验中,bufbomb中的test()函数将会调用getbuf()函数,getbuf()函数再调用gets()从标准输入设备读入字符串。

系统函数gets()未进行缓冲区溢出保护。其代码如下:

int getbuf()
{
    char buf[12];
    Gets(buf);
    return 1;
}

我们的目标是使getbuf()返回时,不返回到test(),而是直接返回到指定的smoke()函数。

为此,我们可以通过构造并输入大于getbuf()中给出的数据缓冲区的字符串而破坏getbuf()的栈帧,替换其返回地址,将返回地址改成smoke()函数的地址。

1.2 解题过程

对于第一关:
在这里插入图片描述
分析getbuf()函数的汇编代码,可以发现,getbuf()在保存%ebp的旧值后,将%ebp指向%esp所指的位置,然后将栈指针减去0x28来分配额外的20个字节的地址空间。字符数组buf的位置用%ebp下0x18(即24)个字节来计算。然后调用Gets()函数,读取的字符串返回到%ebp-0x18,即%ebp-24。

具体的栈帧结构如下:
在这里插入图片描述
从以上分析可得,只要输入不超过11个字符,gets返回的字符串(包括末尾的null)就能够放进buf分配的空间里。长一些的字符串就会导致gets覆盖栈上存储的某些信息。

随着字符串变长,下面的信息会被破坏:
在这里插入图片描述
因此,我们要替换返回地址,需要构造一个长度至少为32的字符串,其中的第0 ~ 11个字符放进buf分配的空间里,第12 ~ 23个字符放进程序分配后未使用的空间里,第24~27个字符覆盖保存的%ebp旧值,第28-31个字符覆盖返回地址。

由于替换掉返回地址后,getbuf()函数将不会再返回到test()中,所以覆盖掉test()的%ebp旧值并不会有什么影响。也就是说我们构造的长度为32的字符串前28个字符随便是啥都行,而后面四个字符就必须能表示smoke()函数的地址。所以我们要构造的字符串就是“28个任意字符+smoke()地址”。任意的28个字符都用十六进制数00填充就行。

利用objdump查看smoke()函数的地址如下:
在这里插入图片描述
由此可得smoke()函数的地址是0x08048eb0,考虑小端法表示,对应的我们要构造的字符串是:

00000000000000000000000000000000000000000000000000000000b08e0408

将输入的字符串保存到0.txt中。

1.3 最终结果截图

在这里插入图片描述
在这里插入图片描述

步骤2 返回到fizz()并准备相应参数

2.1 解题思路

这一关要求返回到fizz()并传入自己的cookie值作为参数,破解的思路和第一关是类似的,构造一个超过缓冲区长度的字符串将返回地址替换成fizz()的地址,只是增加了一个传入参数,所以在读入字符串时,要把fizz()函数读取参数的地址替换成自己的cookie值,具体细节见解题过程。

2.2 解题过程

首先还是利用objdunp查看并分析fizz()函数的汇编代码:
在这里插入图片描述
从汇编代码可知,fizz()函数被调用时首先保存%ebp旧值并分配新的空间,然后读取%ebp-0x8地址处的内容作为传入的参数,要求传入的参数是自己的cookie值。也就是说传入的参数其实是存在%ebp-0x8处的,具体的栈帧结构如下:
在这里插入图片描述
对应到getbuf()函数中的栈帧结构如下:
在这里插入图片描述
由以上结构不难判断出,我们需要读入buf的字符串为“28个任意字符+fizz()的地址+4个任意的字符+自己的cookie值”,每个字符还是用十六进制数表示。

为此,首先利用objdump得到fizz()的地址为0x08048e60:
在这里插入图片描述
再利用makecookie生成自己的cookie值为0x7e9ebd07:
在这里插入图片描述
任意的字符依旧用00表示,则最后我们得到构造出来的字符串为:

00000000000000000000000000000000000000000000000000000000608e04080000000007bd9e7e

将该字符串保存在1.txt中:
在这里插入图片描述
然后通过sendstring将1.txt转换成二进制格式,再通过管道输入到bufbomb中:
在这里插入图片描述

2.3 最终结果截图

从以上过程可得,最后成功利用缓冲区溢出漏洞,让getbuf()直接返回到fizz()函数并传入自己的cookie值作为参数,最终结果截图如下:
在这里插入图片描述

步骤3 返回到bang()且修改global_value

3.1 解题思路

这一关要求先修改全局变量global_value的值为自己的cookie值,再返回到band()。为此需要先编写一段代码,在代码中把global_value的值改为自己的cookie后返回到band()函数。将这段代码通过GCC产生目标文件后读入到buf数组中,并使getbuf函数的返回到buf数组的地址,这样程序就会执行我们写的代码,修改global_value的值并调用band()函数。具体细节见解题过程。

3.2 解题过程

首先,为了能精确地指定跳转地址,先在root权限下关闭Linux的内存地址随机化:
在这里插入图片描述
用objdump查看bang()函数的汇编代码如下:
在这里插入图片描述

很明显,bang()函数首先读取0x804a1c4和0x804a1d4的地址的内容并进行比较,要求两个地址中的内容相同:
在这里插入图片描述
用gdb调试命令查看:
在这里插入图片描述
gdb可以发现,0x804a1c4就是全局变量global_value的地址,0x804a1d4是cookie的地址。因此,我们只要在自己写的代码中,把地址0x804a1d4的内容存到地址0x804a1c4就行了。

再利用objdump得到bang()函数的入口地址为0x08048e10:
在这里插入图片描述
到这里,就可以确定我们自己写的代码要干的事情了。首先是将global_value的值设置为cookie的值,也就是将0x804a1c4的值设置为0x804a1d4的值,然后将bang()函数的入口地址0x08048e10压入栈中,这样当函数返回的时候,就会直接取栈顶作为返回地址,从而调用bang()函数。接着函数返回,此时返回的地址就是上一条语句中压入栈中的地址,也就是bang()函数的入口地址了。

为此,新建一个名为3.s的文件,里面写相应的汇编代码:
在这里插入图片描述
通过gcc编译该汇编代码,再反编译输出到code.txt中:
在这里插入图片描述
查看mycode.txt中生成的对应汇编代码的机器码如下:
在这里插入图片描述
在将这段机器码放入buf数组中后,为了能让getbuf()返回到buf数组处执行我们的代码,需要想办法得到buf数组的地址。为此,用gdb断点调试查看执行getbuf()时ebp的值为0xffffb148:
在这里插入图片描述
从第一关中对getbuf()函数栈帧结构的分析可知buf数组的首地址为%ebp-0x18,即0xffffbaf0

综上所述,最后我们要构造的字符串为自己写的汇编代码生成的机器码(20个字符)+8个任意字符+buf数组的首地址,任意字符依然取00,则对应的字符串为:

8b1425d4a10408891425c4a1040868108e0408c30000000000000000f0baffff

将该字符串保存在2.txt中:
在这里插入图片描述
然后通过sendstring将2.txt转换成二进制格式,再通过管道输入到bufbomb中:
在这里插入图片描述

3.3 最终结果截图

根据以上解题过程,利用缓冲区溢出,让程序执行自己写的汇编代码对全局变量进行修改后,成功调用了bang()函数。最后结果截图如下:
在这里插入图片描述

### 设置和执行缓冲区溢出攻击实验 #### 实验环境准备 为了安全地研究缓冲区溢出攻击,在受控环境中搭建测试平台至关重要。通常建议使用虚拟机来创建隔离的实验环境,这样可以有效防止潜在的安全风险扩散到其他系统。 安装必要的软件包并配置开发工具链对于构建易受攻击的应用程序副本非常重要[^1]。例如,在Linux环境下编译C/C++源码时,默认情况下GCC启用了栈保护机制(-fstack-protector),这会阻止某些类型的缓冲区溢出漏洞被利用成功。因此需要通过特定选项禁用这些防护措施以便于观察原始缺陷行为: ```bash gcc -o vulnerable_program source_code.c -z execstack -fno-stack-protector -no-pie ``` 上述命令行参数解释如下: - `-z execstack`:使堆栈可执行; - `-fno-stack-protector`:关闭栈保护功能; - `-no-pie`:不生成位置无关代码(PIC/PIE)[^3]; #### 创建目标应用程序 编写一段存在明显边界检查缺失问题的小型服务端应用作为练习对象。这里给出一个简单的例子用于说明目的而非实际部署场景下推荐的做法: ```c #include <stdio.h> #include <string.h> void handle_client(char *input){ char buffer[64]; strcpy(buffer, input); // 存在一个明显的缓冲区溢出隐患 } int main(){ printf("Enter your message:\n"); char client_input[200]; fgets(client_input,sizeof(client_input),stdin); handle_client(strtok(client_input,"\n")); } ``` 这段代码缺乏基本的数据长度验证逻辑,允许外部输入超过内部缓存容量从而引发越界写入错误。 #### 构造恶意载荷 基于已知的服务端处理函数结构特征设计能够触发异常状况的有效负载(payload)。考虑到现代操作系统普遍采用随机化地址空间布局(ASLR)策略增加预测难度,可能还需要额外手段绕过此类障碍才能实现稳定控制流转移指向自定义指令序列的目的[^2]。 一种常见做法是在本地调试过程中反复尝试直至找到合适的偏移量组合,使得返回指针恰好覆盖所需修改的位置而不影响原有正常操作流程。之后将精心构造的一系列机器码片段附加在其后形成完整的攻击字符串发送给服务器等待响应变化确认效果是否达成预期目标。 #### 验证与分析结果 当一切就绪之后运行经过特殊定制后的客户端模拟器向宿主机发起连接请求同时提交之前准备好的数据包内容。如果一切顺利的话应该能看到原本不应该发生的非法访问现象发生,比如进程意外终止或是获得了更高层次的操作权限等表现形式。 值得注意的是整个过程务必严格遵循法律法规以及道德准则开展活动切勿滥用所学知识危害他人利益造成不良后果。 #### 安全加固措施探讨 针对发现的问题采取适当的技术方案加以改进消除安全隐患。具体可以从以下几个方面入手考虑加强系统的整体安全性: - 启用编译期优化选项增强二进制文件鲁棒性; - 应用层面上实施严格的输入校验过滤可疑字符集; - 运行时期间启用各类内核级防护组件抵御未知威胁; - 对敏感资源实行最小授权原则减少暴露面扩大可能性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alex_SCY

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值