GZTime’s Puzzle Adventure 通关总结

链接: PGv1, 作者GZTime

前言

去年暑假玩的来着,当时有个 OIer 朋友在我们的小群里求助这个解密,然后我们四五个人就晚上十点多开始研究这些题,除了一个是 Phoer(友链里的哲学家就是他) ,都是 OIer 朋友,一群非常好的朋友。

还记得当初是我第一个发现图片暗藏玄机,后面有个根据图像写函数的题那位 Phoer 写的飞快,还有一道根据截图找在哪个动漫的多少秒,啊这,那几个我一个也不认识,他们几个二次元几乎一眼就看出来了。不过很遗憾,我们几个并不都是从开始一直一起走到通关的,有几个朋友在各方面原因的作用下到一半就没有继续了。有一关叫迷宫,那关或需要毅力,或需要代码能力,还记得当时我果断研究该怎么写程序走迷宫,该怎么发包,最后踉踉跄跄从第一天下午五点鼓捣到了第二天下午五点,终于把这关过了,但是靠毅力走的同志两个小时走出去了,只能说非常强,多少有些惭愧。

后面的关卡越来越难,基本必须要集思广益才能通过了。这里插叙一下,当时我走迷宫的时候,来了一队CTFer,似乎是三四个小时就把所有题秒了,后来者据上,成为通关的第一梯队;当时作为第二梯队的我还在健身房里,眼睁睁的看着他们把题过了,而我却碰不到电脑。作为第二梯队的一员,因为去健身被迫边栽树边乘凉。到家的时候第二梯队的其他人貌似已经将第二关推进了将近三分之二,我通过QQ群消息观察法成功与他们同步进度,追上大部队,并且一同将剩余三分之一攻克,看到通关页时,太快乐了,那两三天真的相当充实,也无比令人怀念。

今天突然想起来这个解密,打开当初的连接,啊,还在,又打开讨论群,啊,之前关了又重新开了。看着之前写下的代码,我觉得不如重新通关一遍,并且写篇攻略总结与纪念一下,在其中学到了很多。同时,我分享给了我的一众好友,祝他们好运。

那我们开始吧!

—— 2021.6.16 晚自习于课桌上

通关总结

第零关

据说是传统把戏了,不过我也不清楚怎么据说的,我甚至忘了我怎么过这关的了。

Start

作者的故事从一封信开始,所以我们直接点击 “a letter” 。

第一关 Letter

正式开始了。

Letter

确实是一封信,左上角是邮编,右上角是邮票。在邮编的位置可以输入数字,然后正文部分看起来像乱码的文本会根据不同的输入发生不同的变化。不难想到,这段文本或许是根据输入的邮编进行解密。所以这封信要寄到哪里呢?通过不懈的观察网页源文件——其实我当时第一下就点到了邮票的图片,发现了端倪——我们发现邮票如果在黑色背景下会显现出河流与经纬线:北纬三十八度。以这个图片为线索,在地图上寻找,就可以发现经纬线的交点是吴忠市,查到邮政编码填到该填的位置,就可以看到信的内容和通关提醒了。

Stamp

751100

在毫无头绪的情况下不如仔细研究页面的所有内容,或许会有所收获。

第二关 Memory

是首很好听的歌呀。

Memory

观察到每行的字符后面都有零到两个等号,直接试base64。上网找一个base64解码网站,果然是,然后就可以知道原文是什么了。通过背单词的前置技能,我们可以发现在原文中有的单词是缺少字母的,结合关卡名,将缺少的这些字母拼起来即是答案。

btw,书名号扩起来的地方是歌名的摩尔斯电码和挖空的摩尔斯电码。

immortal

这关显著考验挑战者对编码类型的熟悉程度。

第三关 Anime

我只能说,我还真没看过…

Anime

对于见多识广的朋友来说,这关完全可以凭借记忆通过,而对于我这类对此领域不了解的朋友,这就是一道很有计算机精神的题了。众所周知,学好计算机的必要技能就是熟练高效使用搜索引擎,尤其是对于这样一个不熟悉的领域,所以我们不妨来的直接点,直接去找以图搜图的网站,确定下动漫名称,更具体的还可以确定集数,然后开始看。之后就可以确定通关所需要的集数分钟和秒数了。只需要填出来两个,过关。

通过截图与用截图查到的网页共同获取信息,比如通过血小板的介绍确定这是血小板第一次出场的那集,又或是通过第四张图找到这季完结的文章,再通过画面风格在第一季最后一集确定位置。(记得多找几个片源核验时间)

第三个是: 1 9 56 第四个是 12 20 2

这关是非常有趣的一关,如果你不幸没有看过这些番,那这很考验你的信息检索与获取能力。

第四关 Math

这关考验数学直觉。

