Skip to content

Shinym@s 初探 00 - 开始

Published: at 20:42

最近对那个 SC 的汉化脚本很感兴趣,简单了解了一下,发现是通过 prismJsp 实现的注入。正好个人对 SC 的代码也很感兴趣,于是就有了这系列(前提不咕)的文章。

在这里提前注明,这个系列中所有的脚本、测试的环境均为 Chrome 隐身模式,测试链接为 https://shinycolors.enza.fun/tutorial 。(期待后文吃书)

ToC

简介

现在 SC 和之前相比安全性更高了,全局变量也没了,都藏起来了,之前写的防止自动音频暂停的脚本[1]自然也用不了了。之前的脚本是这样的:

1
(function () {
2
const mute = setInterval(() => {
3
if (aoba && aoba.soundManager) {
4
aoba.soundManager.mute = () => {};
5
clearInterval(mute);
6
}
7
}, 1000);
8
})();

可以看到,在之前的那个版本,aoba 是全局变量,随便改,随便操作,但现在就不一样了。

工具

在整个逆向过程中,我试图整理出一套适合前端逆向使用的工具(特别是 SC),于是 ShinyHelper 就诞生了。

目前它的功能还很简单,只有下面所说的这几个。但随着源码阅读的深入,我也会逐渐完善的。

目前源码托管在 GitHub 的私有仓库,原仓库可能不再更新。下面是对应 Organization一个公开仓库,想要看源码的可以在这个系列的任意一篇文章下面留言,根据认识情况加人(

预处理:拦截 Raven

还记得上一篇文章里提到的 拦截 Raven 吗?这就是为了这个项目而生的。我们需要简化调用栈层数,因为 SC 的层数已经爆炸多了。在读到后面你会发现,SC 是通过 eval 执行代码的,因此断点很多时候都没法打。能简化层数的话我们是一定需要尝试简化的。这里直接用到了上文对应的代码,并使用了 chrome.webRequest.onBeforeRequestListener 进行脚本加载的拦截。

源码:主页

这里我们记录下主页的全部源码,一是方便下文观看,二是之后如果有更新方便对比。记录的时间为:2020年 04月 04日 星期六 17:27:28 CST

1
<!doctype html>
2
<html lang="ja">
3
<head>
4
<!-- Google Tag Manager -->
5
<script>
6
(function (w, d, s, l, i) {
7
w[l] = w[l] || [];
8
w[l].push({
9
"gtm.start": new Date().getTime(),
10
event: "gtm.js",
11
});
12
var f = d.getElementsByTagName(s)[0],
13
j = d.createElement(s),
14
dl = l != "dataLayer" ? "&l=" + l : "";
15
j.async = true;
16
j.src = "https://www.googletagmanager.com/gtm.js?id=" + i + dl;
17
f.parentNode.insertBefore(j, f);
18
})(window, document, "script", "dataLayer", "GTM-WGSNLMS");
19
</script>
20
<!-- End Google Tag Manager -->
21
<meta charset="utf-8" />
22
<!--<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, user-scalable=no"/>-->
23
<meta
24
name="viewport"
25
content="width=device-width, initial-scale=1.0, user-scalable=no"
26
/>
27
<link
28
rel="preload"
29
href="//sdk.enza.fun/enza-platform-v1.26.js?v=20191213"
30
as="script"
31
/>
32
<title>アイドルマスター シャイニーカラーズ</title>
33
<style>
34
body#prim-body {
35
margin: 0;
36
text-align: center;
37
background-color: #feffff;
38
}
39
body#prim-body::before {
40
background: url("/background.jpg?v=16d1afd1ce064e56b7683669c99ac0f559a74307")
41
no-repeat center center;
42
background-size: cover;
43
-webkit-background-size: cover;
44
display: block;
45
position: fixed;
46
top: 0;
47
left: 0;
48
width: 100%;
49
height: 100vh;
50
content: "";
51
z-index: -1;
52
}
53
#ezpf-root .ezpf-drawer-whole-container {
54
width: calc(100vh * 1.77778);
55
margin: 0 auto;
56
left: 0;
57
right: 0;
58
}
59
60
@media screen and (max-aspect-ratio: 16/9) {
61
#ezpf-root .ezpf-drawer-whole-container {
62
width: 100%;
63
}
64
}
65
div.scrollable::-webkit-scrollbar {
66
display: none;
67
}
68
</style>
69
<link
70
rel="icon"
71
type="image/png"
72
href="/favicon_16.73f3ef3a310d483e315f10410e758ae2.png"
73
sizes="16x16"
74
/>
75
<link
76
rel="icon"
77
type="image/png"
78
href="/favicon_32.3e898b9094141d728f13a21c3170866c.png"
79
sizes="32x32"
80
/>
81
<meta name="apple-mobile-web-app-title" content="シャニマス" />
82
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
83
<meta name="theme-color" content="#8adfff" />
84
<link
85
rel="apple-touch-icon"
86
sizes="512x512"
87
href="/icon_512x512.81bceb463844edc063d7bfbfc13d5cfa.png"
88
/>
89
<link
90
rel="apple-touch-icon"
91
sizes="192x192"
92
href="/icon_192x192.a3842ac040d3600657c1d8aa98b3db4d.png"
93
/>
94
<link rel="manifest" href="" />
95
</head>
96
<body id="prim-body">
97
<script src="//sdk.enza.fun/enza-platform-v1.26.js?v=20191213"></script>
98
<script
99
src="https://cdn.ravenjs.com/3.24.0/raven.min.js"
100
crossorigin="anonymous"
101
></script>
102
103
<script>
104
window.console.log = function () {};
105
window.console.warn = function () {};
106
window.console.group = function () {};
107
window.console.groupCollapsed = function () {};
108
window.console.groupEnd = function () {};
109
</script>
110
111
<script
112
type="text/javascript"
113
src="/enza-game.min.js?v=16d1afd1ce064e56b7683669c99ac0f559a74307"
114
integrity="sha256-EWB86GCqFjjckbQ9jk1U4NJepe5nL7mo6CRFZ/WsWOc="
115
crossorigin="anonymous"
116
></script>
117
<script
118
type="text/javascript"
119
src="/pixi-particles.min.js?v=16d1afd1ce064e56b7683669c99ac0f559a74307"
120
integrity="sha256-ymWZ7CMdFIoQaSG8dGrN43LKRMuR1UZtFWNUz9+NFX8="
121
crossorigin="anonymous"
122
></script>
123
<script
124
type="text/javascript"
125
src="/pixi-ae.min.js?v=16d1afd1ce064e56b7683669c99ac0f559a74307"
126
integrity="sha256-6kTowyDbHgB5y0Lu5mRS2Ge4f2SXSXmnbjCWJUcgsNI="
127
crossorigin="anonymous"
128
></script>
129
<script
130
type="text/javascript"
131
src="/env.js?v=b244f34c59273c8f992c7f1098e13369"
132
></script>
133
<script
134
type="text/javascript"
135
src="/commons.chunk-f60f4558ccbd2af54df8.js"
136
integrity="sha256-qFBtZT+9qBOxncri0yeMQ3psS9lTQpnvXuRvqid/+u8="
137
crossorigin="anonymous"
138
></script>
139
<script
140
integrity="sha256-FZRsC6ZKInAh3e1v2UVGAjruWcFZQmqDJMxo2xCjuu8="
141
type="text/javascript"
142
src="/app-10fa5e5e15ff0052b150.js"
143
></script>
144
<script id="tagjs" type="text/javascript">
145
(function () {
146
var tagjs = document.createElement("script");
147
var s = document.getElementsByTagName("script")[0];
148
tagjs.async = true;
149
tagjs.src = "//s.yjtag.jp/tag.js#site=CvxwH8J";
150
s.parentNode.insertBefore(tagjs, s);
151
})();
152
</script>
153
<noscript>
154
<iframe
155
src="//b.yjtag.jp/iframe?c=CvxwH8J"
156
width="1"
157
height="1"
158
frameborder="0"
159
scrolling="no"
160
marginheight="0"
161
marginwidth="0"
162
></iframe>
163
</noscript>
164
</body>
165
</html>

