# Pwnable.tw re-alloc

说明:因为没有合适的 glibc 资源(自已懒),这里只是进行了一个理论分析。

# 环境保护

dreamcat@ubuntu:~/Desktop/pwnable/re-alloc$ checksec --file=re-alloc
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   83) Symbols	  Yes	1		2		re-alloc
dreamcat@ubuntu:~/Desktop/pwnable/re-alloc$ strings libc-9bb401974abeef59efcdd0ae35c5fc0ce63d3e7b.so | grep ubuntu
GNU C Library (Ubuntu GLIBC 2.29-0ubuntu2) stable release version 2.29.
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.
dreamcat@ubuntu:~/Desktop/pwnable/re-alloc$

2.29 的一个 glibc,这里要注意的就是对 tcache 一个保护机制,将 tcache_chunk 的包括指针指向了 tcache。没有 pie 的保护。

而且 RELRO 没有全开,就意味着我们可以修改 got 表。只要我们有办法泄露出 libc。以及任意地址写。

# 代码分析

int menu()
{
  puts("$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
  puts(&byte_402070);
  puts("$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
  puts("$   1. Alloc               $");
  puts("$   2. Realloc             $");
  puts("$   3. Free                $");
  puts("$   4. Exit                $");
  puts("$$$$$$$$$$$$$$$$$$$$$$$$$$$");
  return printf("Your choice: ");
}

主要实现了增删,全局没有 malloc 函数,但是 realloc 内在内部调用 malloc。

void *__libc_realloc (void *oldmem, size_t bytes)
{
  mstate ar_ptr;
  INTERNAL_SIZE_T nb;         /* padded request size */
  void *newp;             /* chunk to return */
  void *(*hook) (void *, size_t, const void *) =
    atomic_forced_read (__realloc_hook);
  if (__builtin_expect (hook != NULL, 0))
    return (*hook)(oldmem, bytes, RETURN_ADDRESS (0));
#if REALLOC_ZERO_BYTES_FREES
  if (bytes == 0 && oldmem != NULL)
    {
      __libc_free (oldmem); return 0;
    }
#endif
  /* realloc of null is supposed to be same as malloc */
  if (oldmem == 0)
    return __libc_malloc (bytes);
  /* chunk corresponding to oldmem */
  const mchunkptr oldp = mem2chunk (oldmem);
  /* its size */
  const INTERNAL_SIZE_T oldsize = chunksize (oldp);
  if (chunk_is_mmapped (oldp))
    ar_ptr = NULL;
  else
    {
      MAYBE_INIT_TCACHE ();
      ar_ptr = arena_for_chunk (oldp);
    }
  /* Little security check which won't hurt performance: the allocator
     never wrapps around at the end of the address space.  Therefore
     we can exclude some size values which might appear here by
     accident or by "design" from some intruder.  We need to bypass
     this check for dumped fake mmap chunks from the old main arena
     because the new malloc may provide additional alignment.  */
  if ((__builtin_expect ((uintptr_t) oldp > (uintptr_t) -oldsize, 0)
       || __builtin_expect (misaligned_chunk (oldp), 0))
      && !DUMPED_MAIN_ARENA_CHUNK (oldp))
      malloc_printerr ("realloc(): invalid pointer");
  checked_request2size (bytes, nb);
  if (chunk_is_mmapped (oldp))
    {
      /* If this is a faked mmapped chunk from the dumped main arena,
	 always make a copy (and do not free the old chunk).  */
      if (DUMPED_MAIN_ARENA_CHUNK (oldp))
	{
	  /* Must alloc, copy, free. */
	  void *newmem = __libc_malloc (bytes);
	  if (newmem == 0)
	    return NULL;
	  /* Copy as many bytes as are available from the old chunk
	     and fit into the new size.  NB: The overhead for faked
	     mmapped chunks is only SIZE_SZ, not 2 * SIZE_SZ as for
	     regular mmapped chunks.  */
	  if (bytes > oldsize - SIZE_SZ)
	    bytes = oldsize - SIZE_SZ;
	  memcpy (newmem, oldmem, bytes);
	  return newmem;
	}
      void *newmem;
#if HAVE_MREMAP
      newp = mremap_chunk (oldp, nb);
      if (newp)
        return chunk2mem (newp);
#endif
      /* Note the extra SIZE_SZ overhead. */
      if (oldsize - SIZE_SZ >= nb)
        return oldmem;                         /* do nothing */
      /* Must alloc, copy, free. */
      newmem = __libc_malloc (bytes);
      if (newmem == 0)
        return 0;              /* propagate failure */
      memcpy (newmem, oldmem, oldsize - 2 * SIZE_SZ);
      munmap_chunk (oldp);
      return newmem;
    }
  if (SINGLE_THREAD_P)
    {
      newp = _int_realloc (ar_ptr, oldp, oldsize, nb);
      assert (!newp || chunk_is_mmapped (mem2chunk (newp)) ||
	      ar_ptr == arena_for_chunk (mem2chunk (newp)));
      return newp;
    }
  __libc_lock_lock (ar_ptr->mutex);
  newp = _int_realloc (ar_ptr, oldp, oldsize, nb);
  __libc_lock_unlock (ar_ptr->mutex);
  assert (!newp || chunk_is_mmapped (mem2chunk (newp)) ||
          ar_ptr == arena_for_chunk (mem2chunk (newp)));
  if (newp == NULL)
    {
      /* Try harder to allocate memory in other arenas.  */
      LIBC_PROBE (memory_realloc_retry, 2, bytes, oldmem);
      newp = __libc_malloc (bytes);
      if (newp != NULL)
        {
          memcpy (newp, oldmem, oldsize - SIZE_SZ);
          _int_free (ar_ptr, oldp, 0);
        }
    }
  return newp;
}
libc_hidden_def (__libc_realloc)

大致意思就是如果 realloc (NULL,size) 就会调用 malloc。

realloc (ptr,0),调用 free。程序也是用这里实现的 free

realloc (ptr,chunksize (ptr)), 不做任何事情。

如果申请一个更大的空间,会检查当前堆块的附近是否有足够的空间,没有,则重新申请,然后将数据 copy 过去,并 free 原来的 chunk

申小空间会将多余的空间释放掉。

程序只允许我们存储两个 chunk 再 heap [2], 并且限制了我们的申请大小位 0x78。

int allocate()
{
  _BYTE *v0; // rax
  unsigned __int64 v2; // [rsp+0h] [rbp-20h]
  unsigned __int64 size; // [rsp+8h] [rbp-18h]
  void *v4; // [rsp+18h] [rbp-8h]
  printf("Index:");
  v2 = read_long();
  if ( v2 > 1 || heap[v2] )
  {
    LODWORD(v0) = puts("Invalid !");
  }
  else
  {
    printf("Size:");
    size = read_long();
    if ( size <= 0x78 )
    {
      v4 = realloc(0LL, size);
      if ( v4 )
      {
        heap[v2] = v4;
        printf("Data:");
        v0 = (_BYTE *)(heap[v2] + read_input(heap[v2], (unsigned int)size));		// 一字节溢出
        *v0 = 0;
      }
      else
      {
        LODWORD(v0) = puts("alloc error");
      }
    }
    else
    {
      LODWORD(v0) = puts("Too large!");
    }
  }
  return (int)v0;
}

allocate 函数里存在 off_by_one 的 null 溢出。

程序并没有输出的功能。