文章

修改一下自己玩的音游.

修改一下自己玩的音游.

先前咱就在玩 Hexa Hysteria 的,然后是死了不知道多久的制作组更新了个不知道有什么更新内容的小版本,于是进入游戏提示要去 play 更新(不让登录)。

唔的咱玩的本来就是 无限钥匙+付费曲目免费游玩 的 super 破解版,咋更新?

于是想了一下,正好当天凌晨搞了一下 vpn Android, 干脆把这个登录界面的 version check 干掉,直接登录就好了。那一部分不知道什么鬼的更新内容,就不要了。

于是洛铃打算如法炮制,反编译,再去阅读混淆后的代码。

但是 Hexa Hysteria 是用 unity 制作的,核心代码都在 libil2cpp.so 于是… IDA,启动!!!

其实一开始没有启动,先是尝试了一下更改版本为高版本,例如 99.12.1.4 ,以及别的什么东西,发现单纯改版本好像也不行。而且签名会覆盖,再考虑到自己不会更新,于是把版本改成了 code = 99xxxxx 和 name = Final,包名 com.in.kawaiis.W???.HexaHysteria

然后发现不能改 app id,会出错,改了版本也容易卡住。

首先咱是先按部就班下载了 Il2CppDumper , 一个专门解 unity il2cpp的程序(Perfare 真乃神人也!!!)。幸运的是,这没有被加密,很顺利地拿到了大部分的方法和字段的真实名称,再将 libil2cpp.so 给到 ida,用脚本的方式载入 ida_with_struct_py3.py 并 将导出的 script.jsonil2cpp.h 选择输入进去,在漫长等待后得到了一个可读性较高的数值。

屏幕截图 2026-06-02 120912

首先从字符串常量开始找:

屏幕截图 2026-06-02 120912

锁定关键词 StartMenu

根据关键词继续找:

屏幕截图 2026-06-02 132151

CheckGameVersion 大抵是这个,还有 VersionNeedUpdatefillVersionCode 还有 VersionToInt (WC为什么不读取versionCode读versionName呀wc,难怪这里“final”或巨大版本转int直接卡组…)

于是找到了 CheckGameVersion 中的逻辑

屏幕截图 2026-06-02 141433

便把两个参数再改成一样的了。


avatar

那么关于GameVersion的部分呢?特别是VersionNeedUpdate

ChatGPT Avatar </a> 那段代码里其实没有 GameVersion 或 VersionNeedUpdate 的逻辑——你贴给我的整个函数都是 ConfirmUserSyncDataAsync 的状态机,处理的是用户登录后的存档同步流程,跟版本检查无关。

你说的 GameVersionVersionNeedUpdate 应该是另一个函数。如果你想让我分析,把对应的 ARM 汇编给我就行。

不过既然你提了,我猜大概长这样:

1
2
3
4
5
6
7
// 大概率是个类似这样的方法
public static bool VersionNeedUpdate()
{
   var currentVersion = GameVersion.Current;       // 当前版本号
   var latestVersion = GetLatestVersionFromServer(); // 从服务器拉
   return latestVersion > currentVersion;
}

或者更具体一点,结合 Unity IL2CPP 的常见写法,可能是在启动时检查:

1
2
3
4
5
OnlineFunctionalityManager.Start()
 → 请求服务器 /api/version
 → 拿到最新版本号
 → 跟本地 GameVersion 对比
 → 如果需要更新 → 弹更新提示 / 跳商店

把你手上 GameVersionVersionNeedUpdate 的那段 ARM 发过来,我给你反编译成 C#。

avatar il2cpp:0000000002365A34 ; try { ; method il2cpp:0000000002365A34 MOV X0, XZR il2cpp:0000000002365A38 BL UnityEngine.Application\(get_version il2cpp:0000000002365A38 ; } // starts at 2365A34 il2cpp:0000000002365A3C CBZ X28, loc_2365EF4 il2cpp:0000000002365A40 ; try { il2cpp:0000000002365A40 MOV X1, X0 ; localVersionString il2cpp:0000000002365A44 MOV X2, X20 ; latestVersionString il2cpp:0000000002365A48 BL HexaHysteria.UI.StartMenu.StartMenuOnlineFunctionality\)VersionNeedUpdate il2cpp:0000000002365A48 ; } // starts at 2365A40 —

…省略少许调教


ChatGPT Avatar </a>

直接说结论:中间可以 NOP 掉一部分,但你原本改的 CSET → MOV W0, #0 还是得留着,否则返回值不可控。

哪些能 NOP,哪些不能

; === 序言(不能动)===
STR  X20, [SP,#var_20]!         ; 分配栈帧 + 保存 X20
STP  X19, X30, [SP,#0x20+var_10] ; 保存 X19 和返回地址
; ===================

; === 中间(可以全 NOP)===
MOV  X0, X1                     ; 可以 NOP
MOV  X19, X2                    ; 可以 NOP
BL   fillVersionCode_0_0        ; 可以 NOP
MOV  X20, X0                    ; 可以 NOP
MOV  X0, X19                    ; 可以 NOP
BL   fillVersionCode_0_0        ; 可以 NOP
MOV  X19, X0                    ; 可以 NOP
MOV  X0, X20                    ; 可以 NOP
MOV  X1, XZR                    ; 可以 NOP
BL   VersionToInt               ; 可以 NOP
MOV  W20, W0                    ; 可以 NOP
MOV  X0, X19                    ; 可以 NOP
MOV  X1, XZR                    ; 可以 NOP
BL   VersionToInt               ; 可以 NOP
; ===================

; === 结尾(不能全 NOP,必须保留寄存器恢复和返回)===
LDP  X19, X30, [SP]             ; ← 不能 NOP!否则崩溃
CMP  W20, W0                    ; 可以 NOP(因为下面直接 MOV W0, #0)
CSET W0, LT                     ; ← 改成 MOV W0, #0
LDR  X20, [SP], #0x20           ; ← 不能 NOP!否则栈乱掉
RET                             ; ← 不能 NOP!

修改建议

两个都改,双重保险:

地址改前改后
0x2364A1CCSET W0, LT (1A 9F B7 E0)MOV W0, #0 (00 00 80 52)
中间任意位置全部 NOP (1F 20 03 D5)全部 NOP

NOP 指令是 1F 20 03 D5(AArch64 的 nop),4 字节,跟原来所有指令长度一致,直接覆盖就行。

NOP 掉中间的好处

看着舒服,一眼就知道这函数不会干活了:

STR  X20, [SP,#var_20]!
STP  X19, X30, [SP,#0x20+var_10]
NOP
NOP
NOP            ; 全是 NOP
NOP
...            ; 一直 NOP 到 BL VersionToInt 都 NOP 掉
LDP  X19, X30, [SP]
NOP            ; CMP 也 NOP 掉
MOV  W0, #0    ; 直接返回 0
LDR  X20, [SP], #0x20
RET

简单粗暴,IDA 里 Ctrl+F2 → Assemble 一个个改就行。

然后又进入 VersionNeedUpdate,全部改了 NOP,返回个 0


但不知道为何也许是因为 arm, assemble用不了要用 edit bytes。。

屏幕截图 2026-06-02 143959

保存后自己签个名,测试,幸运的是即使库被修改但是没有校验。

image

然后,启动!

image

image

本文由作者按照 CC BY 4.0 进行授权

© 洛铃. 保留部分权利。

本站总访问量 本站访客数人次

🚩🚩🚩🚩🚩🚩