首页
社区
课程
招聘
[原创] Cocos2dlua手游 Lua解密与资源解密实战
发表于: 2021-7-24 22:17 29047

[原创] Cocos2dlua手游 Lua解密与资源解密实战

2021-7-24 22:17
29047

发过理论篇浅谈Cocos2d-x下Lua文件的保护方式 – 翻车鱼 (blog.shi1011.cn),是时候来实战一番了。

起因

一位不知是港澳台哪地的师傅,加了我的QQ

image-20210724155648914

于是我和他进行了一场漫长的探讨

尽管轮子在手,但是依旧无法反编译出.lua(因为加了一层自定义加密)

结果就是:喜提样本一份

大致结构如下

其中.lua64为标准LuaJit文件

.lua的文件头有点奇怪

image-20210724163218589

.lua文件头:abcd

再看资源文件.png,发现也是加密的

image-20210724163414596

综上,我们需要实现的目标

这个不多介绍了窝,在理论篇中已经提到了。对于此样本的LuaJit的版本是2.1.0-Beta2,并且没有魔改Opcode

想要解密.lua文件,了解coco2d-x加载Lua的流程必不可少

参考官方Docs,搭建所需环境

image-20210724163932820

由于Android的应用层是从Activity开始的,也就是创建完一个Cocos2dx后src文件夹下的Java文件,其中主要看Activity创建时的操作

这个方法很简单,就只调用了父类Cocos2dxActivityonCreate方法

在这个方法中主要看Cocos2dxActivity的初始化方法init,Cocos2dxHandler是工具辅助类,不是重点。

首先在init中先看this.mGLSurfaceView = this.onCreateView();this.mGLSurfaceView是一个Cocos2dxGLSurfaceView

进入到Cocos2dxGLSurfaceView这个类,可以看到时继承于GLSurfaceView(可以把GLSurfaceView看成一个视图,里面有个方法设置了这个视图的渲染器,然后通过这个渲染器来进行画面的渲染)
在Android中,GLSurfaceView是一个支持OpenGL的渲染视图,通过继承SurfaceView中的surface来渲染OpenGL
并提供了以下特性(来源于网上)

重点是自定义的渲染器,也就是Cocos2dx引擎封装的渲染器

进入Cocos2dxRenderer类,看到他是继承GLSurfaceView.Renderer接口,这个接口定义了三个方法:

onSurfaceCreated

nativeInit是一个Native函数

具体实现在frameworks\cocos2d-x\cocos\platform\android\javaactivity-android.cpp

重点是cocos2d::Application::getInstance()->run()

applicationDidFinishLaunching,没错这就是游戏逻辑的入口了

LuaEngine::getInstance();这段代码实例化了LuaEngine

下面分析LuaEngine初始化过程

接下来是_defaultEngine->init();

继续进入

下面是stack->init()

重点在cocos2dx_lua_loader方法(这个方法在frameworks\cocos2d-x\cocos\scripting\lua-bindings\manual\Cocos2dxLuaLoader.cpp)

通过此处的代码,可以了解到cocos2d-x是如何搜索指定的lua文件

同时也会明白require为何可以使用 "." 来设定文件路径了,比如:

再来看下stack->luaLoadBuffer的实现:

至此,Lua文件的加载流程结束

下面是Lua文件主要加载流程图

img

了解了Coco2d-x Lua文件基本的加载流程,可以帮助我们很快的定位关键方法

由于所有的Lua文件加载必定经过cocos2d::LuaStack::luaLoadBuffer,所以可以直接定位,进行回溯

定位至cocos2dx_lua_loader

image-20210724202549151

luaLoadBuffer前调用了decodeLuaData,怀疑这就是解密Lua的关键方法

下面是伪代码

此方法首先获取文件数据,而后判断文件头,文件头正是abcd

可以肯定这就是解密.lua的算法

如果不确定,我们可以使用Frida进行Hook验证猜想

我这里就不Hook了

要实现算法的还原,我们就要依据伪代码或是汇编代码翻译成可用代码,可以发现IDA所反汇编的伪代码中包含了一些特定的宏,例如:LOBYTE

要了解这些宏的用途,我们可以参考ida_root/plugins/def.h

我这里使用了Python

解密后,发现还是LuaJit,那么直接类同.lua64反汇编即可

image-20210724204751819

回到Lua文件加载流程分析时提到的LuaStack::init()方法中,调用了register_all_cocos2dx

在大量注册函数中寻找到关键方法lua_register_cocos2dx_Image

lua_cocos2dx_Image_initWithImageFile加载了我们的图片文件

下面看cobj->initWithImageFile

下面是图片数据格式解码方法initWithImageData

图片资源的加载到这里就结束了

img

根据调用流程,我们可以直接定位方法cocos2d::Image::initWithImageData

向上可以追溯到cocos2d::Image::initWithImageFile

image-20210724211445149

看到了可疑函数cocos2d::Image::decodePngData

明显是一个算法函数,看方法cocos2d::decodePng

这里也可以尝试Frida Hook验证猜想

同样的,我就不Hook了

解密结果

image-20210724212037493

写不出总结啦,大概最大的感想就是:对照源码逆向好爽!!!

 
 
 
 
 
 
.
├── assets
│   ├── res
│   │   ├── ani
│   │   │   └── logo
│   │   │       └── logo.png
│   ├── src
│   │   ├── main.lua
│   │   └── main.lua64
├── lib
│   ├── arm64-v8a
│   │   ├── libBugly.so
│   │   └── libcocos2dlua.so
│   └── armeabi-v7a
│       ├── libBugly.so
│       └── libcocos2dlua.so
└── stamp-cert-sha256
.
├── assets
│   ├── res
│   │   ├── ani
│   │   │   └── logo
│   │   │       └── logo.png
│   ├── src
│   │   ├── main.lua
│   │   └── main.lua64
├── lib
│   ├── arm64-v8a
│   │   ├── libBugly.so
│   │   └── libcocos2dlua.so
│   └── armeabi-v7a
│       ├── libBugly.so
│       └── libcocos2dlua.so
└── stamp-cert-sha256
 
 
 
 
 
 
 
