# 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 溢出。
程序并没有输出的功能。