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

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

1 基础知识

本次实验的本质是绕过操作系统的安全机制,劫持程序执行流,最终获取shell。整个过程条件逐渐变难,从任务一的文件可直接修改返回值调用getshell函数,到任务二利用程序fgets漏洞调用,任务三则进一步增加难度,通过手动利用机器码编写一个getshell函数来解决程序中无getshell函数情况,在这一过程中,按照逻辑顺序,需要掌握以下基础知识:

1.1 二进制可执行文件内部结构

对于pwn1文件,由于其为32位linux的ELF文件,由于无法直接查看源代码,必须先进行反汇编。

1.1.1 ELF文件格式

linux下的可执行文件格式,在实验中重点关注.text代码段,里面存放着机器指令。

1.1.2 反汇编工具objdump

使用objdump -d pwn1可以将机器码转为汇编代码。通过阅读汇编,我们定位到了 main、foo和未调用的getShell函数。

1.1.3 程序运行机制

程序通过main调用foo执行正常业务,而getShell虽然存在,但正常流程永远不会执行到它。

1.2 任务一 直接修改机器指令

为了执行未被调用的 getShell,我们需要改变程序的执行流。

1.2.1 call指令与机器码

call指令(机器码以e8开头)通过计算目标地址与下一条指令地址的偏移量(使用补码表示)来实现跳转。

1.2.2 EIP 寄存器

指令指针寄存器,CPU 永远根据 EIP 指向的地址去取指令。

1.2.3 十六进制编辑

通过修改ELF文件中的机器码(将e8 d7改为e8 c3),改变偏移量,让main强行调getShell。这是在程序最底层操作的思想。

1.3 任务二 BOF攻击

诚然第一问修改文件确实方便,但现实中很难获得写权限,我们需要利用运行时的漏洞,而gets()函数不做输入数量检查,自然成为了本任务可利用的漏洞。

1.3.1 函数栈帧结构

函数调用时,栈上会依次存放:局部变量缓冲区、保存的EBP、函数返回地址。

1.3.2 缓冲区溢出(BOF)

gets()函数不检查输入长度,输入超长字符串会直接溢出缓冲区,覆盖栈上方的 EBP 和返回地址。

1.3.3 GDB 调试定位(任务2/3均有)

GDB是Linux下最强大的程序调试工具,被称为“程序显微镜”。在本次实验中,它主要用于三个场景:一是定位溢出偏移量,通过运行程序并输入超长字符串,观察崩溃时EIP寄存器的值(如0x35353535),反推出覆盖返回地址需要填充多少字节;二是附加运行中的进程,在任务三中用gdb -p进程号附加到正在运行的pwn1上,查看栈的真实布局;三是查看内存与寄存器,使用info r查看EIP、ESP等寄存器的值,使用x/16x $esp以十六进制形式查看栈内存,从而精确定位shellcode的真实地址。

1.3.4 payload构造(任务2/3均有)

payload是攻击者精心构造的恶意输入数据。在缓冲区溢出攻击中,它有如下严格的结构:第一层是填充区,用来填满缓冲区并覆盖保存的EBP(例如28个A加4个B);第二层是返回地址,用来覆盖函数的返回地址,决定程序接下来跳到哪里执行(例如getShell的地址或栈上shellcode的地址);第三层是shellcode,一段用来执行恶意操作的机器码(如获取shell);此外还可以包含NOP滑行区,填充空指令,只要CPU跳到这里,就会顺滑地执行到后面的shellcode。在实验中,我使用perl命令生成payload文件,再通过管道符将其喂给目标程序,触发溢出。

1.4 任务三 注入shellcode

由于攻击难度进一步困难(即没有getshell函数),所以在本任务中,我们需要自己写一段机器码(shellcode),并注入到栈上。

1.4.1 shellcode

一段用于获取交互式shell的机器码(如execve("/bin/sh"))。它本质上是数据,但拥有代码的执行能力。

1.4.2 RNS Payload 布局

为了让程序跳过来执行shellcode,payload结构为[填充区]+[返回地址]+[NOP滑行区]+[shellcode]。NOP是空指令,作为滑行区,只要CPU跳到这里,就会顺滑地执行到后面的shellcode。

1.4.3 内存保护机制

1.4.3.1 ASLR(地址随机化)

默认开启会导致每次运行栈地址都变。需要用

echo "0" > /proc/sys/kernel/randomize_va_space

将其关闭,让地址固定。

1.4.3.2 NX/DEP(栈不可执行)

默认开启导致栈上的shellcode无法执行。需要用execstack或检查readelf确认文件是否有RWE权限,确保栈可执行。在本实验中pwn1文件已经是可栈执行文件,故无需再改

1.5 NOP,JNE,JE,JMP,CMP汇编指令的机器码

1.5.1 NOP(空指令)

作用:不执行任何操作,仅占用一个字节的指令周期,CPU执行后继续执行下一条指令。在任务三中,NOP被大量填充为“滑行区”,只要CPU跳转到NOP区域任意位置,就会顺滑地执行到后面的shellcode。
机器码:90

