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

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

第十章 软件安全攻防--缓冲区溢出和shellcode

一、实践内容

软件安全概述

  • 攻击者能够轻易地对系统和网络实施攻击,很大程度上是因为安全漏洞在软件中的大规模存在。攻击者可以利用这些漏洞来违背系统和网络的安全属性。大多数成功攻击都是利用和破解已公布但未被修改的软件安全漏洞或不安全的软件配置。

软件安全漏洞威胁

  • 软件自从诞生之日起,就和 bug 形影不离,而其中可以被攻击者利用并导致危害的安全缺陷(Security bug)被称为软件安全漏洞(Software Vulnerability)。
    美国国家标准技术研究院NIST 将安全漏洞定义为: “在系统安全流程、设计、实现或内部控制中所存在的缺陷或弱点,能够被攻击者所利用并导致安全侵害或对系统安全策略的违反”,包括三个基本元素:
    • 系统的脆弱性或缺陷、
    • 攻击者对缺陷的可访问性,
    • 攻击者对缺陷的可利用性。

因此一个安全脆弱性或缺陷真正被称为安全漏洞,必须是攻击者具备至少一种攻击工具或技术能够访问和利用到这一缺陷。软件安全漏洞则被定义为在软件的需求规范、开发阶段和配置过程中引入的缺陷实例,其执行会违反安全策略。软件安全漏洞同样符合安全漏洞的三个基本元素, 同时被限制于在计算机软件中。

软件安全困境

  • 软件安全困境三要素:

    • 复杂性:计算机软件经过数1年的发展,现代软件已经变得非常复杂,而且发展趋势表明,软件的规模还会更快地膨胀,变得更加复杂。而软件规模越来越大,越来越复杂,也就意味着软件的bug会越来越多。虽然这其中大多数 bug 并不会造成安全问题,或者无法被攻击者所利用,但只要攻击者能够从中发现出少数几个可利用的安全漏洞,他们就可以利用这些安全漏洞来危害软件的使用者。

    • 可扩展性:导致软件安全困境的第二个要素是软件的可扩展性。现代软件为了支持更加优化的软件架构,支持更好的客户使用感受,往往都会提供一些扩展和交互渠道。但正是现代可扩展软件本身的特性使得安全保证更加困难,首先,很难阻止攻击者和恶意代码以不可预测的扩展方式来入侵软件和系统;其次,分析可扩展性软件的安全性要比分析一个完全不能被更改的软件要因难得多。

    • 连通性:互联网的普及使得全球更多的软件系统都连通在一起,不仅是接入互联网的计算机数量快速增加,一些控制关键基础设施的重要信息系统也与互联网建立起了连通性。高度的连通性使得—个小小的软件缺陷就有可能影呐非常大的范围,从而引发巨大的损失。

软件安全漏洞类型

  • 内存安全违规类:内存安全违规类漏洞是在软件开发过程中在处理RAM (random- access memory)内存访问时所引入的安全缺陷,如缓冲区溢出漏洞和Double Free、Use-after Free等不安全指针。内存安全违规类漏洞主要出现在C\C++等编程语言所编写的软件之中,由于这类语言支持任意内存的分配和归还、任意指针的转换、计算等操作,而这类操作通常都没有内存安全保障。

  • 输入验证类:输入验证类安全漏洞是指软件程序在对用户输入进行数据验证存在的错误,没有保证输入数据的正确性、合法性和安全性,从而导致可能被恶意攻击与利用。输入验证类安全输入验证类是软件程序在对用户进行数据验证存在的错误,主要包含XSS攻击、SQL注入攻击、HTTP响应。

  • 竞争条件类:第一类竞争条件漏洞是TOCTTOU,程序检查一个谓词状态之后通过另一进程对谓词条件进行修改,使得检查时刻和使用时刻不一致;另一类竞争条件漏洞是符号链接竞争问题,主要是程序以不安全的方式创建文件所导致的漏洞,使得可以插入恶意用户的内容。

  • 权限混淆与提升类:是指计算机程序由于自身编程疏忽或被第三方欺骗,从而滥用其权限,或赋予第三方不该给予的权限。权限混淆与提升类漏洞的具体技术形式主要有Web应用程序中的跨站请求伪造、Clickjacking、FTP反弹攻击、权限提升、越狱等。

