# Pwnable.tw re-alloc

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

# 环境保护

1
2
3
4
5
6
7
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。以及任意地址写。

# 代码分析

1
2
3
4
5
6
7
8
9
10
11
12
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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 溢出。

程序并没有输出的功能。

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

dreamcat WeChat Pay

WeChat Pay

dreamcat Alipay

Alipay

dreamcat PayPal

PayPal