package org.cocos2dx.lua;
 
import android.os.Bundle;
import org.cocos2dx.lib.Cocos2dxActivity;
 
public class AppActivity extends Cocos2dxActivity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.setEnableVirtualButton(false);
        super.onCreate(savedInstanceState);
        // Workaround in https://stackoverflow.com/questions/16283079/re-launch-of-activity-on-home-button-but-only-the-first-time/16447508
        if (!isTaskRoot()) {
            // Android launched another instance of the root activity into an existing task
            //  so just quietly finish and go away, dropping the user back into the activity
            //  at the top of the stack (ie: the last state of this task)
            // Don't need to finish it again since it's finished in super.onCreate .
            return;
        }
 
        // DO OTHER INITIALIZATION BELOW
 
    }
}
package org.cocos2dx.lua;
 
import android.os.Bundle;
import org.cocos2dx.lib.Cocos2dxActivity;
 
public class AppActivity extends Cocos2dxActivity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.setEnableVirtualButton(false);
        super.onCreate(savedInstanceState);
        // Workaround in https://stackoverflow.com/questions/16283079/re-launch-of-activity-on-home-button-but-only-the-first-time/16447508
        if (!isTaskRoot()) {
            // Android launched another instance of the root activity into an existing task
            //  so just quietly finish and go away, dropping the user back into the activity
            //  at the top of the stack (ie: the last state of this task)
            // Don't need to finish it again since it's finished in super.onCreate .
            return;
        }
 
        // DO OTHER INITIALIZATION BELOW
 
    }
}
@Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        // Workaround in https://stackoverflow.com/questions/16283079/re-launch-of-activity-on-home-button-but-only-the-first-time/16447508
        if (!isTaskRoot()) {
            // Android launched another instance of the root activity into an existing task
            //  so just quietly finish and go away, dropping the user back into the activity
            //  at the top of the stack (ie: the last state of this task)
            finish();
            Log.w(TAG, "[Workaround] Ignore the activity started from icon!");
            return;
        }
 
        this.hideVirtualButton();
 
        onLoadNativeLibraries();
 
        sContext = this;
        this.mHandler = new Cocos2dxHandler(this);
 
        Cocos2dxHelper.init(this);
 
        this.mGLContextAttrs = getGLContextAttrs();
        this.init();
 
        if (mVideoHelper == null) {
            mVideoHelper = new Cocos2dxVideoHelper(this, mFrameLayout);
        }
 
        if(mWebViewHelper == null){
            mWebViewHelper = new Cocos2dxWebViewHelper(mFrameLayout);
        }
 
        if(mEditBoxHelper == null){
            mEditBoxHelper = new Cocos2dxEditBoxHelper(mFrameLayout);
        }
 
        Window window = this.getWindow();
        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
 
        // Audio configuration
        this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
    }
@Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
 
        // Workaround in https://stackoverflow.com/questions/16283079/re-launch-of-activity-on-home-button-but-only-the-first-time/16447508
        if (!isTaskRoot()) {
            // Android launched another instance of the root activity into an existing task
            //  so just quietly finish and go away, dropping the user back into the activity
            //  at the top of the stack (ie: the last state of this task)
            finish();
            Log.w(TAG, "[Workaround] Ignore the activity started from icon!");
            return;
        }
 
        this.hideVirtualButton();
 
        onLoadNativeLibraries();
 
        sContext = this;
        this.mHandler = new Cocos2dxHandler(this);
 
        Cocos2dxHelper.init(this);
 
        this.mGLContextAttrs = getGLContextAttrs();
        this.init();
 
        if (mVideoHelper == null) {
            mVideoHelper = new Cocos2dxVideoHelper(this, mFrameLayout);
        }
 
        if(mWebViewHelper == null){
            mWebViewHelper = new Cocos2dxWebViewHelper(mFrameLayout);
        }
 
        if(mEditBoxHelper == null){
            mEditBoxHelper = new Cocos2dxEditBoxHelper(mFrameLayout);
        }
 
        Window window = this.getWindow();
        window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN);
 
        // Audio configuration
        this.setVolumeControlStream(AudioManager.STREAM_MUSIC);
    }
public Cocos2dxGLSurfaceView onCreateView() {
        Cocos2dxGLSurfaceView glSurfaceView = new Cocos2dxGLSurfaceView(this);
        //this line is need on some device if we specify an alpha bits
        // FIXME: is it needed? And it will cause afterimage.
        // if(this.mGLContextAttrs[3] > 0) glSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
 
        // use custom EGLConfigureChooser
        Cocos2dxEGLConfigChooser chooser = new Cocos2dxEGLConfigChooser(this.mGLContextAttrs);
        glSurfaceView.setEGLConfigChooser(chooser);
 
        return glSurfaceView;
    }
