20242410 2025-2026-1 《网络与系统攻防技术》实验一 逆向与BOF基础的实验报告

20242410 2025-2026-1 《网络与系统攻防技术》实验一 逆向与BOF基础实验报告

姓名:刘禹 学号:20242410


1. 基础知识

做实验前先把几个绕不开的底层概念理清楚,不然对着汇编看天书:

  • call指令的跳转原理:x86下call 目标函数的机器码格式是e8 + 4字节补码偏移,CPU执行到这条指令时,EIP已经自动指向下一条指令地址,真正的跳转目标是「当前EIP + 4字节偏移」,不是直接写死的绝对地址。要改跳转目标,只要重算补码偏移就行。
  • 函数栈帧结构:函数调用时栈从高地址往低地址增长,一个完整栈帧从低到高依次是局部变量缓冲区、保存的旧ebp、返回地址——返回地址存的就是函数执行完要跳回去的位置,ret指令就是把这个地址弹给EIP。
  • 小端字节序:x86架构存多字节数据时,低字节放低地址、高字节放高地址。往栈上写函数地址的时候必须倒着写,不然EIP拿到的是完全错乱的地址。
  • BOF核心本质:像gets()这类函数不检查输入长度,输入超长就会沿着栈往高地址写,覆盖到返回地址时,程序执行流就完全被我们控制了。

2. 实验内容

实验靶标是自编译的32位Linux ELF程序pwn1_liuyu_20242410,程序里有三个函数:main调用存在溢出漏洞的foo(回显用户输入),还有一个藏着的getShell函数(调用system弹shell,但正常流程不会调用)。本次实验要完成三个递进任务:

  1. 手工修改可执行文件里的call指令,直接让main跳转到getShell
  2. 构造超长输入字符串,靠BOF漏洞覆盖栈上返回地址,触发getShell
  3. 关闭系统防护后,在栈上注入自构造的shellcode,让程序运行我们自己的机器指令

3. 实验过程

任务一:修改机器指令劫持执行流

  1. 先把靶标准备好:用gcc以32位、关栈保护、栈可执行的参数编译生成pwn1_liuyu_20242410,反汇编确认三个函数地址:getShell在0x8049186,foo在0x80491b1,main里调用foo的call指令在0x80491f7。
    alt text
    alt text
  2. 算新的跳转偏移:call下一条指令地址是0x80491fc,要跳到getShell的话,新偏移是0x8049186 - 0x80491fc = -0x76,对应的补码是0xffffff8a。
  3. 因为是自己编译的文件,直接用gdb在调试状态下修改内存里的call指令字节,把原来指向foo的call改成指向getShell。
    alt text
  4. 运行程序后直接拿到#号shell,敲ls验证执行成功。
    alt text

任务二:BOF溢出覆盖返回地址

  1. 用gdb启动程序,输入6组每组8个字符的测试串触发崩溃,查看EIP寄存器的值:输入第6组'6'时EIP变成0x36363636,说明从缓冲区开头到返回地址一共需要40个填充字符。
    alt text
  2. 构造payload:前面40个'A'填满缓冲区和旧ebp位置,后面按小端序拼上getShell地址\x86\x91\x04\x08,末尾加回车,用perl生成payload文件,xxd校验字节正确。
    alt text
  3. 用管道把payload喂给pwn1,程序正常溢出后直接跳到getShell弹出shell,执行ls验证成功。
    alt text

任务三:注入自定义shellcode

  1. 先关防护:把/proc/sys/kernel/randomize_va_space设为0关闭ASLR地址随机化,因为编译时已经加了-z execstack参数,栈默认标记为可执行,不需要额外跑execstack命令。
    alt text
  2. 按照RNS布局构造payload:前面40个A填充到返回地址位置,返回地址后面拼上6个NOP滑行区,再拼接25字节的execve("/bin/sh") shellcode。
    alt text
  3. 因为自编译程序的栈地址和原版讲义有偏移,在gdb下断在foo的ret指令上,观察栈上shellcode的实际地址,调试控制EIP跳转。
    alt text

4. 问题及解决方案

  • 问题1:gcc编译32位程序时反复报头文件缺失、找不到32位libgcc库
    解决方案:新Kali自带gcc版本过新,直接安装gcc-12和对应multilib包,用老版本gcc编译,避开新版本的32位适配bug;把编译参数里的-Wno-implicit-function-declaration加上,屏蔽gets函数的废弃警告。
  • 问题2:长命令粘贴到终端时被自动换行截断,导致perl写文件时引号未闭合报错
    解决方案:把长perl命令拆成两条短命令分别写入,或者拉宽终端窗口,避免粘贴时命令被拆成多行。
  • 问题3:自己编译的pwn1栈缓冲区大小比讲义里的原版多了8字节,按32字节偏移构造payload时根本盖不到返回地址
    解决方案:不要硬套讲义的偏移数,直接反汇编foo函数看lea指令的偏移值,现场算当前程序的真实溢出长度,重新调整填充字符数,就解决了。
  • 问题4:gdb里修改call指令时一开始静态地址写不进去
    解决方案:gdb刚加载程序时代码段还没映射到内存,先敲start启动程序把代码段加载进来,再写内存指令就正常了。

5. 学习感悟、思考等

最开始看BOF原理的时候总觉得很玄乎,实际做下来才发现本质特别朴素:就是程序写代码的时候偷懒,没检查输入长度,写多了就把栈上存的返回地址冲掉了,CPU傻乎乎地就跳到我们写的地方去了。
这次做实验最大的感受是别上来就抄现成的exp和偏移数——我们自己编译的pwn1,缓冲区大小和讲义里的原版就差了8个字节,照着讲义填32个字符半天没反应,自己看反汇编算出来是40字节的时候,一下就通了。之前总觉得汇编难,其实对着栈帧图一步步看每个指令干了啥,根本没那么吓人。
还有就是遇到段错误别慌,一步一步调:看EIP跳到哪了,栈上写了啥,哪里不对改哪里,比直接搜现成答案印象深多了。最后也明白为什么现在系统要做Canary、NX、ASLR这些防护——就是从最早这些简单的栈溢出攻击一路演化出来的攻防对抗,把以前总在课本上看到的概念真的在机器上跑通的时候,那种理解是完全不一样的。

参考资料

posted @ 2026-10-11 18:07  终极无敌暴龙战士  阅读(6)  评论(0)    收藏  举报