0%

malloc源码分析

Malloc源码分析

重点关注各种bin的问题,了解熟悉堆基础结构,示例代码是2.35_3.7version

__libc_malloc

malloc实则调用__libc_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
#if IS_IN (libc)
void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim;

_Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2,
"PTRDIFF_MAX is not more than half of SIZE_MAX");

if (!__malloc_initialized) // 如果堆没初始化
ptmalloc_init (); // 初始化
#if USE_TCACHE
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes;
if (!checked_request2size (bytes, &tbytes)) // 判断size不能太小
{
__set_errno (ENOMEM);
return NULL;
}
size_t tc_idx = csize2tidx (tbytes); // size to tcache idx

MAYBE_INIT_TCACHE (); // tcache初始化有关

DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins // 类似fastbin max
&& tcache // 在找tcache
&& tcache->counts[tc_idx] > 0)
{
victim = tcache_get (tc_idx); // 获得chunk
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); // 获得可用的main_arena

victim = _int_malloc (ar_ptr, bytes); // _int_malloc获得堆块
/* 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);

/*
#define arena_for_chunk(ptr) \
(chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)

过以下检测需要满足的要求,只需满足一条即可
1. victim 为 0
2. IS_MMAPPED 为 1
3. NON_MAIN_ARENA 为 0
*/

assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || // 判断是否有 或者是mmap出来的
ar_ptr == arena_for_chunk (mem2chunk (victim))); // 检查
return victim;
}

__libc_malloc函数进行了如下步骤

  1. 判断堆是否初始化了,如果没有,则去初始化

  2. 判断size是否合理,不能太小

  3. 尝试从tcache里面获取

  4. 尝试从__int_malloc调用里获取

这里尝试从tcache里面获取细节:

1
2
3
4
5
6
7
8
9
10
11
12
size_t tc_idx = csize2tidx (tbytes); // size to tcache idx

MAYBE_INIT_TCACHE (); // tcache初始化有关

DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins // 类似fastbin max
&& tcache // 在找tcache
&& tcache->counts[tc_idx] > 0)
{
victim = tcache_get (tc_idx); // 获得chunk
return tag_new_usable (victim);
}

可见堆取出来的tcache是没有多余检查的,重点在于查看tcache→counts是否有空位,有空位可以直接取出来。

_int_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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
static void *
_int_malloc (mstate av, size_t bytes)
{
INTERNAL_SIZE_T nb; /* normalized request size */
unsigned int idx; /* associated bin index */
mbinptr bin; /* associated bin */

mchunkptr victim; /* inspected/selected chunk */
INTERNAL_SIZE_T size; /* its size */
int victim_index; /* its bin index */

mchunkptr remainder; /* remainder from a split */
unsigned long remainder_size; /* its size */

unsigned int block; /* bit map traverser */
unsigned int bit; /* bit map traverser */
unsigned int map; /* current word of binmap */

mchunkptr fwd; /* misc temp for linking */
mchunkptr bck; /* misc temp for linking */

#if USE_TCACHE
size_t tcache_unsorted_count; /* count of unsorted chunks processed */
#endif

/*
Convert request size to internal form by adding SIZE_SZ bytes
overhead plus possibly more to obtain necessary alignment and/or
to obtain a size of at least MINSIZE, the smallest allocatable
size. Also, checked_request2size returns false for request sizes
that are so large that they wrap around zero when padded and
aligned.
*/

if (!checked_request2size (bytes, &nb))
{
__set_errno (ENOMEM);
return NULL;
}

/* There are no usable arenas. Fall back to sysmalloc to get a chunk from
mmap. */
if (__glibc_unlikely (av == NULL)) // 如果av没有 调用sysmalloc通过mmap分配chunk并返回
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}

/*
If the size qualifies as a fastbin, first check corresponding bin.
This code is safe to execute even if av is not yet initialized, so we
can try it without checking, which saves some time on this fast path.
*/

#define REMOVE_FB(fb, victim, pp) \
do \
{ \
victim = pp; \
if (victim == NULL) \
break; \
pp = REVEAL_PTR (victim->fd); \
if (__glibc_unlikely (pp != NULL && misaligned_chunk (pp))) \
malloc_printerr ("malloc(): unaligned fastbin chunk detected"); \
} \
while ((pp = catomic_compare_and_exchange_val_acq (fb, pp, victim)) \
!= victim); \