// ......
public void init() {
 
        // FrameLayout 初始化窗口布局
        ViewGroup.LayoutParams framelayout_params =
            new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                                       ViewGroup.LayoutParams.MATCH_PARENT);
 
        mFrameLayout = new ResizeLayout(this);
 
        mFrameLayout.setLayoutParams(framelayout_params);
 
        // Cocos2dxEditText layout 初始化Cocos2dx的文本编辑布局
        ViewGroup.LayoutParams edittext_layout_params =
            new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                                       ViewGroup.LayoutParams.WRAP_CONTENT);
        Cocos2dxEditBox edittext = new Cocos2dxEditBox(this);
        edittext.setLayoutParams(edittext_layout_params);
 
 
        mFrameLayout.addView(edittext);
 
        // Cocos2dxGLSurfaceView 初始化Cocos2dx视图
        this.mGLSurfaceView = this.onCreateView();
 
        // ...add to FrameLayout 将Cocos2dxGLSurfaceView加入到当前的窗口布局中
        mFrameLayout.addView(this.mGLSurfaceView);
 
        // Switch to supported OpenGL (ARGB888) mode on emulator
        // this line dows not needed on new emulators and also it breaks stencil buffer
        //if (isAndroidEmulator()) // 在模拟器中切换支持OpenGL模式的渲染(ARGB888)
        //   this.mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
 
        // 设置Cocos2dx的渲染器
        this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer());
        //设置Cocos2dx的文本编辑
        this.mGLSurfaceView.setCocos2dxEditText(edittext);
 
        // Set framelayout as the content view
        // 把显示布局(即Cocos2dx的视图)绑定到Activity上,建立显示窗口
        setContentView(mFrameLayout);
    }
public Cocos2dxGLSurfaceView onCreateView() {
        Cocos2dxGLSurfaceView glSurfaceView = new Cocos2dxGLSurfaceView(this);
        //this line is need on some device if we specify an alpha bits
        // FIXME: is it needed? And it will cause afterimage.
        // if(this.mGLContextAttrs[3] > 0) glSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
 
        // use custom EGLConfigureChooser
        Cocos2dxEGLConfigChooser chooser = new Cocos2dxEGLConfigChooser(this.mGLContextAttrs);
        glSurfaceView.setEGLConfigChooser(chooser);
 
        return glSurfaceView;
    }
// ......
public void init() {
 
        // FrameLayout 初始化窗口布局
        ViewGroup.LayoutParams framelayout_params =
            new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                                       ViewGroup.LayoutParams.MATCH_PARENT);
 
        mFrameLayout = new ResizeLayout(this);
 
        mFrameLayout.setLayoutParams(framelayout_params);
 
        // Cocos2dxEditText layout 初始化Cocos2dx的文本编辑布局
        ViewGroup.LayoutParams edittext_layout_params =
            new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                                       ViewGroup.LayoutParams.WRAP_CONTENT);
        Cocos2dxEditBox edittext = new Cocos2dxEditBox(this);
        edittext.setLayoutParams(edittext_layout_params);
 
 
        mFrameLayout.addView(edittext);
 
        // Cocos2dxGLSurfaceView 初始化Cocos2dx视图
        this.mGLSurfaceView = this.onCreateView();
 
        // ...add to FrameLayout 将Cocos2dxGLSurfaceView加入到当前的窗口布局中
        mFrameLayout.addView(this.mGLSurfaceView);
 
        // Switch to supported OpenGL (ARGB888) mode on emulator
        // this line dows not needed on new emulators and also it breaks stencil buffer
        //if (isAndroidEmulator()) // 在模拟器中切换支持OpenGL模式的渲染(ARGB888)
        //   this.mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
 
        // 设置Cocos2dx的渲染器
        this.mGLSurfaceView.setCocos2dxRenderer(new Cocos2dxRenderer());
        //设置Cocos2dx的文本编辑
        this.mGLSurfaceView.setCocos2dxEditText(edittext);
 
        // Set framelayout as the content view
        // 把显示布局(即Cocos2dx的视图)绑定到Activity上,建立显示窗口
        setContentView(mFrameLayout);
    }
 
 
@Override
    public void onSurfaceCreated(final GL10 GL10, final EGLConfig EGLConfig) {
        Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
        this.mLastTickInNanoSeconds = System.nanoTime();
        mNativeInitCompleted = true;
    }
@Override
    public void onSurfaceCreated(final GL10 GL10, final EGLConfig EGLConfig) {
        Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
        this.mLastTickInNanoSeconds = System.nanoTime();
        mNativeInitCompleted = true;
    }
private static native void nativeInit(final int width, final int height);
private static native void nativeInit(final int width, final int height);
JNIEXPORT void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv*  env, jobject thiz, jint w, jint h)
{
    auto director = cocos2d::Director::getInstance();
    auto glview = director->getOpenGLView();
    if (!glview)
    {
        glview = cocos2d::GLViewImpl::create("Android app");
        glview->setFrameSize(w, h);
        director->setOpenGLView(glview);
 
        cocos2d::Application::getInstance()->run();
    }
    else
    {
        cocos2d::Director::getInstance()->resetMatrixStack();
        cocos2d::EventCustom recreatedEvent(EVENT_RENDERER_RECREATED);
        director->getEventDispatcher()->dispatchEvent(&recreatedEvent);
        director->setGLDefaultValues();
        cocos2d::VolatileTextureMgr::reloadAllTextures();
    }
    cocos2d::network::_preloadJavaDownloaderClass();
}
JNIEXPORT void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv*  env, jobject thiz, jint w, jint h)
{
    auto director = cocos2d::Director::getInstance();
    auto glview = director->getOpenGLView();
    if (!glview)
    {
        glview = cocos2d::GLViewImpl::create("Android app");
        glview->setFrameSize(w, h);
        director->setOpenGLView(glview);
 
        cocos2d::Application::getInstance()->run();
    }
    else
    {
        cocos2d::Director::getInstance()->resetMatrixStack();
        cocos2d::EventCustom recreatedEvent(EVENT_RENDERER_RECREATED);
        director->getEventDispatcher()->dispatchEvent(&recreatedEvent);
        director->setGLDefaultValues();
        cocos2d::VolatileTextureMgr::reloadAllTextures();
    }
    cocos2d::network::_preloadJavaDownloaderClass();
}
int Application::run()
{
    // Initialize instance and cocos2d.
    if (! applicationDidFinishLaunching())
    {
        return 0;
    }
 
    return -1;
}
int Application::run()
{
    // Initialize instance and cocos2d.
    if (! applicationDidFinishLaunching())
    {
        return 0;
    }
 
    return -1;
}
bool AppDelegate::applicationDidFinishLaunching()
{
    // set default FPS
    Director::getInstance()->setAnimationInterval(1.0 / 60.0f);
 
    // register lua module
 
      // 初始化 LuaEngine, 在 getInstance 中会初始化 LuaStack, LuaStack 初始化 Lua 环境相关
    auto engine = LuaEngine::getInstance();
    // 将 LuaEngine 添加到脚本引擎管理器 ScriptEngineManager 中
    ScriptEngineManager::getInstance()->setScriptEngine(engine);
    // 获取 Lua 环境
    lua_State* L = engine->getLuaStack()->getLuaState();
    // 注册额外的 C++ API 相关,比如 cocosstudio, spine, audio 相关
    lua_module_register(L);
 
    // 设置 cocos 自带的加密相关
    register_all_packages();
 
    // 在 LuaStack::executeScriptFile 执行脚本文件时,会通过 LuaStack::luaLoadBuffer 对文件进行解密
    LuaStack* stack = engine->getLuaStack();
    stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));
 
    //register custom function
    //LuaStack* stack = engine->getLuaStack();
    //register_custom_function(stack->getLuaState());
 
