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

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

一、实践内容

1. 软件安全概述

1.1 软件安全漏洞威胁

安全漏洞:可以被攻击者利用并导致危害的安全缺陷被称为安全漏洞。安全漏洞的范畴不限于软件安全漏洞,还包括硬件、个人与组织管理中存在的、能够被攻击者利用来破坏安全策略的弱点。

三个基本元素:系统的脆弱性或缺陷、攻击者对缺陷的可访问性、攻击者对缺陷的可利用性。一个安全脆弱性或缺陷真正被称为安全漏洞,必须是攻击者具备至少一种攻击工具或技术能够访问和利用到这一缺陷。

1.2 软件安全困境

困境三要素:

  • 复杂性:随着计算机软件的发展,现代软件已经变得非常复杂,而且发展趋势表明,软件的规模还会更快地膨胀,变得更加复杂。源代码行数(LOC)是目前衡量软件规模的一个重要度量指标。
  • 可扩展性:软件的这些可扩展性功能和机制促成了现代软件的蓬勃发展,也更好地适应了用户的使用需求,然而正是现代可扩展软件本身的特性使得安全保证更加困难。
  • 连通性:互联网的普及使得全球更多的软件系统都连通在一起,不仅是接入互联网的计算机数量快速增加,一些控制关键基础设施的重要信息系统也与互联网建立起了连通性。高度的连通性使得一个小小的软件缺陷就有可能影响非常大的范围。

1.3 软件安全漏洞类型

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

内存安全违规类

内存安全违规类漏洞是在软件开发过程中再处理RAM内存访问时所引入的安全缺陷,如缓冲区溢出漏洞等。

缓冲区溢出漏洞是一种最基础的内存安全问题。

不安全指针是指在计算机程序中存在的并没有指向适当类型对象的非法指针,在对这些指针进行引用时,往往会发生一些不可预期的后果,导致程序内存访问错误。出现不安全指针的情况有很多,包括内存释放例程缺陷、两次释放、释放内存被重新引用、指针的不安全类型转换等。

输入验证类

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

输入验证类安全漏洞根据输入位置、恶意输入内容被软件程序的使用方式的不同,又包含格式化字符串、SQL 注入、代码注入、远程文件包含、 目录遍历、XSS、HTTP Header注入、HTTP响应分割错误等多种安全漏洞技术形式。

竞争条件类

竞争条件类缺陷是系统或进程中一类比较特殊的错误,通常在设计多进程或多线程处理的程序中出现,是指处理进程的输出或者结果无法预测,并依赖于其他进程事件发生的次序或时间时,所导致的错误。

权限混淆与提升类

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

权限提升漏洞通常发生于一些拥有特权的应用程序中,由于存在安全缺陷,可以使得获取特权的安全检查被绕过,或者使用特权的安全假设被攻破。

2. 缓冲区溢出基础概念

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

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

根本原因:现代计算机系统的基础架构——冯·诺伊曼体系存在本质的安全缺陷,即采用了“存储程序”的原理,计算机程序的数据和指令都在同一内存中进行存储,没有严格分离。

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

为了理解缓冲区溢出攻击的原理和具体机制,以及其他安全软件安全漏洞问题及其利用方法,我们需要掌握计算机程序的底层运行机理,熟悉计算机编程语言、汇编语言、操作系统等基础知识及相关的实践技能:

  • 编译器与调试器的使用

  • 汇编语言基础知识

    对于人类来说,二进制程序是不可读的,根本看不出来机器干了什么。为了解决可读性的问题,以及偶尔的编辑需求,就诞生了汇编语言。汇编语言是二进制指令的文本形式,与指令是一一对应的关系。比如,加法指令00000011写成汇编语言就是 ADD。只要还原成二进制,汇编语言就可以被 CPU 直接执行,所以它是最底层的低级语言。

    工作流程

    从应用的角度一般将寄存器分为4类:

    • 通用寄存器:主要用于普通的算术运算,保存数据、地址、偏移量、计数值等。
    • 段寄存器: 一般用作段基址寄存器,在IA32构架中是16位的。
    • 控制寄存器: 用来控制处理器的执行流程。
    • 其他寄存器: 中值得关注的是“扩展标志”eflags寄存器,由不同的标志位组成,用于保存指令执行后的状态和控制指令执行流程的标志信息。
  • 进程内存管理:“堆”和“栈”

  • 函数调用过程:

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

2.3 缓冲区溢出攻击原理

缓冲区溢出漏洞根据缓冲区在进程内存空间中的位置不同,又分为栈溢出、堆溢出和内核溢出这三种具体技术形态:

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