if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) // 判断是否在fastbin内
{
idx = fastbin_index (nb); // 获得对应idx
mfastbinptr *fb = &fastbin (av, idx); // 获得对应指针
mchunkptr pp;
victim = *fb;

if (victim != NULL) // 开始在这个fastbinY里面摁找
{
if (__glibc_unlikely (misaligned_chunk (victim))) // 会校验内存是否对齐
malloc_printerr ("malloc(): unaligned fastbin chunk detected 2");

if (SINGLE_THREAD_P) // 如果是单线程模式 直接REVEAL_PTR获得原始指针并赋值给fb
*fb = REVEAL_PTR (victim->fd);
else
REMOVE_FB (fb, pp, victim); // 也是一样的道理
if (__glibc_likely (victim != NULL)) // 如果找到了
{
size_t victim_idx = fastbin_index (chunksize (victim)); // 先获得idx
if (__builtin_expect (victim_idx != idx, 0)) // idx是否对的上(也就是相当于校验了size)
malloc_printerr ("malloc(): memory corruption (fast)");
check_remalloced_chunk (av, victim, nb); // 给DEBUG用的
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb); // 如果同时开启了tcache 获得对应tcache idx
if (tcache && tc_idx < mp_.tcache_bins) // 如果合理
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL)
{
if (__glibc_unlikely (misaligned_chunk (tc_victim)))
malloc_printerr ("malloc(): unaligned fastbin chunk detected 3");
if (SINGLE_THREAD_P)
*fb = REVEAL_PTR (tc_victim->fd);
else
{
REMOVE_FB (fb, pp, tc_victim);
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx); // 将剩下的tcache丢到fastbin里 里面几乎没有校验 如果修改了fastbin fd然后申请到这里 fastbin丢到了tcache 是否实现了漏洞利用
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}

/*
If a small request, check regular bin. Since these "smallbins"
hold one size each, no searching within bins is necessary.
(For a large request, we need to wait until unsorted chunks are
processed to find best fit. But for small ones, fits are exact
anyway, so we can check now, which is faster.)
*/

if (in_smallbin_range (nb)) // 如果在smallbin范围内
{
idx = smallbin_index (nb); // 获得对应idx
bin = bin_at (av, idx); // 获得指针bin

if ((victim = last (bin)) != bin) // 因为是双向链表 所以直接搜索
{
bck = victim->bk; // 它的后一位
if (__glibc_unlikely (bck->fd != victim)) // fd校验
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb); // 设置victim的inuse位
bin->bk = bck; // 取出smallbin对应bin链表的第一位
bck->fd = bin;

if (av != &main_arena) // 如果不是主线程
set_non_main_arena (victim); // 设置为不是主线程的堆块
check_malloced_chunk (av, victim, nb); // for debug
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
/* While bin not empty and tcache not full, copy chunks over. */
tcache_put (tc_victim, tc_idx); // 和fastbin里面同样的思路
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

/*
If this is a large request, consolidate fastbins before continuing.
While it might look excessive to kill all fastbins before
even seeing if there is space available, this avoids
fragmentation problems normally associated with fastbins.
Also, in practice, programs tend to have runs of either small or
large requests, but less often mixtures, so consolidation is not
invoked all that often in most programs. And the programs that
it is called frequently in otherwise tend to fragment.
*/

else
{
idx = largebin_index (nb);
if (atomic_load_relaxed (&av->have_fastchunks))
malloc_consolidate (av); // 对fastbin chunks进行consolidate先
}

/*
Process recently freed or remaindered chunks, taking one only if
it is exact fit, or, if this a small request, the chunk is remainder from
the most recent non-exact fit. Place other traversed chunks in
bins. Note that this step is the only place in any routine where
chunks are placed in bins.

The outer loop here is needed because we might not realize until
near the end of malloc that we should have consolidated, so must
do so and retry. This happens at most once, and only when we would
otherwise need to expand memory to service a "small" request.
*/

#if USE_TCACHE
INTERNAL_SIZE_T tcache_nb = 0;
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
tcache_nb = nb;
int return_cached = 0;

tcache_unsorted_count = 0;
#endif

for (;; )
{
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) // 取unsorted bin
{
bck = victim->bk;
size = chunksize (victim);
mchunkptr next = chunk_at_offset (victim, size);

/*
1. 检验size大小是否合理
2. 检验下一个chunk的size大小是否合理
3. 检验下一个chunk的presize是否等于当前的size
4. 检验bck->fd和victim->fd
5. 检验下一个chunk的pre_inuse位
*/

if (__glibc_unlikely (size <= CHUNK_HDR_SZ) // 检验
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < CHUNK_HDR_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");

/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
// 对unsortedbin里面的chunk进行拆分
if (in_smallbin_range (nb) && // 在small bin范围内
bck == unsorted_chunks (av) && // victim必须是unsortedbin里面唯一的chunk
victim == av->last_remainder && // victim在last_remainder里面
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) // victim要大于申请长度+0x20
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb); // 获得切分后的remainder
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; // remainder加入到unsortedbin里面
av->last_remainder = remainder; // 记录last_remainder
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size)) // 如果在largebin范围内 需要清空fd_nextsize和bk_nextsize
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
// 设置remainder的状态位 以及 victim的状态位
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);