#if CC_64BITS
    FileUtils::getInstance()->addSearchPath("src/64bit");
#endif
    FileUtils::getInstance()->addSearchPath("src");
    FileUtils::getInstance()->addSearchPath("res");
    if (engine->executeScriptFile("main.lua"))
    {
        return false;
    }
 
    return true;
}
bool AppDelegate::applicationDidFinishLaunching()
{
    // set default FPS
    Director::getInstance()->setAnimationInterval(1.0 / 60.0f);
 
    // register lua module
 
      // 初始化 LuaEngine, 在 getInstance 中会初始化 LuaStack, LuaStack 初始化 Lua 环境相关
    auto engine = LuaEngine::getInstance();
    // 将 LuaEngine 添加到脚本引擎管理器 ScriptEngineManager 中
    ScriptEngineManager::getInstance()->setScriptEngine(engine);
    // 获取 Lua 环境
    lua_State* L = engine->getLuaStack()->getLuaState();
    // 注册额外的 C++ API 相关,比如 cocosstudio, spine, audio 相关
    lua_module_register(L);
 
    // 设置 cocos 自带的加密相关
    register_all_packages();
 
    // 在 LuaStack::executeScriptFile 执行脚本文件时,会通过 LuaStack::luaLoadBuffer 对文件进行解密
    LuaStack* stack = engine->getLuaStack();
    stack->setXXTEAKeyAndSign("2dxLua", strlen("2dxLua"), "XXTEA", strlen("XXTEA"));
 
    //register custom function
    //LuaStack* stack = engine->getLuaStack();
    //register_custom_function(stack->getLuaState());
 
#if CC_64BITS
    FileUtils::getInstance()->addSearchPath("src/64bit");
#endif
    FileUtils::getInstance()->addSearchPath("src");
    FileUtils::getInstance()->addSearchPath("res");
    if (engine->executeScriptFile("main.lua"))
    {
        return false;
    }
 
    return true;
}
 
LuaEngine* LuaEngine::getInstance(void)
{
    if (!_defaultEngine)
    {
        _defaultEngine = new (std::nothrow) LuaEngine();
        _defaultEngine->init();
    }
    return _defaultEngine;
}
LuaEngine* LuaEngine::getInstance(void)
{
    if (!_defaultEngine)
    {
        _defaultEngine = new (std::nothrow) LuaEngine();
        _defaultEngine->init();
    }
    return _defaultEngine;
}
bool LuaEngine::init(void)
{
    _stack = LuaStack::create();
    _stack->retain();
    return true;
}
bool LuaEngine::init(void)
{
    _stack = LuaStack::create();
    _stack->retain();
    return true;
}
LuaStack *LuaStack::create()
{
    LuaStack *stack = new (std::nothrow) LuaStack();
    stack->init();
    stack->autorelease();
    return stack;
}
LuaStack *LuaStack::create()
{
    LuaStack *stack = new (std::nothrow) LuaStack();
    stack->init();
    stack->autorelease();
    return stack;
}
bool LuaStack::init()
{
    // 初始化Lua环境并打开标准库
    _state = lua_open();
    luaL_openlibs(_state);
    toluafix_open(_state);
 
    // Register our version of the global "print" function
    // 注册全局函数print到lua中,它会覆盖lua库中的print方法
    const luaL_Reg global_functions [] = {
        {"print", lua_print},
        {"release_print",lua_release_print},
        {nullptr, nullptr}
    };
    // 注册全局变量
    luaL_register(_state, "_G", global_functions);
 
    // 注册cocos2d-x引擎的API到lua环境中
    g_luaType.clear();
    register_all_cocos2dx(_state);
    register_all_cocos2dx_backend(_state);
    register_all_cocos2dx_manual(_state);
    register_all_cocos2dx_module_manual(_state);
    register_all_cocos2dx_math_manual(_state);
    register_all_cocos2dx_shaders_manual(_state);
    register_all_cocos2dx_bytearray_manual(_state);
 
    tolua_luanode_open(_state);
    register_luanode_manual(_state);
#if CC_USE_PHYSICS
    // 导入使用的physics相关API
    register_all_cocos2dx_physics(_state);
    register_all_cocos2dx_physics_manual(_state);
#endif
 
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
    // 导入ios下调用object-c相关API
    LuaObjcBridge::luaopen_luaoc(_state);
#endif
 
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
    // 导入android下调用java相关API
    LuaJavaBridge::luaopen_luaj(_state);
#endif
    register_all_cocos2dx_deprecated(_state);
    register_all_cocos2dx_manual_deprecated(_state);
 
    tolua_script_handler_mgr_open(_state);
 
    // add cocos2dx loader
    // 添加Lua的加载器,该方法将cocos2dx_lua_loader方法添加到Lua全局变量package下的loaders成员中
   // 当requires加载脚本时,Lua会使用package下的loaders中的加载器,即cocos2dx_lua_loader来加载
    // 设定cocos2dx_lua_loader,可以使得我们自定义设置搜索路径相关,且拓展实现对脚本的加密解密相关
    addLuaLoader(cocos2dx_lua_loader);
 
    return true;
}
bool LuaStack::init()
{
    // 初始化Lua环境并打开标准库
    _state = lua_open();
    luaL_openlibs(_state);
    toluafix_open(_state);
 
    // Register our version of the global "print" function
    // 注册全局函数print到lua中,它会覆盖lua库中的print方法
    const luaL_Reg global_functions [] = {
        {"print", lua_print},
        {"release_print",lua_release_print},
        {nullptr, nullptr}
    };
    // 注册全局变量
    luaL_register(_state, "_G", global_functions);
 
    // 注册cocos2d-x引擎的API到lua环境中
    g_luaType.clear();
    register_all_cocos2dx(_state);
    register_all_cocos2dx_backend(_state);
    register_all_cocos2dx_manual(_state);
    register_all_cocos2dx_module_manual(_state);
    register_all_cocos2dx_math_manual(_state);
    register_all_cocos2dx_shaders_manual(_state);
    register_all_cocos2dx_bytearray_manual(_state);
 
    tolua_luanode_open(_state);
    register_luanode_manual(_state);
#if CC_USE_PHYSICS
    // 导入使用的physics相关API
    register_all_cocos2dx_physics(_state);
    register_all_cocos2dx_physics_manual(_state);
#endif
 
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC)
    // 导入ios下调用object-c相关API
    LuaObjcBridge::luaopen_luaoc(_state);
