一、实验说明
1.1实验环境:
【操作系统】
Ubuntu16.04.07 LTS 32bit
内核:4.15.0-112-generic
【软件版本】
gcc: 5.4.0
make: 4.1
1.2 重要说明
本次实验参考了
https://www.cnblogs.com/lqerio/p/12870943.html
【6】 缓冲区溢出 一个字节溢出 指针修改内存_xbffoo-CSDN博客
建议了解:【C语言】你必须知道的栈区细节—函数栈帧(动态详解)_标识出此时栈底(ebp)和栈顶(esp)的位置及对应取值-CSDN博客
二 实验内容
2.1 实验准备
2.1.1 下载安装gdb-peda,prelink
2.1.2 关闭保护机制
取消地址随机化:
同时在编译时设置相应参数:
-fno-stack-protector:禁用堆栈溢出保护
-z execstack:关闭数据溢出保护
2.1.3 安装漏洞程序
1.make
2.sudo make install
2.1.3 分析shellcode构造
编写shellcode C代码如下:
gcc编译后成功弹出shell:
对编译的二进制程序使用objdump反汇编
objdump -d shellcode > shellcode.S
可见汇编代码
由此编写汇编代码如下:
编译链接运行如下:
使用如下命令获取shellcode对应机器码
\x31\xc9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x31\xc0\xb0\x0b\xcd\x80
构造如下C程序验证机器码:
编译时关闭数据溢出保护和堆栈溢出保护
执行:
说明机器码正确。
同时,查询到构造shell还有另一方法
static const char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";
同样编写程序验证,可成功引发shell。
第一种方法使用了execve("/bin/sh", ["/bin/sh", NULL], NULL) 调用来执行 /bin/sh,通过栈来设置参数,实验过程中发现在构造vul2的exploit2时,会导致vul2的buf数组起始位置发生变化,而第二种方法通过寄存器设置参数,可以避免此问题,两种构造shell的机器码均记录下来,分别存储为
shllcode1.h(第一种方法),shellcode.h(第二种方法)
2.2 漏洞程序一
2.2.1 漏洞分析
分析:
bar 函数中使用 strcpy 函数将 arg 拷贝到 out 中,这里的 out 是一个局部数组,大小为 256 字节。但strcpy 函数不会检查目标数组的大小,而是简单地将源字符串复制到目标地址,这可能导致缓冲区溢出:如果参数 arg 的长度超过了 out 的大小(256 字节),strcpy 将继续写入超出缓冲区 out 的部分,导致栈上的其他数据被覆盖。这种情况就是经典的栈溢出漏洞,因为溢出的数据会覆盖函数调用栈中重要的数据,如返回地址和函数参数,从而可能影响程序的控制流程和执行。同时,调用bar函数的foo 函数接受命令行参数 argv,并将 argv[1] 作为参数传递给 bar 函数,其中 argv[1] 是 main 函数中的命令行参数 argv 的第二个元素。这意味着,如果在命令行中提供的参数超过了程序预期的范围,就会导致 bar 函数试图将过长的字符串复制到 buf 中,从而造成缓冲区溢出。
foo函数栈帧如下,当写入264字节时第261-264字节会覆盖原有返回地址。
2.2.2 构造payload
根据上述漏洞原理,构造payload,向buf中写入264字节的数据引起溢出,其中260~263为新的返回地址,即为写入shellcode机器码的开始地址,如下构造程序:
编译:gcc exploit1.c -o exploit1 -fno-stack-protector -z execstack
调试:gdb -e ./exploiy1 -s /tmp/vul1 –directory=../vulnerables
进入调试后,使用catch exec命令,是程序由exploit1通过execve执行vul1时中断,便于调试:
执行vul1后,在foo函数下断点,以便找到buf数组起始位置从而判断返回地址:
使用指令x/1xw buf可见buf起始地址,使用此地址替换上述的0x00000000
再次编译,重复上述操作,确认buf起始地址没有发生改变,由此payload构造成功。
使用gcc exploit1.c -o exploit1 -fno-stack-protector -z execstack编译后执行,成功获取具有root权限的shell:
2.3 漏洞程序二
2.3.1 漏洞分析
程序vul2与vul1非常相似,但限制了拷贝的字符串的长度,使得不会出现通过缓冲区溢出直接覆盖返回地址的情况,但其在确认字符串长度时使用了<=符号,导致字符串拷贝时可能多复制一个字节,这样我们就可以修改bar栈帧中的old ebp,也就是调用它的foo函数的ebp的最后一个字节。回到foo函数后执行leave指令(相当于mov esp,ebp pop ebp两条指令)时将被修改后的ebp传给esp,若此时esp+4指向shellcode的地址,那么执行ret指令时我们就能成功跳转至shellcode机器码所在位置从而获取shell.
总体过程描述大致如下图
2.3.2 构造payload
根据上述原理,首先在gdb中调试获取buf的地址:
调试vul2,反汇编foo函数,获取bar函数地址为0x804851a
构造一个200字节的payload并编译exploit2.c。使用gdb -e exploit2 -s /tmp/vul2联合调试,在bar函数处下断点,打印ebp和buf值
catch exec
r
b *0x0804851a
c
p $edp
p &buf
获取到buf范围是0xbffffcb8-0xbffffd80共200字节, ebp为0xbffffd8c, 如果覆盖掉ebp最后一字节为00,改变ebp为0xbffffd00,那么ret存放地址应当为0xbffffd04-0xbffffd08,注意是小端存储。D04-cb8=4c=76字节。
编写程序,构造数组:
在此过程中曾使用第一种shell构造方法,但实验过程中发现buf数组地址总是发生变化,经查询发现第一种方法通过栈来设置参数,推测可能是使用的栈导致了vul2的内存空间被占用,网上查询到第二种方法,如上文所述存在shellcode.h中,最终程序编写如下
编译运行:
成功获取具有root的权限的shell
2.4 漏洞程序三
2.4.1 漏洞分析
程序输入为整数count和字符串in。采用了if判断语句来防止溢出,存在的漏洞主要是当count为一个足够小的负数时,可以满足 if 的条件进入memcpy函数,而memcpy接受的内存长度类型为size_t,在32位中即为unsigned int,共计4字节(32位),可以表示的最大值是 2^32 – 1=4,294,967,295,其函数原型如下:
当负数足够小时,负数*sizeof(widget_t)可能溢出产生正数。故要对vul3栈溢出攻击只需要构造合适的负数count产生溢出和字符串in覆盖buf,然后覆盖ebp,ret即可。由程序代码可知widget_t结构体占4字节,buf数组占用20*1000=20000字节,若count*20>20000即可导致数组越界,因此count的4字节32位二进制中除去第一位符号位为1外剩余位应当>=1001=0b001111101001,因此传入的count的二进制可为0b 1000 0000 0000 0000 0000 0011 1110 1001,计算机中负数以补码存储,对应为0b 0111 1111 1111 1111 1111 1100 0001 0111,其对应十进制为-2,147,484,649。buf大小为20000字节,故返回地址在20004~20008字节处。覆盖该地址内存为buf的起始地址,然后buf覆盖为shellcode即可。
2.4.2 构造payload
类似方法获取buf起始地址:
得到buf基址 0xbfffa220
编写程序,构造数组:
使用命令gcc -o exploit3 exploit3.c -z execstack -fno-stack-protector编译运行