check_malloced_chunk (av, victim, nb); // DEBUG的时候才有用
void *p = chunk2mem (victim); // 获得指向user_data的指针
alloc_perturb (p, bytes); // 如果设置了perturb_type,将chunk的user data初始化为perturb_type ^ 0xff
return p;
}
// 将chunk从unsortedb bin 中移除,如果大小合适,直接返回给用户
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

/* Take now instead of binning if exact fit */

if (size == nb) // 如果大小正合适
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
#if USE_TCACHE
/* Fill cache first, return to user only if cache fills.
We may return one of these chunks later. */
if (tcache_nb // 优先填满cache
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (victim, tc_idx);
return_cached = 1;
continue;
}
else
{
#endif
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
#if USE_TCACHE
}
#endif
}

/* place chunk in bin */
// 大小不合适要将chunk丢到适应的bin 也就是unsortedbin 2 largebin
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd; // 这里是没有校验的 如果能够修改到smallbin的fd 这里就可以控制fwd
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk)) // 如果小于当前chunk的最小size,那就加入它 -> large bin attack
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize; // 这里导致的
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else // 不然就根据fd_nextsize找到不比它大的chunk
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}

if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim; // 这里就能attack
bck->fd = victim;

#if USE_TCACHE
/* If we've processed as many chunks as we're allowed while
filling the cache, return one of the cached ones. */
++tcache_unsorted_count;
if (return_cached
&& mp_.tcache_unsorted_limit > 0
&& tcache_unsorted_count > mp_.tcache_unsorted_limit)
{
return tcache_get (tc_idx);
}
#endif

#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
}

#if USE_TCACHE
/* If all the small chunks we found ended up cached, return one now. */
if (return_cached) // 这里拿回来
{
return tcache_get (tc_idx);
}
#endif

/*
If a large request, scan through the chunks of current bin in
sorted order to find smallest that fits. Use the skip list for this.
*/

if (!in_smallbin_range (nb)) // 如果申请的是Large chunk就搜寻large bin
{
bin = bin_at (av, idx);

/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin // 如果对应的largebin不为空
&& (unsigned long) chunksize_nomask (victim)
>= (unsigned long) (nb)) // 而且大小符合 找到第一个不小于申请大小的largebin chunk
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;

/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) // 如果大小一样大
&& chunksize_nomask (victim)
== chunksize_nomask (victim->fd))
victim = victim->fd; // 直接改动fd的chunk 而不需要东nextsize指针

remainder_size = size - nb; // 看看剩下多少size
unlink_chunk (av, victim);

/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}
/* Split */
else // 切割后丢到unsorted bin里
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("malloc(): corrupted unsorted chunks");
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

/*
Search for a chunk by scanning bins, starting with next largest
bin. This search is strictly by best-fit; i.e., the smallest
(with ties going to approximately the least recently used) chunk
that fits is selected.

The bitmap avoids needing to check that most blocks are nonempty.
The particular case of skipping all bins during warm-up phases
when no chunks have been returned yet is faster than it might look.
*/
// 通过bitmap来搜寻bin 一个bitmap能够检查32个bin
++idx;
bin = bin_at (av, idx);
block = idx2block (idx);
map = av->binmap[block];
bit = idx2bit (idx);

