分类
大千 中文文章

一只菜鸡关于《Project NANO》的解谜记录

Real entry

网页关键内容:

<!--哦,我的老伙计,你怎么误打误撞到这里来了。-->
<!--认真告诉你,这里没东西,去研究下那个二维码吧。-->
<div class="inside-qr">
    <p>哦,来了,我的老伙计。</p>
    <p>都来到这里了,肯定不能太难是吧。</p>
    <div class="hint">
        <p>所以我要跟你打包票,这一关的题目真的<strong>只在这个二维码</strong>里面了。</p>
        <p>要不,拆开看看?</p>
    </div>
    <img "https://cdn.jsdelivr.net/gh/hanlin-studio/Media@master/vigen.png" />
    <div class="hidden">
        <a href="https://cdn.jsdelivr.net/gh/hanlin-studio/Media@master/vigen.png">打开这个图片</a>
    </div>
</div>
一个看起来好像 QR 码但是明显被打乱了的图片。
那个「二维码」?图片

一个二维码图片。下载下来之后跑一遍 binwalk ,发现里面藏着一个 ZIP 压缩包,压缩包里面有个 README 文件,内容如下:

This is a 5*5 n-Puzzle. Please recover it.

Scramble STEP: JNPSVWVGMKHGMEPAMEYJJXZ
Magical square solution to the 15-puzzle
拼图是说的这种东西。Wikimedia,公有领域

根据提示,可以看出图片是被当作拼图来打乱过的。

不过我到最后也没想出来这个打乱步骤的提示是什么意思。知道这个图片是被分成 5×5 的小格子,按照 QR 码的格式,能够先手动拼出其中的 17 格。

被分成 5x5 的格子并且尝试拼好一大部分的那个图片。(抱歉,这个实在是描述不出来了)
青色:可以当作线索的区域;灰色:能确定的格子。蓝色:可能是右下角的格子;红色;不可能在右下角的格子。

这里面不能完全确定但是还有些线索的只有右下角的格子:要求第一个像素必须是黑色的。这样在剩余的 8 个格子里面只有 4 个格子是可能在右下角的,其他七个格子肉眼没法看出线索。

有了这些条件,我们可以推测出剩余的可能性有

4\times7!=2\,0160\text{ 种}

于是把上面的图片拆成了 25 个小图片,写了一个简陋的脚本来暴力破解这两万多种可能性。

(至少比 25!=15511210043330985984000000={15\text{\footnotesize秭}5112\text{\footnotesize垓}1004\text{\footnotesize京}3330\text{\footnotesize兆}9859\text{\footnotesize亿}8400\text{\footnotesize万}} 种可能性要好得多。)