Math

第一个一看就是正弦图像,直接过;

第二个一看就是开口向下,过原点,前两个系数异号的抛物线,先随便蒙一个,根据左上角的当前残差微调,过掉;

第三个一看就是个幂函数,并且当 x<1x < 1 时,图像没有 y=x2y = x^2 抖,所以先试一个 x4x^4 ,发现图像比给定图像更平缓,则试 x3x^3 的绝对值,成功过掉;

第四个图像非常神奇,根据图像风格像极了 tan(x) ,注意到图像左右对称,则必然存在 xx 要么取绝对值要么偶数次幂,观察得到图像越来越密,则试 tan(x2)tan(x^2) ,成功通过;

第五个图像更加神奇,因为图像左右对称,只分析右边的图像,像极了开方,但是还差点啥,随便乘几个系数发现 xx 前的系数应该在三和四之前,猛的发现关卡条件还给了两个常数,果断尝试 PI ,成功。

过关。

  1. sin(x)
  2. -x^2 + 3x
  3. abs(x^3)
  4. tan(x^2)
  5. sqrt(x * PI)

数学直觉的本质不是蒙吗,往靠谱的方向蒙一蒙。

第五关 Fans

六百万粉丝,数码男孩的硬核浪漫礼物。

Fans

确实,线索都在图片里,而下面的几行正是一个图片。随着箭头的指引,通过对图片的仔细观察,我们不难发现在图片的右下角有一串数字,直接扔到存在或者不存在的搜索引擎上,可以惊奇的发现这就是b站up主老师好我叫何同学的uid,通过直觉,这必然是我们要找的了;再在图片右上角,有一个六百万,结合本关名称,自然拨云见日,直接打开何同学的合照网站。

可以猜到下面的四串字符都是何同学粉丝的名字,而名字旁边的黑白块自然是二进制,并且方括号表示数组,那二进制自然是数组下标了(此处重点,数组下标从零计数)。于是我们在网站里找到这四个人的名字,箭头则表示右面那个人。(这里有点需要注意的地方,两个二进制之间的 & 无实意,仅表示连接。)数组下标即表示右面那个名字的第几个字符,八个字符拼在一起,过关。

lumodays

考验了挑战者对二进制的基本认识,对数组的认识,与信息提取能力。

第六关 ???

用Safari打开这一关记得再打开一个新标签页。

???

点击下载之后,我们发现这个压缩包有密码,寻找线索开始。

按照惯例,先看源文件:我们惊奇的发现背景图片里面似乎有拉长的字母,通过ps或其他工具处理可以得到这串字符,但是作为密码输入到zip却不对,我们为了知道这串字符在字体中到底是哪几个字符,对字符进行处理,并且找一个字体搜索网站丢进去,就可以得到字体与字母数字的对应关系,我们惊奇的发现倒数第二个是大写L,打开成功。

打开之后得到了一个非常像某种压缩文件格式(类似于jar包)的文件目录,于是通过在搜索引擎搜索,发现这就是 .xlsx 文件;于是我们把解压后的文件夹压缩,并且改扩展名,用 Excel 打开(在网页标签处有提示),可以发现这是一个左上两个方向都有数字的表格。于是我们猜测:假设表格只有黑白两色,每个数字表示有多少个黑色方格相邻,即可以解出多个答案,最像答案的那个解自然就是答案,如果觉得不像建议更换一下某些方块的颜色求别的解。(建议分别用不同的颜色表示一定不能填、一定要填、可能要填、可能要填推导的不能填)

3Lhy0-EA

考验扫雷能力?

第七关 Code

最开始我也一脸懵来着,但是冷静观察给定文本之后还是能解决本关的任意文本的。

Code

注意到作者提到了 Codec 这个软件,所以我们或许能从其中获得帮助。 (苹果用户只好从网页寻求帮助了,当然,自己编写解码程序也未尝不可)

首先这关每次打开出现的乱码都是不同的,我这里仅对一种进行总结,其他的没有本质区别。

我拿到的是一段以等号结尾的由大小写字母组成的文本,看到等号,按照惯例,先试试base64。

解码之后可以得到由小写字母a - e 与数字组成的文本,并且每两个字符与其他字符用空格分开,这必然是16进制了。

但是解码完我们发现这些数字难以让我们联想到什么,ASCII的话则数字太大了,所以我盲猜16进制的文本是被翻转过的。

将16进制文本翻转解码后得到的10进制数字大小显然是在128以内的,转化为ascii码可以得到一段等号开头的英语大小写文本,故技重施,直接翻转然后base64。

然后就成功得到: Your Key is: [*********] ,将key提交,通关。

每个人的应该不一样,我就不写了。

和之前的编码关一样,冷静观察文本,对照脑中的编码特征寻找线索。