for (;; )
{
/* Skip rest of block if there are no more set bits in this block. */
if (bit > map || bit == 0)
{
do
{
if (++block >= BINMAPSIZE) /* out of bins */
goto use_top;
} // 找到block不为空的最小bin的对应bit
while ((map = av->binmap[block]) == 0);

bin = bin_at (av, (block << BINMAPSHIFT));
bit = 1;
}

/* Advance to bin with set bit. There must be one. */
while ((bit & map) == 0)
{
bin = next_bin (bin);
bit <<= 1;
assert (bit != 0);
}

/* Inspect the bin. It is likely to be non-empty */
victim = last (bin);

/* If a false alarm (empty bin), clear the bit. */
if (victim == bin)
{
av->binmap[block] = map &= ~bit; /* Write through */
bin = next_bin (bin);
bit <<= 1;
}

else
{
size = chunksize (victim);

/* We know the first chunk in this bin is big enough to use. */
assert ((unsigned long) (size) >= (unsigned long) (nb));

remainder_size = size - nb;

/* unlink */
unlink_chunk (av, victim);

/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}

/* Split */
else
{
remainder = chunk_at_offset (victim, nb);

/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("malloc(): corrupted unsorted chunks 2");
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;

/* advertise as last remainder */
if (in_smallbin_range (nb))
av->last_remainder = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

use_top: // 从top_chunk切割
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).

We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhausted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/

victim = av->top;
size = chunksize (victim);

if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (atomic_load_relaxed (&av->have_fastchunks)) // 如果有fastbin需要整理
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}

/*
Otherwise, relay to handle system-dependent cases
*/
else // top chunk大小不够需要sysmalloc函数进行分配
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
}
}

_int_malloc里面做了如下步骤:

  1. 对于满足fastbin进行处理,如果找到了需要将多的fastbin丢到tcache里面

  2. 对于满足smallbin进行处理,如果找到了需要将多的smallbin丢到tcache里面

  3. 如果1.2都不满足,需要将fastbin进行malloc_consolidate操作,也就是向前向后merge操作

  4. 在unsortedbin里面找寻满足要求的堆块,如果满足small chunk大小而且unsortedbin里面只有一个,而且是last_remainder,尝试切割,如果可以切割就返回给用户,不能切割就将unsortedbin 丢到largebin或者smallbin里面

  5. 在large bin里面寻找合适的堆块

  6. 通过bitmap寻找chunk

  7. 最后都不满足则从top_chunk里面切割

细节:

尝试从fastbin里面获取

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
#define REMOVE_FB(fb, victim, pp)			\
do \
{ \
victim = pp; \
if (victim == NULL) \
break; \
pp = REVEAL_PTR (victim->fd); \
if (__glibc_unlikely (pp != NULL && misaligned_chunk (pp))) \
malloc_printerr ("malloc(): unaligned fastbin chunk detected"); \
} \
while ((pp = catomic_compare_and_exchange_val_acq (fb, pp, victim)) \
!= victim); \

