导航

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

一.概况

本次作业属于哪门课 网络攻防实践
作业要求 软件安全攻防--缓冲区溢出和shellcode
收获 关于Shellcode总算懂了一点点,还有汇编语言结合之前学的操作系统教程有了新的认识

二、知识点总结

1.软件安全概述

安全漏洞在软件开发周期的各个环节(包括设计、编码、发布等)中都可能被引入,而只有软件设计与开发人员充分认识到安全漏洞的危害,掌握安全漏洞机理,以及如何避免漏洞的安全编程经验,并在软件厂商的软件开发生命周期中切实执行安全设计开发的流程,才有可能尽量减少发布软件中的安全漏洞数量,降低它们对网络与现实世界所带来的影响与危害。

1.1.软件安全漏洞威胁

可以被攻击者利用并导致危害的安全缺陷被称为安全漏洞。安全漏洞的范畴不局限于软件安全漏洞,还包括硬件、个人与组织管理中存在的、能够被攻击者利用来破坏安全策略的弱点,但是软件安全漏洞是目前最常见,也是影响范围最大的安全漏洞类型。

1.2.软件安全困境

著名信息安全专家提出的软件安全“困境三要素”,它们是复杂性、可扩展性和连通性,软件的这三个要素共同作用,使得软件的安全风险管理成为了一个巨大的挑战,从而很难排除安全漏洞。

  • 复杂性:计算机软件规模巨大、软件指令复杂度高,存在大量的bug。
  • 可扩展性:软件的兼容性、跨平台性降低了软件自身的安全性。
  • 连通性:连通性加强了多终端设备和多通信系统之间的连通交互,但同时也扩大了软件缺陷的影响范围,加剧了恶意程序的攻击传播速度。

1.3.软件安全漏洞类型

作为软件安全漏洞标准目录CVE的维护机构,MITRE曾给出了在CVE中归档的安全漏洞类型统计情况及发展趋势,从安全漏洞的技术机理方面一共列举了37类,并统计了2001-2006年中最流行的Top10安全漏洞类型

软件安全漏洞类型从技术上主要包括如下几类:

  • 内存安全违规类
    内存安全违规类漏洞是在软件开发过程中在处理RAM内存访问时所引入的安全缺陷,如缓冲区溢出漏洞和Double Free、Use-after-Free等不安全指针问题等。内存安全违规类漏洞主要变现在C/C++等编程语言所编写的软件程序中,由于这类语言支持任意的内存分配与归还、任意的指针计算、转换,而这些操作通常没有进行保护确保内存安全,因而非常容易引入此类漏洞。而Java等更现代化的编程语言通常则通过禁用指针计算与转换,实施内存垃圾跟踪与收集等机制,从而能够有效地解决此类安全漏洞。
    缓冲区溢出漏洞是一种最基础的内存安全问题;不安全指针是指在计算机程序中存在的并没有指向适当类型对象的非法指针,在对这些指针进行引用时,往往会发生一些不可预期的后果,导致程序内存访问错误,而一旦攻击者可以控制这些指针指向的内存内容,那他们就可以利用这些问题构造出恶意攻击,获得软件的控制权。

  • 输入验证类
    输入验证类安全漏洞是指软件程序在对用户输入进行数据验证存在的错误,没有保证输入数据的正确性、合法性和安全性,从而导致可能被恶意攻击与利用,输入验证类安全漏洞根据输入位置、恶意输入内容被软件程序的使用方式的不同,又包含格式化字符串、SQL注入、代码注入、远程文件包含、目录遍历、XSS、HTTP Header注入、Http响应分割错误等多种安全漏洞技术形式。输入验证类安全漏洞,特别是针对目前流行的Web应用程序的输入验证类漏洞,近年来已经成为攻击者最普遍利用的目标。

  • 竞争条件类
    竞争条件类缺陷是系统或进程中一类比较特殊的错误,通常在涉及多进程或多线程处理的程序中出现,是指处理进程的输出或者结果无法预测,并依赖于其他进程事件发生的次序或事件时,所导致的错误。符号链接竞争问题是另一种由于程序以不安全的方式创建文件所导致的竞争条件类漏洞,恶意用户可以创建一个符号链接,指向无权访问的文件,当存在漏洞的特权程序创建或写操作与符号链接同一名字的文件时,实际上将会对已存在文件进行修改,从而插入恶意用户所期望的一些内容。

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

2.缓冲区溢出基础概念

