20242306 2026-2027-1 《网络与系统攻防技术》 实验1实验报告

20242306 2026-2027-1 《网络与系统攻防技术》 实验1实验报告

1. 基础知识

本次实验针对Linux 32位ELF程序pwn1栈缓冲区溢出漏洞展开。

栈帧基础:函数调用时,栈上会存放缓冲区、EBP栈基址以及函数返回地址。函数执行ret指令时,会读取栈上保存的返回地址完成跳转。程序中gets()函数不会校验输入数据的长度,当输入内容超出缓冲区容量,多余数据就会向上覆盖返回地址,实现程序控制流劫持。实验中EIP记录下一条待执行指令地址,ESP标识栈顶位置。

汇编与机器码:本次实验涉及多种汇编指令。NOP是空操作指令,机器码0x90;JNE、JE分别为不等跳转、相等跳转;CMP用于比较操作数并修改标志位;JMP是无条件跳转;CALL调用函数,会预先压入返回地址。修改这类指令对应的机器码,就可以改变程序执行流向。

ELF相关工具:objdump用来对程序进行反汇编,查看汇编指令;hexedit直接修改二进制文件内的机器码;checksec查看ELF程序的安全保护配置;gdb用于动态调试程序,定位缓冲区溢出的偏移量。Shellcode是注入到内存中的机器指令,一般用来获取交互式shell。

保护机制:NX保护会阻止在栈内存上直接执行代码;ASLR机制会随机化动态库的内存基址。本次实验关闭这两项保护,降低漏洞复现的难度。

2. 实验内容

本次实践对象是名为pwn1的Linux可执行文件,该程序正常执行流程是main调用foo函数,foo函数会简单回显任何用户输入的字符串。程序同时包含getShell代码片段,正常情况下不会被执行,本次实验完成三个实践任务:

  1. 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数
  2. 利用foo函数的BOF漏洞,构造攻击输入字符串,覆盖返回地址,触发getShell函数
  3. 注入自实现的shellcode,执行该shellcode获取交互式shell

3. 实验过程

实验开始前先临时关闭系统ASLR地址随机化,保证后续所有实验步骤的内存地址固定,执行sudo echo 0 > /proc/sys/kernel/randomize_va_space后,再执行cat /proc/sys/kernel/randomize_va_space验证,输出为0代表地址随机化已经成功关闭。

(1)任务一:修改可执行文件跳转到getShell

使用objdump -d pwn1对程序进行反汇编,解析出程序的汇编指令与对应机器码。检索反汇编输出,定位main、foo、getShell三个关键函数的虚拟地址。main 函数中call指令负责调用 foo 函数,call 指令后跟随的 4 字节不是直接内存地址,而是相对偏移,依靠该偏移完成跳转。根据公式计算出跳转到getShell所需要的新偏移,转换为小端序的 4 字节机器码。

WPS图片

[objdump反汇编结果,查看main函数call指令以及各个函数地址]

使用 hexedit 工具打开 pwn1 二进制文件,根据计算得到的文件偏移 定位 call 指令对应的字节位置,将原有偏移字节替换为计算完成的新偏移数值,保存修改后的二进制文件。

WPS图片

[hexedit编辑界面,显示修改机器码的位置]

直接运行修改后的pwn1程序,成功跳过foo函数的输入回显流程,直接弹出shell。运行修改后的pwn1程序,没有出现foo函数原本等待用户输入、回显字符串的提示,直接弹出命令行提示符,说明程序执行流已经成功被篡改,跳过了原本的foo函数逻辑,直接跳转到了内置的getShell函数。

WPS图片(1)

[修改二进制后程序运行结果截图]

(2)任务二:利用BOF漏洞覆盖返回地址

本任务利用foo函数中gets()造成的栈溢出漏洞,通过构造超长输入数据覆盖栈上返回地址,劫持程序控制流。
通过 gdb 反汇编 foo 函数,观察栈上缓冲区分配大小。foo 函数为缓冲区分配空间为 0x1c 字节,gets()不会对输入长度做任何限制,当输入数据超出缓冲区大小,栈上保存的 EBP、返回地址会被输入数据覆盖。
在干净调试环境env‑i gdb ./pwn1下开展调试。生成 pattern 测试字符串作为输入,运行程序触发崩溃。使用info r查看寄存器状态,观察 EIP 寄存器的值,EIP 被输入字符串的后 4 字节覆盖,证明可以控制函数返回地址。利用 pattern 工具计算出从缓冲区起始位置到返回地址的填充字节数。得到偏移之后,payload 结构确定为:填充字节 + getShell 函数小端格式入口地址。

WPS图片

[checksec查看程序保护机制输出]
首先使用msf-pattern_create生成长度100的无重复测试字符串,用于后续定位溢出覆盖返回地址的精确偏移长度。
补充截图1_生成pattern字符串

【msf-pattern_create生成测试字符串的终端输出】
在gdb中运行程序,将生成的字符串灌入程序触发缓冲区溢出,观察程序崩溃时EIP寄存器被覆盖成的值。
WPS图片(1)

[gdb崩溃界面,info r查看EIP寄存器]
将崩溃时EIP的值传入msf-pattern_offset,计算出从缓冲区起始位置到覆盖返回地址需要填充的字节长度,最终得到精确偏移为32字节。这个32字节偏移意味着:从输入字符串开头算起,前32个字节是用来填满缓冲区、覆盖保存EBP的垃圾内容,从第33个字节开始就会覆盖栈上保存的返回地址,后续构造payload时只需要用32个'A'填充,后面拼接目标函数地址就可以劫持程序执行流。
WPS图片(2)