if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ())) // 判断是否在fastbin内
{
idx = fastbin_index (nb); // 获得对应idx
mfastbinptr *fb = &fastbin (av, idx); // 获得对应指针
mchunkptr pp;
victim = *fb;

if (victim != NULL) // 开始在这个fastbinY里面摁找
{
if (__glibc_unlikely (misaligned_chunk (victim))) // 会校验内存是否对齐
malloc_printerr ("malloc(): unaligned fastbin chunk detected 2");

if (SINGLE_THREAD_P) // 如果是单线程模式 直接REVEAL_PTR获得原始指针并赋值给fb
*fb = REVEAL_PTR (victim->fd);
else
REMOVE_FB (fb, pp, victim); // 也是一样的道理
if (__glibc_likely (victim != NULL)) // 如果找到了
{
size_t victim_idx = fastbin_index (chunksize (victim)); // 先获得idx
if (__builtin_expect (victim_idx != idx, 0)) // idx是否对的上(也就是相当于校验了size)
malloc_printerr ("malloc(): memory corruption (fast)");
check_remalloced_chunk (av, victim, nb); // 给DEBUG用的
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb); // 如果同时开启了tcache 获得对应tcache idx
if (tcache && tc_idx < mp_.tcache_bins) // 如果合理
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL)
{
if (__glibc_unlikely (misaligned_chunk (tc_victim)))
malloc_printerr ("malloc(): unaligned fastbin chunk detected 3");
if (SINGLE_THREAD_P)
*fb = REVEAL_PTR (tc_victim->fd);
else
{
REMOVE_FB (fb, pp, tc_victim);
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx); // 将剩下的tcache丢到fastbin里 里面几乎没有校验 如果修改了fastbin fd然后申请到这里 fastbin丢到了tcache 是否实现了漏洞利用
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}

对满足要求的fastbin有如下校验:

  • fastbin的内存需要对齐

  • 对fd指向的堆块内存需要对齐

  • 对size进行校验,需要满足这个size就是这个fastbinY里面的

如果有满足的fastbin之后遍历victim之后的堆块,将他们全部丢到tcache里面

这个过程有fd指向的堆块内存对齐校验,这里如果修改了fd为伪造的地址,而且满足这个victim→fd→fd = 0,在将其丢到tcache的同时可能可以导致一个非法利用

对于tcache拿出来同样是没有size校验的,这里是一个可能的漏洞点

尝试从smallbin里面获取

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
  if (in_smallbin_range (nb)) // 如果在smallbin范围内
{
idx = smallbin_index (nb); // 获得对应idx
bin = bin_at (av, idx); // 获得指针bin

if ((victim = last (bin)) != bin) // 因为是双向链表 所以直接搜索
{
bck = victim->bk; // 它的后一位
if (__glibc_unlikely (bck->fd != victim)) // fd校验
malloc_printerr ("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset (victim, nb); // 设置victim的inuse位
bin->bk = bck; // 取出smallbin对应bin链表的第一位
bck->fd = bin;

if (av != &main_arena) // 如果不是主线程
set_non_main_arena (victim); // 设置为不是主线程的堆块
check_malloced_chunk (av, victim, nb); // for debug
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
/* While bin not empty and tcache not full, copy chunks over. */
tcache_put (tc_victim, tc_idx); // 和fastbin里面同样的思路
}
}
}
#endif
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

操作和fastbin类似,寻找到指定size的chunk之后进行校验,随后将多余的丢到tcache里面,然后返回

校验点:

  • victim→bk→fd == victim

这里校验只有一处,随后进行的操作是

1
2
3
4
bck = victim->bk;

bin->bk = bck; // 取出smallbin对应bin链表的第一位
bck->fd = bin;

这里如果能够victim的bk指向我们伪造好的fake_chunk,满足fake_chunk→fd = victim,则是满足要求能够申请出来

随后执行将剩下的丢到tcache里面,这里面一点点校验都没有

直接丢bin→bk进入tcache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;
/* While bin not empty and tcache not full, copy chunks over. */
tcache_put (tc_victim, tc_idx); // 和fastbin里面同样的思路
}
}

这里感觉很有操作空间

  1. 纯粹的tcache打法,可以控制victim→bk = fake_chunk,满足下面条件即可在tcache里面任意地址分配

    1
    2
    3
    4
    5
    6
    fake_chunk_A→fd = victim,
    fake_chunk_A→bk = fake_chunk_B,
    fake_chunk_B→fd = CONTROL_ADDR,
    fake_chunk_B→bk = fake_chunk_C,
    fake_chunk_C→fd = fake_chunk_B,
    fake_chunk_C→bk = bin_addr // 双向链表
  2. 类largebin attack打法,利用里面的bck→fd = bin,也就是满足

    1
    2
    3
    fake_chunk_A→fd = victim,
    fake_chunk_A->bk = _IO_list_all_something_else,
    _IO_list_all_something_else->bk = bin_addr //需要满足