缓冲区溢出是最早被发现也是最基础的软件安全漏洞技术类型之一。

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

  • 缓冲区溢出基本概念
    缓冲区溢出是计算机程序中存在的一类内存安全违规类漏洞,在计算机程序向特定缓冲区填充数据时,超出了缓冲区本身的容量,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏系统运行完整性。理想情况下,程序应检查每个输入缓冲区的数据长度,并不允许输入超出缓冲区本身分配的空间容量,但是大量程序总是假设数据长度与所分配的存储空间是相匹配的,因而很容易产生缓冲区溢出漏洞。
    细究缓冲区溢出攻击发生的根本原因,可以认为是现代计算机系统的基础架构----冯诺依曼体系存在本质的安全缺陷,即采用了“存储程序”的原理,计算机程序的数据和指令都在同一内存中进行存储,而没有严格的分离,这一缺陷使得攻击者可以将输入的数据,通过利用缓冲区溢出漏洞,覆盖修改程序在内存空间中与数据区相邻存储的关键指令,从而达到使程序执行恶意注入指令的攻击目的。

2.2.缓冲区溢出攻击背景知识

2.2.1.编译器与调试器的使用

类UNIX平台上进行程序的调试经常使用GDB调试器,如果需要在Linux等类UNIX平台上分析程序安全漏洞、调试渗透攻击代码,GDB是一个必须掌握的工具。
对于Windows平台,使用VS等高度集成的开发环境非常方便,对于二进制文件的调试,常用的有WInDbg、开源的有OllyDbg、商业的Softice和IDA Pro等。

2.2.2.汇编语言基础知识

汇编语言,尤其是IA32(Intel 32位)架构下的汇编语言,是理解软件安全漏洞机理,掌握软件渗透攻击代码技术的底层基础。这是因为对于软件安全漏洞分析而言,一般情况下我们无法得到被分析软件的源代码,因此只能在反汇编技术的支持下,通过阅读和理解汇编代码,来对软件安全漏洞的机理进行分析;其次,在编写的渗透攻击代码中,也会包含以机器指令形式存在的Shellcode,如何理解和编写Shellcode也需要汇编语言的知识和技能。
在IA32汇编语言中,首先我们需要熟悉常用的寄存器和它们对应的功能,我们从应用的角度一般将寄存器分为4类,即通用寄存器、段寄存器、控制寄存器和其他寄存器。通用寄存器如eax、ebx、ecx、edx等,主要用于普通的算术运算,保存数据、地址、偏移量、计数值等。段寄存器在IA32架构中是16位的,一般用作段基址寄存器。控制寄存器用来控制处理器的执行流程,其中最关键的是eip,也被称为“指令指针”,它保存了下一条即将执行的机器指令的地址,因而也成为各种攻击控制程序执行流程的关键攻击目标对象,而如何修改与改变将要被装载至eip寄存器的内存数据,以及修改为何地址,是包括缓冲区溢出在内渗透攻击的关键所在。其他寄存器中值得关注的是“扩展标志”eflags寄存器,由不同的标志位组成,用于保存指令执行后的状态和控制指令执行流程的标志信息。

在IA32架构汇编语言中,又分为Intel和AT&T两种具有很多差异的汇编格式。在类UNIX平台下,通常使用AT&T汇编格式,而在DOS/Windows平台下,则主要是使用Intel汇编格式

2.2.3.进程内存管理

所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等。不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内存是事先静态分配和统一回收的,而有些却是按需要动态分配和回收的。
对任何一个普通进程来讲,它都会涉及到5种不同的数据段。稍有编程知识的朋友都能想到这几个数据段中包含有“程序代码段”、“程序数据段”、“程序堆栈段”等。不错,这几种数据段都在其中,但除了以上几种数据段之外,进程还另外包含两种数据段。下面我们来简单归纳一下进程对应的内存空间中所包含的5种不同的数据区。

代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。
数据段:数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配的变量和全局变量。
BSS段:BSS段包含了程序中未初始化的全局变量,在内存中 bss段全部置零。
堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
:栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自互不干涉的进程地址空间。该空间是块大小为4G的线性虚拟空间,用户所看到和接触到的都是该虚拟地址,无法看到实际的物理内存地址。利用这种虚拟地址不但能起到保护操作系统的效果(用户不能直接访问物理内存),而且更重要的是,用户程序可使用比实际物理内存更大的地址空间。
在讨论进程空间细节前,这里先要澄清下面几个问题:
  • 4G的进程地址空间被人为的分为两个部分——用户空间与内核空间。用户空间从0到3G(0xC0000000),内核空间占据3G到4G。用户进程通常情况下只能访问用户空间的虚拟地址,不能访问内核空间虚拟地址。只有用户进程进行系统调用(代表用户进程在内核态执行)等时刻可以访问到内核空间。
  • 用户空间对应进程,所以每当进程切换,用户空间就会跟着变化;而内核空间是由内核负责映射,它并不会跟着进程改变,是固定的。内核空间地址有自己对应的页表(init_mm.pgd),用户进程各自有不同的页表。
  • 每个进程的用户空间都是完全独立、互不相干的。