1.5.2 CMP(比较指令)

作用:对两个操作数执行减法运算,但不保存结果,仅根据运算结果修改标志寄存器(ZF、SF、OF等)。CMP通常与条件跳转指令配合使用,根据比较结果决定是否跳转。
机器码:3C(与AL比较)、3D(与AX比较)等,寻址方式不同操作码也不同,常见格式为3C/3D/38/39。

1.5.3 JE(相等跳转)

作用:条件转移指令,当ZF标志位为1(即上一次比较结果相等)时跳转到目标地址。JE与JZ是同一条指令的两个名称,机器码完全相同。
机器码:74

1.5.4 JNE(不相等跳转)

作用:条件转移指令,当ZF标志位为0(即上一次比较结果不相等)时跳转到目标地址。JNE与JNZ是同一条指令的两个名称。
机器码:75

1.5.5 JMP(无条件跳转)

作用:不依赖任何条件,直接跳转到目标地址执行。JMP根据跳转距离分为短转移(8位位移)和近转移(16/32位位移),机器码不同。
机器码:段内短转移为EB,段内近转移为E9,段间远转移为EA,间接转移为FF。

2 实验内容

2.1 任务一 直接修改机器指令

手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。

2.2 任务二 BOF攻击

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

2.3 任务三 注入shellcode

注入一个自己制作的shellcode并运行这段shellcode。

3 实验过程

3.1 任务一 直接修改机器指令

3.1.1 实验环境搭建与目标程序运行检验

首先将pwn1拖入Kali虚拟机,并使用chmod +x命令赋予执行权限,随后运行./pwn1确认程序功能正常。接着使用objdump -d pwn1 | more进行反汇编,定位到main函数的关键指令:80484b5: e8 d7 ff ff ff call 8048491 。
其中e8为call指令操作码,d7ffffff为补码表示的偏移量。

实验环境搭建与目标程序运行

反编译代码

3.1.2 计算偏移量并修改机器码

计算新偏移量:目标getShell地址为0804847d,下一条指令地址为80484ba,差值为-0x3d,补码为c3ffffff,因此只需将d7改为c3。
使用十六进制编辑器wxHexEditor打开pwn1,搜索e8d7,将其修改为e8c3并保存

任务一修改完成

3.1.3 实验一结果

经修改返回地址至getshell函数,此时运行pwn1程序应直接弹出$,劫持成功

劫持完成

3.2 任务二 BOF攻击

任务二则是利用foo函数中gets()不检查输入长度的漏洞,通过构造输入字符串覆盖返回地址,从而跳转到getShell。
使用gdb pwn1进入调试,设置断点break *0x080484ae,运行程序并输入测试字符串1111111122222222333333334444444455555555,程序停在断点处。通过info r查看寄存器,发现ebp的值为0x34343434(四个字符4),eip为断点地址。这说明第29至32字节覆盖了EBP,因此返回地址位于第33至36字节,即需要填充28字节加4字节覆盖EBP,再加4字节覆盖返回地址。
据此构造Payload

perl -e 'print "A" x 28; print "B" x 4; print "\x7d\x84\x04\x08\x0a"' > input_bof

末尾的\x0a为回车符,确保gets()能正确结束读取。使用xxd input_bof验证十六进制内容无误后,执行(cat input_bof; cat) | ./pwn1触发攻击。

任务二结果
程序先回显了AAAA...BBBB的缓冲区内容,随后成功弹出$提示符,输入whoami返回kali,说明返回地址被成功覆盖并跳转到了getShell,任务二完成,劫持成功。

3.3 任务三 注入shellcode

3.3.1 确认文件与环境准备

首先通过readelf -l pwn1 | grep GNU_STACK确认文件具有RWE权限,即栈可执行,无需额外设置;再使用echo "0" | sudo tee /proc/sys/kernel/randomize_va_space关闭地址随机化(ASLR),确保栈地址固定。

3.3.2 构造初始payload

32个A+返回地址+8个NOP+Shellcode。在第一个终端执行(cat input_shellcode; cat) | ./pwn1让程序挂起,在第二个终端用ps -ef | grep pwn1找到进程号,并通过sudo gdb -p 进程号附加到运行中的进程。设置断点break *0x080484ae并继续执行,在第一个终端按回车后程序停在断点处,此时在GDB中执行x/16x $esp查看栈内存,关键行显示0xffffcf2c: 0xffffced0 0x90909090 0x90909090 0x6850c031,据此确定NOP滑行区的真实起始地址为0xffffcf30。

3.3.3 发动攻击

将返回地址修正为0xffffcf30(小端序为\x30\xcf\xff\xff)重新生成Payload。在第一个终端执行(cat input_shellcode; cat) | ./pwn1并回车,成功弹出$提示符,输入whoami返回kali,任务三完成。

实验3

