- 20199107 2019-2020-2 《网络攻防实践》第10次作业
- 1.实践内容
- 软件安全概述
- 缓冲区溢出基础概念
- Linux平台上的栈溢出与shellcode
- Windows平台上的栈溢出与shellcode
- 堆溢出攻击
- 缓冲区溢出攻击的防御技术
- 2.实践过程
- 3.学习中遇到的问题及解决
- 4.实践总结
- 5.其他参考文献
- 1.实践内容
20199107 2019-2020-2 《网络攻防实践》第10次作业
这个作业属于哪个课程 | 《网络攻防实践》 |
这个作业的要求在哪里 | 《网络攻防实践》第10次作业 |
我在这个课程的目标是 | 学习新知识、考试拿高分 |
这个作业在哪个具体方面帮助我实现目标 | 学习缓冲区溢出和shellcode相关知识 |
作业正文 | 见下文 |
其他参考文献 | 见文末 |
1.实践内容
软件安全概述
-
软件安全漏洞
软件、硬件、个人和组织管理中能被供给制利用来破坏安全策略的弱点称为安全漏洞,软件安全漏洞是其中最常见、影响最大的 -
软件安全困境
造成软件安全困境的三因素:-
复杂性:现代软件已经非常复杂,且向着更复杂发展,越发庞大的规模也代表着更多的bug和漏洞等
-
可扩展性:现代软件为了支持更优化的软件架构和更好的用户感受,会提供扩展和交互渠道,而攻击者可能会用难以预测的方式利用这些渠道进行攻击,此外,分析可扩展的软件也比不可更改的软件更困难
-
连通性:高度的连通性使得很小的软件缺陷也能影响巨大的范围,也使得自动化攻击变得可能
-
-
软件安全漏洞类型
-
内存安全违规类:是指在处理RAM内存访问时引入的缺陷,例如缓冲区溢出、两次释放、释放内存被重现引用,主要出现在C/C++等支持任意内存分配归还、不安全指针操作的语言的程序中
-
输入验证类:是指没有保证用户输入的正确性、合法性和安全性造成的漏洞,根据输入位置、输入内容和和被软件使用方式的不同包括,格式化字符串、SQL注入、代码注入、远程文件包含、目录遍历、http header注入http响应分割错误等
-
竞争条件类:是指处理进程的输出或结果无法预测,并依赖于其他进程导致的错误。常见于多进程、多线程程序
-
权限混淆与提升类:指程序由于疏忽或第三方欺骗滥用权限或赋予第三方不该给予的权限造成的漏洞,例如CSRF、clickjacking、FTP反弹攻击、权限提升、越狱等
-
缓冲区溢出基础概念
-
基本概念:指程序向缓冲区填数据时超出缓冲区本身范围,外溢数据覆盖了相邻空间的合法数据,常见于C/C++的memcpy(),strcpy()等内存与字符串复制函数的引用位置,因为这些函数不检查内存越界,一般也没有边界保护
-
背景知识
-
编译器与调试器的使用:对C/C++等语言编写的代码,要先用编译器编译,再用连接器连接,才能生成OS平台上的可执行文件。调试器则是程序开发人员在运行时刻调试与分析程序行为的基本工具。对于最常使用的C/C++,最著名的编译与连接器是GCC。类UNIX平台上进行程序的调试经常使用GDB调试器。Windows平台上则常用Visual Studio、VS.net等集成了编译器、连接器、调试器甚至其他更多功能的IDE
-
汇编语言基础知识:汇编语言是理解软件安全漏洞机理,掌握软件渗透攻击代码技术的底层基础。因为很多情况下无法得到被分析软件的源代码,只能在反汇编技术支持下读汇编代码,且渗透攻击中也有汇编语言写的shellcode。要了解常见的汇编指令,例如PUSH,POP,JMP,CALL等,以及各种寄存器的作用,在IA32中,从应用的角度一般将寄存器分为4类
-
通用寄存器主要用于普通的算术运算,保存数据、地址、偏移量、计数值等
-
段寄存器一般用作段基址寄存器
-
控制寄存器用来控制处理器的执行流程。其中最关键的是被称为“指令指针”的eip,它保存下一条要执行的机器指令的地址
-
其他寄存器中值得关注的是“扩展标志”寄存器,由不同的标志位组成,用于保存指令执行后的状态和控制指令执行流程的标志信息
-
-
进程内存管理
-
Linux操作系统中的进程内存空间布局和管理机制:
程序在执行时,系统在内存中会为程序创建一个虚拟的内存地址空间,在32位机上即4GB的空间大小,用于映射物理内存,并保存程序的指令和数据。内存空间3GB以下为用户态空间,3GB-4GB为内核态空间。操作系统将可执行程序加载到新创建的内存空间中,程序一般包含.text(包含程序指令)、.bss(包含未经初始化的数据)和.data(包含静态初始化的数据)三种类型的段。则主要包含未经初始化的数据,两者都被映射至可写的内存空间中。加载完成后,系统开始为程序初始化“栈”和“堆”
“栈”是一种后进先出的数据结构,其地址空间从高地址向低地址增长,程序运行的环境变量env、运行参数argv、运行参数数量argc都被放置在“栈”底,然后是主函数及调用“栈”中各个函数的临时保存信息
“堆”则是一种先进先出的数据结构,用于保存程序动态分配的数据和变量,其地址空间从低地址往高地址增长,与“栈”正好相反
程序执行时,就会按照程序逻辑执行.text中的指令,并在“堆”和“栈”中保存和读取数据,然而程序并不能正确地区分指令和数据,所以当我们修改内存空间中影响程序执行逻辑的敏感位置,并将恶意数据作为指令提交给处理器时,它仍会执行 -
Windows操作系统中的进程内存空间布局和管理机制:
Windows操作系统的进程内存空间2GB-4GB为内核态地址空间,用于映射Windows内核代码和一些核心态DLL,并用于存储一些内核态对象,0GB-2GB为用户态地址空间。高地址段映射大量程序共用系统DLL,1GB位置装载进程本身引用的DLL,可执行代码段从0x400000开始,同样有栈和堆存储各进程的执行数据
-
-
函数调用过程:栈结构与函数调用过程的底层细节是理解栈溢出攻击的重要基础,因为栈溢出攻击就是针对函数调用过程中返回地址在栈中的存储位置,进行缓冲区溢出,从而改写返回地址,达到让处理器指令寄存器跳转至攻击者指定位置执行恶意代码的目的,在IA32构架寄存器中,两个与栈密切相关的寄存器为ebp和esp,分别保存当前运行函数的栈底地址和栈顶地址,而两个密切相关的指令为push和pop,分别是将数据压入栈,及将栈顶数据弹出至特定寄存器,程序进行函数调用的过程有如下三个步骤:
-
调用:将参数和下一条指令地址入栈并跳转到函数入口地址
-
序言:对调用函数的栈基址入栈保存,创建函数自身栈结构等
-
返回:恢复调用者栈顶栈底指针,执行下一条指令
-
-
缓冲区溢出原理
-
根据缓冲区在内存空间位置不同分为:
-
栈溢出:是指利用缺乏边界保护的缓冲区变量,修改栈上的返回地址等敏感信息
-
堆溢出:类似栈溢出,但是存储在堆上的缓冲区变量被溢出
-
内核溢出:是进程内存空间内核态中存储的缓冲区变量被溢出
-
-
缓冲区溢出攻击的三个挑战:
-
如何找出要覆盖的敏感位置(通常是返回地址)?
-
将敏感位置的值改为什么?通常是恶意指令的起始位置,但如何确定注入指令在目标程序的位置并完成控制权移交?
-
执行什么代码达到攻击目的(payload,shellcode)?
-
-
-
Linux平台上的栈溢出与shellcode
-
栈溢出攻击技术:按照攻击数据的构造方式不同,主要有NSR、RNS和RS三种模式。其中NSR和RNS模式适用于本地缓冲区溢出和远程栈溢出攻击,而RS模式只能用于本地。
-
NSR模式:主要适用于被溢出的缓冲区变量比较大,足以容纳Shellcode的情况,其攻击数据是从低地址到高地址的的构造方式是,先构造一堆Nop指令(着陆区),之后填充Shellcode,再加上一些期望覆盖RET返回地址的跳转地址
-
RNS模式:一般用于被溢出的变量比较小,不足于容纳Shellcode的情况。攻击数据从低地址到高地址的构造方式是,首先填充一些期望覆盖RET返回地址的跳转地址,然后是一堆Nop指令,最后再是Shellcode。
-
RS模式:能够精确地定位出Shellcode在目标漏洞程序进程空间中的起始地址,因此也就无需引入Nop空指令构建着陆区。这种模式是将Shellcode放置在目标漏洞程序执行时的环境变量中,环境变量位于栈底,其位置是固定的,可以通过公式
ret=0xc0000000-sizeof(void*)-sizeof(FILENAME)-sizeof(Shellcode)
计算。
-
-
shellcode实现技术:
-
本地shellcode:本地shellcode产生的5个通用步骤:
-
(1)用高级语言(通常用C)编写shellcode程序(通过execve()启动/bin/sh)
-
(2)编译并反汇编shellcode程序
-
(3)从汇编代码分析执行流程
-
(4)整理生成的汇编代码,尽量减小它的体积并使它可注入,并通过嵌入C语言进行运行测试和调试
-
(5)提取汇编代码所对应的opcode二进制指令,创建Shellcode指令数组
-
在Linux本地Shellcode中,在执行execve()启动shell前还会用setreuid(0)提权至root,并在执行execve()后执行exit()函数使程序能正常退出
-
-
远程shellcode:原理与本地shellcode一致,区别在于shellcode程序要创建socket监听指定端口,启动命令行shell并将其输入输出与socket绑定
-
Windows平台上的栈溢出与shellcode
-
栈溢出攻击技术,原理上与Linux的大致相同,差异主要有:
-
对程序运行过程中废弃栈的处理方式差异:Windows会向废弃栈中写入一些随机的数据,而Linux则不进行任何的处理。
-
进程内存空间的布局差异:Linux进程内存空间中栈底指针在0xc0000000之下,栈中变量一般在0xbfff附近,在这些地址中没有空字节。Windows平台的栈位置处于0x00FFFFFF以下的用户内存空间,一般在0x0012附近,这些内存地址的首字节均为0x00空字节
-
系统功能调用实现方式差异:Linux系统中通过“int 80”中断处理来调用系统功能,而Windows系统则是通过操作系统中更为复杂的API及内核处理例程调用链来完成系统功能调用
-
-
shellcode实现技术,
-
对比Linux,需要考虑:
-
shellcode必须可以找到所需要的Windows32 API函数,并生成函数调用表
-
为了能够使用这些API函数,shellcode必须找到目标程序已加载的函数地址
-
shellcode需考虑消除空字节,以免在字符串操作函数中被截断
-
shellcode需确保自己可以正常退出,并使原来的目标程序进程继续运行或终止
-
在目标系统环境存在异常处理和安全防护机制时,shellcode需进一步考虑如何应对这些机制
-
-
本地shellcode:典型的本地shellcode同样也是启动一个命令行Shell,即
command.com
或cmd.exe
,过程可参考Linux本地shellcode实现技术 -
远程shellcode:
-
(1)创建一个服务器端socket,并在指定的端口上监听
-
(2)通过accept()接受客户端的网络连接
-
(3)创建子程序,运行“cmd.exe”,启动命令行
-
(4)创建两个管道,命令管道将服务器端socket接收(recv)到的客户端通过网络输入的执行命令,连接至cmd.exe的标准输入;然后输出管道将cmd.exe的标准输出连接至服务器端socket的发送(send),通过网络将运行结果反馈给客户端
-
-
堆溢出攻击
堆溢出是缓冲区溢出中第二种类型的攻击方式,由于堆中的内存分配与管理机制较栈更复杂,不同操作系统平台的实现机制具有显著的差异。堆中没有可以直接覆盖的返回地址,因此堆溢出攻击比栈溢出更难
-
函数指针改写:要求被溢出的缓冲区临近全局函数指针存储地址,且在其低地址方向上。此时向缓冲区填充数据,如果没有边界控制和判断,就可以覆盖函数指针所在的内存区,改写函数指针的指向地址,则程序在使用这个函数指针的时候就会执行shellcode
-
C++类对象虚函数表改写:使用了虚函数机制的C++类,如果它的类成员变量中存在可被溢出的缓冲区,那么就可以进行堆溢出攻击,通过覆盖类对象的虚函数指针,使其指向一个特殊构造的虚函数表,从而转向执行攻击者恶意注入的指令。
-
Linux下堆管理glibc库free()函数本身漏洞:Linux操作系统的堆管理是通过glibc库来实现的,通过称为Bin的双向循环链表来保存内存空闲块的信息。glibc库中的free()函数在处理内存块回收时,会将被释放的空闲块和与之相邻的块合并,利用精心构造的块可以在合并时覆盖Bin前指针的内容
缓冲区溢出攻击的防御技术
-
尝试杜绝缓冲区溢出的防御技术:解决缓冲区溢出攻击最根本的方法是编写正确的、不存在缓冲区溢出安全漏洞的软件代码。例如,通过Fuzz测试寻找安全漏洞并修复,在编译器引入针对缓冲区的边界保护检查机制
-
允许溢出但不让程序改变运行流程的防御技术:第二种防御技术允许溢出发生,但对可能影响到程序流程的关键数据结构实施严密的安全保护,不让程序改变其执行流程,从而阻断溢出攻击
-
无法让攻代码执行的防御技术:第三种防御技术尝试解决冯·诺依曼体系的本质缺陷,通过堆栈不可执行限制来防御缓冲区溢出攻击。例如,从硬件上支持特定内存页设置为不可执行,操作系统通过内核补丁等支持堆栈不可执行,在随机位置加载函数地址或堆栈起始地址
2.实践过程
五一福利好,只要求原理,不要求实践
3.学习中遇到的问题及解决
- 问题1:汇编语言和操作系统的相关知识使我头大,汇编不说了,操作系统方面,尤其是一些特别细特别具体的,例如什么Windows32 API,glibc库中的free(),完全没有了解过这些东西里面具体是什么
- 问题1解决方案:边看边百度,基本要什么都有
4.实践总结
千言万语汇成一句话:感谢老师,感谢五一劳动节!
5.其他参考文献
- 《网络攻防技术与实践》(诸葛建伟著)