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

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

1.基础知识

1.1本实验用到的机器指令

实验对象是32位x86程序。指令的机器码由操作码和必要的操作数编码组成,所以不能把某条指令的某部分当作完整指令。下面列出一些常见到的编码;cb表示1字节相对位移,cd表示4字节相对位移,/r表示后续还有指定操作数的ModR/M字节。

指令 常见机器码 作用
NOP 90 空操作,执行后继续读取下一条指令;连续的90可作为跳转落点附近的填充。
CMP 如39 /r、3B /r、83 /7 ib 比较两个操作数,更新零标志ZF等状态标志,不保存减法结果。
JE / JZ 短跳74 cb;近跳0F 84 cd ZF=1时跳转。
JNE / JNZ 短跳75 cb;近跳0F 85 cd ZF=0时跳转。
JMP 短跳EB cb;近跳E9 cd 无条件跳转。
CALL 相对近调用E8 cd 压入返回地址,再按相对位移转到被调函数。

上面说的相对位移都是以这条指令结束后地址为基准。比如任务一修改的是CALL的位移,而不是把目标函数的绝对地址直接写进E8后面的四个字节。

1.2栈帧、返回地址与BOF

main调用foo时,call把返回地址压栈;foo的函数序言再保存旧EBP。反汇编中的lea -0x1c(%ebp), %eax说明传给gets的输入区从EBP-0x1c开始,距保存的EBP为28字节。gets不检查输入长度,因此在这次试验中,前28字节到达该位置,接下来的4字节覆盖旧EBP,第33~36字节覆盖返回地址。foo执行ret时会从栈中取出这个地址,作为下一条指令的位置。

1.3反汇编、十六进制编辑与字节序

objdump -d用于查看ELF文件的指令地址、机器码和反汇编结果;vi中的:%!xxd与:%!xxd -r分别把文件转换为十六进制视图、再转换回原始字节。x86使用小端序,例如地址0x0804847d写入输入文件时,四个字节依次是\x7d\x84\x04\x08。任务一需要核对文件中的机器码,任务二和任务三还要核对载荷文件及栈中的实际字节。

2.实验内容

靶程序pwn1正常运行时由main调用foo,后者读取并回显输入;程序中另有正常流程不会调用的getShell。本次实验依次完成了下面三项任务:

  1. 修改可执行文件中main的调用指令,使其转向getShell。
  2. 利用foo的缓冲区溢出覆盖返回地址,使程序返回到getShell。
  3. 构造包含Shellcode的输入,使程序返回到栈上的指令并执行。

3.实验过程

3.1环境与文件准备

实验在Kali Linux 2026.2虚拟机中进行,系统为x86_64,有运行该32位ELF文件所需的i386库。然后主机名和文件名分别改成了我的名字拼音和学号。

3.2任务一:修改调用目标

3.2.1计算CALL的相对位移

objdump -d pwn20242402_2 | grep -A 6 '<main>:'显示:main中地址0x080484b5的指令为e8 d7 ff ff ff,反汇编目标是foo(0x08048491),如图1所示。该指令长5字节,计算位移时使用下一条指令地址0x080484ba。

task1_01_disasm_before

图1 main原本调用foo

原位移d7 ff ff ff按小端序解释为有符号数-0x29,所以0x080484ba - 0x29 = 0x08048491。目标getShell的入口为0x0804847d,新位移是0x0804847d - 0x080484ba = -0x3d,四字节补码为c3 ff ff ff。所以要把e8 d7 ff ff ff改为e8 c3 ff ff ff。

3.2.2修改并复核文件

用vi pwn20242402_2打开文件,执行:%!xxd,搜索d7ff后定位到000004b0一行。图2中选中的d7位于文件偏移0x4b6;我只把它改为c3,再执行:%!xxd -r和:wq写回二进制文件。

task1_02_vi_hex_search

图2定位CALL位移中的d7

再次反汇编看一下,图3显示0x080484b5处已变成e8 c3 ff ff ff,反汇编结果为call 804847d <getShell>。运行./pwn20242402_2后,终端出现Shell,并能执行whoami、id和ls -l pwn*(图4)。

task1_03_disasm_after

图3修改后的调用目标为getShell

task1_04_run_getshell

图4运行结果与用户信息

3.3任务二:覆盖foo的返回地址

3.3.1确定覆盖位置

对pwn20242402_1反汇编。图5中getShell从0x0804847d开始;foo通过lea -0x1c(%ebp), %eax把输入区地址传给gets。从输入区到保存的返回地址共有0x1c + 4 = 32字节,因此返回地址落在输入的第33~36字节。

task2_01_disasm_foo

图5 foo的输入区位置及gets调用

在GDB中输入测试串1111111122222222333333334444444412345678。程序发生段错误后,ebp=0x34343434,eip=0x34333231(图6)。这分别对应了4444和第33~36字节的1234,确认了覆盖位置。

task2_02_gdb_eip_offset

图6测试串中的1234进入EIP

3.3.2生成载荷并观察结果

把前32字节保留为填充,将第33~36字节换成getShell地址的小端序\x7d\x84\x04\x08。用Perl写进input20242402,末尾加上换行,并用xxd检查:图7的偏移0x20处为7d84 0408,随后是0a。

task2_03_perl_input_xxd