4 问题及解决方案

  • 问题1:从物理机拖拽进Kali虚拟机的pwn1文件无法直接运行,报错提示权限不足。
  • 问题1解决方案:使用chmod +x pwn1命令给文件添加可执行权限,再次用ls -l确认权限变为-rwx--x--x,即可正常运行。
  • 问题2:使用vi的十六进制模式修改机器指令时,容易因为按错按键导致文件损坏或修改未生效。
  • 问题2解决方案:改用图形化十六进制编辑器wxHexEditor,直接通过Ctrl+F搜索e8d7定位目标字节,手动将d7覆盖为c3,按Ctrl+S保存即可,操作更直观不易出错。
  • 问题3:安装wxHexEditor后,在终端输入wxhexeditor提示command not found。
  • 问题3解决方案:通过dpkg -L wxhexeditor | grep bin查询实际安装路径,发现可执行文件名为/usr/bin/wxHexEditor,注意大小写后使用wxHexEditor命令即可正常打开。
  • 问题4:任务二中最初使用32个A填充缓冲区,但覆盖的是EBP而不是返回地址,导致攻击失败。
  • 问题4解决方案:在GDB中设置断点,输入测试字符串后通过info r查看到ebp=0x34343434,据此推出EBP覆盖位置在第29至32字节,返回地址在第33至36字节,因此将Payload改为28个A加4个B再加4字节返回地址,成功劫持执行流。
  • 问题5:任务二中Payload末尾未添加回车符时,gets()函数会一直等待输入,导致程序卡住无法触发溢出。
  • 问题5解决方案:在perl生成Payload时,在末尾加入\x0a(回车符),确保gets()读取到换行后正确结束输入,从而顺利触发栈溢出。
  • 问题6:任务三中直接在普通终端使用GDB run调试得到的地址构造Payload,运行时却报illegal hardware instruction崩溃。
  • 问题6解决方案:意识到GDB run与普通终端运行时的栈布局不同,改为使用两个终端配合,第一个终端运行程序使其挂起,第二个终端用sudo gdb -p 进程号附加到真实运行的进程,通过x/16x $esp查看到真实的Shellcode起始地址为0xffffcf30,修正Payload后成功获取Shell。
  • 问题7:新版Kali2026中execstack工具已被官方移除,无法通过apt-get install execstack安装。
  • 问题7解决方案:通过readelf -l pwn1 | grep GNU_STACK检查文件权限,发现输出中已包含RWE标志,说明该文件天生支持栈可执行,无需再额外设置,直接跳过关防护步骤即可继续实验。
  • 问题8:任务一和任务二混用同一个pwn1文件,导致任务二时程序直接弹Shell,无法正常调试溢出偏移量。
  • 问题8解决方案:从物理机重新复制一份未经修改的原版pwn1文件覆盖到实验目录,确保任务二和任务三使用的是原始有漏洞的程序,避免任务一的修改干扰后续实验。

5 学习感悟与思考

这次实验是我第一次真正深入到最底层去理解程序是如何运行的,整个过程虽然磕磕绊绊,但收获远比想象中大。
最开始拿到pwn1这个文件时,我试图用记事本打开,发现全是乱码。直到了解并使用objdump反汇编,才看到那些汇编指令和机器码。任务一里,我将机器码里的一个字节d7改成了c3,程序就绕过执行foo而是去执行getShell了,一个字节的修改就能彻底改变程序的执行路径,这让我对机器指令的精确性有了很直观的认识。
任务二让我真正理解了栈溢出的本质。这次通过GDB调试,亲眼看到自己输入的字符把栈上的EBP和返回地址一个个覆盖掉,程序崩溃时EIP变成了0x34333231。这个结果虽然在意料之中,但看到的那一刻才真正直观完整的建立起BOF攻击的概念
任务三是整个实验里最复杂的,也是我遇到问题最多的一步,最开始我猜了几个地址去试,从0xffffd320到0xffffced0都失败了,每次看到illegal hardware instruction都挺崩溃的。后来通过查询DeepSeek,将这个过程改成两个终端配合,一个挂起进程,进而保证栈地址不会一直被修改,而另一个用gdb -p附加进去,通过x/16x $esp直接查看栈的真实布局,才看到那一行0x90909090,算出真正的起始地址是0xffffcf30。通过这次攻击,让我想起了平时网络攻防课上所说的暴力攻击,并非是靠纯猜纯试,靠的是严谨的计算。
另外,这次实验也让我对操作系统的防护机制有了更具体的认识。虽然实验较复杂,但是如果这些攻击发生在真实物理环境,也是极危险的。基于此,我了解了ASLR、NX/DEP、栈保护。真实的物理系统正是靠这些保护全部开启,才能避免如此凶险的攻击。
最后,这次实验也让我意识到自己的不足。比如execstack装不上、wxHexEditor命令大小写写错、地址算错这些问题,都让我花了额外的时间去排查。但这些弯路本身也是一种训练,每一次报错、排查、修正,都比单纯跑通一遍命令更能加深理解。网络安全这条路很长,这次实验只是一个起点,但至少让我对最底层攻防有了更具体的认知。

6 参考资料

posted @ 2026-10-09 19:08  叶十口  阅读(9)  评论(0)    收藏  举报