20199136 2019-2020-2 《网络攻防实践》第十周作业

1. 实践内容


软件安全攻防——缓冲区溢出和shellcode


1.1 软件安全概述

1.1.1.软件安全漏洞威胁

安全漏洞的定义:在系统安全流程、设计、实现或内部控制中存在的缺陷或弱点,能被攻击者所利用并导致安全侵害或对系统安全策略的违反
软件安全漏洞定义:在软件的需求规范、开发阶段和配置过程中引入的缺陷实例,其执行会违反安全策略。

1.1.2.软件安全困境

困境三要素:复杂性、可扩展性、连通性

  • 复杂性:软件规模越来越大,越来越复杂,也就意味着软件的bug会越来越多。
  • 可扩展性:攻击者可能以不可预测的扩展方式来入侵软件和系统,且可扩展性软件的的安全分析要比分析一个完全不能更改的软件要困难得多。
  • 连通性:高度的连通性使小小的缺陷可能影响非常大的范围。网络的连通性使得不需要人为干涉的自动化攻击成为可能。

1.1.3.软件安全漏洞类型

从技术上主要包括:

  • 内存安全违规类:在软件开发时在处理RAM内存访问时引入的安全缺陷。
    • 缓冲区溢出漏洞:最基础的内存安全问题
    • 不安全指针:计算机程序中存在的并没有指向适当类型对象的非法指针。
  • 输入验证类:软件程序在对用户输入进行数据验证存在的错误,没有保证输入数据的正确性、合法性和安全性,从而导致可能被恶意攻击与利用。
    • 格式化字符串漏洞
    • SQL注入、代码注入、XSS、HTTP Header注入等web应用程序漏洞
  • 竞争条件类:处理进程的输出或者结果无法预测,并依赖于其他进程事件发生的次序或时间时导致的错误。
    • TOCTTOU漏洞:检查时刻和使用时刻条件状态不一致
    • 符号链接竞争问题:程序以不安全的方式创建文件所导致
  • 权限混淆与提升类:滥用权限,或赋予第三方不该给予的权限
    • FTP反弹攻击
    • 权限提升漏洞
    • “越狱”

1.2 缓冲区溢出基础概念

1.2.1.概念

缓冲区溢出是一类内存安全违规类漏洞。计算机程序向特定缓冲区填充数据时,超出了缓冲区本身的容量,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏系统运行完整性。

1.2.2.背景知识

  • 编译器与调试器的使用

    • 最常用的C/C++编程语言,最著名编译与连接器为GCC,最基本用法是执行“gcc -c test.c”命令进行编译,生成test.o,然后执行“gcc –o test test.o”进行连接,生成test可执行程序,可以使用“gcc test.c –o test”同时完成编译和连接过程。
    • 类unix平台进行程序的调试经常使用GDB调试器。GDB调试器提供程序断点管理、执行控制、信息查看等多种类型的功能指令。
  • 汇编语言基础知识

    • 在intel32位汇编语言中,常用的寄存器可分为四类:通用寄存器、段寄存器、控制寄存器和其他寄存器。
      • 通用寄存器主要用于普通的算术运算,保存数据、地址、偏移量、计数值等。
      • 段寄存器在IA32构架中是16位的,一般用作段基址寄存器。
      • 控制寄存器用来控制处理器的执行流程。
      • 其他寄存器中值得关注的是“扩展标志”eflags寄存器,由不同的标志位组成,用于保存指令执行后的状态和控制指令执行流程的标志信息。
    • 在IA32构架汇编语言中,又分为Intel和AT&T两种具有很多差异的汇编格式。在类UNIX平台下,通常使用AT&T汇编格式,而在DOS/Windows平台下,则主要使用Intel汇编格式。
  • 进程内存管理

    • Linux操作系统:程序在执行时,系统在内存中会为程序创建一个虚拟的内存地址空间,在32位机上即4GB的空间大小,用于映射物理内存,并保存程序的指令和数据。操作系统将可执行程序加载到新创建的内存空间中,程序一般包含.text、.bss和.data三种类型的段。加载完成后,系统为程序初始化“栈”和“堆”。程序执行时,按照程序逻辑执行.text中的指令,在“栈”和“堆”中保存和读取数据。
    • Windows操作系统的进程内存空间2GB-4GB为内核态地址空间,用于映射Windows内核代码和一些核心态DLL,并用于存储一些内核态对象,0GB-2GB为用户态地址空间。
  • 函数调用过程

    • 栈是一种最基本的LIFO后进先出抽象数据结构,主要被用于实现程序中的函数或过程调用,在栈中会保存函数的调用参数、返回地址、调用者栈基址、函数本地局部变量等数据。
    • 在IA32构架寄存器中,两个与栈密切相关的寄存器为ebp和esp,分别保存当前运行函数的栈底地址和栈顶地址,而两个密切相关的指令为push和pop,分别是将数据压入栈,及将栈顶数据弹出至特定寄存器。
    • 程序进行函数调用的过程有如下三个步骤:
      • 调用:将参数和下一条指令地址入栈并跳转到函数入口地址
      • 序言:对调用函数的栈基址入栈保存,创建函数自身栈结构等
      • 返回:恢复调用者栈顶栈底指针,执行下一条指令

