安全路透社
当前位置:安全路透社 > 安全客 > 正文

【技术分享】现代化的堆相关漏洞利用技巧

翻译:hac425

前言


现在的漏洞绝大部分都出在了堆这一部分,所以了解一些现在常用的漏洞利用技巧是非常必要的。


堆的概念


堆是一个用于动态分配的内存池。使用malloc()函数可以从堆中申请内存。而使用free()函数可以释放由malloc函数申请的内存。下面来看看一个程序运行起来后它的一个内存布局是怎样的。

t01240e5ab9e13bf0d0.png

下面给出一个通过动态分配使用堆内存的例子。

int main()
{
    char * buffer = NULL;
    /* allocate a 0x100 byte buffer */
    buffer = malloc(0x100);
    /* read input and print it */
    fgets(stdin, buffer, 0x100);
    printf(“Hello %s!\n”, buffer);
    /* destroy our dynamically allocated buffer */
    free(buffer);
    return 0;
}

下面来看看堆和栈的区别。

从内存分配的时期来看,堆内存的分配在程序运行时进行,而栈内存的分配会在编译时进行。

从存储的东西来看,堆中一般会存储所需内存比较大的缓冲区,结构体,类对象等等,而栈中一般存储局部变量、返回地址、函数的参数….

从分配的方式来看,堆内存一般由程序员自己分配,通常使用malloc/calloc/recalloc/分配,free释放,或者 new/delete来管理堆内存,而栈内存的使用是由编译器自动完成的。

堆有很多种实现,其中比较主流的有dlmalloc ptmalloc jemalloc,还有些程序自己实现了堆的管理机制.再继续往下讲前,来做个小测验。猜一猜下面的malloc调用计算机(32位)实际上会分配多少字节的数据。

malloc(32);
malloc(4);
malloc(20);
malloc(0);

来看看答案,看你能答对几个

malloc(32);   – 40 bytes
malloc(4);    – 16 bytes
malloc(20);   – 24 bytes
malloc(0);    – 16 bytes

下面使用程序来验证下,其输出就是它实际分配到的内存地址和大小

t012a14f1d4890cde2b.png

 堆的内存分配是以chunk为单位进行分配的,下面以一张图来形象的看看每个堆chunk的结构。

t015f429a7c84120538.png

下面看看源代码中对这个chunk结构的描述。做了注释。

struct malloc_chunk { 
    INTERNAL_SIZE_T prev_size;   如果前一个堆块是被释放的,这个值为前一个堆快的大小,否则为0
    INTERNAL_SIZE_T size;  最后一位表示前一个堆块是否在使用, 为 1 表示在使用
    struct malloc_chunk* fd;  前向指针,指向前一个堆快的起始位置<包含堆首部的起始地址,不是malloc返回的给用户使用的指针
    struct malloc_chunk* bk;  后向指针,指向后一个堆快的起始位置<包含堆首部的起始地址,不是malloc返回的给用户使用的指针
    struct malloc_chunk* fd_nextsize; /* Only used for large blocks: pointer to next larger size. */
    struct malloc_chunk* bk_nextsize; /* Only used for large blocks: pointer to next larger size. */
 };

如果想继续深入了解堆分配相关细节的话可以去 https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/


几种常见堆漏洞的利用技巧


一、堆缓冲区溢出

堆缓冲区溢出和栈上的缓冲区溢出非常类似,他们主要区别就是缓冲区所处的位置不同。我们先来看看如果我们不断的向堆申请内存,那么堆中的那些chunk的分布情况。

t01214c3c0d4ed9ef84.png

发生溢出后的情况是

t014ef8b8ebc50e922e.png

如果溢出覆盖的内存只是一些很简单的数据类型,其实并没有什么用,但是在堆上分布着很多复杂的数据结构的比如,结构体、对象。而在在这些数据中可能会存储着一些有趣的东西,比如一个函数指针、虚表指针等,一旦我们通过溢出重写了这些特殊的字段我们就有可能控制程序的流程,实现代码执行。

这里来一个Demo,假设存在这样一个结构体。

struct toystr {
    void (* message)(char *);
    char buffer[20];
};

触发堆溢出的代码为