Linux系统可能会采取对抗缓冲区溢出的防范措施,取消措施才能重现基础的缓冲区溢出过程:

  • 取消“栈上数据不可执行”保护:echo 0 > /proc/sys/kerne/exec-shield

  • 取消“地址空间随机化”保护:echo 0 > /proc/sys/kernel/randomize_va_space

  • 编译时取消“/GS”保护:加上gcc编译选项 –fno-stack-protecto

    栈溢出安全漏洞代码

include <stdio.h>

void return_input(void){
char array[30];   //字符串缓冲区,长度为30字节
gets(array);   //将输入存入array缓冲区
    printf("%s\n", array);
}

int main (void){
    return_input();
    return 0;
}

gets()函数没有进行换抽取边界检查与保护,若用户输入超过30字节,输入数据将覆盖array缓冲区上方的EBP和RET,RET返回地址可能无法访问,从而造成段错误。

构建成功的栈溢出攻击

#include <stdio.h>
#include <string.h>
char shellcode[]=
// setreuid(0,0);
"\x31\xc0" // xor %eax,%eax
"\x31\xdb" // xor %ebx,%ebx
"\x31\xc9" // xor %ecx,%ecx
"\xb0\x46" // mov $0x46,%al
"\xcd\x80" // int $0x80
// execve /bin/sh
"\x31\xc0" // xor %eax,%eax
"\x50" // push %eax
"\x68\x2f\x2f\x73\x68" // push $0x68732f2f
"\x68\x2f\x62\x69\x6e" // push $0x6e69622f
"\x89\xe3" // mov %esp,%ebx
"\x8d\x54\x24\x08" // lea 0x8(%esp,1),%edx
"\x50" // push %eax
"\x53" // push %ebx
"\x8d\x0c\x24" // lea (%esp,1),%ecx
"\xb0\x0b" // mov $0xb,%al
"\xcd\x80" // int $0x80
// exit();
"\x31\xc0" // xor %eax,%eax
"\xb0\x01" // mov $0x1,%al
"\xcd\x80"; // int $0x80


char large_string[128];
int main(int argc, char **argv){
	char buffer[96];
	int i;
	long *long_ptr = (long *) large_string;
	for (i = 0; i < 32; i++)
		*(long_ptr + i) = (int) buffer;
	for (i = 0; i < (int) strlen(shellcode); i++)
		large_string[i] = shellcode[i];
	strcpy(buffer, large_string);
	return 0;
}

黑客为获得目标程序或系统的访问控制权,还需解决如下三个挑战:

  • 如何找出缓冲区溢出要覆盖和修改的敏感位置?
  • 将敏感位置的值修改成什么?
  • 执行什么代码指令来达到攻击目的?

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

3.1 Linux平台栈溢出攻击技术

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

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

  • RNS模式:一般用于被溢出的变量比较小,不足于容纳Shellcode的情况。攻击数据从低地址到高地址的构造方式是首先填充一些期望覆盖RET返回地址的跳转地址,然后是一堆Nop指令填充出“着陆区”,最后在是Shellcode。在溢出攻击之后,攻击数据将在RET区段即溢出了目标漏洞程序的小缓冲区,并覆盖了栈中的返回地址,然后跳转至Nop指令所构成的“着陆区”,并最终执行Shellcode。

  • RS模式:精确地定位出shellcode在目标漏洞程序进程空间中的起始地址,因此也就无需引入Nop空指令构建着陆区。将Shellcode放置在目标漏洞程序执行时的环境变量中,由于环境变量是位于Linux进程空间的栈底位置,因而不会受到各种变量内存分配与对齐因素的影响,其位置是固定的,可以通过如下公式进行计算:
    $$
    ret=0xc0000000−sizeof(void∗)−sizeof(filename)−sizeof(shellcode)ret=0xc0000000−sizeof(void∗)−sizeof(filename)−sizeof(shellcode)
    $$

3.2 Linux平台的Shellcode实现技术

五个步骤:

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

实现机制:

Linux系统本地Shellcode通常提供的功能就是为攻击者启动一个命令行Shell。在获得汇编语言实现的Shellcode之后,我们可以通过查找Intel opcode指令参考手册,即可获得opcode二进制指令形式的Shellcode

Linux系统中一个最简单的本地Shellcode的产生过程,即Shellcode的通用方法:

  • 先用高级编程语言,通常用C,来编写Shellcode程序

  • 编译并反汇编调试这个 Shellcode程序

  • 从编译语言代码级别分析程序执行流程

  • 整理生成的汇编代码,尽量减小它的体积并使它可注入,并通过嵌入C语言进行运行测试和调试

  • 提取汇编代码所对应的opcode二进制指令,创建Shellcode指令数组。

在Linux本地Shellcode中,往往还会在运行execve()启动shell之前,调用setreuid(0)将程序运行权限提升至Root用户,这样才能利用本地溢出攻击来提升权限,在执行execve()函数之后还需要执行exit()函数,从而在溢出攻击之后能够使程序正常退出。