#endif
 
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
    // 导入android下调用java相关API
    LuaJavaBridge::luaopen_luaj(_state);
#endif
    register_all_cocos2dx_deprecated(_state);
    register_all_cocos2dx_manual_deprecated(_state);
 
    tolua_script_handler_mgr_open(_state);
 
    // add cocos2dx loader
    // 添加Lua的加载器,该方法将cocos2dx_lua_loader方法添加到Lua全局变量package下的loaders成员中
   // 当requires加载脚本时,Lua会使用package下的loaders中的加载器,即cocos2dx_lua_loader来加载
    // 设定cocos2dx_lua_loader,可以使得我们自定义设置搜索路径相关,且拓展实现对脚本的加密解密相关
    addLuaLoader(cocos2dx_lua_loader);
 
    return true;
}
int cocos2dx_lua_loader(lua_State *L)
    {
        // 后缀为luac和lua
        static const std::string BYTECODE_FILE_EXT    = ".luac";
        static const std::string NOT_BYTECODE_FILE_EXT = ".lua";
 
        // require传入的要加载的文件名,例如:require("a.b") 查找文件为:a/b.lua
        std::string filename(luaL_checkstring(L, 1));
        size_t pos = filename.rfind(BYTECODE_FILE_EXT);
         // 去掉后缀名".luac"或“.lua”
        if (pos != std::string::npos && pos == filename.length() - BYTECODE_FILE_EXT.length())
            filename = filename.substr(0, pos);
        else
        {
            pos = filename.rfind(NOT_BYTECODE_FILE_EXT);
            if (pos != std::string::npos && pos == filename.length() - NOT_BYTECODE_FILE_EXT.length())
                filename = filename.substr(0, pos);
        }
 
        // "." 替换为 "/"
        pos = filename.find_first_of('.');
        while (pos != std::string::npos)
        {
            filename.replace(pos, 1, "/");
            pos = filename.find_first_of('.');
        }
 
        // search file in package.path
        Data chunk;
        std::string chunkName;
        FileUtils* utils = FileUtils::getInstance();
 
        // 获取 package.path 的变量
        lua_getglobal(L, "package");
        lua_getfield(L, -1, "path");
        // 通过 package.path 获取搜索路径相关,该路径为模版路径,格式类似于:
        // ?; ?.lua; c:\Users\?;  /usr/local/lua/lua/?/?.lua 以“;”作为分割符
        std::string searchpath(lua_tostring(L, -1));
        lua_pop(L, 1);
        size_t begin = 0;
        size_t next = searchpath.find_first_of(';', 0);
 
        // 遍历 package.path 中的所有路径,查找文件是否存在,若文件存在则通过 getDataFromFile 读取文件数据
        do
        {
            if (next == std::string::npos)
                next = searchpath.length();
            std::string prefix = searchpath.substr(begin, next-begin);
            if (prefix[0] == '.' && prefix[1] == '/')
                prefix = prefix.substr(2);
 
            pos = prefix.rfind(BYTECODE_FILE_EXT);
            if (pos != std::string::npos && pos == prefix.length() - BYTECODE_FILE_EXT.length())
            {
                prefix = prefix.substr(0, pos);
            }
            else
            {
                pos = prefix.rfind(NOT_BYTECODE_FILE_EXT);
                if (pos != std::string::npos && pos == prefix.length() - NOT_BYTECODE_FILE_EXT.length())
                    prefix = prefix.substr(0, pos);
            }
            pos = prefix.find_first_of('?', 0);
            while (pos != std::string::npos)
            {
                prefix.replace(pos, 1, filename);
                pos = prefix.find_first_of('?', pos + filename.length() + 1);
            }
 
            chunkName = prefix + BYTECODE_FILE_EXT;
            if (utils->isFileExist(chunkName)) // && !utils->isDirectoryExist(chunkName))
            {
                chunk = utils->getDataFromFile(chunkName);
                break;
            }
            else
            {
                chunkName = prefix + NOT_BYTECODE_FILE_EXT;
                if (utils->isFileExist(chunkName) ) //&& !utils->isDirectoryExist(chunkName))
                {
                    chunk = utils->getDataFromFile(chunkName);
                    break;
                }
                else
                {
                    chunkName = prefix;
                    if (utils->isFileExist(chunkName)) // && !utils->isDirectoryExist(chunkName))
                    {
                        chunk = utils->getDataFromFile(chunkName);
                        break;
                    }
                }
            }
 
            // 指定搜素路径下不存在该文件, 下一个
            begin = next + 1;
            next = searchpath.find_first_of(';', begin);
        } while (begin < searchpath.length());
        // 判断文件内容是否获取成功
        if (chunk.getSize() > 0)
        {
            // 加载文件
            LuaStack* stack = LuaEngine::getInstance()->getLuaStack();
            stack->luaLoadBuffer(L, reinterpret_cast<const char*>(chunk.getBytes()),
                                 static_cast<int>(chunk.getSize()), chunkName.c_str());
        }
        else
        {
            CCLOG("can not get file data of %s", chunkName.c_str());
            return 0;
        }
 
        return 1;
    }
