<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>二进制安全 on Megrez&#39;s Blog</title>
        <link>https://megrez1.xyz/categories/%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AE%89%E5%85%A8/</link>
        <description>Recent content in 二进制安全 on Megrez&#39;s Blog</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh-CN</language>
        <copyright>Megrez</copyright>
        <lastBuildDate>Mon, 19 Oct 2026 11:00:00 +0800</lastBuildDate><atom:link href="https://megrez1.xyz/categories/%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AE%89%E5%85%A8/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>glibc 2.39 tcache safe-linking 详解：编码、泄露与伪造</title>
            <link>https://megrez1.xyz/post/tcache-safe-linking/</link>
            <pubDate>Mon, 19 Oct 2026 11:00:00 +0800</pubDate>
            <guid>https://megrez1.xyz/post/tcache-safe-linking/</guid>
            <description>&lt;h2 id=&#34;为什么需要-safe-linking&#34;&gt;为什么需要 safe-linking&#xA;&lt;/h2&gt;&lt;p&gt;glibc 2.32 起，tcache 和 fastbin 的空闲链表指针不再明文存储，而是与 chunk 地址做异或编码。目的是让&amp;quot;只知道堆上内容、不知道堆地址&amp;quot;的攻击者无法直接伪造 fd/next 指针。&lt;/p&gt;&#xA;&lt;h2 id=&#34;编码公式&#34;&gt;编码公式&#xA;&lt;/h2&gt;&lt;p&gt;&lt;code&gt;PROTECT_PTR&lt;/code&gt; 与 &lt;code&gt;REVEAL_PTR&lt;/code&gt; 的定义（glibc 源码 &lt;code&gt;malloc.c&lt;/code&gt;）：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-c&#34; data-lang=&#34;c&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#define PROTECT_PTR(pos, ptr) \&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;    ((__typeof (ptr)) ((((size_t) pos) &amp;gt;&amp;gt; 12) ^ ((size_t) ptr)))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;也就是说，chunk 里存的 &lt;code&gt;next&lt;/code&gt; 字段实际是：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;存储值 = (本 chunk 的 user 区地址 &amp;gt;&amp;gt; 12) ^ 下一个 chunk 的地址&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;关键破绽链上只剩一个节点时&#34;&gt;关键破绽：链上只剩一个节点时&#xA;&lt;/h2&gt;&lt;p&gt;tcache 是单链表。当一个 bin 里只有一个空闲 chunk 时，它的 next 指向 NULL：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;存储值 = (chunk &amp;gt;&amp;gt; 12) ^ 0 = chunk &amp;gt;&amp;gt; 12&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一次 UAF 读就能拿到 &lt;code&gt;chunk &amp;gt;&amp;gt; 12&lt;/code&gt;。有了它，任何指向该 chunk 附近的伪造 next 都可以正确编码：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mask &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; u64(leaked_next)          &lt;span style=&#34;color:#75715e&#34;&gt;# = chunk_addr &amp;gt;&amp;gt; 12&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;poisoned &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; p64(target &lt;span style=&#34;color:#f92672&#34;&gt;^&lt;/span&gt; mask)    &lt;span style=&#34;color:#75715e&#34;&gt;# 写回 next 字段&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;tcache-投毒的完整流程&#34;&gt;tcache 投毒的完整流程&#xA;&lt;/h2&gt;&lt;ol&gt;&#xA;&lt;li&gt;申请三个同 size 的 chunk A、B、C；&lt;/li&gt;&#xA;&lt;li&gt;依次 free，tcache 链变成 C -&amp;gt; B -&amp;gt; A；&lt;/li&gt;&#xA;&lt;li&gt;用 UAF 写改 C 的 next 为 &lt;code&gt;target ^ mask&lt;/code&gt;；&lt;/li&gt;&#xA;&lt;li&gt;连续 malloc 三次：拿到 C、B 之后，第三次返回 target。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;注意 glibc 2.29+ 的 tcache key 双重释放检查，以及 &lt;code&gt;tcache_get&lt;/code&gt; 里的对齐检查（&lt;code&gt;aligned_OK&lt;/code&gt;，目标地址需 16 字节对齐）——所以投毒目标一般选全局函数指针表这类天然对齐的地址。&lt;/p&gt;&#xA;&lt;h2 id=&#34;小结&#34;&gt;小结&#xA;&lt;/h2&gt;&lt;p&gt;safe-linking 提高了一点门槛，但没有改变本质：&lt;strong&gt;只要能泄露堆地址（哪怕只有 &amp;raquo;12 的部分），编码就形同虚设&lt;/strong&gt;。下一篇用一道完整的 UAF 题演示从泄露到 getshell 的全过程。&lt;/p&gt;&#xA;</description>
        </item></channel>
</rss>
