关于某个触摸小游戏的源码逆向以及修改

前言

⚠️ 本文仅为个人学习与技术研究的记录,不涉及任何商业用途。请读者务必遵守相关法律法规,不要将文中方法用于破解、传播或牟利。如需完整体验游戏,请支持正版。

这几天一直在闭关修炼,折腾一堆没见过的新奇玩意,但是不知道发什么文章
直到前几天朋友发了我一款小游戏(🧈)看起来是触摸类型的
需要通过触摸来完成进度条等解锁下一关
以及还有好感度(XY)上升,满了成功,敌方(暂且称呼)观察你触摸时候的两个进度条,上升满了就失败

然后他说:

1
哎呀,神奈酱神奈酱~这个游戏太难了能不能帮我看看

那很气人了,起初我是不想去搞得,但是又看见个奇怪的加密,似乎市面上都没相关解密方案,看了一圈即便有工具,也没相关教程
那我不就上了吗🫠


那么就以这个游戏:女xx的xx课堂作为例子

获取文件

上手我先是观察了一番,发现dex什么的没啥可以下手的,翻了下assets发现有几个奇怪东西,没想到是编译后的.jsc文件

assets

加密的jsc
index.feeb8.jsc
cocos2d-jsb.d6486.jsc
index.09c80.jsc
physics.37b5c.jsc
settings.a6099.jsc

哎呀这不就有意思了吗~
接下来,我们通过010Editor查看这几个jsc,我就先拿其中一个作为例子:
index.feeb8.jsc
通过cocos2d-jsb.d6486.jsc
我们可以得知这个是使用Cocos Creator来进行开发的游戏,那目标很明确了!


确认目标

接下来我们要实现的几个目标就是

  1. 对 APK 进行逆向并获取 Key
  2. 通过 Key 对 JSC 文件进行还原
  3. 对还原后的 JS 进行修改与数据调整
  4. 重新封包 JSC 并打包回 APK

实现目标

ok,那么知道了目标,我们先了解一下知识

神奈的芝士小科普~

Cocos Creator是什么?
这一款 游戏开发引擎/IDE,由 Cocos 引擎团队开发。它主要用于 2D/3D 游戏的快速开发,尤其适合做移动端(Android、iOS)、网页(WebGL/HTML5)、PC 平台的小体量游戏。

  • Cocos Creator 是基于 Cocos2d-x 演化而来的 完整游戏开发工具
  • 它集成了 场景编辑器、UI 编辑器、资源管理、脚本系统 等功能。
  • 使用 JavaScript / TypeScript 编程(以前也支持 Lua),所以对前端开发者比较友好。

开始第一步,获取到so文件并丛中找出key

Cocos Creator的游戏一般会在lib文件夹里面有libcocos2djs.so
lib
libcocos2djs.so
接下来,我们需要通过010Editor,这个神奇的小妙具!
libcocos2djs.so拖入到010Editor

接下来你会看见一堆你看不懂我也看不懂的东西😉总之都看不懂嘿嘿!

libcocos2djs.so in 010
然后我们使用神奇小妙招CTRL + F打开搜索框
我们搜索文本:Cocos Game
787545574
你会看见 巴拉巴拉巴拉 呸!

1
2
3
cocos_jni_env_init.cocos_android_app_init.
Cocos Game.cd2d90bd-ad59-40.
jsb-adapter/jsb-builtin.js.main.js

其中:Cocos Game后面的cd2d90bd-ad59-40就是我们解密需要的key

进行第二步通过 Key 对 JSC 文件进行还原

接下来就需要使用由luckyaibin大佬开发的cocoscreatorjscdecrypt来进行还原和逆向
首先我们先clone他的仓库

1
git clone https://github.com/luckyaibin/cocoscreatorjscdecrypt.git

clone下来之后,会有这几个目录结构
537092882

我们根据readme先对其进行安装需要的东西

1
2
npm install xxtea-node
npm install pako

解密

在安装完成所需要的东西之后,我们打开decode.js
可以看到

