背景:从进入计算机领域已有近八年的时间了,自我感觉在整体轮廓上对各层次都有了一定的了解。但就安全领域来说,却是实实在在的门外汉;然而安全话题却一直引领计算机行业的发展。最近看了不少关于黑客及逆向工程方面的资料,自认有了一定的了解;尤其在学习Xfocus出版的《网络渗透技术》时,发现溢出漏洞的发现和利用原来是这么回事。下面就我所理解的和基于书上所展示的实例,说说Linux环境下的漏洞是怎样的以及是如何利用的。

适宜读者:想了解溢出漏洞的小白;熟悉Linux环境下的C编程,熟悉Gcc与Gdb;熟悉Linux下的AT&T汇编;了解Perl

1. 编程环境
2. 一个溢出漏洞实例
3. 溢出是如何发生的
4. 如何编写及提取ShellCode
5. 怎样利用溢出漏洞

1. 编程环境
  我的测试环境是Red Hat 9.0。当然你也可以使用其他的Linux版本,不过在高版本的Linux环境下可能会有防溢出机制(比如说Ubuntu 7.10);尽管说在这种环境下也许有高人可以做到溢出利用,但这已不属于本文章的范畴。如果你是小白,如果你想一次成功,推荐你先在RH9中测试。
  在利用漏洞时使用的是perl脚本;这里并不需要你有太深的perl功底,只要能理解这里使用的几条语句就行了。当然,在你的Red Hat上一定要安装gcc、gdb和perl解释器,这些在安装光盘里都可以找到。

欢迎大家访问我的博客:http://billstone.cublog.cn

  • 标 题:答复
  • 作 者:billstone
  • 时 间:2009-03-31 15:35:18

2. 一个溢出漏洞实例
  为了在直观上对溢出有个清晰的理解,我们先给出一个非常简单的溢出漏洞实例。首先看一个有溢出漏洞的简单程序vulnerable.c:

代码:
#include <stdio.h>

#include <string.h>



int main(int argc, char *argv[])

{

    char buff[16];



    strcpy(buff, argv[1]);

    printf("\n%s\n", buff);

}

程序中在使用strcpy函数时,因为没有检测字符串的长度而导致当argv[1]串长超过16字节时就会出现缓冲区溢出现象。使用gcc命令将vulnerable.c编译为可执行程序vulnerable,命令为gcc -o vulnerable vulnerable.c,如下图所示:



为了在本地利用该漏洞,我们需要精心构造ShellCode,下面是用perl写的利用程序exploit.pl:
代码:
#!/usr/bin/perl

#

# exploit.pl



$shellcode = 

"\x31\xd2\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69".

"\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80";

# 这里path需要设为本机环境下vulnerable的路径

$path = "/home/bill/Cracker/vulnerable"; 



$ret = 0xbffffffc - (length($path)+1) - (length($shellcode)+1);



$new_retword = pack('l',$ret);



printf("[+] Using ret Shellcode 0x%x\n", $ret);



%ENV=(); $ENV{CC}=$shellcode;



exec "$path",$new_retword x 8;

其中,上面提到的两个程序附在附件中(这里需要注意的是exploit.pl在创建后需要修改属性才能执行)。上面的ShellCode是精心构造过的,在后面的构造shellcode章节将讲述。
运行exploit.pl我们将得到一个shell(sh-2.05b$):



如果你能得到控制台(sh-2.05b$),就代表你已经攻击成功了。下面我们将详细讲述溢出漏洞相关的内容。

欢迎大家讨论!

  • 标 题:答复
  • 作 者:billstone
  • 时 间:2009-03-31 15:41:20

3. 溢出是如何发生的
  由于函数中局部变量的内存分配是发生在栈(Stack)中的,所以如果在某一个函数中定义了缓冲区变量,则这个缓冲区变量所占用的内存空间是在该函数被调用时所建立的栈里。由于对缓冲区的潜在操作(比如字串的复制)都是从内存低址到高址的,而内存中所保存的函数调用返回地址往往就在该缓冲区的上方(高地址)这是由于栈的特性决定的,这就为覆盖函数的返回地址提供了条件。当我们有机会用大于目标缓冲区大小的内容来向缓冲区进行填充时,就可以改写函数保存在函数栈(Statck)中的返回地址,从而使程序的执行流程随着我们的意图而转移。这是冯诺依曼计算机体系结构的缺陷。  
  下面将调试一个简单溢出程序simple_overflow.c来了解IA32架构缓冲区溢出的机制:

代码:
#include <stdio.h>

#include <string.h>



char large[] = "1234512345123451234512345===ABCD";



int main(void)

{

    char small[16];

    

    strcpy(small, large);

}

上面程序中,large字符串的长度是精心构造的,下面会提到;接下来将编译为simple_overflow,并用gdb调试,结果如下:



从图中可以看出,执行结束后eip已经被改为0x44434241,正好是ABCD的ASCII码值的倒置(A->0x41,B->0x42,C->0x43,D->0x44),这是由于IA32(Intel架构)默认字节序是Little_endian方式;而ABCD则正是上述程序中large数组的最后四个字母。也就是说在主程序调用strcpy时,因为large长度远长于small而导致返回地址被覆盖了。接下来我们反汇编程序,看看eip为何会变成0x44434241:



可以看到,在main函数的最开始设置断点(b *0x08048328)执行(r)后esp指向的内容就是main函数的返回地址(0x42015574);上面0x42015571位置的命令(call *0x8(%ebp))就是调用执行main函数。我们的目标就是覆盖这个地址,这样在main函数返回时转入我们的流程。这里,我们记下返回地址存储的地址0xbfffde1c。
  我们关注位置<main+28>上的指令(call 0x8048268<strcpy>):在linux环境下,当调用函数时将把参数从右向左压入栈(push命令)中。在本例中,先压入0x8049420(位置<main+19>),从下图中可以看到该地址为large字符串;接着压入0xffffffe8(%ebp)(位置<main+27>),该地址就是small字符串的首地址。



我们用ebp减去small数组的首地址是24,再加上函数指向最开始要做的保存ebp操作()所需要的4个字节可以得到28。可以验证一下0xbfffde00(small数组的首地址)加上28正好是我们之前记下的0xbfffde1c;这也正是为何我们的large数组需要32(28+4)字节的原因。



  如上所示,继续执行到main函数返回,最后的ret指令让eip等于esp指向的内容,而此时由于执行了有溢出漏洞的strcpy函数,此时esp指向的内容已经被我们修改过了(0x44434241)。这样,eip就变成了可以控制的地址了,也就是说我们达到了可控流程的目的。

  • 标 题:答复
  • 作 者:billstone
  • 时 间:2009-03-31 15:45:45

4. 如何编写及提取ShellCode
  Shellcode是一段机器指令,用于在溢出之后改变系统正常流程,转而执行ShellCode从而完成渗透测试者的功能。1996年,Aleph One在Underground发表的论文给这段代码赋予ShellCode的名称,而这个称呼沿用至今。
  这里我们将编写一个非常简单的ShellCode,它的功能是得到一个命令行,下面是其C代码及执行情况:



程序shellcode运行后相当于又执行了一个“/bin/sh”,接下来用gdb调试以查看其关键代码:


程序中关键在于调用了execve函数,通过调试可以清楚得看到在调用该函数前将三个参数按从右向左的顺序压入栈中:先在<main+33>压入$0x0(即NULL参数),接着在<main+38>压入$ebp-8即指向地址$0x8048408的指针(即name),最后在<main+39>压入地址$0x8048408(即name[0],也就是”/bin/sh”字符串的地址)。接着我们反汇编execev函数(需要重新编译shellcode,使用静态编译,以避免链接干扰。命令为:gcc static o shellcode shellcode.c):



从反汇编代码中可以看到,其中关键使用了一个软中断功能(<execve+36>)。我们在在这个指令位置设断,并查看软中断执行前各寄存器的值:



可以看到,eax保存execve的系统调用号11,ebx保存name[0](即”/bin/sh”),ecx保存name这个指针,edx为0。这样执行软中断后就能执行/bin/sh得到Shell了;接下来,有了以上的分析就可以编写自己的ShellCode了,同时验证上面分析结果的正确性。
  下面,我们使用在C程序中内嵌汇编的方式构造shellcode,具体代码如下。有一点要注意,Linux x86默认的字节序是little-endian,所以压栈的字符串要注意顺序。



