Phigros 第九章 ARG 收官:17:00 归零那一刻,与藏在控制台里的致谢
Solivault 页面上那个跑了一周的倒计时,在 2026-10-02 17:00<00>00>(北京时间)归零了。
这一环之前我写过一次完整复盘,那篇讲的是链条怎么解。这篇只讲收官:归零那一刻到底发生了什么、官方换了什么、以及为了在限流和动态值里抓到它,我最后是怎么做的。
先说结论:归零没有让主页面变成别的东西,只有一个页面换了版本——终点页在 17<01>01> 到 17<02>02> 之间,把源码从混淆版换回了明文版,同时放出了一段只在控制台里打印的致谢。
一、这一周它在数什么
隐藏页 WERJETZTALLEINISTWIRDESLANGEBLEIBEN 上有两条时间线:
- 主页面(
/)从某个时间点起换成了 CRT 风格的滚动码显示器; nosong.html保留着写死的截止时间:
const DEADLINE_TEXT = '2026/10/02 17:00:00';const target = Date.parse(DEADLINE_TEXT.replace(/\//g, '-').replace(' ', 'T') + '+08:00');时间源不止本地时钟,它是按顺序试三个源再取其中一个:站内 ntp.php、timeapi.io 的 UTC 接口、以及 Cloudflare 的 cdn-cgi/trace 里的 ts=。同步到之后每 10 分钟重校一次——换句话说,本地改系统时间没用。
归零后它不会自己跳转,只停在 0:00:00:00。我用无头浏览器读回它的 canvas 文本确认过:
----::----::----::---- ← 还没同步到时间源时的占位00:00:000::000 ← 归零后的显示(重影字符已被我合并)二、主页面是一台滚动码发生器
主页面(51,408 字节)没有倒计时,中央只有一个 XX:XX:XX:XX 形式的 4 组 2 字符随机码,在深红色的眼球/黑洞背景上高速翻转。
它的字符集是全部可打印 ASCII:
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()-_=+[]{}<>?/\|~我前后采样了五次(16<55>55>、跨越 17<00>00> 整点、17<06>06>、17<09>09>、17<12>12>),帧数分别是 189/189、500/500、303/303、254/254、254/254——合计 1500 帧里没有任何一帧重复,字符集里始终没有出现可读单词。所以归零这件事只发生在”谜题”这一层,不在这个显示器上。
主页面背景上那颗红色的眼球/黑洞是间歇性动画,会自己消失再出现。17<06>06> 我截的图里它恰好不在,一度让我怀疑”红眼消失是不是 17<00>00> 后的变化”;同一会话继续采到 17<12>12> 时它又自己回来了(中央亮度 29.3 → 56.3)。所以它是页面自带的装饰节奏,和 17<00>00> 无关,别把它当成收官变化。
页面上没有任何键盘、点击、触摸监听;倒是留了两个给低配设备的开关:?lite 降特效、?black / ?nobg 跳过背景图。JS 是 obfuscator.io 的 base64+RC4 混淆,还带了 Function("return (function() {}.constructor('return this')( ));") 这类反调试和一个 console 方法劫持。
三、17<00>00> 前后:我抓到的完整时间窗
为了确认”是什么在什么时刻变了”,我在归零前后各按固定时间点抓了三个页面,每次都从代理池取一个全新的出口 IP(原因见第六节)。用归一化后的稳定哈希比对:
| 抓取点 | soli(主页面) | nosong | vault(终点页) |
|---|---|---|---|
| 16<53>53> PRE_A | ef360ff1… 51,408 B | 4eed183b… 40,829 B | (代理失败) |
| 16<57>57> PRE_B | ef360ff1… | (代理失败) | f32e5a6b… 68,579 B |
| 17:00<31>31> POST_A | ef360ff1… | 4eed183b… | f32e5a6b… 68,579 B |
| 17:02<32>32> POST_B | ef360ff1… | 4eed183b… | 044171ea… 63,953 B ← 变了 |
| 17:05<56>56> POST_C | ef360ff1… | 4eed183b… | 044171ea… 63,953 B |
也就是说:
- 主页面和 nosong 全程 5 个抓取点的哈希一模一样,一个字节都没动;
- 终点页的切换发生在 17:01<00>00> 到 17:02<32>32> 之间,从 68,579 B 掉到 63,953 B,之后保持稳定。
四、终点页把源码放回来了
新旧两版差 4,626 字节,差的不是功能,而是保护。旧版内联 JS 是混淆的:
(function(_0x4ede78,_0x255225){var _0x104d79=_0x494f,_0x506f5c=_0x4ede78();while(!![])…新版是明文的:
(function () { 'use strict'; var LANGS = { zh: { htmlLang: 'zh-CN', title: 'Sky of Your Vault', …脚本块结构也换了:
| 旧版(17<00>00> 前) | 新版(17<02>02> 后) | |
|---|---|---|
| 主 JS | 42,217 B(混淆) | 34,867 B(明文) |
| 留言板 API | 14,616 B(混淆) | 9,591 B(明文) |
| 陀螺仪 | 藏在混淆里 | 6,383 B(独立明文块) |
| 版本号 | 2.2.0 | 2.4.0 |
**版本号从 2.2.0 跳到 2.4.0,同时把混淆撤了。**时间点很说明问题:倒计时还没归零时,这套源码是加密的(防止提前剧透);归零后立刻放回明文,意思等于”不用再藏了”。
五、那封藏在控制台里的致谢
新增的那段代码在页面加载后做一件很安静的事——它把一整套文本用 %c 格式化后打进 console.log:
console.log.apply(console, [fmtParts.join('')].concat(styleArgs));addChunk('CAST LIST/制作人员名单', titleStyle, 124, 90, '#ffffff', '', true);addChunk('Phigros Chapter 9 ARG', subtitleStyle, 46, 45, '#ffffff', '', true);所以打开终点页、按 F12、切到 Console,就能看到一份彩色排版的制作人员名单。这也是为什么之前要混淆源码——不然提前翻源码就能读到它。
名单全文(原文照录):
CAST LIST / 制作人员名单 Phigros Chapter 9 ARG
关关弗斯 — 本次 ARG 的策划,设计了解谜流程,坐在鸽游大楼里轻哼之人 「大伙都辛苦了!」 「【回头看山坡上方】布豪,DDL 滚成雪球追过来了,我先跑!」
吸管猫 — 使用头顶天线发射模拟信号 「莫西莫西 听得到吗?」
汉堡 — 负责和你的手机、平板、手表、希沃、冰箱、微波炉等家电的 GPU(后面两个如果有的话)打交道 「尝试了一些新的东西,淘汰了一批低端机(bushi)。这次第九章除了本体的制作外还对之前一直有的性能瓶颈做了优化,希望能优化到吧(AI 真是太好用了你知道吗)」 「免责声明:AI 仅用于构思方案与程序设计辅助,所有 CG 动画均由 Pigeon Animation Team 古法匠心手作」 「(关关弗斯:[呆无辜] 虽然但是,这里不是第九章的 cast。问题不大,已读乱回我很喜欢)」
rN — man of english translation & playtesting 「i don’t know how to write cool english sentences, so i will wait for the marriage ceremony instead(?). coo」 「我不晓得咋个能去嚓嚓写出那酷炸天滴句子,所以急救什么时候结婚?坐等婚礼咕哦!」 「(来自关关弗斯的缺德翻译)」
Gaoice — 解密网站的前后端开发,以及林泊百科的运维,配合汉堡师傅测试他的优化 「这次后厨的土豆没炸!!我的测试土豆也没炸!!前端的土豆后端的土豆都没炸!!」 「总之感谢你的炸的土豆!!没熟!!」
Falling_Studio — 做图被抓包之人 「很荣幸能为第二阶段的谜题网站设计视觉!希望大家喜欢」
—— Thanks For Playing!
最后一句是 Thanks For Playing!,配色用的是页面主题的那个青色(#31a8ca)。
名单里 Falling_Studio 那句「第二阶段的谜题网站」是唯一一处提到后续阶段的地方。如果这是分阶段的 ARG,那现在还只是上半场。
六、怎么抓到的:三个麻烦,三个对策
这一轮能抓到 17<00>00> 的时间窗,靠的是三件不得不做的事。
麻烦一:页面上的字是画在 canvas 上的,curl 看不到。
curl 拿到的 HTML 里没有那串滚动码——它是页面里 Math.random() 现算、再用 fillText 画上去的。解决方式是打开真浏览器,在页面脚本执行前包住 canvas 的绘制接口:
// page.addInitScript 里const orig = CanvasRenderingContext2D.prototype.fillText;CanvasRenderingContext2D.prototype.fillText = function (text, x, y) { window.__frames.at(-1)?.t > performance.now() - 120 ? window.__frames.at(-1).chars.push([x, String(text)]) : window.__frames.push({ t: performance.now(), chars: [[x, String(text)]] }); return orig.apply(this, arguments);};之后按帧取回、把 x 坐标分桶去重就能还原出当前的码。之所以要去重,是因为这套视觉是红青重影,同一个字符会在 x±2 各画一次,直接拼接会得到 ZZ33::??tt:: 这种双写形式。
麻烦二:站点限流很紧,直连和普通抓取通道都会被挡。
这个 wiki 有三层拦:自定义的 773「访问被拒绝」(拦爬虫特征 UA、部分接口还要求带 Referer)、nginx 403(请求一密就封,实测连发三五次就中)、以及海外 IP 偶发 502。用代理池绕,而且每个请求都要换新 IP——池子里的 IP 只活两三分钟,复用第二个请求基本必失败。取代理的接口还有 5 秒调用间隔限制:并行跑多个抓取进程时它们共用同一个 key,会互相抢配额(表现为频繁返回「调用间隔超过套餐 5 秒限制」),所以并行度开不高,或者各自排队把节奏拉开。
麻烦三:页面里有两处每次请求都变的动态值,直接比对 md5 会误判成”页面一直在变”。
终点页 <head> 里有一个每次渲染都下发的请求令牌:
<meta name="page-token" content="1790930028.373955ed3dc0834cd18d51f6cc2d272f">而新版明文 JS 里还硬编码了同一个值做占位比较:
if (!pageToken || pageToken === '1790931752.9f96c58c320b8c33a4193bf2d7b39251') pageToken = null;这两处长度固定、所以字节数不变,但内容每次都变。我是先把它们和 Cloudflare 的注入片段一起归一化,再算哈希:
s = re.sub(r'(name="page-token" content=")[^"]*(")', r'\1TOK\2', s)s = re.sub(r"(pageToken === ')[^']*(')", r"\1TOK\2", s)不归一化的后果很直观:同一版页面连抓两次会得到两个不同的 md5,看起来像”页面在不停更新”。
七、顺带修清的机制
**page-token 到底是什么。**从新版明文的留言板脚本里能看到它的去向——它被当成请求头带上:
if (pageToken) options.headers['X-Page-Token'] = pageToken;if (options.body && csrfToken) options.headers['X-CSRF-Token'] = csrfToken;return fetch(API_BASE + '?api=' + name, options)也就是说它是服务端每次渲染下发的一次性令牌,用来给 index.php?api=bootstrap|messages|highlights 这组留言墙接口做防重放。那句硬编码的比较值是开发时留的占位(1790931752 换算过来正好是 17:02<32>32>,也就是页面换版的时刻)。
留言墙还活着。bootstrap 正常发 CSRF,highlights 拉回来的十条留言仍然是清一色 10~40 字符的乱码短串——大家把自己解出来的碎片”呼唤”进了星空,没有一条自然语言。
**多了个陀螺仪脚本。**新版把设备方向控制单独拆成一块:手机倾斜就能转星空(PITCH_MAX 1.15、TILT_K 0.45),需要 DeviceOrientationEvent.requestPermission 的浏览器会先问权限。
八、写在最后
从 9 月 25 日那条 Chaocipher 密文,到今天 17<00>00> 倒计时归零、控制台里浮出 Thanks For Playing!,这一环算是走完了。
整个过程里最让我意外的是收尾的方式:没有新谜题、没有新页面、主页面连一个像素都没变——官方只是把源码的锁打开,然后在控制台里说了声谢谢。用版本号从 2.2.0 跳到 2.4.0 这种细节来标记”这一版是给通关的人看的”,挺温柔的。
至于第二阶段的谜题网站——如果真会有,到时候再抓。
口径说明:本文所有时间窗、字节数、哈希值、名单文本均来自我在 2026-10-02 的实测抓取(各时间点的完整快照都留了档)。链条的解读部分参考了 Phigros 解密 3 群(社区)的整理,收官部分的结论是这次实测得出的。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!