[pattern计算溢出偏移输出截图]
在gdb中对main函数下断点,运行程序后使用x/20x $esp查看栈内存布局,确认栈上缓冲区、EBP、返回地址的位置关系,验证偏移计算结果正确。
WPS图片(1)

[gdb断点调试,查看栈内存布局截图]
断在foo函数入口位置时,打印当前eax寄存器的值,确认栈上缓冲区的起始位置,验证之前计算的32字节偏移没有偏差。
补充截图2_gdb断点打印寄存器
【gdb断点处print $eax的输出结果】
通过ropper工具在系统32位libc库中搜索可利用的jmp esp跳转指令地址,作为后续栈上执行代码的跳板,最终找到可用的gadget地址为0x00084937。
补充截图3_ropper搜索jmp_esp

【ropper搜索jmp esp得到地址0x00084937的输出】

构造payload:前32字节填充任意垃圾字符,后面直接拼接getShell函数的地址,覆盖掉栈上的返回地址,程序执行完foo函数ret指令后,就会跳转到getShell函数执行,成功弹出shell。
WPS图片

[本地BOF攻击成功终端截图]

(3)任务三:注入shellcode

本任务不再调用程序已有的函数,而是把自定义机器指令(shellcode)注入栈内存,让 CPU 执行我们注入的代码。需要栈内存支持可执行,同时关闭地址随机化消除地址不确定问题。
环境配置:执行apt‑get update更新软件源,安装execstack工具。执行execstack -s pwn1开启该程序栈可执行权限;执行execstack -q pwn1确认栈可执行配置生效。
ASLR 地址随机化会每次运行改变库、栈的内存地址,不利于复现。执行命令关闭 ASLR,读取/proc/sys/kernel/randomize_va_space确认已经关闭随机化。
构造攻击 Payload。Payload 结构:偏移填充字符 + 跳板地址 + NOP 雪橇 + shellcode。
NOP(0x90)为空指令,连续大量 NOP 组成 NOP 雪橇,用来抵消 gdb 调试环境与真实运行环境带来的栈地址偏差。只要 CPU 跳转到 NOP 雪橇范围内任意地址,就会一路向后执行,最终运行后面的 shellcode。
在 gdb 中对 foo 函数末尾 ret 返回指令设置断点,将测试 payload 输入程序,程序命中断点后,通过x/4xw $esp查看栈内存布局,确认返回地址、NOP 雪橇、shellcode 在栈中的排布情况。根据栈内存实际情况,把跳板地址设置到 NOP 雪橇中间位置,以此容忍环境带来的地址误差,生成最终 payload 并保存为文件。

WPS图片(2)

[payload构造命令/exp脚本截图]

执行攻击,使用(cat input_shellcode; cat) | ./pwn1将 payload 送入程序触发溢出。cat 保证标准输入保持打开,维持 shell 交互。漏洞触发后程序跳转 NOP 雪橇,执行 shellcode,获取交互式 shell,执行 ls、id 验证执行效果。

WPS图片(3)

[shell交互,执行系统命令截图]

4. 问题及解决方案

  • 问题1:ropper搜索jmp esp时提示找不到文件
    原因:命令参数顺序写错,把libc路径放在了--search前面。解决:使用ropper -f <libc路径> --search "jmp esp"格式执行,调整参数顺序后成功输出gadget地址。
  • 问题2:gdb里粘贴pattern字符串直接报错Undefined command
    原因:还没启动程序就往gdb命令行里粘贴字符串,gdb将其识别为调试命令。解决:先输入run启动程序,等待程序提示输入时再粘贴pattern字符串,程序正常接收输入触发溢出崩溃。
  • 问题3:本地跑exp时偶尔成功偶尔崩溃
    原因:系统默认开启ASLR地址随机化,每次运行栈底地址不固定,shellcode跳转地址不稳定。解决:执行sudo echo 0 > /proc/sys/kernel/randomize_va_space临时关闭地址随机化,exp就能稳定成功获取shell。
  • 问题4:hexedit中不知道保存退出的快捷键
    一开始尝试Ctrl+S没有反应,实际hexedit中按Ctrl+X会弹出保存确认提示,按Y确认即可保存修改后的二进制文件。

5. 学习感悟

整个实验上手的时候全是细碎的坑,从一开始命令输不对报错,到一点点定位溢出偏移、拼payload,最后跑出shell的那一刻,才把栈溢出的完整流程摸明白。
最开始我以为溢出就是输一长串超长字符就行,实际调的时候差几个字节都覆盖不到返回地址。中间还卡了好久——没关ASLR的时候exp时好时坏,我改了半天shellcode才反应过来是地址随机化的问题。
踩完这些坑也终于明白现在系统为什么要加那么多防护机制,自己亲手把流程走一遍,学会了更多东西。

参考资料

  1. 豆包部分文本语言润色以及问题咨询
  2. 课程PPT(第2章 系统攻防之缓冲区溢出和shellcode)
  3. 20232409的实验一报告
posted @ 2026-10-08 00:11  魏华江  阅读(12)  评论(0)    收藏  举报