Windwos操作系统的进程内存空间布局则与Linux系统有一些差异,如下图所示,2GB-4GB为内核态地址空间,用于映射Windows内核代码和一些核心态DLL,并用于存储一些内核态对象,0GB-2GB为用户态地址空间,高地址段映射了一些大量应用进程所共同使用的系统DLL,如Kernel32.dllUser32.dll等,在1GB地址位置用于装载一些应用进程本身引用的DLL文件,可执行代码区间从0x00400000开始,然后是静态内存空间用于保存全局变量与静态变量,“堆”同样是从低地址到高地址增长,用于存储动态数据,“栈”也是从高地址向低地址增长,在单线程进程中一般的“栈”底在0x0012xxxx的位置,而在多线程的进程内存空间中,则拥有多个“堆”和多个“栈”,分布来存储各个线程的执行数据。

2.2.4.函数调用进程

栈结构与函数调用过程的底层细节是理解栈溢出攻击的重要基础,因为栈溢出攻击就是针对函数调用过程返回地址在栈中的存储位置,进行缓冲区溢出,从而改写返回地址,达到让处理器指令寄存器跳转到攻击者指定位置执行恶意代码的目的。
程序进行函数调用的过程主要如下三个步骤:

  • 调用:调用者将函数调用参数、函数调用下一条指令的返回地址压栈,并跳转至被调用函数入口地址。
  • 序言:被调用函数开始执行首先会进入序言阶段,将对调用函数的栈基址进行压栈保存,并创建自身函数的栈结构,具体包括将ebp寄存器赋值为当前栈基址,为本地函数局部变量分配栈地址空间,更新esp寄存器为当前栈顶指针。
  • 返回:被调用函数执行完功能将指令控制权返回给调用者之前,会进行返回阶段的操作,通常执行level 和 ret指令,即恢复调用者的栈顶与栈底指针,并将之前压栈的返回地址装载至指令寄存器eip中,继续执行调用者在函数调用后的下一条指令。

2.3.缓冲区溢出攻击原理

缓冲区溢出漏洞根据缓冲区在进程内存空间中的位置不同,又分为栈溢出、堆溢出和内核溢出这三种具体技术形态,栈溢出是指存储在栈上的一些缓冲区变量由于存在缺乏边界保护问题,能够被溢出并修改栈上的敏感信息(通常是返回地址),从而导致程序流程的改变。堆溢出则是存储在堆上的缓冲区变量缺乏边界保护所遭受溢出攻击的安全问题,内核溢出漏洞存在于一些内核块或程序中,是由于进程内存空间内核态中存储的缓冲区变量被溢出造成的。其中栈溢出在各类缓冲区溢出漏洞中是最容易理解,也是最早被发现和利用的技术形态。

3.Linux平台上的栈溢出与shellcode

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

Linux平台中的栈溢出攻击按照攻击数据的构造方式不同,主要有NSR、RSN和RS三种模式。

  • NSR模式:NSR模式主要适用于被溢出的缓冲区变量比较大,足以容纳Shellcode的情况,其攻击数据从低地址到高地址的构造方式是一堆Nop指令(即空操作指令)之后填充Shellcode,再加上一些期望覆盖RET返回地址的跳转地址,从而构成了NSR攻击数据缓冲区。
  • RNS模式:第二种栈溢出的模式为RNS模式,一般用于被溢出的变量比较小,不足于容纳Shellcode的情况,攻击数据从低地址到高地址的构造方式是首先填充一些期望覆盖RET返回地址的跳转地址,然后是一堆Nop指令填充出“着陆区”,最后再是Shellcode。在溢出攻击后,攻击数据将在RET区段即溢出了目标漏洞程序的小缓冲区,并覆盖了栈中的返回地址,然后跳转到Nop指令所构成的“着陆区”,并最终执行Shellcode
  • RS模式:第三种Linux平台上的栈溢出攻击模式是RS模式,在这种模式下能够精确定位出Shellcode在目标漏洞程序进程空间中的起始地址,因此也就无需引入Nop空指令构建“着陆区”。这种模式是将Shellcode放置在目标漏洞程序执行时的环境变量中,由于环境变量是位于Linux进程空间的栈底位置,因而不会受到各种变量内存分配与对齐因素的影响,其位置是固定的,可以通过如下公式进行计算:
    ret=0xc0000000 - sizeof(void*) - sizeof(FILENAME) - sizeof(Shellcode)

