布置好分析环境和资料

下载源码 https://ftp.gnu.org/gnu/glibc/glibc-2.23.tar.gz

demo源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
char *str;

str = (char *) malloc(15);
strcpy(str, "hello");
printf("String = %s, Address = 0x%lx\n", str, str);

free(str);

return(0);
}

glibc-all-in-one: 2.23-0ubuntu3_amd64

给程序打好patch

修改/usr/lib/debug https://blog.csdn.net/m0_51251108/article/details/127098744

Inconsistency detected by ld.so: dl-call-libc-early-init.c: 37: _dl_call_libc_early_init: Assertion `sym != NULL’ failed!

https://stackoverflow.com/questions/66098387/how-to-run-an-old-binary-on-modern-gnu-linux-distribution

gcc使用特定glibc:

gcc -L/pwntools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64 -Wl,–rpath=/pwntools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64 -Wl,-I/pwntools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so 1.c -o test

https://blog.csdn.net/m0_37876242/article/details/130018202

使用特定ld.so

/pwntools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so /pwntools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6

libc.so.6: version `GLIBC_2.34’ not found

可以从ubuntu16中编译一个 考出来

1
gdb `find ./glibc-2.23 -type d -printf '-d %p '` ./223

b malloc

参考资料和工具

chatgpt4

《ctf权威指南 pwn》

malloc

glibc-2.23/malloc/malloc.c

malloc对应的函数是__libc_malloc(), 这是因为使用了宏

1
strong_alias (__libc_malloc, __malloc) strong_alias (__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
/*------------------------ Public wrappers. --------------------------------*/

void *
__libc_malloc (size_t bytes)
{
mstate ar_ptr;
void *victim; //当前要执行操作的chunk
//检查是否有malloc钩子,有的话调用钩子,常用的覆盖mallochook为onegadget、system等就是这个
void *(*hook) (size_t, const void *)
= atomic_forced_read (__malloc_hook);
if (__builtin_expect (hook != NULL, 0))
return (*hook)(bytes, RETURN_ADDRESS (0));

arena_get (ar_ptr, bytes); //寻找一个合适的arena来分配内存

victim = _int_malloc (ar_ptr, bytes); // 尝试分配内存(重要函数)
/* Retry with another arena only if we were able to find a usable arena
before. */
//如何没有找到合适的内存,就尝试找一个可用的arena(前提是 ar_ptr!=NULL),然后继续分配内存
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) //如果申请了arena, 还需要进行解锁该arena
(void) mutex_unlock (&ar_ptr->mutex);
//检查了什么呢?
assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
ar_ptr == arena_for_chunk (mem2chunk (victim)));
return victim;
}
libc_hidden_def (__libc_malloc)

_int_malloc

检查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
  /*
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.
*/
//fastbin分配,先进后出,比较的是无符号整数,这段代码可以在初始化堆之前运行
if ((unsigned long) (nb) <= (unsigned long) (get_max_fast ()))
{
idx = fastbin_index (nb); //得到对应fastbin大小的下标
mfastbinptr *fb = &fastbin (av, idx); //得到fastbin该大小的头指针
mchunkptr pp = *fb;
do
{ // 如果fastbin中有chunk,后进先出取出来,
victim = pp;
if (victim == NULL)
break;
}
while ((pp = catomic_compare_and_exchange_val_acq (fb, victim->fd, victim))
!= victim);
if (victim != 0) // victim不等于NULL,说明找到了chunk,检查后返回给用户
{ // 检查chunk大小是否和前面确定的idx一样,防止被伪造
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}
check_remalloced_chunk (av, victim, nb); // 检查二次分配?
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

检查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
  /*
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.)
*/
// smallbin范围,先进先出
if (in_smallbin_range (nb)) // 是否在范围内
{
idx = smallbin_index (nb);
bin = bin_at (av, idx); // 这里是获取头吧 bin[x]这个东西?

if ((victim = last (bin)) != bin)
{
if (victim == 0) /* initialization check */ //尚未初始化,进行初始化
malloc_consolidate (av);
else
{ // 检查双向链表是否被破坏
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim))
{
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb); //设置使用标志位
bin->bk = bck;
bck->fd = bin;

if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb); // 检查分配的chunk
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}

计算largebin的idx,整合fastbin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  /*
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.
*/
// 计算largebin的idx(仅仅是计算). 然后整理fastbin、检查有没有fastbin能够合并
else
{
idx = largebin_index (nb);
if (have_fastchunks (av))
malloc_consolidate (av);
}

大的外层for循环

​ 进入大循环,包含了_int_malloc之后所有过程

内层第一个while循环

