为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

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

2012-09-19 6页 pdf 124KB 16阅读

用户头像

is_117578

暂无简介

举报
C++通过覆盖__atexit进行缓冲区溢出攻击 C++通过覆盖__atexit 进行缓冲区溢出攻击 通过覆盖__atexit进行缓冲区溢出攻击 --静态编译版本的heap 溢出 原作者: Pascal BoUChareine 原文: > 译者注:这片文章可能很早就出来了,我看国内也没有人介绍,干脆就 翻译出来一块儿共享吧,如有什么错误的地方,欢迎指正。 mailto:alert7@21cn.com 介绍: 本文讨论了类似通过覆盖.dtors进行缓冲区溢出攻击的技术。归根结底 是想方设法改变程序的执行流程,使之最终执行我们想要执行的代码。本文 假设读...
C++通过覆盖__atexit进行缓冲区溢出攻击
C++通过覆盖__atexit 进行缓冲区溢出攻击 通过覆盖__atexit进行缓冲区溢出攻击 --静态编译版本的heap 溢出 原作者: Pascal BoUChareine 原文: <<__atexit in memory bugs - specific proof of concept with statically linked binaries and heap overflows>> 译者注:这片文章可能很早就出来了,我看国内也没有人介绍,干脆就 翻译出来一块儿共享吧,如有什么错误的地方,欢迎指正。 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 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[ATEX99v_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 来说,这个技术听起来很强大。_
/
本文档为【C++通过覆盖__atexit进行缓冲区溢出攻击】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索