from PIL import Image
from itertools import permutations
from pyzbar.pyzbar import decode
p_0_0 = Image.open("0-0.png")
p_0_1 = Image.open("0-1.png")
p_0_3 = Image.open("0-3.png")
p_0_4 = Image.open("0-4.png")
p_1_0 = Image.open("1-0.png")
p_1_1 = Image.open("1-1.png")
p_1_2 = Image.open("1-2.png")
p_1_3 = Image.open("1-3.png")
p_1_4 = Image.open("1-4.png")
p_2_1 = Image.open("2-1.png")
p_3_0 = Image.open("3-0.png")
p_3_1 = Image.open("3-1.png")
p_3_3 = Image.open("3-3.png")
p_3_4 = Image.open("3-4.png")
p_4_0 = Image.open("4-0.png")
p_4_1 = Image.open("4-1.png")
p_4_3 = Image.open("4-3.png")
p_n_0 = Image.open("n-0.png")
p_n_1 = Image.open("n-1.png")
p_n_2 = Image.open("n-2.png")
p_n_3 = Image.open("n-3.png")
p_y_0 = Image.open("y-0.png")
p_y_1 = Image.open("y-1.png")
p_y_2 = Image.open("y-2.png")
p_y_3 = Image.open("y-3.png")
n = [p_n_0, p_n_1, p_n_2, p_n_3]
y = [p_y_0, p_y_1, p_y_2, p_y_3]
fixed = [
    (0, 0, p_0_0),
    (0, 1, p_0_1),
    (0, 3, p_0_3),
    (0, 4, p_0_4),
    (1, 0, p_1_0),
    (1, 1, p_1_1),
    (1, 2, p_1_2),
    (1, 3, p_1_3),
    (1, 4, p_1_4),
    (2, 1, p_2_1),
    (3, 0, p_3_0),
    (3, 1, p_3_1),
    (3, 3, p_3_3),
    (3, 4, p_3_4),
    (4, 0, p_4_0),
    (4, 1, p_4_1),
    (4, 3, p_4_3),
]
left = [(0, 2), (2, 0), (2, 2), (2, 3), (2, 4), (3, 2), (4, 2)]
d = 20
def join_pic(yy, ns):
    dest: Image = Image.new("RGB", (d*7, d*7), (255,255,255))
    for x, y, img in fixed:
        dest.paste(img, (d * (y + 1), d * (x + 1)))
    for coord, img in zip(left, ns):
        x, y = coord
        dest.paste(img, (d * (y + 1), d * (x + 1)))
    dest.paste(yy, (d*5, d*5))
    return dest
def gen_perm():
    for i in y:
        ii = [j for j in y if j is not i]
        ii.extend(n)
        for j in permutations(ii):
            yield (i, j)
        
for y, ns in gen_perm():
    p = join_pic(y, ns)
    v = decode(p)
    if v:
        print(v)

尝试出来的结果……

python3 test.py
[Decoded(data=b'https://clang.pp.ua/c1An9', type='QRCODE', rect=Rect(left=20, top=20, width=100, height=100), polygon=[Point(x=20, y=119), Point(x=120, y=120), Point(x=119, y=20), Point(x=21, y=21)])]

设计解法:文件名的 vigen.png 提示的是 Vigenère Cipher。用 STEP 当作密钥,JNPSVWVGMKHGMEPAMEYJJXZ 当做密文,可得明文:

RULDDDRRURDRULLLULUUREV

其中:

  • R: right = 右
  • U: up = 上
  • D: down = 下
  • L: left = 左
  • REV 提示「Reverse」,就是反转的意思。具体怎么反转的我也没太仔细研究。(毕竟是暴破出来的。)

按照步骤还原拼图应该就可以了。

(感谢群友 @MiaQAQ 的指点)

Clang

网页主要内容:

<title>./.../.-./---/--</title>
<!-- ... -->
<div id="not-here">
    <p class="what-is-this">这里 和那 里或 许没 什么 关系</p>
    <p><code>1T1023298N757Q13142N10271Q1T1029</code></p>
    <p>或许你需要先看看,嗯,标题。</p>
    <p class="what-is-this">9432bc3b.log</p>
</div>

「日志」内容:

ZeroMe Logs | Mar 27, 2020.
undefined
谜题的出口通向哪里?
来到出口,你抬头看了看门。
忽又想起了 “门上插刀” 这个经典的梗。
刀砸在地上,碎屑溅起来了。
你看了看碎屑,从其中找到了那打开出口的钥匙。
[时序碎片:31 31]
[解锁 丢失的密钥:whereisthefilter]

能够找到的线索:

  • 标题 ./.../.-./---/-- 是摩尔斯电码的 ESROM(Morse 反过来写)
  • 「这里 和那 里或 许没 什么 关系」→两字一组?
  • 1T1023298N757Q13142N10271Q1T1029:不明。
  • whereisthefilter:不是答案,用途不明

「门上插刀」似乎是出题者杜撰出来的一个「梗」。

来自出题团队成员「门上插刀,天上平板,直字拐弯」

看截图来说基本上就是描述日文字形的特点了吧。(是的,同样是汉字,但是不同地方的汉字长得不总是一样的。)

