malloc_state
当我们在 pwndbg 中 p main_arena,打印的东西好像就是这个。
struct malloc_state
{
/* Serialize access. */
__libc_lock_define (, mutex);
/* Flags (formerly in max_fast). */
int flags;
/* Set if the fastbin chunks contain recently inserted free blocks. */
/* Note this is a bool but not all targets support atomics on booleans. */
int have_fastchunks;
/* Fastbins */
mfastbinptr fastbinsY[NFASTBINS];
/* Base of the topmost chunk -- not otherwise kept in a bin */
mchunkptr top;
/* The remainder from the most recent split of a small request */
mchunkptr last_remainder;
/* Normal bins packed as described above */
mchunkptr bins[NBINS * 2 - 2];
/* Bitmap of bins */
unsigned int binmap[BINMAPSIZE];
/* Linked list */
struct malloc_state *next;
/* Linked list for free arenas. Access to this field is serialized
by free_list_lock in arena.c. */
struct malloc_state *next_free;
/* Number of threads attached to this arena. 0 if the arena is on
the free list. Access to this field is serialized by
free_list_lock in arena.c. */
INTERNAL_SIZE_T attached_threads;
/* Memory allocated from the system in this arena. */
INTERNAL_SIZE_T system_mem;
INTERNAL_SIZE_T max_system_mem;
};
其中最常用的大概是 top,因为在打 house of orange 的时候,pwndbg 的 heap 指令会看不到 top_chunk,不过改 heap 显示的最后一个 chunk 的 size 也能解决。
__libc_lock_define (, mutex);
这个函数是给 arena 加锁,保护多线程下的并发访问。对 pwn 来说不重要;
flags 存储 arena 的状态,对 pwn 来说不重要;
have_fastchunks 表示 fastbin 内 是否有 free chunk;
fastbinsY 数组中,不同的索引表示不同的大小,fastbin[i] 表示索引 i 对应的链表表头。
top 存储 top_chunk 所在地址。
last_remainder 指向最近一次小块分割后的剩余部分的首地址。
bins 存储各种大小的空闲 chunk。
Small bins (2-63):固定大小的块
Large bins (64-126):按大小范围分组的块
Unsorted bin (1):临时存放刚释放的块
binmap 标识哪些 bin 非空。
剩下的大概都是和线程有关的,对 pwn 应该不重要。
接下来正式开始 malloc。
__libc_malloc
#if IS_IN (libc) /*当为 libc.so 编译时,IS_IN (libc) 为真*/
void *
__libc_malloc (size_t bytes) /*bytes 就是我们的输入*/
{
mstate ar_ptr; /*mstate 等同于之前提的 malloc_state,应该可以当做 main_arena 理解。*/
void *victim; /*受害者,也就是最后返回的地址*/
_Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2,
"PTRDIFF_MAX is not more than half of SIZE_MAX"); /*确保系统的数据类型满足要求,对 pwn 不重要。*/
if (!__malloc_initialized)
ptmalloc_init (); /*如果未初始化就进行初始化,对 pwn 不重要。*/
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
if (!checked_request2size (bytes, &tbytes)) /*检查请求大小是否超过PTRDIFF_MAX,事实上即使是'0xff'*16也不会超过。对 pwn 不重要。*/
/**/
{
__set_errno (ENOMEM);
return NULL;
}
size_t tc_idx = csize2tidx (tbytes); /*将申请的大小转化为索引,下面会对索引进行检查,太大就不会使用tcache。*/
MAYBE_INIT_TCACHE (); /*尝试初始化tcache*/
DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins /*检查大小范围*/
&& tcache /*检查tcache是否已经初始化*/
&& tcache->counts[tc_idx] > 0) /*这里检查对应大小的tcache的计数是否大于0。*/
{
victim = tcache_get (tc_idx); /*进入 tcache_get 函数获取 victim*/
return tag_new_usable (victim); /*第一处返回点*/
}
DIAG_POP_NEEDS_COMMENT;
#endif
if (SINGLE_THREAD_P)
{
victim = tag_new_usable (_int_malloc (&main_arena, bytes));
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
&main_arena == arena_for_chunk (mem2chunk (victim)));
return 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);
victim = _int_malloc (ar_ptr, bytes);
}
if (ar_ptr != NULL)
__libc_lock_unlock (ar_ptr->mutex);
victim = tag_new_usable (victim);
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
return victim;
}
libc_hidden_def (__libc_malloc)
先跳转阅读 tcache_get 函数:
tcache_get
tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
if (__glibc_unlikely (!aligned_OK (e)))
malloc_printerr ("malloc(): unaligned tcache chunk detected");
tcache->entries[tc_idx] = REVEAL_PTR (e->next);
--(tcache->counts[tc_idx]); /*可以得知,就是这个计数,导致只含一个 tcache chunk 时,改写其 next 不会起作用。处理方法也很简单:再给 tcache 塞一个 chunk 即可。*/
e->key = 0;
return (void *) e;
}