glibc 2.43 free 堆的一些想法

free 堆

glibc 2.43

数据字段被替换的第一个位置

若有以下定义:

int *a = malloc(sizeof(int));
int *b = malloc(sizeof(int));

然后把它们 free 掉:

free(a);
free(b);

再看栈上空间时:

x/4gx a
x/4gx b

得到:

0x555555559010: 0x0000000555555559      0x8e4b6aee6b5319b4
0x555555559020: 0x0000000000000000      0x0000000000000021
0x555555559030: 0x000055500000c549      0x8e4b6aee6b5319b4
0x555555559040: 0x0000000000000000      0x0000000000000411

其中 a 空间记录的 0x0000000555555559 是自身的地址右移 12 位后的值,由于它是第一个堆 chunk,所以其结构体 next 字段为 NULL,故按照计算公式:

\text{encoded data} = \text{next} \oplus (\text{address} \gg 12)

其应当计算作:

0 \oplus (0\text{x}555555559010 \gg 12) = 0\text{x}555555559

同理,b 空间记录的 0x000055500000c549 是自身的地址右移 12 位后的值,由于它是第二个堆 chunk,所以其结构体 next 字段为 0x555555559010,即 a 的 chunk 地址,故按照计算公式:

0\text{x}555555559010 \oplus (0\text{x}555555559030 \gg 12) = 0\text{x}55555550000c549

数据字段被替换的第二个位置

参照上面的程序,a 和 b 的第二段都变成了 0x8e4b6aee6b5319b4。

malloc.c 中生成 tcache key 的方法如下:

// L2895  key 就是 tcache_entry 的第二个字段 → 位于 user data + 8
typedef struct tcache_entry {
  struct tcache_entry *next;
  uintptr_t key;                    // ← 用于检测 double free
} tcache_entry;

static uintptr_t tcache_key;         // L2963  进程级(注意:不是 __thread)

static void tcache_key_initialize (void)      // L2974
{
  if (__getrandom_nocancel_nostatus_direct (&tcache_key, sizeof(tcache_key),
                                            GRND_NONBLOCK) != sizeof (tcache_key))
    tcache_key = 0;                              // getrandom 失败 → 0
  int minimum_bits = __WORDSIZE / 4;             // 16
  int maximum_bits = __WORDSIZE - minimum_bits;  // 48
  while (tcache_key <= 0x1000000
      || tcache_key >= ((uintptr_t) ULONG_MAX) - 0x1000000
      || stdc_count_ones (tcache_key) < minimum_bits
      || stdc_count_ones (tcache_key) > maximum_bits)
    { tcache_key = random_bits();                // 时钟派生的兜底
      tcache_key = (tcache_key << 32) | random_bits(); }
}

static __always_inline void tcache_put_n (...)   // L3016
{
  tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
  e->key = tcache_key;                            // ← 明文写入,不参与 safe-linking
  e->next = PROTECT_PTR (&e->next, *ep);          // ← 只有 next 被 mangle
  ...
}

static __always_inline void *tcache_get_n (...)  // L3057
{
  ...
  e->key = 0;                                     // ← 取出即清零
}

// __libc_free  L3365
if (__glibc_unlikely (e->key == tcache_key))
  return tcache_double_free_verify (e);

源码里只有长度/位密度约束(非 0、>0x1000000、popcount 在 16..48 之间),没有常量。故有结论:同进程内恒定、跨进程随机。

当 popcount = 34 时,正好得到 0x8e4b6aee6b5319b4,所以 a 和 b 的第二段都变成了这个值。

相较于 glibc 2.35,glibc 2.43 的栈有些变化

  • TCACHE_FILL_COUNT 是一个有关填充 tcache 的常量宏,其在 glibc 2.43 中的值为 16,而在 glibc 2.35 中的值为 7。其表示每个 tcache bin 最多能装多少个空闲块的容量上限。它决定一块刚 free() 的 chunk 究竟进 tcache 并被写入 key,还是被送进 _int_free_chunk() 走 unsorted bin(并可能与相邻空闲块合并)。如果有了 16 个tcache bin 存在(即被 free() 了 16 次),第 17 个 chunk 就会被送进 unsorted bin。

  • glibc 2.43 中 tcache_max 最大支持 4 MiB。

  • glibc 2.43 中不存在 fastbin,2.42 及以前的 fastbin 操作不再奏效。

所以 double free 之前要塞满 tcache 然后 fastbin dup 或覆写 key,现在只能用 tcache dup 或者依照 unsorted bin 的方式去做。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