​ 遍历unsortedbin,大小合适就取出,否则放入small/largebin(唯一将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
62
63
64
65
// 进入大循环,包含了_int_malloc之后所有过程
for (;; )
{ //内层的第一个while循环,遍历unsortedbin,大小合适就取出,否则放入small/largebin(唯一将chunk放入这俩的机会)
int iters = 0;
while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
{
bck = victim->bk;
if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
|| __builtin_expect (victim->size > av->system_mem, 0))
malloc_printerr (check_action, "malloc(): memory corruption",
chunk2mem (victim), av);
size = chunksize (victim);

/*
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.
*/

if (in_smallbin_range (nb) && // 用户请求的是smallbin大小
bck == unsorted_chunks (av) && //unsorted bin只有一个chunk
victim == av->last_remainder && //且chunk为last_remainder???
(unsigned long) (size) > (unsigned long) (nb + MINSIZE)) // 并且满足拆分条件时,进行拆分
{
/* split and reattach remainder */
remainder_size = size - nb;
remainder = chunk_at_offset (victim, nb);
unsorted_chunks (av)->bk = unsorted_chunks (av)->fd = remainder;
av->last_remainder = remainder;
remainder->bk = remainder->fd = unsorted_chunks (av);
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;
}
// 从unsorted bin中取出chunk
/* remove from unsorted list */
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)
victim->size |= NON_MAIN_ARENA;
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}

​ 放入small/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
62
63
64
65
66
67
68
69
70
71
 /* place chunk in bin */
// chunk大小不合适,插入对应的bin中, 插入过程就是双链表插入结点的过程
if (in_smallbin_range (size))
{ // bck指向头结点,fwd是头结点的fd结点,chunk会被插入到头结点和fwd结点之间,(smallbin头插)
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else // 否则在largebin的范围内
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index); // 当前largebin
fwd = bck->fd; // 当前bin中最大的chunk
// 需要对双链表进行额外操作,找到合适大小的位置
/* maintain large bins in sorted order */
if (fwd != bck) // 链表不为空
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE; // 先设置PREV_INUSE
/* if smaller than smallest, bypass loop below */
assert ((bck->bk->size & NON_MAIN_ARENA) == 0);
// 如果要申请的大小小于这个largebin的最小的chunk,就把它放到最后面(链尾) (还是看图理解比较好
if ((unsigned long) (size) < (unsigned long) (bck->bk->size))
{
fwd = bck; // largebin本身
bck = bck->bk; // 当前最小chunk

victim->fd_nextsize = fwd->fd;// 最大的chunk
victim->bk_nextsize = fwd->fd->bk_nextsize; // 最小的chunk
// 这一句其实干了两件事,可以分两步来的,
// 先把最小chunk的fd_nextsize指向了victim,然后又把最大chunk的bk_nextsize指向了victim
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else // 如果不小于最小的chunk的话,就通过fd_nextsize找到不比它大的chunk(<=)
{
assert ((fwd->size & NON_MAIN_ARENA) == 0);
while ((unsigned long) size < fwd->size) //小于该chunk大小,就继续往前找
{
fwd = fwd->fd_nextsize;
assert ((fwd->size & NON_MAIN_ARENA) == 0);
}

if ((unsigned long) size == (unsigned long) fwd->size) //等于
/* Always insert in the second position. */
fwd = fwd->fd; // bk为什么不修改呢,后面的chunk??没读懂 =》在后面修改
// 先修改改大的顺序,具体指针后面再改
else // 大于了,要插入进去
{
//下面两句是把victim插入fwd和比它大的chunk之间
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim; // 修改fwd的bk_nextsize值为victim
victim->bk_nextsize->fd_nextsize = victim; // 修改比victim大的chunk的fd_nextsize
}
bck = fwd->bk; // fwd此时已经是第二个了,它的bk就是第一个,
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck; // 插入 bak和fwd之间
victim->fd = fwd;
fwd->bk = victim; // 修改fwd和bck的指针
bck->fd = victim;

#define MAX_ITERS 10000
if (++iters >= MAX_ITERS)
break;
}
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
62
63
64
65
66
67
/*
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.
*/
// 进入largebin了... 不对..不是..这是啥
if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);

/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin && //如果victim等于头结点,说明bin为空
(unsigned long) (victim->size) >= (unsigned long) (nb)) //小于nb,说明大小没有合适的
{//反向遍历bk_nextsize,从最小的大小开始,找到第一个不小于nb的chunk
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;
// 如果该chunk与victim的fd一样大,就选择fd chunk,避免改动nextsize
/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin) && victim->size == victim->fd->size)
victim = victim->fd;

remainder_size = size - nb;
unlink (av, victim, bck, fwd);

/* Exhaust */
if (remainder_size < MINSIZE)
{ //如果该chunk减去nb小于MINSIZE,直接把该chunk返回给用户
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
victim->size |= NON_MAIN_ARENA;
}
/* Split */ // 减去nb大于MINSIZE的话,将remainder放入unsortedbin
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))
{
errstr = "malloc(): corrupted unsorted chunks";
goto errout;
}
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;
}
}

内层第二个while循环