通过编译执行,我们成功得到了shell命令行(sh-2.05b$)。在编写内嵌汇编时一定要注意格式问题;当然最重要的是在执行软中断前一定要使各寄存器的值符合我们之前分析的结果。
  此时,编写工作依然没有完结,要记住我们最终的目的是得到ShellCode,也就是一串汇编指令;而对于strcpy等函数造成的缓冲区溢出,会认为0是一个字符串的终结,那么ShellCode如果包含0就会被截断,导致溢出失败。用objdump看看这个ShellCode是否包含0,命令为:objdump d shellcode_asm | more。注意在此命令下会反汇编所有包含机器指令的section,请自行找到<main>段:



从反汇编结果可以看到,有两条指令”mov $0x0,%edx”和”mov $0xb,%eax”包含0,需要变通一下。我们使用命令”xor %edx,%edx”替换”mov $0x0,%edx”,使用”lea 0xb(%edx),%eax”替换”mov $0xb,%eax”,情况如下:



运行没有问题,再看看这个ShellCode有没有包含0:



可以看到,所有曾出现0的指令全消除了。也许你会说,地址0x80482fd上不就有四个0么;这里我们需要注意,我们需要提取的ShellCode从0x8048304到0x804837a,所以在此范围内没有0。
  到此为止,ShellCode的编写工作已经完美完成了;剩下的就是抽取及测试工作了,下面给出了一个简单的测试程序:



测试成功。看到上面的ShellCode是不是很眼熟?没错,正是在第二章节中我们使用过的ShellCode。到此,ShellCode的编写及抽取工作已经完成,相信您看到这里一定也能写出属于自己的ShellCode了吧:)

  • 标 题:答复
  • 作 者:billstone
  • 时 间:2009-03-31 15:48:37

5. 怎样利用溢出漏洞
  前面我们介绍了溢出是如何产生的,并得到了一个简单的ShellCode。接下来我们将讲述一种在本地攻击存在溢出漏洞程序的方法:把ShellCode放在环境变量里,从而在攻击程序中精确定位ShellCode。下面是示意图:



在执行存在漏洞程序前,我们将ShellCode作为环境变量传递给程序,现在关键是如何定位ShellCode的地址。关于这个问题,我们先看一下堆栈最开始的使用情况,请看下图:



可以看到栈底是固定的,为0xc0000000,向低地址扩展,先是4个字节的0x00,然后是程序路径,接着是环境变量。使用gdb调试simple_overflow可以清楚得看到这一点:



从上图可以看到,假如我们精心构造的ShellCode能作为环境变量存储在程序路径前的一个环境变量中,那么我们将可以精确得定位ShellCode的地址。下面是在第二章节中展示的漏洞程序vulnerable.c,不过为了调试方便,我们在程序最后加了一条getchar()语句:


下面是我们在第二章节中展示的用Perl编写的攻击程序exploit.pl:



上面程序中,shellcode是我们在第四章节中精心构造过的ShellCode,path是本机上vulnerable可执行程序所在的绝对路径(请依据您本机的情况加以修改);ret则精确定位了ShellCode所在地址;new_retword保存了ret地址的长整型值,接着输出该值以便调试;接着设置shellcode为环境变量,最后使用exec调用漏洞程序,参数为连续8个new_retword值。
  首先,在一个控制台运行攻击程序:



通过输出看到ShellCode地址为0xbfffffc5,接着我们在另外一个控制台调试:



我们使用gdb附上(Attach)vulnerable程序,查看0xbfffffc5地址上的数据;通过对比,我们可以确定就是我们精心构造过的Shellcode。
  本地缓冲区溢出比较简单,看到这里相信各位都能明白了。

总结
  溢出利用总是有特定的环境,Windows和Linux下有着显著的区别,而且攻击手段也是层出不穷。本文给大家展示了在Linux环境下使用环境变量方法攻击漏洞程序的简单实例。通过阅读本文,希望大家能有所收获。

题外话
  本文是基于Xfocus的《网络渗透技术》一书所写,所以如果在看本文时能对照该书的第二、第三章节相信会更有收获,并且在书中不但阐述了如何在Linux x86环境下利用缓冲区溢出,还有Win32环境、AIX PowerPC平台及Solaris SRARC平台下的缓冲区溢出利用技术。