缓冲区溢出基础概念

  • 缓冲区溢出基本概念:缓冲区溢出是计算机程序中存在的一类内存安全违规类漏洞,在计算机程序向特定缓冲区内填充数据时,超出了缓冲区本身的容量,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏系统运行完整性,通常多见于C/C++语言程序中,因为对复制的目标缓冲区普遍没有进行严格的边界安全保护。

缓冲区溢出的基本概念与发展过程

  • 缓冲区溢出是计算机程序中存在的一类内存安全违规类漏洞,在计算机程序向特定缓冲区内填充数据时,超出了缓冲区本身的容量,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏系统运行完整性。理想情况下,程序应检查每个输入缓冲区的数据长度,并不允许输入超出缓冲区本身分配的空间容量,但是大量程序总是假设数据长度是与所分配的存储空间是相匹配的,因而很容易产生缓冲区溢出漏洞。

  • 缓冲区溢出攻击发生的根本原因,可以认为是现代计算机系统的基础架构——冯·诺伊曼休系存在本质的安全缺陷,即采用了 “存储程序” 的原理,计算机程序的数据和指令都在同一内存中进行存储而没有严格的分离。这一缺陷使得攻击者可以将输入的数据,通过利用缓冲区溢出漏洞,覆盖修改程序在内存空间中与数据区相邻存储的关键指令,从而达到使程序执行恶意注入指令的攻击目的。

缓冲区溢出攻击背景知识

  • 编译器与调试器的使用:对于高级编程语言编写的源码,需要通过编译器和连接器才能生成可直接在操作系统平台上运行的可执行程序代码。而调试器则是程序开发人员在运行时刻调试与分析程序行为的基本工具。对于通常使用的C/C++编程语言,最著名的编译与连接器是GCC。类UNIX平台上进行程序的调试经常使用GDB调试器。

  • 汇编语言基础知识:汇编语言是理解软件安全漏洞机理,掌握软件渗透攻击代码技术的底层基础。从应用的角度一般将寄存器分为4类,即通用寄存器、段寄存器、控制寄存器和其他寄存器。通用寄存器主要用于普通的算术运算,保存数据、地址、偏移量、计数值等。段寄存器一般用作段基址寄存器。控制寄存器用来控制处理器的执行流程。其他寄存器中值得关注的是“扩展标志”寄存器,由不同的标志位组成,用于保存指令执行后的状态和控制指令执行流程的标志信息。常见的汇编指令有POP,PUSH,JMP,RET等。

  • 进程内存管理:LINXU操作系统中的进程内存空间布局和管理机制,程序在执行时,系统在内存中会为程序创建一个虚拟的内存地址空间,在32位机上4GB的空间大小,用于映射物理内存,并保存程序的指令和数据。内存空间3GB以下为用户态空间,3GB-4GB为内核态空间。Windows操作系统的进程内存空间2GB-4GB为内核态地址空间,用于映射Windows内核代码和一些核心态DLL,并用于存储一些内核态对象,0GB-2GB为用户态地址空间。

  • 函数调用过程:栈溢出攻击是针对函数调用过程中返回地址在栈中的存储位置,进行缓冲区溢出,从而改写返回地址,达到让处理器指令寄存器跳转至攻击者指定位置执行恶意代码的目的。栈是一种最基本的LIFO后进先出抽象数据结构,主要被用于实现程序中的函数或过程调用,在栈中会保存函数的调用参数、返回地址、调用者栈基址、函数本地局部变量等数据。两个与栈密切相关的寄存器为ebp和esp,分别保存当前运行函数的栈底地址和栈顶地址。两个重要指令为push和pop,分别是将数据压入栈,及将栈顶数据弹出至特定寄存器。程序进行函数调用的过程有如下三个步骤:1.调用:调用者将函数调用参数、函数调用下一条指令的返回地址压栈,并跳转至被调用函数入口地址。2.序言:对调用函数的栈基址进行压栈保存,并创建自身函数的栈结构。3.返回:通常执行leave或ret缓冲区溢出攻击背景知识。

缓冲区溢出攻击原理

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

  • 栈溢出:是指存储在栈上的一些缓冲区变量由于存在缺乏边界保护问题,能够被溢出并修改栈上的敏感信息(通常是返回地址),从而导致程序流程的改变。

  • 堆溢出:是存储在堆上的缓冲区变量缺乏边界保护所遭受溢出攻击的安全问题。

  • 内核溢出:由于进程内存空间内核态中存储的缓冲区变量被溢出造成的。

  • 根本问题:用户输入可控制的缓冲区操作缺乏对目标缓冲区的边界安全保护。

Linux平台上的栈溢出与shellcode

