1.实验内容
1.1本周学习内容
1.1.1缓冲区溢出的定义和原因
定义:写入缓冲区的数据量超过该缓冲区能容纳的最大限度,造成溢出的数据改写了与该缓冲区相邻的原始数据的情形。
原因:(直接)由于代码语言的设计问题、程序员的安全意识问题,程序没有严格的内存越界检查;(根本)冯诺依曼体系的安全缺陷,存储中数据和指令没有严格分离。
1.1.2缓冲区溢出攻击的历史
红色代码、冲击波病毒、震荡波病毒、心脏出血、乌克兰断网、勒索病毒。
1.1.3缓冲区溢出基础知识
编译器和连接器:根据高级语言编写的程序,生成可执行程序代码,有gcc、javac。
调试器:在运行时调试与分析程序行为的工具,有gdb、jdb、pdb。
寄存器分类:通用寄存器、段寄存器、控制寄存器、其他寄存器。
EBP栈底指针(在高地址)、ESP栈顶指针(在低地址)、EIP指令指针寄存器(指向下一条将要执行的指令的地址)
堆:程序动态分配的数据和变量。
栈:环境变量/参数和个数以及主函数和调用栈中函数的临时保存信息。
汇编指令:push、pop、mov、sub等。
内核态与用户态。
1.2实验内容简述
名为pwn1的linux可执行文件,在正常情况下运行会调用foo函数,该函数会简单回显用户输入的字符串。该程序同时包含另一个代码片段getShell,会返回一个可用Shell。正常情况下这个代码不会被运行,本次实验使用三种方法尝试调用getshell函数:
修改可执行文件内容,改变程序中的一个函数调用指令,直接跳转到getShell函数。
利用foo函数的缓冲区溢出漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
注入一个自己制作的shellcode(一段用于获取一个交互式的shell的机器指令)并运行这段shellcode。
2.实验过程
2.1修改程序机器指令的方法
下载目标文件pwn1(改名为pwn20222408_1,按照实验要求改名的文件将在下文中进行简称),正常运行,可以回显输入字符串。
[
使用“objdump -d pwn1 | more”命令,反汇编并找到函数调用的相关指令。可以看到main中80484b5位置是跳转到foo函数的指令。
跳转指令中,e8是跳转的意思,d7ffffff为补码,表示当前地址+(-41),所以要调用getShell函数,需要改变后面四个字节。根据计算,应该是-61,即-0x3d,补码为c3ffffff。
按照计算结果需将该条指令改为e8c3ffffff。打开vi后修改过程为:用“:%!xxd”指令将显示模式切换为16进制模式;用“/d7ff”定位修改位置,将d7修改为c3;用“:%!xxd -r”指令转换16进制为原格式;“:wq”保存退出。
再次反汇编,可以看到该条指令现在调用的是getShell函数。
运行修改后文件,可以进入shell。
2.2BOF攻击的方法
首先进行反汇编,可以发现在foo函数中,8048497位置的指令为“lea -0x1c(%ebp),%eax”,即只为后续的读入字符串预留了0x1c=28字节的缓冲区。call调用foo时,原本会在堆栈上压上返回地址80484ba,我需要通过修改,让返回地址的值被覆盖为getShell函数的地址804847d。(图片在2.1中已给出)
使用gdb,输入超过28字节的字符串,再查看各个寄存器的值,发现eip的值是0x35353535,即5555的ascii码。
在输入另一个特定的过长字符串,查看eip的值为0x34333231,即4321的ascii码。这就确定了应该如何设置攻击字符串,即将第33至第36个字符设置为804847d按字节的倒序。
不能通过键盘直接输入,所以先利用perl软件生成包括这样字符串的一个文件。
用“xxd input”命令查看文件内容,可以发现内容正确。
将input的输入,通过管道符“|”,作为pwn的输入。然后可以发现程序调用了getShell函数,可以获取shell了。
2.3注入shellcode的方法
找到一段用于攻击的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\。这段shellcode是启动一个新的/bin/sh shell的机器码,用于后续的攻击操作。
做准备工作,设置堆栈可执行,关闭地址随机化,并进行确认。
根据之前的分析,缓冲区有28字节,足够放这段shellcode,所以采用nop+shellcode+retaddr这一构造结构。
先使用特定命令构造输入(“\x1\x2\x3\x4”是占位符,后续将替换为注入“shellcode”的地址,即foo函数中返回地址的位置,这个地址需要通过gdb分析找到),并将其放入文件“input_shellcode”中,再将这一文件作为pwn的输入。
打开一个新的终端,找到pwn的进程号。
用gdb调试这一进程。
通过反汇编foo,可以发现foo的结束地址是0x080484ae,在这里设置一个断点。
用“c”让程序继续后,在原本的终端中按下回车,让程序运行到断点,发现esp寄存器的内容为0xffffd3bc。
使用命令,以16进制格式显示从地址0xffffd3bc开始的一部分数据,发现此处便有注入的输入0x04030201,说明这个地址正是需要的地址。
将栈顶指针地址再加4字节,就是shellcode应该处于的地址,即0xffffd3bc+4=0xffffd3c0。根据机器存储的方式,可以知道应当将“\x1\x2\x3\x4”更换为“\xc0\xd3\xff\xff”,将分析得到的shellcode保存至文件input_shellcode_2。
将input_shellcode_2作为pwn的输入,成功注入shellcode,获取到了shell。
再尝试结合nc模拟远程攻击。我借用了同学的一台虚拟机。先用“ifconfig”得到IP地址。
在我的虚拟机中,模拟一个有漏洞的网络服务。
再在同学的虚拟机中发送攻击载荷(之前的shellcode),成功获取shell。
3.问题及解决方案
- 问题1:实验刚开始时,我尝试使用OpenEuler进行本次实验,发现pwn1这一文件无法正常运行。
- 问题1解决方案:经过询问,得知需要使用kali虚拟机进行实验,配置kali完成后可以正常进行实验。
- 问题2:在实验过程中发现许多软件没有下载,比如gdb、execstack。
- 问题2解决方案:gdb直接通过“apt install gdb”即安装完成。而execstack在输入类似命令后报错“Unable tolocate package execstack”,于是采用另一种安装方式,先从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”命令进行解压与安装,解决了没有所需软件的问题。
- 问题3:在下载操作之前,也发现kali虚拟机并没有联网,无法进行下载。
- 问题3解决方案:进行了网络检索,按照教程在虚拟网络编辑器中,将VMnetO设置为桥接模式,并桥接至Intel(R) Wi-Fi 6E AX211 160MHZ,再尝试ping任意网站,发现已成功联网。
- 问题4:在shellcode注入过程中,无法获取shell。
- 问题4解决方案:注入失败的原因是输入文件内容不对,经过仔细研究,我最终想出了正确的文件内容,在以此作为输入时成功获取了shell。
4.学习感悟
本次实验中学习了获取系统的shell的三种攻击方法,学习的重点有:反汇编,机器码的阅读,缓冲区溢出攻击,shellcode的含义、作用与使用方式等等。通过进行实验,我对kali虚拟机的各种操作也有了更深入的理解。
在修改程序机器指令的方法中,我通过反汇编找到目标函数的调用指令,并成功将其修改为调用我们期望的函数。这个过程让我更好地理解了对程序的内存布局和指令执行流程。
在BOF攻击的方法中,我通过构造特定的输入字符串,成功覆盖了函数的返回地址,并触发了getShell函数。这个过程让我深刻体会到了缓冲区溢出攻击的作用,也让我意识到在编写代码时,必须进行严格的内存越界检查,以防止此类攻击的发生。
在注入shellcode的方法中,我找到了一段用于攻击的shellcode,并通过一系列复杂的步骤,成功将其注入到目标程序中,获取了shell。这个过程不仅锻炼了我的动手能力,也让我对shellcode的工作原理和攻击流程有了更深入的了解。
课堂上的学习使我对缓冲区溢出攻击有了初步的了解,而在实验中,通过动手实践,辅之以上网查询、询问同学等各种学习方式,我对三种攻击方式都有了更深刻的体会,相信这不仅对我的课堂学习有一定帮助,也将有益于我未来的工作与实践。