20212818 2021-2022-2 《网络攻防实践》实践九报告
作者:互联网
一、实践内容
一)软件安全的概述
1.软件安全的威胁
- NIST将安全漏洞定义为:在系统安全流程、设计、实现或内部控制中所存在的缺陷或弱点,能够被攻击者所利用并导致安全侵害或对系统安全策略的违反。
- 随着计算机技术和网络的发展,软件的功能越来越强大,已经从严格的技术领域扩展到社会生活的方方面面。因此软件安全也已经成为影响经济与社会生活的重要问题。
2.软件安全的困境:为什么软件中会普遍地存在着安全漏洞呢?
- 复杂性:计算机软件经过数十年的发展,现代软件已变得非常复杂,而且研究趋势表明,软件的规模还会更快地膨胀,变得更加复杂。而软件规模越来越大,越来越复杂就意味着软件bug会越来越多。
- 可扩展性:现代软件为了支持更加优化的软件架构,支持更好的客户使用感受,往往都会提供一些扩展和交互渠道。正是现代可扩展软件本身的特性使得安全保证更加困难
- 连通性:互联网的普及使得全球更多的软件系统都连通在一起,不仅是接入互联网的计算机数量在快速增加,一些控制关键的基础设施的重要信息系统也与互联网建立起了连通性。高度的连通性使得一个小小的软件缺陷就可能影响非常大的范围。
3.安全漏洞类型主要存在如下几类:
- 内存安全违规类:是软件开发过程中在处理RAM内存访问时所引起的安全缺陷,如缓冲区溢出漏洞。
- 输入验证类:是指软件程序在对用户输入进行数据验证存时在错误,没有保证输入数据的正确性、合法性和安全性,从而导致可能被恶意攻击和利用。
- 竞争条件类:是系统或进程中一类比较特殊的错误,通常在涉及多进程和多线程处理的程序中出现。
- 权限混淆与提升类:是指计算机程序由于自身编程疏忽或被第三方欺骗,从而滥用其权限,或赋予第三方不该给予的权限。
(二)缓冲区溢出基本概念
1.缓冲区溢出
- 缓冲区溢出是计算机程序中存在的一类内存安全违规类漏洞,在计算机程序向特定缓冲区内填充数据时,超出缓冲区本身的容量,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏系统运行完整性。
2.背景知识
-
编译器与调试器的使用:Linux环境中要掌握GDB调试器、Windows系统中常用Visual Stdio、VC++等。
-
汇编语言:IA32汇编语言中我们要熟悉寄存器和他们的功能,关键寄存器及功能如下表:
-
进程内存管理:了解进程内存管理机制是深入理解软件安全漏洞的必须掌握的内容。
- Linux系统中的程序在执行时,在内存中会为程序创建一个虚拟的内存地址空间,用于映射物理内存,并保存程序的指令和数据。
- Windows操作系统,2GB和4GB为内核态地址空间,用于映射Windows内核代码和一些核心态DLL;0GB~2GB为用户态地址空间,高地址段映射了一些大量应用进程所共同使用的系统DLL,在1GB地址位置用于装载一些应用进程本身所引用的DLL文件。
-
函数调用过程:栈溢出攻击就是针对函数调用过程中返回地址在栈中的存储位置,进行缓冲区溢出,从而改写返回地址,达到让处理器指令寄存器跳转至攻击者指定位置执行恶意代码的目的。
3.缓冲区溢出攻击原理:根据缓冲区在进程内存空间中位置不同,分为栈溢出、堆溢出和内核溢出这三种具体的技术形态。
-
栈溢出:存储在栈上的一些缓冲区变量由于存在缺乏边界保护问题,能够被溢出并修改栈上的敏感信息。
-
堆溢出:存储在堆上一些缓冲区变量缺乏边界保护所遭受的溢出攻击。
-
内核溢出:存在于一些内核模块或程序中。
-
溢出攻击时要考虑的三个问题:需要修改的敏感位置在哪?将敏感位置修改为什么?执行什么代码以达到攻击目的?
(三)Linux平台上栈溢出与Shellcode
1.Linux平台栈溢出攻击技术:NSR、RNS、RS三种模式。
- NSR:主要适用于被溢出的缓冲区变量比较大,足以容纳Shellcode的情况,其攻击数据是从低地址到高地址的构造方式是一堆NOP指令(空操作指令)之后填充Shellcode。
- RNS:一般被用于溢出的变量比较小,不足以容纳Shellcode的情况,攻击数据是首先填充一些期望覆盖RET返回地址的跳转地址,然后是一堆NOP指令填充出着陆区,最后再是Shellcode。
- RS:这种模式不能精确定位Shellcode在目标漏洞程序中的起始地址,因此无需引入Nop空指令构建“着陆区”。
2.Shellcode实现技术
-
Linux系统中,程序通过“int 0x80”软中断来执行系统调用,而在Windows系统中通过核心DLL中提供的API接口来完成系统调用。
-
Linux本地Shellcode实现:下图中通过execve()函数启动/bin/sh提供命令行,但是无法直接将这段c语言代码作为注入攻击负载,我们必须提供以二进制形式存在的Shellcode。
- 所对应的汇编语言代码如下图所示,分别将execve()函数的参数NULL(0x0)、name变量地址、“/bin/sh”字符串地址压入栈中,然后将eax赋值为execve()系统调用号0xb,执行int 0x80软中断,完成开启Shell的功能。
- 先用高级语言编程,Shellcode程序;
- 编译并反汇编调试;
- 分析程序执行流程;
- 整理生成的汇编代码,尽量减少体积;
- 提取汇编代码所对应的opcode二进制指令,创建指令组。
-
远程Shellcode需要让目标程序创建socket监听指定的端口等待客户端连接,dup()2函数能将标准输入输出与socket网络通信通道进行绑定
(四)Windows平台上栈溢出与Shellcode
1.栈溢出攻击技术:与Linux的攻击技术的差异主要体现在三个方面
-
对程序运行中废弃栈的处理方式的差异:程序运行过程中拥有大量的函数调用,当一个函数调用完返回至调用者执行下一条指令前会有恢复栈基和栈顶指针的操作,Windows会向废弃栈中写入一些随机的数据,Linux则不做任何处理。这导致Windows平台中构建缓冲区数据在栈中植入恶意指令时,会面临限制。
-
进程内存空间的布局差异:Linux系统进程内存空间中栈底指针在0xc0x0000000之下,这些地址中没有空字节;Windows系统栈位置处于0x00FFFFFF以下的用户内存空间一般为0x0012****地址附近,而这些地址的首字节均为0x00空字节。
-
系统功能调用的实现方式的差异:Windows通过操作系统中更为复杂的API及内核处理例程调用链来完成系统功能调用。攻击者在执行shellcode就要考虑系统功能调用的区别。
-
Windows系统上典型的Shellcode是启动一个命令行shell,即“command.com”或“cmd.exe”,API中提供了system()函数调用,可以用于启动指定程序或特定命令。
-
远程Shellcode的过程:
- 创建一个服务器端socket,并在指定端口上监听;
- 通过accept接受网络连接;
- 创建子进程,运行cmd.exe启动命令行;
- 创建两个管道,命令管道和输出管道。
(五)堆溢出
Linux在.data、.bss和heap中缓冲区溢出都称为堆溢出。
1.函数指针改写:需要被溢出的缓冲区临近全局函数指针存储地址,且在其地址方向。当缓冲区填充数据时,如果没有边界判断和控制的话,缓冲区溢出后就会自然地覆盖函数指针所在的存储区。攻击者只要能将改函数指针指向恶意构造的shellcode地址,在程序使用函数指针是调用原先期望的函数时,就会调用shellcode。
2.c++类对象虚拟函数表:C++类通过虚拟函数提供了一种Late binding运行过程绑定机制,编译器为每个包含虚拟函数的类建立起虚拟函数表、存放虚拟函数的地址、虚拟函数指针。对于使用了虚拟函数的机制,如果它的类成员变量中存在可被溢出的缓冲区,那么就可进行堆溢出攻击。通过覆盖类对象的虚拟函数指针指向一个特殊构造的虚拟函数表,进而转向执行恶意代码。
3.Linux下堆管理glibc库free()函数本身漏洞:Linux操作系统的堆管理是通过glicbc库实现的,主要目的是增加对多线程环境的支持,同时优化内存分配和回收的算法。
(六)缓冲区溢出攻击防御技术
- 尝试杜绝溢出的防御技术:解决缓冲区溢出的根本办法的编写正确的、不存在缓冲区溢出安全漏洞软件代码,使用一些差错程序,如falt injection等。
- 允许溢出但不让程序改变执行流程的防御技术:对可能影响到程序流程的关键数据结构实施严密的安全保护,不让程序改变其执行流程,从而阻断溢出攻击。
- 无法让关键代码执行的防御技术:尝试冯.诺依曼体系的本质缺陷,通过堆栈不可执行限制来防御。
二、实践过程
实践目标
本次实践的对象是一个名为pwn1的linux可执行文件。
该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。
该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。
-
三个实践内容如下:
-
手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
-
利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
-
注入一个自己制作的shellcode并运行这段shellcode。
-
任务一:
手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。
输入vi pwn1
输入指令%!xxd将显示改变为十六进制
将d7修改为c3。定位到d按下r然后按c,定位到7按下r然后按3
输入:%!xxd -r 恢复成十六进制,然后保存文件,执行pwn1。再次执行objdump -d pwn1查看main函数。可以发现已经修改成功
执行pwd1发现确实成功了
任务二:利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。
反汇编pwn1文件,命令为 objdump -d pwn1
,可以看到 pwn1中所有的函数,包括getshell、foo、main。可以知道如果程序正常执行,会执行函数foo。foo函数的功能:将用户的输入再次输出。所以这里我们可以利用BOF漏洞,通过缓冲区溢出攻击的方式触发getshell函数。当foo函数的返回地址是0x080484ba,即foo函数执行完后会调用的下一条指令地址为0x080484ba。我们只需将这个地址替换为getshell函数的入口地址,就可以触发getshell函数。
首先安装gdb,后面会用到。
使用安装好的gdb调试分析pwn1 ,命令为gdb pwn1。
运行这个文件 ,命令为:r。
输入字符串,输入较短字符串时,程序可以正常结束运行,但是输入字符串较长时,会出现段错误。
我们让它显示具体信息,命令为info r,可以看到现在EIP的内容是0x33333333。
生成input文件,并设置特定的输入串,命令为:perl -e 'print "11111111222222223333333344444444\x7d\x84\x04\x08\x0a"' > input
通过命令ls查看当前目录下的文件,确定input文件已成功生成。查看input文件的16进制信息,命令为:xxd input。
将input文件设置为文件pwn1的输入,命令为(cat input; cat ) | ./pwn1。
任务三:注入一个自己制作的shellcode并运行这段shellcode
三、学习中遇到的问题及解决
四、实践总结
标签:函数,实践,20212818,地址,2021,Linux,缓冲区,Shellcode,溢出 来源: https://www.cnblogs.com/fairry/p/16273750.html