Linux平台栈溢出攻击技术

  • Linux平台栈溢出攻击技术:按照攻击数据的构造方式不同,主要有NSR、RNS和RS三种模式。NSR和RNS模式适用于本地缓冲区溢出和远程栈溢出攻击,而RS模式只能用于本地缓冲区溢出攻击。

​- NSR模式:NSR模式主要适用于被溢出的缓冲区变量比较大,足以容纳Shellcode的情况,其攻击数据从低地址到高地址的构造一堆Nop指令填充Shellcode,加上一些期望覆盖RET返回地址的跳转地址,从而构成了NSR攻击数据缓冲区。

  • RNS模式:概括来说,RNS模式一般用于被溢出的变量比较小,不足于容纳Shellcode的情况。攻击数据从低地址到高地址的构造方式是首先填充一些期望覆盖RET返回地址的跳转地址,然后是一堆Nop指令填充出“着陆区”,最后再是Shellcode。

  • RS模式:RS模式下能够精确地定位出Shellcode在目标漏洞程序进程空间中的起始地址,因此也就无需引入Nop空指令构建“着陆区”。这种模式是将Shellcode放置在目标漏洞程序执行时的环境变量中,由于位置是固定的,可以通过公式计算ret=0xc0000000-sizeof(void*)-sizeof(FILENAME)-sizeof(Shellcode)。

Linux平台的shellcode实现技术

  • Shellcode是一段机器指令,对于我们通常接触的IA32构架平台,Shellcode就是符合Intel 32位指令规范的一串CPU指令,被用于溢出之后改变系统正常流程,转而执行Shellcode以完成渗透测试者的攻击目的,通常是为他提供一个访问系统的本地或远程命令行访问。在Linux操作系统中,程序通过“int 0x80”软中断来执行系统调用,而在Windows操作系统中,则通过核心DLL中提供的API接口来完成系统调用。按照在本地溢出攻击和远程溢出攻击使用场景的不同,又分为本地Shellcode和远程Shellcode。

  • Linux平台的shellcode实现技术:Shellcode是一段机器指令,对于我们通常接触的IA32构架平台,Shellcode就是符合Intel 32位指令规范的一串CPU指令,被用于溢出之后改变系统正常流程,转而执行Shellcode以完成渗透测试者的攻击目的,通常是为他提供一个访问系统的本地或远程命令行访问。在Linux操作系统中,程序通过“int 0x80”软中断来执行系统调用,而在Windows操作系统中,则通过核心DLL中提供的API接口来完成系统调用。按照在本地溢出攻击和远程溢出攻击使用场景的不同,又分为本地Shellcode和远程Shellcode。

  • Linux本地Shellcode实现机制:Linux系统本地Shellcode通常提供的功能就是为攻击者启动一个命令行Shell。在获得汇编语言实现的Shellcode之后,我们可以通过查找Intel opcode指令参考手册,即可获得opcode二进制指令形式的Shellcode。本地Shellcode的产生过程:1.先用高级编程语言,通常用C,来编写Shellcode程序2.编译并反汇编调试这个Shellcode程序3.从编译语言代码级别分析程序执行流程4.整理生成的汇编代码,尽量减小它的体积并使它可注入,并通过嵌入C语言进行运行测试和调试5.提取汇编代码所对应的opcode二进制指令,创建Shellcode指令数组。在Linux本地Shellcode中,往往还会在运行execve()启动shell之前,调用setreuid(0)将程序运行权限提升至Root用户,这样才能利用本地溢出攻击来提升权限,在执行execve()函数之后还需要执行exit()函数,从而在溢出攻击之后能够使程序正常退出。

  • Linux平台的远程Shellcode实现机制:Linux平台上的远程Shellcode实现机制与本地Shellcode实现机制是一样的,通过系统调用完成指定功能。Linux远程Shellcode需要让攻击目标程序创建socket监听指定的端口等待客户端连接,启动一个命令行Shell,并将命令行的输入输出与socket绑定,这样攻击者就可以通过socket客户端连接目标程序所在主机的开放端口,与服务端socket建立起通信通道,并获得远程访问Shell。

Windows平台上的栈溢出与shellcode