不得不吐槽,这个用两个 HTML 块实现的 <details> 真的很草

这里我们拆开来看:

我们会发现,关键的主脚本代码出现了两次,这是由于 preload 的加载策略导致的。具体可以参考 MDN 的这篇文章

window.console 覆盖

这里我们发现,为了防止信息泄露,SC 将 console 的实现覆盖了。这里我们当然不能让它覆盖了(笑),于是同样是使用 Proxy。(已加入工具)

1
window.console = new Proxy(window.console, {
2
get: function (target, key) {
3
if (typeof target[key] === "function") {
4
return target[key].bind(target);
5
}
6
return target[key];
7
},
8
set() {
9
return true;
10
},
11
});

结构分析

这里我们来对主页加载的脚本进行简单的分析。其实上一步禁止 console 覆盖之后就已经暴露出很多东西了,比如:

框架锁定(笑)
框架锁定(笑)

抛去没有作用的脚本,剩下的存在真实效用的脚本还有这些:

可以看出,和 enza 有关的有 enza-platformenza-game,和游戏本体有关的有 commons.chunkappenv,和渲染有关的有 pixi-particlespixi-ae(这二者都是扩展,不知道为什么没有打包进去)。

在这里先提一句,后文我们需要习惯 ezxx 的缩写,比如 ezpf 就是 enza-platformezg 就是 enza-game 的缩写。这是他们源码里自己的写法(笑)

⚠️ 破坏性更新提示

在后续的版本中,enza 移除了 common.chunk。

enza-platform

顾名思义,这里都是 enza 这个平台对应的相关函数。比如:

弹个框这种的
弹个框这种的

实际代码逻辑中应该只有支付、登录方面会涉及到和平台交互的地方(我猜)。

enza-game

在知道了 PIXI 之后,可以发现 enza-game 中大部分的模块其实都是 PIXI 的内容,掺杂了其他诸如 sceneManager 啊,soundManager 之类的内容,都属于复用型代码,这里不细讲,留到后续篇目慢慢分析。初步看来是 TypeScript + Webpack + PIXI,也不知道具体是不是这样(

结语

这篇文章可以说是什么都没说,只简单地开了个头。摸鱼摸了一天,这文章写的我都有点忘记目的了(不是)。总之出发点我现在也不知道是什么了,什么都不多说了,我去P卡了(

(小插曲

From enza-game
From enza-game

PIXI v4.7.0,2018 年 1月 18 日发布
PIXI v4.7.0,2018 年 1月 18 日发布

2 月 7 日就事前登录了
2 月 7 日就事前登录了

《爆肝一个多月》