int cocos2dx_lua_loader(lua_State *L)
    {
        // 后缀为luac和lua
        static const std::string BYTECODE_FILE_EXT    = ".luac";
        static const std::string NOT_BYTECODE_FILE_EXT = ".lua";
 
        // require传入的要加载的文件名,例如:require("a.b") 查找文件为:a/b.lua
        std::string filename(luaL_checkstring(L, 1));
        size_t pos = filename.rfind(BYTECODE_FILE_EXT);
         // 去掉后缀名".luac"或“.lua”
        if (pos != std::string::npos && pos == filename.length() - BYTECODE_FILE_EXT.length())
            filename = filename.substr(0, pos);
        else
        {
            pos = filename.rfind(NOT_BYTECODE_FILE_EXT);
            if (pos != std::string::npos && pos == filename.length() - NOT_BYTECODE_FILE_EXT.length())
                filename = filename.substr(0, pos);
        }
 
        // "." 替换为 "/"
        pos = filename.find_first_of('.');
        while (pos != std::string::npos)
        {
            filename.replace(pos, 1, "/");
            pos = filename.find_first_of('.');
        }
 
        // search file in package.path
        Data chunk;
        std::string chunkName;
        FileUtils* utils = FileUtils::getInstance();
 
        // 获取 package.path 的变量
        lua_getglobal(L, "package");
        lua_getfield(L, -1, "path");
        // 通过 package.path 获取搜索路径相关,该路径为模版路径,格式类似于:
        // ?; ?.lua; c:\Users\?;  /usr/local/lua/lua/?/?.lua 以“;”作为分割符
        std::string searchpath(lua_tostring(L, -1));
        lua_pop(L, 1);
        size_t begin = 0;
        size_t next = searchpath.find_first_of(';', 0);
 
        // 遍历 package.path 中的所有路径,查找文件是否存在,若文件存在则通过 getDataFromFile 读取文件数据
        do
        {
            if (next == std::string::npos)
                next = searchpath.length();
            std::string prefix = searchpath.substr(begin, next-begin);
            if (prefix[0] == '.' && prefix[1] == '/')
                prefix = prefix.substr(2);
 
            pos = prefix.rfind(BYTECODE_FILE_EXT);
            if (pos != std::string::npos && pos == prefix.length() - BYTECODE_FILE_EXT.length())
            {
                prefix = prefix.substr(0, pos);
            }
            else
            {
                pos = prefix.rfind(NOT_BYTECODE_FILE_EXT);
                if (pos != std::string::npos && pos == prefix.length() - NOT_BYTECODE_FILE_EXT.length())
                    prefix = prefix.substr(0, pos);
            }
            pos = prefix.find_first_of('?', 0);
            while (pos != std::string::npos)
            {
                prefix.replace(pos, 1, filename);
                pos = prefix.find_first_of('?', pos + filename.length() + 1);
            }
 
            chunkName = prefix + BYTECODE_FILE_EXT;
            if (utils->isFileExist(chunkName)) // && !utils->isDirectoryExist(chunkName))
            {
                chunk = utils->getDataFromFile(chunkName);
                break;
            }
            else
            {
                chunkName = prefix + NOT_BYTECODE_FILE_EXT;
                if (utils->isFileExist(chunkName) ) //&& !utils->isDirectoryExist(chunkName))
                {
                    chunk = utils->getDataFromFile(chunkName);
                    break;
                }
                else
                {
                    chunkName = prefix;
                    if (utils->isFileExist(chunkName)) // && !utils->isDirectoryExist(chunkName))
                    {
                        chunk = utils->getDataFromFile(chunkName);
                        break;
                    }
                }
            }
 
            // 指定搜素路径下不存在该文件, 下一个
            begin = next + 1;
            next = searchpath.find_first_of(';', begin);
        } while (begin < searchpath.length());
        // 判断文件内容是否获取成功
        if (chunk.getSize() > 0)
        {
            // 加载文件
            LuaStack* stack = LuaEngine::getInstance()->getLuaStack();
            stack->luaLoadBuffer(L, reinterpret_cast<const char*>(chunk.getBytes()),
                                 static_cast<int>(chunk.getSize()), chunkName.c_str());
        }
        else
        {
            CCLOG("can not get file data of %s", chunkName.c_str());
            return 0;
        }
 
        return 1;
    }
 