思源黑体中「门上插刀 天上平板 直字拐弯」这十二个字在日本、中国大陆、台湾、香港和韩国的书写习惯的区别对比。「插」、「平」、「板」、「字」、「拐」、「弯」等字的区别没有在此标注。

我说泥萌出题能不能加混淆提示了啦。

于是乎,俺到这里……
……就卡住了。

翌日,得到了出题者给出的提示:

NanoApe「参考解谜之履第三期解析(跑」

网上搜索「解谜之履第三期解析」,得到了一篇微博

当中第九关有着这样的谜题:

战役 Ending with codes

06068N86858488818187858389

Are you seeking the clue which is quite important?
FIRST… YOU MAY TRY ‘ESROM’.

题目原文

观察发现那句英文的空格是不对的,把单词前空了两格的首字母提出了得到【ASCII】,提示使用ASCII码表。把“ESROM”反过来得到【Morse】,提示使用摩斯电码,以及把密文“反过来”。
根据提示操作,把上面超长的字符串转成摩斯电码,再将点线互换,再次转成字符串再对照ASClI码16进制那栏,得到密码【qq:1093662084】
【另外一种解法会认为将摩斯电码逆过来看。但是这种情况下,频繁出现的8会变成2。但是ASC码中,2字打头的除了16进制那栏,都无法对应符号,16进制那栏对应的都是标点符号,所以这种考虑被排除了。至于为什么是先摩斯再ASCII,是因为英语中提到了FIRST。】

引用自题解

所以基本上这道题是同样的意思。(谁能想到 ESROM 代表的反转是把「嘀」和「嗒」换过来了啦摔)所以……

  1. 1T1023298N757Q13142N10271Q1T1029
  2. 转换成摩尔斯电码
  3. 嘀嗒对调
  4. 转换回字母
  5. 按照两字一字节用 ASCII 解码,得到
next: /hizeronet

Hi Zeronet

从本题目以降,感谢群友 Lawson(@N5cA934A)的协作。

网页关键内容:

<div id="not-here">
    <p class="what-is-this" v-bind:title="hint?'真的没有吗…':'哎?不是这个意思?'">No hint</p>
    <p>No hint</p>
    <p class="what-is-this" v-bind:title="'Wcc_b)||UbZh&{R^\|R[P]V|P]^cWTa{iX_'">...</p>
</div>

能看出来的是:

  • URL 里面的提示:Zeronet,一个去中心化的网站托管技术。
  • v-bind:title:这应该是 Vue.js 了。(不过这跟解谜有啥关系?)
  • 'Wcc_b)||UbZh&{R^|R[P]V|P]^cWTa{iX_':一串密码。直觉判断应该是能解出一个 Zeronet 地址的。(Zeronet 地址长得是像一串 Base64 一样的 35 位字符,而这串字符刚好也是 35 位。)

然后就是出题组成员的各种玄妙的提示……

Piggy「这一关不解那个(那 35 个字符的地址)也能做」
Piggy「就是得足够莽(」
Piggy「no hint
Piggy「『真的没有 hint 嘛』」

Piggy 这样强调「hint」这个词我也只能想到那个二分逻辑式(hint?'真的没有吗…':'哎?不是这个意思?')里面把 hint 取伪。但是仍然是毫无头绪。(果然俺还是不适合这种解谜的么 orz)

于是乎,俺到这里……
……刚解出来一关……
……就又卡住了。

翌日,在一番苦思冥想之后,发现了这个是要用脑筋急转弯的思路来解的。就是那种「把冰变成水怎么最快」的思路。所以……

\text{hizeronet}-\text{hint}=\text{zeroe}

然后那串代码什么的基本上就是绕远路解法了(虽然我也不知道怎么才能把那远路绕出来)。

Zeroe

页面主要内容

<div id="here-is">
    <p><small>ZEROMAIL_</small></p>
    <div class="subject">E</div>
    <p><small>From: <a href="mailto:clanggame@groups.163.com">霂</a></small></p>
    <hr>
    <p>告诉你个秘密,其实我挺喜欢对着些温馨些的场景看来看去。</p>
    <p>有时候对着张桌面的照片都能看上半天,那种温暖的感觉便自心底发出。</p>
    <p>要不我给你一段录像,你来看看其中的奥秘?</p>
    <p><a href="https://cdn1.ihcr.top/clang-game/record.rar" target="_blank">[Video File]</a></p>
    <p class="hint">答案就在其中,请坐和放宽。</p>
    <p class="hint">哦对,看的时间别太长了,会出现幻觉的。</p>
    <p class="what-is-this">ffa04ddc.log</p>
</div>

「日志」内容:

ZeroMe Logs | Apr 1, 2020.
abc 19:23:01
所以我们又来到了这个题,你有什么思绪吗?
Piggy 19:23:35
思考
abc 19:24:00
哇,这视频这么长,真要我一天25小时高强度温馨看桌面嘛???
Piggy 19:24:35
一定会有更高效的方法来着,你看这压缩包的体积并不大。
abc 19:26:00
我记得有款现代视频处理工具来着,好像是格式工厂都在用的内核?
先不管了,让我来分析一下这个视频……
Piggy 19:26:53
去搜索引擎找一找吧……
一定不需要一帧一帧出来看的。
[解锁提示: iwhvgqetai]
abc 19:28:00
等等,这是啥?
[时序碎片: 39 33]

那个「现代视频处理工具」、「格式工厂都在用的内核」指的是 FFMpeg1Wikipedia。经过一番搜索,发现 CTF 常用的一个手法就是在长视频里面隐藏 flag。本来视频是有 80 多万帧的,我开始是着每秒只提取一帧,然后逐帧比对,没有比对出什么。但是每一帧都提取我的硬盘会吃不消的。光是这三万多帧的逐帧比对就花了电脑两个多小时,这个数字乘以 25 真的搞不来。

经过群友一只废喵@NebulaNeko 的抓帧,得到以下两帧内容:

Wow_Awes0me_
ViDeo_Proc355ing

事后谈

  1. FFMpeg 有一个 mpdecimate 的 filter 专门用来解去除重复画面的。当时不知怎的没搜到。
  2. 群友 Suzunari Shizuku@GPLv3 提示说「给了一串神秘字符,用之前关卡出现的 key 解密可以得到 hint」。拿着之前日志里面的「iwhvgqetai」和 Clang 的日志里面给的「whereisthefilter」,塞进他们喜欢的Vigenère Cipher,就真的给出了 mpdecimate 这个提示。

Wow awesome video processing

页面主要内容:

ZeroMe Logs | Apr 2, 2020.
[部分讯息经过加密]
泠 17:32:13
看到这个「Project NANO」,你有啥想法没?
霂 17:32:59
有啊有啊,看到这名字我想到了[Uvebfuvzn]的一家餐馆,是意大利风味的。
泠 17:33:55
......?那这家餐馆在哪呢?
霂 17:34:25
知道知道,我这就去给你搜搜......
霂 17:36:58
你要不先联系一下他们的老板吧?
泠 17:37:55
行,你把电话给我吧。
答案为纯数字电话号码,或许是国际长途。
请不要因此拨通任何号码,给他人带来不必要的麻烦。
答案验证框还在老地方,别看我。
似乎答案不对?多找找或者换换格式。
> |

没有任何提示,经过各种试错,发现加密方式是 ROT13。

ROT13(Uvebfuvzn) = Hiroshima

Hiroshima = 广岛县,搜索广岛县叫做 NANO 的意大利餐馆,会找到一个这个:

带着国家代码把电话号码输进去,就是答案。

《一只菜鸡关于《Project NANO》的解谜记录》上的一条回复

大学学的信息安全,就像是其他专业有acm,每年学长都会给我们准备ctf的题。
回想起来当时和同学通宵做题抢分,真是有意思。
一转眼都毕业三年了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*