2015/5/12 18:36:42
时隔这么久,自己终于重新开始了安全之路,虽然每天的工作和研读的论文也都是安全领域的技术,但是自己心里真正的安全还是漏洞的研究。因此,自己决定业余来自学这部分,今天算是一个正式的开始吧!
今天来简单介绍下Linux漏洞,因为Linux给予了用户更大的自由度和操控性,因此更加适合进行安全研究,我们也从Linux开始。这部分主要分为以下小节:
- 栈的操作
- 缓冲区溢出的基本原理
- 本地缓冲区溢出漏洞攻击框架
- 小缓冲区的漏洞攻击框架
一、栈的操作
栈是操作系统进程内存中特有的结构,当系统运行一个程序,程序执行所需要的信息、数据都会以进程的形式存放到内存中供随时读写,这时内存中程序代码的大致结构就像下面这个样子:
栈是一种特殊的数据结构,可以想象出餐馆叠在一起的盘子,每次放上一个新盘子,之前最上面的盘子就到了下面,新盘子成为了最顶上的盘子;而取盘子时,总是从上向下依次取出,这样的数据结构我们称之为先进后出(FILO),栈就是一种这样的结构。
栈的增长顺序,在任何一个系统中都是由高内存向低内存扩展,我能想到一个直观的理由是系统默认可以从低内存开始处理数据(不考虑大端存储的话)。
栈有两个基本的操作,一个是将数据/元素压入栈中,即PUSH操作;另一个则相反,是将栈顶的元素弹出,存储到寄存器中,即POP操作。
内存中每个进程在内存栈段中都有自己的栈,而且栈始终是由最高内存地址向最低内存地址反向增长的。关于栈有两个重要的寄存器,一个是扩展基地址指针EBP,它指向栈底,即较高地址;另一个是扩展栈顶指针ESP,指向栈的较低地址。
系统在实际运行程序时,栈的应用往往表现在函数调用。每个函数都会有自己的栈,因此当发生一次函数调用时,调用函数和被调用函数都要进行一些栈的“交接”。比如对于调用函数来说,需要做以下工作:
- 调用程序首先将被调用函数的参数按照逆序压入栈中,从而对函数调用进行设置;
- 将当前的EIP指针(扩展指令指针,只向CPU下一步要处理的指令)保存到栈上,这样在被调用函数返回时就可以在离开的地方继续执行,这个地址被称作返回地址;
- 执行call命令,将被调用函数的地址放入EIP中执行;
这样,调用函数将当前的运行状态完整保存到栈中,同时压入了被调用函数的参数,接下来,就是被调用函数的工作了,我们一般称之为“开场白”:
- “开场白”:保存当前EBP到栈中,然后将ESP设置为EBP,然后ESP减小,留出存放变量的空间;
- “收场白”:其实要简单些,这里主要是将ESP增加到EBP,释放清空栈,然后弹出EIP,继续之前中断的执行,一般就是利用leave和ret两个指令完成。
我在自己的虚拟机Ubuntu12.04上编写了一个简单的C程序,然后使用disassemble命令反汇编程序文件,可以看到清晰的“开场白”和“收场白”:
二、缓冲区溢出
缓冲区,本身是系统用于临时存放数据的一块内存区域,我们经常会用到变量复制的操作,将一个缓冲区的数据复制到另一个缓冲区,但是遗憾地是并非所有的函数都执行了严格的缓冲区大小检查操作。因此,当向一个缓冲区中复制超过其大小的数据时,数据就会超出其边界,覆盖影响到其相邻的区域。
假设我们现在有一个具有两个参数的函数调用过程,我们通过此时缓冲区在内存中的结构来说明下缓冲区溢出的原理。
在被调用函数得到执行权之前,调用函数会讲被调用函数的参数逆序压进栈中,这里就是Temp1和Temp2,然后压栈保存当前的EIP指针,最后利用Call命令将被调用函数的地址设置为EIP开始执行。
被调用函数的栈从EBP开始,到ESP为止,中间可以是函数内部定义的局部变量。如果函数内部定义了一个变量name是一个10字节的数组,当将一个100字节的Temp2复制到name时无疑会出现数据的越界,比如覆盖到EBP,甚至到达EIP,而此时原先的EIP便遭到了破坏,造成了缓冲区溢出。
缓冲区溢出的结果一般会造成拒绝服务,但是这是一个相对比较好的结果,因为起码程序给出了提示,有些时候EIP会被攻击者控制并以用户级访问权限执行恶意代码。而第三种则是最糟糕的情况,也就是EIP被控制并在系统级或根级执行恶意代码,此时攻击者获得了系统最高权限。
三、本地缓冲区溢出漏洞攻击
本地缓冲区溢出漏洞攻击要比远程漏洞攻击容易些,因为能够访问系统内存空间,并且更容易调试攻击代码。
一般一个漏洞攻击程序由三个部分构成:
- NOP雪橇:汇编代码中有一个空指令NOP,意味着不执行任何操作,而只是移动到吓一跳命令。缓冲区溢出攻击的思路是通过缓冲区溢出覆盖控制EIP,使得EIP跳转到我们的攻击代码,而有时控制计算的并非十分精确,现实中也存在多种情况,因此我们需要添加一段NOP指令作为返回地址的“缓冲”,以防EIP超出攻击代码的范围。
- Shellcode:上面所说EIP需要指向一个攻击代码,这个代码就是我们的Shellcode了。原本Shellcode的作用仅仅是返回一个执行shell,也是因此得名,但是现在的Shellcode可以执行更为复杂的攻击。Shellcode是执行黑客命令的机器代码,因此实质上是二进制码,通常以十六进制表示。
- 重复返回地址:确定好NOP雪橇和Shellcode的地址后,就需要确定EIP指向的地址,并且进行一定程度的重复,作为缓冲区溢出的填充,一定要覆盖到EIP,作为EIP指向Shellcode才可以实施攻击。
最后,我们将以上三个部分组合起来,就是一个缓冲区溢出攻击的框架:
四、小缓冲区的溢出漏洞攻击
一般Shellcode不会太大,但是也不会太小,如果我们发现存在漏洞的缓冲区很小,不足以存放上面的Shellcode,那该如何呢?
方法就是将Shellcode存放到环境变量的位置,然后缓冲区覆盖EIP指向Shellcode。
这样之所以可以成功,是因为所有Linux ELF文件在映射到内存中时会将最后的相对地址设为0xbfffffff。环境变量和参数存储在这个区域,这些数据的下面就是函数栈,结构如图:
这样,我们就可以继续将EIP指向Shellcode执行攻击。
PS:
好了,Linux漏洞的基础知识就介绍到这里了,一些内存、堆栈的知识可以参考我之前的文章。今天虽然讲漏洞,但是没有列出代码和实验演示,感兴趣地朋友可以去Metasploit的网站浏览更多信息,那里有许多攻击实验的分享。
Refer: Gray Hat Hacking: The Ethical Hacker‘s Handbook, Third Edition