1.2.3.缓冲区溢出攻击原理

  • 缓冲区溢出漏洞根据缓冲区在进程内存空间中的位置不同,分为栈溢出、堆溢出和内核溢出

    • 栈溢出:存储在栈上的一些缓冲区变量由于存在缺乏边界保护问题,能够被溢出并修改站上的敏感信息,从而导致程序流程的改变。
    • 堆溢出:存储在堆上的缓冲区变量缺乏边界保护所遭受溢出攻击的安全问题
    • 内核溢出:存在于一些内核模块或程序,是由于进程内存空间内核态中存储的缓冲区变量被溢出造成的


举例:

#include <stdio.h>
void return_input(void){
char array[30];
gets (array);
printf("%s\n",array);
int main (void)
{
return_input();
return 0;
}
  • 在执行上面的程序的时候,要用户自己手动和输入30个字节来赋值给字符数组。但是万一输入超过了30个字节,就会溢出qrrqy的缓冲区,覆盖到缓冲区上方的EBP和RET,这样就会修改两者的值。

  • 通过上面的实例可以看出,这个实例存在着两个因素。首先是不安全的操作缺乏对缓冲区边界的保护。上述用gets对30字节长度的数组进行赋值就不大行。然后,这个需要时用户可以人为的控制的,用户的输入可以影响到这个函数。如果用户不能控制,就不能被外界利用。

  • 在攻击者想要攻击的时候,就要考虑下面的问题.首先,怎么找到缓冲区溢出要覆盖的重要位置?然后,要将重要位置的数值修改成什么?为了获得控制权,需要通过修改敏感位置的值,然后让程序的下一条的指令变成攻击者想要他执行的指令。然后第三个问题,要执行什么指令?在获得控制权之后,想要实现什么功能?实现这种功能的代码被称作payload。因为一般攻击者想获得一个远程shell,所以也叫shellcode。


1.3 Linux平台上的栈溢出和shellcode

1.3.1.Linux平台栈溢出攻击技术

  • Linux平台中的栈溢出攻击按照攻击数据的构造方式不同,主要有NSR、RNS和RS三种模式
    • NSR:主要适用于被溢出的缓冲区变量比较大,足以容纳Shellcode的情况,其攻击数据从低地址到高地址的构造方式是一堆Nop指令之后填充Shellcode,再加上一些期望覆盖RET返回地址的跳转地址,从而构成了NSR攻击数据缓冲区。
    • RNS:一般用于被溢出的变量比较小,不足于容纳Shellcode的情况。
    • RS:能够精确地定位出Shellcode在目标漏洞程序进程空间中的起始地址,因此也就无需引入Nop空指令构建“着陆区”。这种模式是将Shellcode放置在目标漏洞程序执行时的环境变量中,由于环境变量是位于Linux进程空间的栈底位置,因而不会受到各种变量内存分配与对齐因素的影响,其位置是固定的。可以通过如下公式进行计算:
      ret=0xc0000000-sizeof(void*)-sizeof(FILENAME)-sizeof(Shellcode)
  • Linux平台上的远程栈溢出攻击:原理与本地栈溢出是一样的,区别在于用户输入传递的途径不同,以及Shellcode的编写方式不同。本地栈溢出的用户输入途径主要为argv命令行输入、文件输入等,而远程栈溢出攻击的用户输入传递途径则是通过网络,存在远程栈溢出漏洞往往是一些网络服务进程或网络应用程序。NSR和RNS模式也都适用于远程栈溢出,使用场景也主要取决于被溢出的目标缓冲区大小是否足够容纳Shellcode。

1.3.2.Linux平台的shellcode实现技术

Shellcode就是符合Intel 32位指令规范的一串CPU指令,被用于溢出之后改变系统正常流程,转而执行Shellcode以完成渗透测试者的攻击目的,通常是为他提供一个访问系统的本地或远程命令行访问。

  • Linux本地shellcode实现机制:
    Linux系统本地Shellcode通常提供的功能就是为攻击者启动一个命令行Shell。
    Shellcode的通用方法:
    • 先用高级编程语言,通常用C,来编写Shellcode程序;
    • 编译并反汇编调试这个Shellcode程序;
    • 从汇编语言代码级别分析程序执行流程;
    • 整理生成的汇编代码,尽量减小它的体积并使它可注入,并可通过嵌入C语言进行运行测试和调试;
    • 提取汇编代码所对应的opcode二进制指令,创建Shellcode指令数组。
  • Linux远程shellcode实现机制:
    ​ - 原理与本地一致,通过执行一系列的系统调用来完成指定的功能。实现方法步骤也是首先给出高级语言的功能代码实现,然后通过反汇编调试编译后的二进制程序,特权、优化和整理所获得的汇编代码,并最终产生opcode二进制指令代码。
    • Linux远程Shellcode需要让攻击目标程序创建socket监听指定的端口等待客户端连接,启动一个命令行Shell,并将命令行的输入输出与socket绑定,这样攻击者就可以通过socket客户端连接目标程序所在初级的开放端口,与服务端socket建立起通信通道,并获得Shell。在Linux系统中,dup2()函数能够将标准输入输出与socket的网络通信通道进行绑定,使得socket的远程输入连接至命令行标准输入,将命令行标准输出连接至远程网络输出,因而完成远程Shell的功能。

1.4 Windows平台shellcode实现技术

  • 为了使Windows中的shellcode能够调用操作系统功能以完成攻击目标,并在期望注入的不同目标程序中正常运行,我们需考虑以下问题:
    • shellcode必须可以找到所需的Windows 32 API函数,并生成函数调用表
    • 为使用API函数,shellcode必须找出目标程序已加载的函数地址
    • shellcode需考虑消除空字节,以避免在字符串操作函数中被截断
    • shellcode需确保自己可以正常退出,并使原来的目标程序进程继续运行或终止
    • 在目标系统环境存在异常处理和安全防护机制时,shellcode需进一步考虑如何对抗这些机制。
  • Windows本地shellcode
    • 典型的本地Shellcode同样也是启动一个命令行Shell,即command.comcmd.exe
    • Windows32系统API中提供了一个system()函数调用,可以用于启动指定程序或运行特定命令,在调用system("command.com")之后即可启动命令行程序。
  • Windows远程shellcode
    Windows远程Shellcode的大致过程如下:
    • 创建一个服务器端socket,并在指定的端口上监听;
    • 通过accept()接受客户端的网络连接;
    • 创建子进程,运行“cmd.exe”,启动命令行;
    • 创建两个管道,命令管道将服务器端socket接收(recv)到的客户端通过网络输入的执行命令,连接至cmd.exe的标准输入;然后输出管道将cmd.exe的标准输出连接至服务器端socket的发送(send),通过网络将运行结果反馈给客户端。

1.5 堆溢出攻击

  • 堆中没有可以直接覆盖并修改指令寄存器指针的返回地址,往往需要利用在堆中一些会影响程序执行流程的关键变量。

  • 函数指针改写:要求被溢出的缓冲区临近全局函数指针存储地址,且在其低地址方向上。

    • 在向缓冲区填充数据时,如果没有边界控制和判断的话,缓冲区溢出就会自然的覆盖函数指针所在的内存区,从而改写函数指针的指向地址,则程序在使用这个函数指针调用原先的期望函数的时候就会转而执行shellcode。
  • C++类对象虚函数表改写

    • C++类通过虚函数提供了一种Late bingding运行时绑定机制,编译器为每个虚函数的类建立起虚函数表、存放虚函数的地址,并在每个类对象的内存区中放入一个指向虚函数表的指针。
  • Linux下堆管理glibc库free()函数本身漏洞

    • Linux操作系统的堆管理是通过glibc库来实现的。堆内存管理的算法称为dlmalloc,它实现的glibc库中的内存块结构使用了被称为Bin的双向循环链表来保存内存空闲块的信息。
    • glibc库中的free()函数在内存回收的过程中,需要将已经释放的空闲块和与之相邻的空闲块进行合并。通过精心构造空闲块,在空闲块合并的过程中,将会发生位置覆盖。

1.6 缓冲区溢出攻击的防御技术

  • 尝试杜绝缓冲区溢出的防御技术
    • 解决缓冲区溢出攻击最根本的方法是编写正确的、不存在缓冲区溢出安全漏洞的软件代码,但由于C/C++语言作为效率优先的语言,很容易就会出现缓冲区溢出。
    • 尝试一些高级的查错程序,通过Fuzz注入测试来寻找程序漏洞,但是这不能找到所有的漏洞
    • 或者通过在编译器上引入针对缓冲区的边界检查保护机制。
  • 允许溢出但不让程序改变运行流程的防御技术
    • 这种防御技术允许溢出发生,但对可能影响到程序流程的关键数据结构实施严密的安全保护,不让程序改变其执行流程,从而阻断溢出攻击。
  • 无法让攻代码执行的防御技术
    • 这种防御技术通过堆栈不可执行限制来防御缓冲区溢出攻击。
    • 例如引入对基于硬件NX保护机制、linux平台的PaX堆栈不可指行内核补丁,等等。

4. 实践总结

纸上得来终觉浅,绝知此事要躬行。

posted @ 2020-05-05 20:38  考完了改名了  阅读(168)  评论(0编辑  收藏  举报