windows平台栈溢出攻击技术

  • 成功攻击应用程序中栈溢出漏洞密切相关的主要有以下三点:
    • 对程序运行过程中废弃栈的处理方式差异:Windows平台会向废弃栈中写入一些随机的数据,而Linux则不进行任何的处理。
    • 进程内存空间的布局差异:Linux进程内存空间中栈底指针在0xc0000000之下,在这些地址中没有空字节。Windows平台的栈位置处于0x00FFFFFF以下的用户内存空间,这些内存地址的
      首字节均为0x00空字节。
    • 系统功能调用实现方式差异:Linux系统中通过“int 80”中断处理来调用系统功能,而Windows系统则是通过操作系统中更为复杂的API及内核处理例程调用链来完成系统功能调用。

Windows平台shellcode实现技术

  • Windows平台Shellcode实现技术
    • shellcode必须可以找到所需要的Windows32 API函数,并生成函数调用表
    • 为了能够使用API函数,shellcode必须找到目标程序已加载的函数地址
    • shellcode需考虑消除空字节,以免在字符串操作函数中被截断
    • shellcode需确保自己可以正常退出,并使原来的目标程序进程继续运行或终止
    • 在目标系统环境存在异常处理和安全防护机制时,shellcode需进一步考虑如何应对这些机制

Windows远程shellcode

  • 创建一个服务器端socket,并在指定的端口上监听
  • 通过accept()接受客户端的网络连接
  • 创建子程序,运行“cmd.exe”,启动命令行
  • 创建两个管道,命令管道将服务器端socket接收(recv)到的客户端通过网络输入的执行命令,连接至cmd.exe的标准输入;然后输出管道将cmd.exe的标准输出连接至服务器端socket的发送(send),通过网络将运行结果反馈给客户端

堆溢出攻击

  • 堆溢出是缓冲区溢出中第二种类型的攻击方式,由于堆中的内存分配与管理机制较栈更复杂,不同操作系统平台的实现机制具有显著的差异。堆中没有可以直接覆盖的返回地址,因此堆溢出攻击比栈溢出更难

  • 函数指针改写:此种攻击方式要求被溢出的缓冲区临近全局函数指针存储地址,且在其低地址方向上。如果向缓冲区填充数据的时候,如果没有边界控制和判断的话,缓冲区溢出就会自然的覆盖函数指针所在的内存区,从而改写函数指针的指向地址,则程序在使用这个函数指针调用原先的期望函数的时候就会转而执行shellcode

  • C++类对象虚函数表改写:使用了虚函数机制的C++类,如果它的类成员变量中存在可被溢出的缓冲区,那么就可以进行堆溢出攻击,通过覆盖类对象的虚函数指针,使其指向一个特殊构造的虚函数表,从而转向执行攻击者恶意注入的指令

  • Linux下堆管理glibc库free()函数本身漏洞:Linux操作系统的堆管理是通过glibc库来实现的,通过称为Bin的双向循环链表来保存内存空闲块的信息。glibc库中的free()函数在处理内存块回收时,会将被释放的空闲块和与之相邻的块合并,利用精心构造的块可以在合并时覆盖Bin前指针的内容。

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

  • 尝试杜绝溢出:解决缓冲区溢出攻击最根本的方法是编写正确的、不存在缓冲区溢出安全漏洞的软件代码。

    • 最直接的方法就是写不存在缓冲区溢出的代码。
    • 但是很多效率优先的代码很容易就出现缓冲区溢出。因此研究出了很多工具来帮助经验不足的程序员来编写安全的程序,还有一些查错程序,但是都不能完全检查出所有漏洞。
  • 允许溢出但不让程序改变运行流程的防御技术:允许溢出发生,但对可能影响到程序流程的关键数据结构实施严密的安全保护,不让程序改变其执行流程,从而阻断溢出攻击。

    • 允许溢出的产生,但是对关键的数据进行严格的保护,阻断溢出攻击。
    • 黑客们则想到了绕过的方法,包括c++中的虚函数表指针、覆盖SEH异常处理结构等方法。
  • 无法让攻击代码执行的防御技术:IA64、AMD64、Alpha 等新的 CPU 硬件体系框架都引入对基于硬件 NX 保护机制,从硬件上支持对特定内存页设置成不可执行,Windows XP SP2、Linux 内核 2.6 及以后版本都支持硬件 NX 保护机制,与橾作系统配合来提升系统的安全性。

    • 尝试解决冯诺依曼体系的本质缺陷,通过堆栈不可执行来防御攻击。
    • 相应的,黑客也提出了return-2-libc攻击技术,不是让cpu去执行代码,而是在系统中查找已经存在的代码,然后按顺序组合。

posted on 2020-05-06 22:21  洛桑曲珍20199301  阅读(184)  评论(0编辑  收藏  举报

导航