1
2
3
4
5
6
7
8
var fs = require("fs");
var path = require("path")
var pako = require("pako")
var xxtea = require("xxtea-node");

var FILEPATH = path.resolve('./assets');
var KEY = "cd2d90bd-ad59-40" //cocoscreator 的 工程加密key
var UNZIP = true //是否启用压缩

这一段神奇代码


在此之前看一下这个原理~

解密和加密原理(可跳过)

点击展开原理
  • 目标:遍历 ./assets 目录及其所有子目录,找到所有 .jsc 文件。

  • 操作

    1. 使用 XXTEA 算法对 .jsc 文件进行解密。
    2. 如果 UNZIP = true,则继续使用 pako(zlib)进行解压缩。
    3. 解密解压后的文件保存为同目录的 .jsc.js 文件。

所以,这段代码的用途是 把 Cocos Creator 编译后的加密 .jsc 文件还原成可读的 .js 源码


2. 执行流程

2.1 遍历文件

1
fileDisplay(FILEPATH);
  • 默认 FILEPATH = ./assets
  • 递归调用 fileDisplay(filePath)
  • 遍历所有子目录,找到 .jsc 文件后调用 xxteaDecode

2.2 解密逻辑

1
var res = xxtea.decrypt(data, xxtea.toBytes(KEY))
  • .jsc 文件是 Cocos Creator 编译后对 .js 文件加密的产物。

  • KEY 必须和 Cocos Creator 工程里配置的一致:

    1
    var KEY = "cd2d90bd-ad59-40"
  • 如果 KEY 错误,会导致:

    1
    res == null

    并输出 解密失败


2.3 解压逻辑

1
2
3
4
if (UNZIP) {
console.log("开始解压", filename)
res = pako.inflate(res)
}
  • Cocos Creator 默认会对 .jsc 文件先 压缩加密

  • 所以正确流程是:

    1. 先解密
    2. 再解压
  • 如果 UNZIPfalse,就直接得到解密但未解压的源码。


2.4 写入新文件

1
2
var newName = filename + ".js"
fs.writeFile(newName, res, function(error){ ... })
  • .jsc 文件同目录下生成一个 .jsc.js 文件。

  • 比如:

    1
    assets/src/main.jsc   →   assets/src/main.jsc.js

那么根据脚本我们需要在

1
2
var FILEPATH = path.resolve('./assets');
var KEY = "cd2d90bd-ad59-40" //cocoscreator 的 工程加密key

当前目录新建一个assets的文件夹,并打开decode.js里面的KEY,填入我们逆向后的KEY
接下来我们执行

1
node decode.js

会出现以下命令行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PS F:\Reverse Tool\cocoscreatorjscdecrypt> node .\decode.js
F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.feeb8.jsc
F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.09c80.jsc
F:\Reverse Tool\cocoscreatorjscdecrypt\assets\cocos2d-jsb.d6486.jsc
F:\Reverse Tool\cocoscreatorjscdecrypt\assets\settings.a6099.jsc
F:\Reverse Tool\cocoscreatorjscdecrypt\assets\physics.37b5c.jsc
开始解压 F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.feeb8.jsc
开始解压 F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.09c80.jsc
开始解压 F:\Reverse Tool\cocoscreatorjscdecrypt\assets\physics.37b5c.jsc
开始解压 F:\Reverse Tool\cocoscreatorjscdecrypt\assets\settings.a6099.jsc
开始解压 F:\Reverse Tool\cocoscreatorjscdecrypt\assets\cocos2d-jsb.d6486.jsc
写入完毕: F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.09c80.jsc.js
写入完毕: F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.feeb8.jsc.js
写入完毕: F:\Reverse Tool\cocoscreatorjscdecrypt\assets\settings.a6099.jsc.js
写入完毕: F:\Reverse Tool\cocoscreatorjscdecrypt\assets\physics.37b5c.jsc.js
写入完毕: F:\Reverse Tool\cocoscreatorjscdecrypt\assets\cocos2d-jsb.d6486.jsc.js
PS F:\Reverse Tool\cocoscreatorjscdecrypt>

506736571