利用方法2应该打不了(

都不满足则对fastbin进行malloc_consolidate操作

操作细节如下,也就是向前向后合并

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
static void malloc_consolidate(mstate av)
{
mfastbinptr* fb; /* current fastbin being consolidated */
mfastbinptr* maxfb; /* last fastbin (for loop control) */
mchunkptr p; /* current chunk being consolidated */
mchunkptr nextp; /* next chunk to consolidate */
mchunkptr unsorted_bin; /* bin header */
mchunkptr first_unsorted; /* chunk to link to */

/* These have same use as in free() */
mchunkptr nextchunk;
INTERNAL_SIZE_T size;
INTERNAL_SIZE_T nextsize;
INTERNAL_SIZE_T prevsize;
int nextinuse;

atomic_store_relaxed (&av->have_fastchunks, false);

unsorted_bin = unsorted_chunks(av);

/*
Remove each chunk from fast bin and consolidate it, placing it
then in unsorted bin. Among other reasons for doing this,
placing in unsorted bin avoids needing to calculate actual bins
until malloc is sure that chunks aren't immediately going to be
reused anyway.
*/
/*将fastbin中的chunk移除并合并,然后放入unsortedbin*/
maxfb = &fastbin (av, NFASTBINS - 1);
fb = &fastbin (av, 0);
do { // 外层循环遍历fastbinY的每一个fast bin
p = atomic_exchange_acq (fb, NULL); // 替换p的值为fp
if (p != 0) { // 如果P == 0 则说明该fastbin为空
do {
{
if (__glibc_unlikely (misaligned_chunk (p))) // 检查是否对齐
malloc_printerr ("malloc_consolidate(): "
"unaligned fastbin chunk detected");

unsigned int idx = fastbin_index (chunksize (p)); // 获得idx
if ((&fastbin (av, idx)) != fb) // 检查fastbin的size是否对的上
malloc_printerr ("malloc_consolidate(): invalid chunk size");
}

check_inuse_chunk(av, p); // for debug
nextp = REVEAL_PTR (p->fd); // 获得相同大小的下一个fastbin

/* Slightly streamlined version of consolidation code in free() */
size = chunksize (p);
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);

if (!prev_inuse(p)) { // 向后合并
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
if (__glibc_unlikely (chunksize(p) != prevsize)) // 需要前者的presize和size相同
malloc_printerr ("corrupted size vs. prev_size in fastbins");
unlink_chunk (av, p); // 开始unlink
}

if (nextchunk != av->top) { // 判断下一个如果不是top_chunk
nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

if (!nextinuse) { // 判断是否能向前合并
size += nextsize;
unlink_chunk (av, nextchunk);
} else
clear_inuse_bit_at_offset(nextchunk, 0);

first_unsorted = unsorted_bin->fd; // 这里如果unsortedbin已经被破坏掉fd可控之后
unsorted_bin->fd = p; // 链入
first_unsorted->bk = p; // 这里会影响

if (!in_smallbin_range (size)) {
p->fd_nextsize = NULL;
p->bk_nextsize = NULL;
}

set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;
set_foot(p, size);
}
// 否则被top chunk合并
else {
size += nextsize;
set_head(p, size | PREV_INUSE);
av->top = p;
}

} while ( (p = nextp) != 0);

}
} while (fb++ != maxfb);
}

可以归纳为

  1. 对fastbin检查是否对齐以及size是否对的上

  2. 如果pre_inuse位为0,向后合并(向上),校验pre_size是否等于size

  3. 如果下一个堆块为top_chunk,和top_chunk合并

  4. 否则查看下一个堆块的next_inuse位,这里的next_chunk是根据chunk+size偏移过去的,这里的next_inuse为next_chunk→next_chunk来获得的,如果next_chunk也释放了,那就合并,链入到unsortedbin里面

  5. 然后依次寻找同一个fastbinY里面的fastbin逻辑是找它的fd指针,这里没有对fd进行校验,不过没什么用

尝试在unsortedbin里面找寻堆块