第八关 Dingtalk

拔河比赛。

Dingtalk

提示就在黑板上,只要点赞足够多就能通关,具体要多少呢?21亿。

打开浏览器的开发者工具,查看网络,我们可以发现当我们点一次赞或者点一次踩都会执行 uploadlikes 这个操作,然后发现这是向 api 发送 post 包。于是我们有了 api 和包格式,可以写一个简单的程序自动点赞。需要注意的是,通过对包中 append 进行修改,可以增加单次发包的赞数,最大是一千万;发包是要设置零点几秒的时间间隔的。

不会编程发包的挑战者推荐去学一学,后面还会用到的。

经过短暂的等待,过关。

当然,把 append 修改为负数可以点踩,与点赞的挑战者拔河,最小是负五百万。

通关代码Python版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
import time
import json

headers = {
'cookie': 'token=YourToken' //输入你的token
}
host = 'https://puzzle.gztime.cc'
url = host + '/api/live/uploadlikes'

cnt = 0
while(True):
cnt += 1
r = requests.post(url, data = {'append': 10000000}, headers = headers)
print(cnt, json.loads(r.text)['status'])
time.sleep(0.325)

第九关 Trees

Trees

可以从图里很快的获取信息:二叉树遍历。

作者给定了每棵树两种遍历的结果,先按照遍历规则求出第三种遍历结果;然后观察图片可以知道,需要把两棵树的结果白的在上两两交叉拼起来,就可以得到结果。

注意"."别忘了,并且遍历结果不唯一,寻找最像答案的答案。

TreesAreAlwaysThere.

插曲关 Time

Time

可以通过修改电脑时间的方法,但是不推荐,因为2048年你的 token 会过期。

通过查阅源代码,不难发现在time.js中,会发送一个本地时间的POST包到服务器API。

所以可以更改这段代码,设置一个时间发过去。

在控制台执行如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var host = document.location.origin;
$.ajax({
url: host + "/api/time",
dataType: "json",
type: "POST",
headers: {
"If-Unmodified-Since": new Date("January 1, 2151 00:00:00").toUTCString(),
},
success: (data) => {
$("#0-msg").append($(data["msg"]));
setTimeout(() => {
$("#0-msg").fadeIn(1000);
}, 500);
},
});

Windows 10 能把时间改到 2150年,但是 MacOS 不行。

第十关 喜极而泣.jpg

Emoji

中间有个巨大的表情,后面有个二维码,我们可以先在页面检查器中将表情设置为不可见,然后就能一览二维码全貌;或者在源文件中也可以直接看到二维码的样子,但是有些不同,下文会说到。

同时,通过翻阅源代码 emoji.js 成功获得提示:

你听说过AZTEC吗?

然后通过查询,我们得到这正是该二维码名称,并且咱们日常中使用的叫 QR Code 。

从手机上找到解码器,发现并不行,为嘛呢。仔细对比网上的其他 AZTEC 码,不难发现这个二维码的正中央是白色的,而其他都是黑色的。所以对这个二维码进行反色即可得到可以扫描的二维码。好巧不巧,源文件中的二维码正是反色之后的,通过分析 CSS 可以知道网页中显示的二维码多了一个滤镜filter: invert(1);这正是对图片进行反色的代码,所以就不用我们自己来更改图片了。

Dreams_are_a1ways_be_with_you

第十一关 Maze

迷宫,重头戏…

第一个靠手走出来的大佬走了两个小时,我努力写程序花了整24小时。

Maze

有了之前发 post 点赞的经验,这次咱们查看源代码就知道每个按键都是如何提交到服务器的。

包中,名为drc的参数有'n', 's', 'w', 'e'四种值,分别表示了按下向哪个方向的按钮,并且可以找到 resetstep 两种按钮的 API ,这就说明我们可以通过发 POST 包,来走迷宫。

由于这个地图够大,我们需要写一个栈模拟递归来进行dfs,这里由于剪枝的必要不大故不考虑。

不难发现在包的返回数据中,status即为这步是否走成功,newedges即为当前的墙是怎么样的,不难发现每种墙的情况都对应了一个数,将这个数变成二进制之后我们得到了这个整数实际是对墙的状态压缩:从右到左每一位分别表示北、南、西、东。

在栈中存入五个我们需要的变量:当前坐标xy,当前墙的情况,上一步是怎么走的(用于回溯)和当前坐标尝试了几个方向。

下面是走迷宫 Python3 栈模拟递归版,有主要注释。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import requests
import json
import os
import time
import numpy
import sys

dircs = ["n","s","w","e"]
host = 'https://puzzle.gztime.cc'
reset_url = host + '/api/maze/reset'
url = host + '/api/maze/step'

headers = {
'cookie':'token=' #这里写你的token
}