之后我们打开asstes就会看到我们解密好的js文件
852546917


然后第三步 对还原后的 JS 进行修改与数据调整

我们之前了解到,这个游戏判断

  1. 👀 视觉进度条:被发现时上升,满格判定失败
  2. 🔊 声音进度条:触摸时上升,满格判定失败
  3. ❤️ 爱心进度条:互动时上升,满格则通关

那么我们的目标就是

  1. 视觉进度条:被发现时上升,满格则无效化(不触发事件)
  2. 声音进度条:触摸时上升,满格则无效化(不触发事件)
  3. 爱心进度条:互动时上升,满格则通关(照旧)
  4. 爱心进度条:互动时上升速度加快,以及可全自动上升

修改数据

我们找到改游戏的具体业务逻辑:index.feeb8.jsc.js
在这个解密后的 .js 文件中,游戏的核心逻辑主要在 update() 方法里:
例子:

1
2
3
4
5
6
7
8
9
10
if (this.core.lookValue > 100) {
this.core.lookValue = 100;
this.core.gameOver(false); // 失败
}

if (this.core.feelValue > 99.5) {
this.core.feelValue = 100;
this.core.gameOver(true); // 胜利
}

在index.feeb8.jsc.js里面找到

1
2
e.prototype.update = function (t) {
var e = this;

开头几行之后插入以下内容

1
2
3
4
5
6
7
8
if (!this.gameover && this.core.feelValue < 100) {
this.core.feelValue += 20 * t; // 每秒上涨20
if (this.core.feelValue >= 100) {
this.core.feelValue = 100;
this.playStatus = 2;
this.core.gameOver(true); // 触发成功通关
}
}

但是,你会发现,游戏无法结算了???
问题不big!
我们只需要再动点小手术:
cc.sys.localStorage.setItem("Level", "x") → 用于记录解锁进度;
还有可能 scheduleOnce 延迟回调,用于弹出结算、解锁动画等。
在多个 feelValue 满了 的地方都有类似处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
this.core.feelValue = 100;
this.playStatus = 2;
this.hand.active = !1;
this.faceAnim.play("lv1h");
this.soundUtil.stopEffectById(this.cxAudioPlayID);
this.soundUtil.playcxEffect(5);
this.eye.active = !1;
this.eyeActive = !1;

parseInt(cc.sys.localStorage.getItem("Level")) < 2 &&
cc.sys.localStorage.setItem("Level", "2");

this.scheduleOnce(function () {
e.core.reduceFeelValue = !0;
e.soundUtil.playHighEffecr();
}, 1);

this.scheduleOnce(function () {
e.core.isWatchMode ? e.initItems() : e.core.gameOver(!0);
}, 5);

然后我们就直接抄!别管这么多,3721就是抄!
80!80!80!
只需要在自动上涨成功时,模仿正常通关的逻辑

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
if (!this.gameover && this.core.feelValue < 100) {
this.core.feelValue += 20 * t;
if (this.core.feelValue >= 100) {
this.core.feelValue = 100;
this.playStatus = 2;
this.gameover = true;

if (parseInt(cc.sys.localStorage.getItem("Level")) < 2) {
cc.sys.localStorage.setItem("Level", "2");
}

this.hand && (this.hand.active = false);
this.eye && (this.eye.active = false);
this.eyeActive = false;
this.faceAnim && this.faceAnim.play("lv1h");
this.soundUtil.stopEffectById(this.cxAudioPlayID);
this.soundUtil.playcxEffect(5);

this.scheduleOnce(() => {
this.core.reduceFeelValue = true;
this.soundUtil.playHighEffecr();
}, 1);

this.scheduleOnce(() => {
this.core.isWatchMode ? this.initItems() : this.core.gameOver(true);
}, 5);
}
}

但是里面是还有个画廊,和三个问号的隐藏关卡?
我测你牛魔
正常游戏流程中存在如下语句

1
2
3
cc.sys.localStorage.setItem("E1", "1");
cc.sys.localStorage.setItem("E2", "2");
cc.sys.localStorage.setItem("E3", "4");

要解锁怎么办?
V50KFC即可解锁后续
仍然在 update 函数中插入这段代码即可:

1
2
3
4
5
6
7
8
cc.sys.localStorage.setItem("Level", "999");
cc.sys.localStorage.setItem("E1", "7");
cc.sys.localStorage.setItem("E2", "7");
cc.sys.localStorage.setItem("E3", "7");
cc.sys.localStorage.setItem("E4", "7");
cc.sys.localStorage.setItem("E5", "7");
cc.sys.localStorage.setItem("E6", "7");

有些特殊情况会出现四个update函数

如果你不知道,对不上,就全部注入一遍
而我这里则是

1
2
3
4
5
e.prototype.update = function (t) {
var e = this;
if ((1 === this.core.gameStatus || 4 === this.core.gameStatus) && 3 !== this.playStatus) {
...

这个主控函数
要是还是不结算?
那我也没招了
628141480

最后第四步,重新封包 JSC 并打包回 APK

我们找到crack.js
打开这个文件,还是依旧,修改KEY

1
2
3
4
5
6
7
8
9
10
const md5File = require('md5-file')

var fs = require("fs");
var path = require("path")
var pako = require("pako")
var xxtea = require("xxtea-node");

var FILEPATH = path.resolve('./assets');
var KEY = "cd2d90bd-ad59-40" //cocoscreator 的 工程加密key
var UNZIP = true //是否启用压缩

修改完成后,我们执行

1
node .\crack.js e

我们可以得到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PS F:\Reverse Tool\cocoscreatorjscdecrypt> node .\crack.js e
invalid F:\Reverse Tool\cocoscreatorjscdecrypt\assets\cocos2d-jsb.d6486.jsc
F:\Reverse Tool\cocoscreatorjscdecrypt\assets\cocos2d-jsb.d6486.jsc.js 加密
开始压缩 F:\Reverse Tool\cocoscreatorjscdecrypt\assets\cocos2d-jsb.d6486.jsc.js
写入完毕: F:\Reverse Tool\cocoscreatorjscdecrypt\assets\cocos2d-jsb.d6486.jsc.jsc
invalid F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.09c80.jsc
F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.09c80.jsc.js 加密
开始压缩 F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.09c80.jsc.js
写入完毕: F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.09c80.jsc.jsc
invalid F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.feeb8.jsc
F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.feeb8.jsc.js 加密
开始压缩 F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.feeb8.jsc.js
写入完毕: F:\Reverse Tool\cocoscreatorjscdecrypt\assets\index.feeb8.jsc.jsc
invalid F:\Reverse Tool\cocoscreatorjscdecrypt\assets\physics.37b5c.jsc
F:\Reverse Tool\cocoscreatorjscdecrypt\assets\physics.37b5c.jsc.js 加密
开始压缩 F:\Reverse Tool\cocoscreatorjscdecrypt\assets\physics.37b5c.jsc.js
写入完毕: F:\Reverse Tool\cocoscreatorjscdecrypt\assets\physics.37b5c.jsc.jsc
invalid F:\Reverse Tool\cocoscreatorjscdecrypt\assets\settings.a6099.jsc
F:\Reverse Tool\cocoscreatorjscdecrypt\assets\settings.a6099.jsc.js 加密
开始压缩 F:\Reverse Tool\cocoscreatorjscdecrypt\assets\settings.a6099.jsc.js
写入完毕: F:\Reverse Tool\cocoscreatorjscdecrypt\assets\settings.a6099.jsc.jsc
PS F:\Reverse Tool\cocoscreatorjscdecrypt>

750925590
这样我们就成功打包回去了~

结语

最后我们能看到后缀名为.jsc.jsc这个就是封回去的~
我们只需要根据apk源目录替换回去即可正常使用

在研究过程中,我们学会了很多,对 Cocos开发的游戏结构的理解,也能积累 逆向分析与调试技巧。
⚠️ 但必须强调:逆向与修改只能用于学习研究,禁止商业用途与非法传播。
要不然…
你进去了我就会
239131391