coolguy = malloc(sizeof(struct toystr));
lameguy = malloc(sizeof(struct toystr));
coolguy->message = &print_cool;
lameguy->message = &print_meh;
printf("Input coolguy's name: ");
fgets(coolguy->buffer, 200, stdin); // oopz...
coolguy->buffer[strcspn(coolguy->buffer, "\n")] = 0;
printf("Input lameguy's name: ");
fgets(lameguy->buffer, 20, stdin);
lameguy->buffer[strcspn(lameguy->buffer, "\n")] = 0;
coolguy->message(coolguy->buffer);
lameguy->message(lameguy->buffer);
coolguy = malloc(sizeof(struct toystr));
lameguy = malloc(sizeof(struct toystr));
coolguy->message = &print_cool;
lameguy->message = &print_meh;
printf("Input coolguy's name: ");
fgets(coolguy->buffer, 200, stdin); // 这里有一个很明显的堆溢出
coolguy->buffer[strcspn(coolguy->buffer, "\n")] = 0;
printf("Input lameguy's name: ");
fgets(lameguy->buffer, 20, stdin);
lameguy->buffer[strcspn(lameguy->buffer, "\n")] = 0;
coolguy->message(coolguy->buffer);
lameguy->message(lameguy->buffer);

t01fe8c850d4fbb1fd2.png

堆溢出之后

t01c8ae0d73fc7a8d25.png

通过溢出可以将lameguymessage函数指针设置为0x41414141(AAAA),后来当lameguy调用message函数时就可以劫持程序流程到0x41414141,覆盖对象的虚表指针也能实现类似的效果。


二、UAF漏洞

UAF漏洞大家应该听的比较多了,因为最近几年各个主流的浏览器,adobe flash都爆出了非常多的uaf漏洞。那么UAF漏洞到底是一种怎么样的漏洞呢?简单的来说就是一块内存已经被释放了,但是在程序中还存在对这块内存的引用,并且在一定情形下还会使用这块内存的数据。由于这块已经被释放的内存还可以被程序的其他地方申请使用,那么这块内存的数据就是不可信的,这样就有可能造成一些很严重的问题。话不多说上图。

假设这样一个场景,栈中存在着一个指向一块堆内存的指针

t016525b5a565cae8a1.png

之后将指针指向的那块内存释放掉,于是现在我们拥有了一个悬垂指针,由于释放后的内存在程序执行过程中还可以继续申请到这块内存,如果之后的某个操作申请了这块内存,那么我们就可以修改这块内存的数据,而那个悬垂指针却一无所知,还会义无反顾的使用这块内存,这样就会发生一些问题.

为了利用一个UAF漏洞,我们通常需要分配一个与存在漏洞的对象大小相同但类型不同的对象来占用那块已经被释放的内存。假设现在有以下两个类型

struct toystr {
    void (* message)(char *);
    char buffer[20];
};
struct person {
    int favorite_num;
    int age;
    char name[16];
};

可以知道上面两个类型的大小是一样的,这就为漏洞利用奠定了基础.现在假设toystr存在一个UAF漏洞,即当toystr对象被释放后仍然还有一个指向他的指针,设它为P,之后新建一个person对象,来占据刚刚被释放的那块内存,此时我们将person.favorite_num的值设为0x41414141,之后在调用P->message(),我们就能控制程序流程到0x41414141,完成漏洞利用.

现在UAF漏洞非常火,这是为什么呢,原因有以下几点.

1.他不会需要造成内存崩溃 

2.他可以被用来进行信息泄露 

3.他还可以用来控制eip实现代码执行


三、破坏堆的元数据

要利用这种漏洞,需要我们对堆的管理机制非常的了解,常用的方式有伪造chunk,利用unlink实现一个任意地址写…下面来看看两种不同状态下堆的元数据都有哪些。

堆在使用时

t01de4a841ac918eff0.png

堆被释放后。

t018e750fa4210e73fb.png

这种类型的漏洞利用在各大ctf中经常被考到,而在现实环境下往往无法利用特别是在windows下,不过理解这种东西还是挺有帮助的.大家可以在下面的资源中学到这种漏洞利用技术的详细情况。

总结


在现实环境下,针对堆相关漏洞的利用,更多的是先控制堆的布局,之后触发漏洞,修改一些存在堆中的特殊对象的特殊数据,比如虚表,表示对象的长度值等等,之后进一步利用劫持 eip实现代码执行。


参考链接


https://kitctf.de/writeups/0ctf2015/freenote/

https://sploitfun.wordpress.com/2015/03/04/heap-overflow-using-malloc-maleficarum/

http://acez.re/ctf-writeup-hitcon-ctf-2014-stkof-or-modern-heap-overflow/

http://wapiflapi.github.io/2014/11/17/hacklu-oreo-with-ret2dl-resolve/

http://phrack.org/issues/66/10.html

http://dl.packetstormsecurity.net/papers/attack/MallocMaleficarum.txt


原文链接:http://security.cs.rpi.edu/courses/binexp-spring2015/lectures/17/10_lecture.pdf

未经允许不得转载:安全路透社 » 【技术分享】现代化的堆相关漏洞利用技巧

赞 (0)
分享到:更多 ()

评论 0

评论前必须登录!

登陆 注册