具体代码如下:

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
      while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) // 取unsorted bin
{
bck = victim->bk;
size = chunksize (victim);
mchunkptr next = chunk_at_offset (victim, size);

/*
1. 检验size大小是否合理
2. 检验下一个chunk的size大小是否合理
3. 检验下一个chunk的presize是否等于当前的size
4. 检验bck->fd和victim->fd
5. 检验下一个chunk的pre_inuse位
*/

if (__glibc_unlikely (size <= CHUNK_HDR_SZ) // 检验
|| __glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): invalid size (unsorted)");
if (__glibc_unlikely (chunksize_nomask (next) < CHUNK_HDR_SZ)
|| __glibc_unlikely (chunksize_nomask (next) > av->system_mem))
malloc_printerr ("malloc(): invalid next size (unsorted)");
if (__glibc_unlikely ((prev_size (next) & ~(SIZE_BITS)) != size))
malloc_printerr ("malloc(): mismatching next->prev_size (unsorted)");
if (__glibc_unlikely (bck->fd != victim)
|| __glibc_unlikely (victim->fd != unsorted_chunks (av)))
malloc_printerr ("malloc(): unsorted double linked list corrupted");
if (__glibc_unlikely (prev_inuse (next)))
malloc_printerr ("malloc(): invalid next->prev_inuse (unsorted)");

/*
If a small request, try to use last remainder if it is the
only chunk in unsorted bin. This helps promote locality for
runs of consecutive small requests. This is the only
exception to best-fit, and applies only when there is
no exact fit for a small chunk.
*/
// 对unsortedbin里面的chunk进行拆分
if (in_smallbin_range (nb) && // 在small bin范围内
bck == unsorted_chunks (av) && // victim必须是unsortedbin里面唯一的chunk
victim == av->last_remainder && // victim在last_remainder里面
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) // victim要大于申请长度+0x20
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb); // 获得切分后的remainder
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder; // remainder加入到unsortedbin里面
av->last_remainder = remainder; // 记录last_remainder
remainder->bk = remainder->fd = unsorted_chunks (av);
if (!in_smallbin_range (remainder_size)) // 如果在largebin范围内 需要清空fd_nextsize和bk_nextsize
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
// 设置remainder的状态位 以及 victim的状态位
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);

check_malloced_chunk (av, victim, nb); // DEBUG的时候才有用
void *p = chunk2mem (victim); // 获得指向user_data的指针
alloc_perturb (p, bytes); // 如果设置了perturb_type,将chunk的user data初始化为perturb_type ^ 0xff
return p;
}
// 将chunk从unsortedb bin 中移除,如果大小合适,直接返回给用户
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

/* Take now instead of binning if exact fit */

if (size == nb) // 如果大小正合适
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
#if USE_TCACHE
/* Fill cache first, return to user only if cache fills.
We may return one of these chunks later. */
if (tcache_nb // 优先填满cache
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (victim, tc_idx);
return_cached = 1;
continue;
}
else
{
#endif
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
#if USE_TCACHE
}
#endif
}

/* place chunk in bin */
// 大小不合适要将chunk丢到适应的bin 也就是unsortedbin 2 largebin
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd; // 这里是没有校验的 如果能够修改到smallbin的fd 这里就可以控制fwd
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk)) // 如果小于当前chunk的最小size,那就加入它 -> large bin attack
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize; // 这里导致的
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else // 不然就根据fd_nextsize找到不比它大的chunk
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}

if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim; // 这里就能attack
bck->fd = victim;

#if USE_TCACHE
/* If we've processed as many chunks as we're allowed while
filling the cache, return one of the cached ones. */
++tcache_unsorted_count;
if (return_cached
&& mp_.tcache_unsorted_limit > 0
&& tcache_unsorted_count > mp_.tcache_unsorted_limit)
{
return tcache_get (tc_idx);
}
#endif

#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
}

#if USE_TCACHE
/* If all the small chunks we found ended up cached, return one now. */
if (return_cached) // 这里拿回来
{
return tcache_get (tc_idx);
}
#endif

可以简化为:

  1. 对unsortedbin进行校验,具体校验如下:这里的校验还是比较多的

    1
    2
    3
    4
    5
    1. 检验size大小是否合理
    2. 检验下一个chunk的size大小是否合理
    3. 检验下一个chunk的presize是否等于当前的size
    4. 检验bck->fd和victim->fd
    5. 检验下一个chunk的pre_inuse位
  2. 如果申请大小范围在smallbin里面,而且unsortedbin唯一,而且在last_remainder里面,申请大小也满足要求,那就切割last_remainder

  3. 如果大小刚好满足要求,那就返回给用户

  4. 如果大小不满足要求,那就执行unsortedbin 2 largebin,将unsortedbin丢到largebin里面,largebin attack就是在这里发生。

这里重点关注large bin attack发生:

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
// 大小不合适要将chunk丢到适应的bin 也就是unsortedbin 2 largebin
if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd; // 这里是没有校验的 如果能够修改到smallbin的fd 这里就可以控制fwd
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk)) // 如果小于当前chunk的最小size,那就加入它 -> large bin attack
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize; // largebin attack 这里导致的
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else // 不然就根据fd_nextsize找到不比它大的chunk
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}