用C语言编写Linux本地shellcode(非注入攻击负载)

#include <stdio.h>

int execve(const char *filename, char *const argv[ ], char *const envp[ ])
    
int main ()
{
    char * name[2];
    name[0] = "/bin/sh"; 
    name[1] = NULL;
    execve( name[0], name, NULL ); //execve函数启动/bin/sh提供命令行
    return 0;
}

execve()第一个参数filename字符串所代表的文件路径,第二个参数是利用数组指针来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。

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

4.1 Windows平台栈溢出攻击技术

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

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

4.2 Windows平台Shellcode实现技术

为了使得Windows中的Shellcode能够调用操作系统功能以完成攻击目标,并能够在期望注入的不同目标程序中正常运行,我们需要考虑如下问题:

  1. Shellcode必须可以找到所需的Windows32 API函数,并生成函数调用表;
  2. 为了能够使用这些API函数,Shellcode必须找出目标程序已加载的函数地址,或者需自行加载所需函数库,获得所需函数在目标程序进程内存空间中的加载地址,并获得它们在目标程序进程内存空间中的加载地址;
  3. Shellcode需考虑消除空字节,以避免在字符串操作函数中被截断,如果目标程序有更进一步的过滤规则,那么Shellcode在编写时需添加一些编码机制,以顺利通过过滤器;
  4. Shellcode需确保自己可以正常退出,并使原来的目标程序进程继续运行或终止;
  5. 在目标系统环境存在异常处理和安全防护机制时,Shellcode需进一步考虑如何对抗这些机制。

Windows本地Shellcode

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

编写Shellcode最简单的方式就是使用硬编码的函数地址,比如system()函数在Windows XP特定版本的目标程序内存空间中的加载地址为0x77bf93c7,那么我们就可以在Shellcode中使用Call 0x77bf93c7指令,让EIP指令寄存器跳转至硬编码的函数入口地址执行。system()函数是由msvcrt.dll动态链接库所导出的API函数,而攻击目标并不一定加载了该DLL,为了确保Shellcode能够正确地调用所需函数,一般需要将所需函数的动态链接库装载至目标程序内存中,然后再查询获得该函数的加载地址

Windows远程Shellcode

实现过程大概如下:

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

5. 堆溢出攻击

一个典型的Linux程序在其进程内存空间中通常有如下的一些数据区:包含已初始化全局数据的.data段、包含未经初始化数据的.bss段、运行时刻动态分配内存的数据区heap等。在这些内存数据区,数据分配与增长方向是从低地址到高地址。因此,在.data、.bss和heap缓冲区溢出的情形,都被称为堆溢出。

函数指针改写

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

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

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

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

Linux操作系统中的堆管理是通过glibc库实现的,使用的内存管理算法被称为dlmalloc,使用了被称为Bin的双向循环链表来存储内存空闲块信息。glibc库中的free()函数提供了将4字节值写入任意内存地址的机会。free()函数在处理内存块回收时,需要将己被释放的空闲块和与之相邻的空闲块进行合并,因此将会把符合条件的空闲块从Bin链表中unlink摘出来,合并之后再将新的空闲块插回链表中。攻击者可以利用此特性构造空闲块,达成覆盖。

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

尝试杜绝溢出的防御技术

解决缓冲区溢出攻击的最根本的办法是编写正确地、不存在缓冲区溢出安全漏洞的软件代码。但由于C/C++语言作为效率优先的语言,很容易就会出现缓冲区溢出。因此研究人员开发了一些工具和技术来帮助那些经验不足的程序员编写安全正确的程序,包括一些高级的查错程序,如fault injection等。

允许溢出但不让程序改变执行流程的防御技术

第二种防御技术允许溢出发生,但对可能影响到程序流程的关键数据结构实施严密的安全保护,不让程序改变其执行流程,从而阻断溢出攻击。

无法让攻击代码执行的防御技术

第三种防御技术尝试解决冯▪诺依曼体系的本质缺陷,通过堆栈不可执行限制来防御缓冲区溢出攻击。LA64、AMD64、Alpha等新的CPU硬件体系框架都引入对基于硬件NX保护机制,从硬件上支持对特定内存页设置成不可执行,如 Windows XP SP2、Linux内核2.6 及以后版本都支持硬件NX保护机制,与操作系统配合来提升系统的安全性。

二、实践过程

本次没有实践作业

三、学习中遇到的问题及解决

对汇编语言和编译器调试器那方面不太了解,本科的时候也没接触过,在网上看了些入门知识,希望后期能搞懂。

四、学习感想和体会

本章的内容设计的不仅限于要讲的各种溢出漏洞,也包括一些汇编语言等底层知识,虽然没有实践作业,但是也要加强理论方面的学习。

posted @ 2020-05-06 10:16  王昱皓  阅读(282)  评论(0编辑  收藏  举报