图7载荷中的返回地址字节

执行(cat input20242402; cat) | ./pwn20242402_1。第二个cat用来保持键盘可以继续输入;图8中whoami与id返回了当前信息,说明本次输入使程序进入了getShell。

task2_04_bof_getshell

图8返回地址覆盖后的交互结果

3.4任务三:运行栈上的Shellcode

3.4.1准备运行条件

任务三要从栈上取指。对pwn20242402_1执行execstack -s,随后execstack -q返回X pwn20242402_1;同时将/proc/sys/kernel/randomize_va_space从2设为0。图9是这两项设置及查询结果。

task3_01_env_prep

图9 execstack -q返回X,ASLR查询值为0

3.4.2构造探测载荷

载荷中的25字节x86 Linux Shellcode用于调用execve("/bin/sh"):

\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

其中31 c0清零EAX,中间的压栈与寄存器赋值准备程序路径和参数,b0 0b cd 80发起系统调用。考虑到输入区到返回地址只有32字节,本次就采用课件里面的RNS布局,把Shellcode放在返回地址之后:32字节填充 + 返回地址 + 6字节NOP + Shellcode。

先以\x04\x03\x02\x01占据返回地址,生成input_shell20242402_1。图10的xxd输出显示填充、占位地址、90和Shellcode依次排布。探测文件末尾保留\x00而不写换行;配合后续管道中的第二个cat,gets会继续等待输入,给GDB留出挂载时间。

task3_02_payload_placeholder

图10占位返回地址与后续NOP、Shellcode

3.4.3定位栈上落点

左面运行(cat input_shell20242402_1; cat) | ./pwn20242402_1,右面用GDB挂载进程8958,在foo的ret指令0x080484ae设置断点。左面输入回车后,右面命中断点(图11)。

此时ESP=0xffffd27c,该地址存放占位值0x01020304;紧接着的0xffffd280开始是90,Shellcode首字节31 c0位于0xffffd286。因此把返回地址指向NOP区起点0xffffd280,对应的小端字节是\x80\xd2\xff\xff。

task3_03_gdb_attach_esp

图11 ESP、占位返回地址和NOP区的位置

3.4.4写入最终载荷

将占位地址替换为\x80\xd2\xff\xff,在文件末尾加入换行,生成input_shell20242402_2。图12的xxd输出显示偏移0x20处为80d2 ffff;把该文件输入pwn20242402_1后,whoami和id均有响应。说明这次运行中,返回地址落入NOP区,随后执行了栈上的Shellcode。

图12最终载荷与本地运行结果

图12最终返回地址字节及Shell交互结果

3.4.5本机回环连接演示

图13是开了俩个终端:左侧用nc.traditional在端口28234监听并启动pwn20242402_1;右侧通过nc 127.0.0.1 28234发送同一载荷。连接中执行whoami、hostname和id后得到响应,验证了本机回环连接下的交互过程。

task3_05_nc_remote_attack

图13同一台虚拟机上的监听端与连接端

4.问题及解决方案

问题1:工具缺失。 第一次运行GDB时,终端提示gdb未安装(图14)。准备任务三时,本机软件源也未找到execstack包。

prob1_gdb_not_found

图14 gdb未安装时的终端提示

解决方案: 安装gdb后继续调试。针对execstack,实验时使用基于Python struct的脚本修改ELF中PT_GNU_STACK的执行权限标志;图9的execstack -q返回X,确认了该文件在本次实验中的栈执行标记。

问题2:把Shellcode放进较小的输入区后发生段错误。 该布局把待执行指令放在返回地址之前。Shellcode包含push指令,执行时ESP向低地址移动并写入栈;结合本程序的28字节输入区,这种布局存在覆盖后续指令的风险。

解决方案: 改为图10~12所示的RNS布局,让返回地址先落在高地址处的NOP区,再进入Shellcode。本次运行得到图12的交互结果。

问题3:探测进程提前退出,无法挂载。 最初在占位载荷末尾写入换行,gets随即结束读取,程序返回到占位地址并退出,来不及在第二个终端设置断点。

解决方案: 探测文件改用\x00结尾,不写换行;第二个cat保持输入流打开。GDB挂载并设置断点后,再由左终端输入回车,取得图11中的ESP值。

5.学习感悟、思考等

这次实验里最直观的变化发生在任务一:只改了CALL位移中的一个字节,main的调用目标就从foo变成了getShell。任务二没有修改磁盘上的程序,而是通过第33~36字节覆盖返回地址。两种做法都改变了执行位置,但要核对的字节分别在文件和进程栈中。

任务三让我意识到,算对覆盖偏移还不够。最初的布局在运行中出了段错误;改成RNS后,又因为探测文件提前写入换行而失去了挂载机会。最后在ret前看到ESP=0xffffd27c,再把跳转点定为0xffffd280,整个推导才与截图中的栈内容对上。以后分析类似问题,我会先确认输入字节、栈上落点和程序停下来的时机,再解释运行结果。

本实验的栈上代码执行还依赖图9所示的运行条件。对于写代码而言,更直接的提醒是给输入限定长度,避免gets这样的“不负责”函数把普通输入变成返回地址覆盖。

参考资料

posted @ 2026-10-11 23:22  weare219  阅读(4)  评论(0)    收藏  举报