最近在看一个Cocos Creator 类型游戏的逻辑时,遇到了游戏界某厂商的加固(到处都是这家的身影,懂的都懂),看了下应该是免费版,还是比较简单的。
1. 原理
Cocos Creator 是一个游戏引擎。使用 JavaScript/TypeScript 写游戏逻辑,引擎负责渲染、物理、事件。在代码打包时通过选择某加固,就会将main.js加密,在运行时解密交由V8引擎执行。
2. 判断
解压apk后,在assets中找到main.js,这个是游戏运行时的主要代码逻辑文件。

通过运行
xxd apk/extracted/assets/main.js | head -1
可以很清晰看到某加固厂商的标识。
6e 65 74 65 61 73 65
3. 分析
3.1 V8 引擎是怎么执行JS代码的
V8 执行一段 JS 源码,分三步:
拿到源码字符串(一段文本,比如 "function foo(){...}")。
编译:V8 把源码解析、编译成字节码 bytecode
执行字节码
第 2 步编译完成后,第 1 步那段"源码文本"就没用了,会被释放掉——V8 只留字节码。所以通过dump内存是不可行(已踩坑)。通过查看V8引擎的源码,发现V8 提供了一个接口来接收"源码文本":
v8::String::NewExternalOneByte(Isolate*, ExternalOneByteStringResource* resource)
3.2 需要注意的点
这个点需要在源码"被交给 V8、还没编译"的那一刻拦截。而这一刻发生在启动后 1~2 秒内,所以必须冷启动(让进程挂起启动、先装好钩子再放行)
4. 破解
4.1 环境与工具
环境: Ubuntu 22.04
工具: Florida 17.10.1(frida去特征版)
手机:Pixel 6 自编译AOSP 15
4.2 找目标函数的符号名
v8 引擎是在 libcocos.so,所以先找到对应的符号名
readelf -W --dyn-syms libcocos.so | grep NewExternalOneByte

4.3 HOOK脚本
// 输出到 App 私有目录
var OUT = '/data/data/xx.xx.xx/js_all.txt';
var f = null, cnt = 0, installed = false;
function openFile(){ if(!f) f = new File(OUT, 'wb'); }
// 只保留"register/function/exports"的字符串,过滤噪声
function dumpIf(s, tag){
if (s && s.length > 1200 && (s.indexOf('register')>=0 || s.indexOf('function')>=0 || s.indexOf('exports')>=0)){
openFile();
f.write('\n=====' + tag + '#' + (cnt++) + ' len=' + s.length + '=====\n');
f.write(s); f.flush();
}
}
function exp(mod, name){
try { return Process.getModuleByName(mod).getExportByName(name); } catch(e){}
try { return Module.getGlobalExportByName(name); } catch(e){}
return null;
}
function install(){
if (installed) return true;
var oneByte = exp('libcocos.so', '_ZN2v86String18NewExternalOneByteEPNS_7IsolateEPNS0_29ExternalOneByteStringResourceE');
if (!oneByte) return false; // 库没加载/符号没解析到 → 先返回,等 dlopen 回调再试
Interceptor.attach(oneByte, { onEnter: function(a){
try {
// a[1] = 第2个参数 = ExternalOneByteStringResource* 对象指针
// 该对象内存布局: [+0 vtable指针][+8 data指针][+16 length]
var res = a[1];
var dataPtr = res.add(Process.pointerSize).readPointer();
var len = res.add(Process.pointerSize*2).readU64().toNumber();
var s = (len>0 && len<30000000) ? dataPtr.readUtf8String(len) : dataPtr.readCString();
dumpIf(s, 'EXT1'); // 拿到明文
} catch(e){}
}});
installed = true;
send('[*] V8 string hook installed');
return true;
}
if (!install()){
var dl = exp('libc.so', 'android_dlopen_ext') || exp('libdl.so', 'android_dlopen_ext');
if (dl){
Interceptor.attach(dl, {
onEnter: function(a){ try { this.p = a[0].readCString(); } catch(e){} },
onLeave: function(){ if (this.p && this.p.indexOf('libcocos')>=0){ install(); } }
});
send('[*] waiting for libcocos via dlopen');
} else send('[!] no dlopen');
}4.4 导出代码


至此整个代码已被解密,可分析逻辑。
[招生]科锐逆向工程师培训(2026年7月3日实地,远程教学同时开班, 第56期)!