require "cocos.cocos2d.Cocos2d"
require "cocos.cocos2d.Cocos2dConstants"
require "cocos.cocos2d.functions"
require "cocos.cocos2d.Cocos2d"
require "cocos.cocos2d.Cocos2dConstants"
require "cocos.cocos2d.functions"
int LuaStack::luaLoadBuffer(lua_State *L, const char *chunk, int chunkSize, const char *chunkName)
{
    int r = 0;
 
    // 判断是否加密,若lua脚本加密,则解密后在加载脚本文件
    // luaL_loadbuffer 用于加载并编译Lua代码,并将其压入栈中
    if (_xxteaEnabled && strncmp(chunk, _xxteaSign, _xxteaSignLen) == 0)
    {
        // decrypt XXTEA
        xxtea_long len = 0;
        unsigned char* result = xxtea_decrypt((unsigned char*)chunk + _xxteaSignLen,
                                              (xxtea_long)chunkSize - _xxteaSignLen,
                                              (unsigned char*)_xxteaKey,
                                              (xxtea_long)_xxteaKeyLen,
                                              &len);
        unsigned char* content = result;
        xxtea_long contentSize = len;
        skipBOM((const char*&)content, (int&)contentSize);
        r = luaL_loadbuffer(L, (char*)content, contentSize, chunkName);
        free(result);
    }
    else
    {
        skipBOM(chunk, chunkSize);
        r = luaL_loadbuffer(L, chunk, chunkSize, chunkName);
    }
 
// 判定内容是否存在错误
#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
    if (r)
    {
        switch (r)
        {
            case LUA_ERRSYNTAX:
                // 语法错误
                CCLOG("[LUA ERROR] load \"%s\", error: syntax error during pre-compilation.", chunkName);
                break;
 
            case LUA_ERRMEM:
                // 内存分配错误
                CCLOG("[LUA ERROR] load \"%s\", error: memory allocation error.", chunkName);
                break;
 
            case LUA_ERRFILE:
                 // 文件错误
                CCLOG("[LUA ERROR] load \"%s\", error: cannot open/read file.", chunkName);
                break;
 
            default:
                // 未知错误
                CCLOG("[LUA ERROR] load \"%s\", error: unknown.", chunkName);
        }
    }
#endif
    return r;
}
int LuaStack::luaLoadBuffer(lua_State *L, const char *chunk, int chunkSize, const char *chunkName)
{
    int r = 0;
 
    // 判断是否加密,若lua脚本加密,则解密后在加载脚本文件
    // luaL_loadbuffer 用于加载并编译Lua代码,并将其压入栈中
    if (_xxteaEnabled && strncmp(chunk, _xxteaSign, _xxteaSignLen) == 0)
    {
        // decrypt XXTEA
        xxtea_long len = 0;
        unsigned char* result = xxtea_decrypt((unsigned char*)chunk + _xxteaSignLen,
                                              (xxtea_long)chunkSize - _xxteaSignLen,
                                              (unsigned char*)_xxteaKey,
                                              (xxtea_long)_xxteaKeyLen,
                                              &len);
        unsigned char* content = result;
        xxtea_long contentSize = len;
        skipBOM((const char*&)content, (int&)contentSize);
        r = luaL_loadbuffer(L, (char*)content, contentSize, chunkName);
        free(result);
    }
    else
    {
        skipBOM(chunk, chunkSize);
        r = luaL_loadbuffer(L, chunk, chunkSize, chunkName);
    }
 
// 判定内容是否存在错误
#if defined(COCOS2D_DEBUG) && COCOS2D_DEBUG > 0
    if (r)
    {
        switch (r)
        {
            case LUA_ERRSYNTAX:
                // 语法错误
                CCLOG("[LUA ERROR] load \"%s\", error: syntax error during pre-compilation.", chunkName);
                break;
 
            case LUA_ERRMEM:
                // 内存分配错误
                CCLOG("[LUA ERROR] load \"%s\", error: memory allocation error.", chunkName);
                break;
 
            case LUA_ERRFILE:
                 // 文件错误
                CCLOG("[LUA ERROR] load \"%s\", error: cannot open/read file.", chunkName);
                break;
 
            default:
                // 未知错误
                CCLOG("[LUA ERROR] load \"%s\", error: unknown.", chunkName);
        }
    }
#endif
    return r;
}
 
 
 
 
 
 
_BYTE *__fastcall decodeLuaData(cocos2d::Data *a1, int *size)
{
  _BYTE *v3; // r0
  _BYTE *v4; // r5
  int v6; // r0
  int v7; // r2
  int v8; // r3
  bool v9; // cc
  unsigned int v10; // r1
  _BYTE *v11; // r12
  _BYTE *v12; // r0
  char v13; // t1
  _BYTE *v14; // r0
  char v15[4]; // [sp+0h] [bp-18h]
 
  v3 = (_BYTE *)cocos2d::Data::getBytes(a1);
  v4 = v3;
  if ( *size > 8 && *v3 == 'a' && v3[1] == 'b' && v3[2] == 'c' && v3[3] == 'd' )
  {
    v6 = _hexToDecimal(v3 + 4, 4);
    v7 = *size;
    v8 = 0;
    v9 = *size <= 8;
    v15[3] = v6;
    v15[2] = (unsigned __int16)(v6 - 2048) >> 8;
    v10 = (unsigned int)(v6 - 2048) >> 24;
    v15[1] = (unsigned int)(v6 - 2048) >> 16;
    v15[0] = (unsigned int)(v6 - 2048) >> 24;
    if ( !v9 )
    {
      v11 = v4 - 1;
      v12 = v4 + 7;
      while ( 1 )
      {
        v13 = *++v12;
        ++v8;
        *++v11 = v13 ^ v10;
        v7 = *size;
        if ( *size <= v8 + 8 )
          break;
        LOBYTE(v10) = v15[v8 & 3];
      }
    }
    v14 = &v4[v7 - 8];
    *v14 = 0;
    v14[1] = 0;
    v14[2] = 0;
    v14[3] = 0;
    v14[4] = 0;
    v14[5] = 0;
    v14[6] = 0;
    v14[7] = 0;
    *size -= 8;
  }
  return v4;
}
_BYTE *__fastcall decodeLuaData(cocos2d::Data *a1, int *size)
{
  _BYTE *v3; // r0
  _BYTE *v4; // r5
  int v6; // r0
  int v7; // r2
  int v8; // r3
  bool v9; // cc
  unsigned int v10; // r1
  _BYTE *v11; // r12
  _BYTE *v12; // r0
  char v13; // t1
  _BYTE *v14; // r0
  char v15[4]; // [sp+0h] [bp-18h]
 
  v3 = (_BYTE *)cocos2d::Data::getBytes(a1);
  v4 = v3;
  if ( *size > 8 && *v3 == 'a' && v3[1] == 'b' && v3[2] == 'c' && v3[3] == 'd' )
  {
    v6 = _hexToDecimal(v3 + 4, 4);
    v7 = *size;
    v8 = 0;
    v9 = *size <= 8;
    v15[3] = v6;
    v15[2] = (unsigned __int16)(v6 - 2048) >> 8;
    v10 = (unsigned int)(v6 - 2048) >> 24;
    v15[1] = (unsigned int)(v6 - 2048) >> 16;
    v15[0] = (unsigned int)(v6 - 2048) >> 24;
    if ( !v9 )
    {
      v11 = v4 - 1;
      v12 = v4 + 7;
      while ( 1 )
      {
        v13 = *++v12;
        ++v8;
        *++v11 = v13 ^ v10;
        v7 = *size;
        if ( *size <= v8 + 8 )
          break;
        LOBYTE(v10) = v15[v8 & 3];
      }
    }
    v14 = &v4[v7 - 8];
    *v14 = 0;
    v14[1] = 0;
    v14[2] = 0;
    v14[3] = 0;
    v14[4] = 0;
    v14[5] = 0;
    v14[6] = 0;
    v14[7] = 0;
    *size -= 8;
  }
  return v4;
}
 
 
 
 
 