if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim; // 这里就能attack
bck->fd = victim;

具体看代码细节即可,比较通用固定了

除了large bin attack之外还有需要注意的是在unsortedbin里面找到满足要求的堆块之后,如果大小合适,会将unsortedbin 先丢到tcache里面,不过这里要求victim→fd = unsortedbin(av),所以利用不了,但是可以增加tcache的数量。

尝试在largebin里面找

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
if (!in_smallbin_range (nb)) // 如果申请的是Large chunk就搜寻large bin
{
bin = bin_at (av, idx);

/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin // 如果对应的largebin不为空
&& (unsigned long) chunksize_nomask (victim)
>= (unsigned long) (nb)) // 而且大小符合 找到第一个不小于申请大小的largebin chunk
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;

/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) // 如果大小一样大
&& chunksize_nomask (victim)
== chunksize_nomask (victim->fd))
victim = victim->fd; // 直接改动fd的chunk 而不需要东nextsize指针

remainder_size = size - nb; // 看看剩下多少size
unlink_chunk (av, victim);

/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}
/* Split */
else // 切割后丢到unsorted bin里
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("malloc(): corrupted unsorted chunks");
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

这里需要关注的是申请largebin的时候,如果能够切割largebin,是会切割largebin的。

切割后的largebin会加入到unsortedbin里面,还会链入remainder。

从top_chunk切割

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
    use_top: // 从top_chunk切割
/*
If large enough, split off the chunk bordering the end of memory
(held in av->top). Note that this is in accord with the best-fit
search rule. In effect, av->top is treated as larger (and thus
less well fitting) than any other available chunk since it can
be extended to be as large as necessary (up to system
limitations).

We require that av->top always exists (i.e., has size >=
MINSIZE) after initialization, so if it would otherwise be
exhausted by current request, it is replenished. (The main
reason for ensuring it exists is that we may need MINSIZE space
to put in fenceposts in sysmalloc.)
*/

victim = av->top;
size = chunksize (victim);

if (__glibc_unlikely (size > av->system_mem))
malloc_printerr ("malloc(): corrupted top size");

if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
av->top = remainder;
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);

check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

/* When we are using atomic ops to free fast chunks we can get
here for all block sizes. */
else if (atomic_load_relaxed (&av->have_fastchunks)) // 如果有fastbin需要整理
{
malloc_consolidate (av);
/* restore original bin index */
if (in_smallbin_range (nb))
idx = smallbin_index (nb);
else
idx = largebin_index (nb);
}

/*
Otherwise, relay to handle system-dependent cases
*/
else // top chunk大小不够需要sysmalloc函数进行分配
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
}
}

这里可以简化为:

  1. 判断top_chunk_size不能太大

  2. 如果够切割,直接切一块给用户

  3. 如果top_chunk不够,而且有fastbin,需要先对fastbin进行整理

  4. 实在不够通过sysmalloc函数进行分配

这里malloc没有考虑top_chunk size不够的情况

当申请不到应该会返回空,此时会发生堆扩展,扩展堆的大小同时更新top_chunk

可能的漏洞点

fastbin2tcache

这里校验比较多,漏洞程度不好评价,需要有一个内存能够完全可控,伪造fake_chunk才能申请,或者找到一块申请的内存满足条件。

这个过程只有size和内存对齐校验,只需要满足fake_chunk→fd = 0即可丢到tcache的条件

smallbin2tcache

漏洞程度为任意地址分配

smallbin丢到tcache没有任何校验

只需要第一个smallbin满足校验,之后第二、第三个smallbin改掉fd即可链入tcache里面

malloc_consolidate

堆块向前向后合并,造成overlap,这个好用

largebin attack

这个通用且固定 好用

通过unsortedbin增加tcache数量

当unsortedbin满足申请要求的时候,会先丢到tcache里面

可能有用

总结

2.35以后主流打法主要是三个

tcache、largebinattack、overlap

主要是在smallbin2tcache里面以及tcache自身UAF导致的tcache打法

而lagebinattack更是个模板

overlap比较灵活,造成堆块overlap之后可操作空间会大大扩大,甚至可以伪造fake_chunk