def reset():
requests.post(reset_url, headers = headers)

xx = [0, 0, -1, 1]
yy = [1, -1, 0, 0]
global vis, tot

def inMaze(x, y) : return 0 <= x <= 63 and 0 <= y <= 63

def solve():
global vis, tot
stack = []
stack.append([0, 0, 6, 0, 0])
while stack:
time.sleep(0.25)
now = stack[-1]
tot += 1
x = now[0]; y = now[1]; walls = now[2]; pre = now[3]; cnt = now[4]
if x == 63 and y == 63:
print('Congratulations!')
break
vis[x][y] = True
print(f'Step {tot}, Now At ({x}, {y})')
if cnt == 15: #如果四个方向都试过,回溯
requests.post(url, headers = headers, data = {'drc' : dircs[pre]})
stack.pop()
continue;
for i in range(4):
cnt = cnt | (1 << i) #状态压缩,四位保存四个方向有没有被尝试过
stack[-1][4] = cnt #更新当前尝试状态
if inMaze(x + xx[i], y + yy[i]):
if vis[x + xx[i]][y + yy[i]] or (walls & (1 << i)): continue
r = requests.post(url, headers = headers, data = {'drc' : dircs[i] })
tmp = 1 #如果当前在dircs中为偶数号元素,则它的反方向为当前编号加一
if i % 2 : tmp = -1 #如果当前在dircs中为奇数号元素,则它的反方向为当前编号减一
stack.append([x + xx[i], y + yy[i], json.loads(r.text)['newedges'], i + tmp, 0])
#下一个要走的点入栈
break

vis = numpy.zeros((64, 64), dtype = numpy.int32)
tot = 0
reset()
solve()

话说在写这里之前,我一直以为我当时是用栈模拟递归过的这道题,但我一回看当时的代码,发现写的啥也不是,原来我是dfs手动开栈过的,然后后面一直在自我催眠…

第十二关 NO12

齐心协力大闯关

NO12

作者当时把我们所有到达这一关的人拉了个群,一起过掉这一关,但是当他们冲刺通关的时候我竟然在健身房健身…导致最后就拿了23名。

这关我没什么好思路,就把每个线索都在哪放在这吧。

  1. 5 <==== 34_zgjsjxhmf 在Excel第二个Sheet中,第五行有一行白色的字,箭头指向五这个行标

  2. <==0b_0000_1111== |p{xNzLpG?Br 在Trees的html注释中,此处移位的位数是二进制的

  3. <==8== [ <5j9k):n9988 ] Time那关,通过翻源代码可以找到没有显示的三个字母TXT,即是提示我们找这个域名的TXT记录

通过对这三个线索进行操作,并且根据链接合理性左移应该是减小,再按照合理的顺序拼起来,就可以得到下一关的链接了。

这里放一个用来移位的C++代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<bits/stdc++.h>
using namespace std;
char c[50];
int b;
int main() {
cin >> c >> b;
int len = strlen(c);
for(int i = 0; i < len; i++) {
cout << char(c[i] - b);
}
cout << endl;
return 0;
}

./Zubeneschamali?k=a803c4-b1c!2f1100

第十三关 Ending

现在进行时表将来,翻译为“眼瞅”…

Ending

虽然说是结束了,但是背景中还是可以看到 “KEEP GOING” 字样的,说明没有结束。

按照惯例,先看源代码,可以发现作者要我们解密一段密文。通过调整CSS中的元素可见性,我们可以看到,两个输入框,一个是key,一个是key_text,但是分别在哪呢?

背景中有个.svg的矢量图,用谷歌浏览器打开这个图,可以发现下方中间有一个长方块,将那个方块设置一个display: none 的CSS之后,可以看到后面写着key:not_now。所以我们将not_now填入第一个方框,并且执行 end.js 中的 RC4 解码函数。

然后会提示一句泰戈尔的飞鸟集,并且让寻找其中缺失的部分,在搜索引擎中可以查到。将这段内容填入第二个方框,执行 MD5 解码函数,通关。

后记

当我写完最后一关时,我所在小区旁边的广场在放 Hotel California ,一首可以让我心静下来的歌曲。

原本预计十几天就能写完,最终写了两个多月,其中也遇到了无数的小插曲。当然,其本质原因还是我咕咕咕了。

还记得去年我就是这天通关的,这一年我感觉我从代码能力到计算机素养各方面都有很大提升。当时通关之后,我就感觉我学到了很多。又走过了一年,我重走当时解题过程,感觉又学到了一些东西。

话说这个解密一直都被我用来试其他自称计算机好的人的底,但是有人靠作弊通关是我真没想到的。

感谢作者 GzTime 耐心的在我写总结期间给我帮助!

——2021.8.26 于学校对面租的房子