import struct
from loguru import logger
 
 
def _hexToDecimal(data, offset):
    if offset <= 0:
        return 0
    return struct.unpack(">I", data[:offset])[0]
 
 
def LOBYTE(d):
    return d & 0xff
 
 
def decodeLuaData(data, size):
    v4 = bytearray(data)
    v15 = [0] * 4
    if size > 8 and data[0] == ord('a') and data[1] == ord('b') and data[2] == ord('c') and data[3] == ord('d'):
        logger.info("find special exts")
        v6 = _hexToDecimal(data[4:], 4)
 
        v15[3] = v6
        v15[2] = (v6 - 2048) >> 8
        v10 = (v6 - 2048) >> 24
        v15[1] = (v6 - 2048) >> 16
        v15[0] = (v6 - 2048) >> 24
 
        if size > 8:
            i = 0
            while 1:
                v13 = v4[8 + i]
                v4[i] = v13 ^ v10
                i += 1
                if size <= i + 8:
                    break
                v10 = LOBYTE(v15[i & 3])
        size -= 8
 
        return v4[:-8]
import struct
from loguru import logger
 
 
def _hexToDecimal(data, offset):
    if offset <= 0:
        return 0
    return struct.unpack(">I", data[:offset])[0]
 
 
def LOBYTE(d):
    return d & 0xff
 
 
def decodeLuaData(data, size):
    v4 = bytearray(data)
    v15 = [0] * 4
    if size > 8 and data[0] == ord('a') and data[1] == ord('b') and data[2] == ord('c') and data[3] == ord('d'):
        logger.info("find special exts")
        v6 = _hexToDecimal(data[4:], 4)
 

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2021-7-24 22:21 被sunfishi编辑 ,原因:
收藏
免费 13
支持
分享
打赏 + 51.00雪花
打赏次数 2 雪花 + 51.00
 
赞赏  闪星星   +50.00 2021/08/18 大佬我也要加你QQ
赞赏  wx_N_495   +1.00 2021/08/02 宁哥哥的爱
最新回复 (14)
雪    币: 1392
活跃值: (5172)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
2
以前大都是xxtea那种形式  源码加密。很多变成了luajit的字节码 可就麻烦了。虽然也有还原的。但是还原度很差
2021-7-25 09:52
0
雪    币: 298
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
mason大佬tql
2021-7-26 12:34
0
雪    币: 6573
活跃值: (3888)
能力值: (RANK:200 )
在线值:
发帖
回帖
粉丝
4
tql
2021-7-26 19:46
0
雪    币: 164
活跃值: (61)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
像大佬学习了
2021-7-26 21:22
0
雪    币: 4311
活跃值: (2662)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
太强了
2021-8-2 09:25
0
雪    币: 463
活跃值: (2706)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
7
luajit 反汇编感觉没啥比较好的工具
2021-8-2 10:05
0
雪    币: 252
活跃值: (1204)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
cocos2dx-lua加载流程讲解这么细,谢谢大佬分享
2021-8-2 10:11
0
雪    币: 7115
活跃值: (639)
能力值: (RANK:1290 )
在线值:
发帖
回帖
粉丝
9
这玩意最早应该是我给他们的方案。大概是6,7年前吧。 当时还做了一个魔改的lua解释器给dota传奇做保护。想想时间过的好快。  
2021-8-2 19:41
0
雪    币: 446
活跃值: (352)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
大佬,我也要加你QQ
2021-8-18 01:04
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
支持一下
2021-8-18 06:39
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
我这里有个修改了opcode的始终搞不定,有能力的来,Q 68056971,有尝,怎么联系博主
2021-8-25 15:20
0
雪    币: 187
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13
大佬
2022-4-12 15:14
0
雪    币: 116
活跃值: (1012)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
再次支持
2023-3-24 16:57
0
雪    币: 11
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
大佬  能联系我吗  有偿gaga3125
2024-9-1 14:02
0
游客
登录 | 注册 方可回帖
返回
//