d;d++){var s=q[d-15],t=q[d-2],u=l[0],v=l[4],w=l[7]+(c(v,6)^c(v,11)^c(v,25))+(v&l[5]^~v&l[6])+m[d]+(q[d]=16>d?q[d]:q[d-16]+(c(s,7)^c(s,18)^s>>>3)+q[d-7]+(c(t,17)^c(t,19)^t>>>10)|0),x=(c(u,2)^c(u,13)^c(u,22))+(u&l[1]^u&l[2]^l[1]&l[2]);l=[w+x|0].concat(l),l[4]=l[4]+w|0}for(d=0;8>d;d++)l[d]=l[d]+r[d]|0}for(d=0;8>d;d++)for(e=3;e+1;e--){var y=l[d]>>8*e&255;i+=(16>y?0:"")+y.toString(16)}return i};
sha256('ZooTeam')
```
两段代码无论选择那段执行,得到的结果都会是“afb111ae9f3569141b2cfa77cf7e1722b10f9bba421a5c939bbcb06d4f1cb812”。
输入答案,会看到下一关入口提示,输入下面的代码,来到第五关。
```js
location.href=document.querySelector('.crypto-go-on').innerText.replace(/.*\//,'/')
```
## 左门:第五关

这里默认展示了一个看起来小时候玩的华容道拼图的界面,鼠标戳戳点点发现有的内容可以转动,有的不可以,元素不可移动,结合这关的路径名字“route”,猜测应该是要将所有元素转为正确的角度,完成通关密码的提示图案。
因为一共是九个图片元素,所以这里猜测容器尺寸是不正确的,应该先做调整。
```js
document.querySelector('.zoo-card-box').style="width:348px;height:348px;"
```
在输入了上面这段代码后,图片展示为了 3x3 的模式,但是因为有的元素不可旋转,所以我们无法得到正确的答案。
源码中包含正确的通关密码,也包含了旋转逻辑,这块对于具体业务实现不感兴趣,直接给出如何旋转为正确答案的代码吧。
```js
var degs = [180,90,270,180,180,270,180,180,90];
Array.from(document.querySelectorAll('.zoo-card-box img')).map((n,i)=>{
n.style.transform = "rotate("+degs[i]+"deg)";
})
```
当图片旋转正确,答案也明确的展示到了页面上:“FA4m9YQ”。
输入答案,继续执行下面的代码,前往第六关。
```js
location.href=document.querySelector('.rotate-it-mask a').href
```
## 左门:第六关

第六关的提示非常明显,需要我们进行 Cookie 设置,使用第二关提示中的 Cookie 进行设置即可。
```js
document.cookie="user=ZooTeam; path=/";
```
至此,左门后的谜题就都攻略完毕了,接下来试试右边的门后的内容吧。
## 右门:第三关

这个页面打开后,展示了一个标准的“ 404 ”,检查渲染后的 DOM 结构后可以看到,有一个样式被设置为 `display: none;` 的 `` 标签中包含了下一关的地址。
控制台直接执行下面的代码即可:
```js
location.href = document.querySelector('p').innerText.replace(location.host,'')
```
## 右门:第四关

打开这关页面后,会看到醒目的“Any application that can be written in JavaScript, will eventually be written in JavaScript”。
在控制台中会看到一行输出提示“Atwood:ayplctotacnerteijvsrpwleetalbwitnnaacitnapiainhtabwitnnaacitilvnulyerteijvsrp”。
继续检查 DOM 元素会看到 `` 标签上默认属性写着“请搜索:Rail-fence Cipher”,以及一个被设置隐藏,但是类名上写着需要我们解密的文本字符串。
```js
nciyurraighsomshvwreototdcyttogauainhnxlvlsaiaidxiefoaeedntiyuutaeokduhwoerpicnrtltoteeteeimgclne
```
对“Rail-fence Cipher”密文进行加解密,可以选择使用在线工具 [http://www.online.crypto-it.net/eng/rail-fence.html](http://www.online.crypto-it.net/eng/rail-fence.html)
或者下面的代码对字符串进行分析和“解密”:
```js
// 修改自 http://0x2013.blogspot.com/2013/10/rail-fence-cipher-html-javascript.html
function encode(input, lineCount) {
const maxLineNumber = lineCount - 1;
let text = input.toLowerCase().replace(/[^a-z]/g, "");
let result = "";
for (let line = 0; line < maxLineNumber; line++) {
let skip = 2 * (maxLineNumber - line);
for (let i = line, j = 0; i < text.length; j++) {
result += text.charAt(i);
if (line == 0 || j % 2 == 0) {
i += skip;
} else {
i += 2 * maxLineNumber - skip;
}
}
}
for (i = line; i < text.length; i += 2 * maxLineNumber)
result += text.charAt(i);
return result;
}
function decode(input, lineCount) {
const text = input.toLowerCase().replace(/[^a-z]/g, "");
const maxLineNumber = lineCount - 1;
var result = new Array(text.length);
let k = 0;
for (let line = 0; line < maxLineNumber; line++) {
let skip = 2 * (maxLineNumber - line);
for (let i = line, j = 0; i < text.length; j++) {
result[i] = text.charAt(k++);
if (line == 0 || j % 2 == 0) {
i += skip;
} else {
i += 2 * maxLineNumber - skip;
}
}
}
for (i = line; i < text.length; i += 2 * maxLineNumber)
result[i] = text.charAt(k++);
return result.join("");
}
```
分别执行下面的代码,可以确认加解密算法和“KEY”长度是有效的。
```js
encode("Any application that can be written in JavaScript, will eventually be written in JavaScript", 2);
// 获得和控制台一致的密文
"ayplctotacnerteijvsrpwleetalbwitnnaacitnapiainhtabwitnnaacitilvnulyerteijvsrp"
decode("ayplctotacnerteijvsrpwleetalbwitnnaacitnapiainhtabwitnnaacitilvnulyerteijvsrp", 2);
// 获得原文的“去空格版”
"anyapplicationthatcanbewritteninjavascriptwilleventuallybewritteninjavascript"
```
继续执行下面的代码,获取下一关的进一步提示:
```js
decode(document.querySelector('.it-looks-like-a-ciphertext').innerText, 2)
"niceifyouarereadingthisyoumusthaveworkedouthowtodecryptitcongratulationthenextlevelismagicalindex"
```
将字符串适当添加空格后,得到提示文本:
> nice if you are reading this you must have worked out how to decrypt it congratulation the next level is magicalindex
最后在控制台执行代码,进入下一关:
```js
location.href="/magicalindex"
```
## 右门:第五关

直接在控制台中检查 DOM 元素会看到下面的内容:
```html
```
而界面中仅展示了九个元素,结合关卡名称,判断第十个元素可能是线索。继续进行分析,会看到样式规则很有意思,刻意将这个元素的 `z-index` 设置为了负数。
```css
.question___20202 {
width: 266px;
height: 180px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
z-index: -2;
}
```
使用控制台代码修改这条样式规则,可以看到通关提示:eikooC。
```js
document.querySelector('.question___20202').style.zIndex=1
```
在控制台中输入代码,看到默认 cookie 中有一条名为 `nextUrl` 的设置内容:
```js
document.cookie
"nextUrl=/level/5"
```
既然得到了下一关的提示,在控制台中执行下面的代码即可:
```js
location.href=document.cookie.replace(/.*=/,'')
```
## 右门:第六关

打开控制台,发现默认有一个接口请求报错。
```js
POST https://2021.zoo.team/api/zoo/key 405
Uncaught (in promise) Error: Request failed with status code 405
at e.exports (umi.js:1)
at HrI/.e.exports (umi.js:1)
at XMLHttpRequest.d.onreadystatechange (umi.js:1)
```
在控制台网络面板中检查该请求细节,会看到相应内容为:
```json
{"success":false,"result":"未 GET 你的请求~"}
```
这里如此刻意,显然是提示我们请求方法不正确,使用 GET 方式再次请求接口。
```js
fetch('https://2021.zoo.team/api/zoo/key', {method:'GET'}).then(r=>r.json()).then(r=>console.log(r))
```
提示果然和预期中一样,有了变化。
```json
{success: false, result: "请继续寻找 key~"}
```
根据提示将第二关的内容拼合到请求中。
```js
fetch('https://2021.zoo.team/api/zoo/key?code=86749',{method:'GET'}).then(r=>r.json()).then(r=>console.log(r))
```
发现结果没有变化,点击页面提示按钮,看到原来需要两个参数。检查之前页面默认发出的请求,看到请求头中有一个请求特别可疑:`key: ZooTeam`。
将这个内容拼在请求地址上:
```js
fetch('https://2021.zoo.team/api/zoo/key?code=86749&key=ZooTeam',{method:'GET'}).then(r=>r.json()).then(r=>console.log(r))
```
服务端输出了我们期待已久的结果:
```json
{success: true, result: "https://2021.zoo.team/ending"}
```
控制台输入最后的代码,再次前往通关页面。
```js
fetch('https://2021.zoo.team/api/zoo/key?code=86749&key=ZooTeam',{method:'GET'}).then(r=>r.json()).then(r=>location.href=r.result);
```
### 真实的第五关
在第一关的时候,我曾说第一关实际是第零关,原因是这个第六关的路由地址是“/level/5”。或许是作者在生成的程序的时候,做了顺序调整,但是更大的可能性是,在做这个游戏的最初,计数方式就是符合程序员“从零开始”的经典套路,而这个路由就是佐证之一。
## 通关

通关页面好像没有什么特别的,基本都是招聘信息和团队介绍云云,这里略。
## 其他:作弊方案
经常看代码的同学会发现这个程序基本由“纯前端”方案构成,所以一切的秘密都在源代码中,打开页面引用的脚本,搜索刚刚题目中的路由,不难找到下面一段“路由定义”。
```js
var r = n("xYkL")
, o = n("bCY9")
, i = [{
path: "/",
component: n("QeBL").default,
exact: !0
}, {
path: "/ending",
component: n("NoKT").default,
exact: !0
}, {
path: "/gate",
component: n("+xzV").default,
exact: !0
}, {
path: "/decrypt",
component: n("7kX6").default,
exact: !0
}, {
path: "/decrypt_2",
component: n("4ZDs").default,
exact: !0
}, {
path: "/rotate",
component: n("zRzk").default,
exact: !0
}, {
path: "/level/5",
component: n("lsGM").default,
exact: !0
}, {
path: "/identity",
component: n("l2Te").default,
exact: !0
}, {
path: "/magicalindex",
component: n("LAmj").default,
exact: !0
}, {
path: "/html/first",
component: n("ypg8").default,
exact: !0
}, {
path: "/html/error",
component: n("f+ft").default,
exact: !0
}];
o["a"].applyPlugins({
key: "patchRoutes",
type: r["ApplyPluginsType"].event,
args: {
routes: i
}
})
```
代码中的 “Path” 字段早已将所有的地址告诉了浏览器,和我们。
## 最后
突然很怀念那段单纯的写前端程序的日子,有些怀念杭州的小伙伴,淘宝的战友们。
谢谢政采云团队,虽然游戏还是有些简单,但是还是带来了一段愉悦的时光。
—EOF