3.2.Linux平台的Shellcode实现技术

事实上,Shellcode是一段机器指令,对于我们通常接触的IA32架构平台,Shellcode就是符合Intel 32位指令规范的一串CPU指令,被用于溢出之后改变系统正常流程,转而执行Shellcode以完成渗透测试者的攻击目的,通常是为他提供一个访问系统的本地或远程命令行访问。

3.2.1.Linux本地Shellcode实现机制

Linux系统本地Shellcode通常提供的功能就是为攻击者启动一个命令行Shell。Linux 中一个最简单的本地 Shellcode 的产生过程, 而这个过程事实上也体现了 Shellcode 的通用方法, 包括如下5个步骤:

  • 先用高级编程语言, 通常用C, 来编写 Shellcode 程序;
  • 编译并反汇编调试这个 Shellcode 程序;
  • 从汇编语言代码级别分析程序执行流程;
  • 整理生成的汇编代码, 尽量减小它的体积并使它可注入, 并可通过嵌入C语言进行运行测试和调试;
  • 提取汇编代码所对应的 opcode 二进制指令, 创建 Shellcode 指令数组。

3.2.2.Linux远程Shellcode实现机制

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

4.Windows平台上的栈溢出与Shellcode

从技术上分析,由于Windows操作系统与Linux操作系统在进程内存空间布局、系统堆栈的处理方式、系统功能调用方式等方面上的实现差异,虽然栈溢出的基础原理和大致流程是一致的,但在具体的攻击实施细节、Shellcode编制等方面还存在着一些差别。

4.1.Windows平台栈溢出攻击技术机理

Windows操作系统平台在很多方面与Linux操作系统具有显著不同的实现机制,而在这些差异中,与成功攻击应用程序中栈溢出漏洞密切相关的主要有如下三点:

  • 对程序运行过程中废弃栈的处理方式差异
  • 进程内存空间的布局差异
  • 系统功能调用的实现方式差异

4.2.Windows平台Shellcode实现技术

由于Windows操作系统并不提供直接的系统调用,而是提供一系列的API接口函数,因此Windows平台上的Shellcode实现较Linux系统有一些差异,编写也更难一些。我们需要考虑如下问题:

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

4.3.Windows本地Shellcode

在Windows平台上, 典型的本地Shellcode 同样也是启动一个命令行Shell, 即command.comcmd.exe, Windows 32的系统API中捉供了system()函数调用, 可以用于启动指定程序或运行特定命令,在调用system ("command.com” )之后即可启动命令行程序。

4.4.Windows远程Shellcode

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

5.堆溢出攻击

堆溢出是缓冲区溢出中的第二种类型的攻击方式,由于堆中的内存分配与管理机制较栈更为复杂,不同操作系统平台的实现机制都具有显著的差异,同时通过堆中的缓冲区溢出控制目标程序执行流程需要更精妙的构造,因此堆溢出攻击的难度较栈溢出要复杂的多,真正掌握、理解并运用堆溢出攻击也更困难一些。

5.1.函数指针改写

需要被溢出的缓冲区临近全局函数指针存储地址, 且在其低地址方向。 在符合这种变量布局的条件下, 当向缓冲区填充数据时, 如果没有边界判断和控制的话, 那么缓冲区溢出之后就会自然地覆盖函数指针所在的内存区,从而改写函数指针的指向地址, 攻击者只要能够将该函数指针指向恶意构造的Shellcode入口地址,在程序使用函数指针调用原先期望的函数时, 就会转而执行Shellcode

5.2.C++类对象虚函数表改写

C++类通过虚函数提供了一种Late binding运行过程绑定的机制,编译器为每个包含虚函数的类建立起虚函数表;对于使用了虚函数机制的C++类, 如果它的类成员变量中存在可被溢出的缓冲区, 那么就可以进行堆溢出攻击, 通过覆盖类对象的虚函数指针, 使只指向一个特殊构造的虚函数表, 从而转向执行攻击者恶意注入的指令。

5.3.Linux下堆管理glibc库free()函数本身漏洞

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

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

  • 尝试杜绝溢出的防御技术
  • 允许溢出但不让程序改变执行流程的防御技术
  • 无法让攻击代码执行的防御技术

posted on 2020-05-05 15:38  IT马保国  阅读(298)  评论(0编辑  收藏  举报