20199306 2019-2020-2 《网络攻防实践》第10周作业
1.实践内容
1.1 软件安全概述
-
软件安全概述:攻击者能够轻易地对系统和网络实施攻击,很大程度上是因为安全漏洞在软件中的大规模存在攻击者可以利用这些漏洞来违背系统和网络的安全属性。大多数成功攻击都是利用和破解已公布但未被修改的软件安全漏洞或不安全的软件配置。软件规模越来越大,越来越复杂,也就意味着软件的漏洞会复杂化。
-
软件安全困境
- 复杂性:软件规模越来越大,越来越复杂,也就意味着软件的bug会越来越多。尽管有些bug不一定会被利用。
- 可扩展性:现代软件为了支持更加优化的软件架构,支持更好的客户使用感受,往往都会提供一些扩展和交互渠道。正是现代可扩展软件本身的特性使得安全保证更加困难。首先攻击者以不可预测的扩展方式来入侵软件和系统;其次,可扩展性软件的的安全分析要比分析一个完全不能更改的软件要困难得多。
- 连通性:连通性使得网络缺陷被放大;网络的连通性使得不需要人为干涉的自动化攻击成为可能。
-
软件安全漏洞类型
- 内存安全违规类:内存安全违规类漏洞是在软件开发过程中在处理RAM (random- access memory)内存访问时所引入的安全缺陷,如缓冲区溢出漏洞和Double Free、Use-after Free等不安全指针。内存安全违规类漏洞主要出现在CC++等编程语言所编写的软件之中,由于这类语言支持任意内存的分配和归还、任意指针的转换、计算等操作,而这类操作通常都没有内存安全保障。
- 输入验证类:输入验证类安全漏洞是指软件程序在对用户输入进行数据验证存在的错误,没有保证输入数据的正确性、合法性和安全性,从而导致可能被恶意攻击与利用。输入验证类安全输入验证类是软件程序在对用户进行数据验证存在的错误,主要包含XSS攻击、SQL注入攻击、HTTP响应。
- 竞争条件类:第一类竞争条件漏洞是TOCTTOU,程序检查一个谓词状态之后通过另一进程对谓词条件进行修改,使得检查时刻和使用时刻不一致;另一类竞争条件漏洞是符号链接竞争问题,主要是程序以不安全的方式创建文件所导致的漏洞,使得可以插入恶意用户的内容。
- 权限混淆与提升类:是指计算机程序由于自身编程疏忽或被第三方欺骗,从而滥用其权限,或赋予第三方不该给予的权限。权限混淆与提升类漏洞的具体技术形式主要有Web应用程序中的跨站请求伪造、Clickjacking、FTP反弹攻击、权限提升、越狱等。
1.2 缓冲区溢出基础概念
-
缓冲区溢出基本概念:缓冲区溢出是计算机程序中存在的一类内存安全违规类漏洞,在计算机程序向特定缓冲区内填充数据时,超出了缓冲区本身的容量,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏系统运行完整性,通常多见于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
-
缓冲区溢出攻击原理:分为栈溢出、堆溢出和内核溢出这三种。
- 栈溢出:是指存储在栈上的一些缓冲区变量由于存在缺乏边界保护问题,能够被溢出并修改栈上的敏感信息(通常是返回地址),从而导致程序流程的改变。
- 堆溢出:是存储在堆上的缓冲区变量缺乏边界保护所遭受溢出攻击的安全问题。
- 内核溢出:是由于进程内存空间内核态中存储的缓冲区变量被溢出造成的。
1.3 Linux平台上的栈溢出与shellcode
-
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实现机制: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。
1.4 Windows平台上的栈溢出与shellcode
-
成功攻击应用程序中栈溢出漏洞密切相关的主要有以下三点:
- 对程序运行过程中废弃栈的处理方式差异:Windows平台会向废弃栈中写入一些随机的数据,而Linux则不进行任何的处理。
- 进程内存空间的布局差异:Linux进程内存空间中栈底指针在0xc0000000之下,在这些地址中没有空字节。Windows平台的栈位置处于0x00FFFFFF以下的用户内存空间,这些内存地址的首字节均为0x00空字节。
- 系统功能调用实现方式差异:Linux系统中通过“int 80”中断处理来调用系统功能,而Windows系统则是通过操作系统中更为复杂的API及内核处理例程调用链来完成系统功能调用。
-
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()函数本身漏洞
- 缓冲区溢出攻击的防御技术:要是缓冲区溢出攻击奏效,攻击者通常先通过溢出植入攻击代码,然后通过修改关键数据结构改变执行流程,最后让攻击代码执行。
- 尝试杜绝溢出的防御技术
- 允许溢出但不让程序改变执行流程的防御技术
- 无法让攻击代码执行的防御技术