首页
论坛
课程
招聘
[原创] Cocos2dlua手游 Lua解密与资源解密实战
2021-7-24 22:17 14215

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

2021-7-24 22:17
14215

前言

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

 

起因

 

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

 

image-20210724155648914

 

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

 

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

 

结果就是:喜提样本一份

样本树

大致结构如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.
├── 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

其中.lua64为标准LuaJit文件

 

.lua的文件头有点奇怪

 

image-20210724163218589

 

.lua文件头:abcd

 

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

 

image-20210724163414596

 

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

  1. 解密.lua文件
  2. 解密.png文件

LuaJit 反汇编

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

Lua文件加载流程

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

Cocos2d-x环境搭建

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

 

image-20210724163932820

从Android Activity揭秘cocos2d-x的世界

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
 
    }
}

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

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
@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);
    }

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

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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);
    }

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

 

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

  1. 管理一个surface,这个surface就是一块特殊的内存,能直接排版到android的视图view上
  2. 管理一个EGL display,它能让opengl把内容渲染到上述的surface上
  3. 用户自定义渲染器(render)
  4. 让渲染器在独立的线程里运作,和UI线程分离
  5. 支持按需渲染(on-demand)和连续渲染(continuous)
  6. 一些可选工具,如调试

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

 

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

  • onSurfaceCreated: 创建GLSurfaceView时被调用,只调用一次,做初始化工作
  • onSurfaceChanged: 当GLSurfaceView的几何体被改变时被调用
  • onDrawFrame: 绘制渲染GLSurfaceView

onSurfaceCreated

1
2
3
4
5
6
@Override
    public void onSurfaceCreated(final GL10 GL10, final EGLConfig EGLConfig) {
        Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
        this.mLastTickInNanoSeconds = System.nanoTime();
        mNativeInitCompleted = true;
    }

nativeInit是一个Native函数

1
private static native void nativeInit(final int width, final int height);

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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();
}

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

1
2
3
4
5
6
7
8
9
10
int Application::run()
{
    // Initialize instance and cocos2d.
    if (! applicationDidFinishLaunching())
    {
        return 0;
    }
 
    return -1;
}

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

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
29
30
31
32
33
34
35
36
37
38
39
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::getInstance();这段代码实例化了LuaEngine

 

下面分析LuaEngine初始化过程

1
2
3
4
5
6
7
8
9
LuaEngine* LuaEngine::getInstance(void)
{
    if (!_defaultEngine)
    {
        _defaultEngine = new (std::nothrow) LuaEngine();
        _defaultEngine->init();
    }
    return _defaultEngine;
}

接下来是_defaultEngine->init();

1
2
3
4
5
6
bool LuaEngine::init(void)
{
    _stack = LuaStack::create();
    _stack->retain();
    return true;
}

继续进入

1
2
3
4
5
6
7
LuaStack *LuaStack::create()
{
    LuaStack *stack = new (std::nothrow) LuaStack();
    stack->init();
    stack->autorelease();
    return stack;
}

下面是stack->init()

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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;
}

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

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
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;
    }

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

 

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

1
2
3
require "cocos.cocos2d.Cocos2d"
require "cocos.cocos2d.Cocos2dConstants"
require "cocos.cocos2d.functions"

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

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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;
}

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

 

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

 

img

Lua解密之自定义文件头

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

快速定位

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

 

定位至cocos2dx_lua_loader

 

image-20210724202549151

 

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

 

下面是伪代码

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
_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;
}

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

 

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

 

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

 

我这里就不Hook了

算法还原

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

 

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

 

我这里使用了Python

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
29
30
31
32
33
34
35
36
37
38
39
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]

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

 

image-20210724204751819

Lua资源加载流程

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

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
TOLUA_API int register_all_cocos2dx(lua_State* tolua_S)
{
    tolua_open(tolua_S);
 
    tolua_module(tolua_S,"cc",0);
    tolua_beginmodule(tolua_S,"cc");
 
    lua_register_cocos2dx_Ref(tolua_S);
    lua_register_cocos2dx_Material(tolua_S);
    lua_register_cocos2dx_Console(tolua_S);
    lua_register_cocos2dx_Node(tolua_S);
    lua_register_cocos2dx_Scene(tolua_S);
    lua_register_cocos2dx_TransitionScene(tolua_S);
    lua_register_cocos2dx_TransitionEaseScene(tolua_S);
    lua_register_cocos2dx_TransitionMoveInL(tolua_S);
    lua_register_cocos2dx_TransitionMoveInB(tolua_S);
    lua_register_cocos2dx_AtlasNode(tolua_S);
    lua_register_cocos2dx_TileMapAtlas(tolua_S);
    lua_register_cocos2dx_TransitionMoveInT(tolua_S);
    lua_register_cocos2dx_TMXTilesetInfo(tolua_S);
    lua_register_cocos2dx_TransitionMoveInR(tolua_S);
    lua_register_cocos2dx_Action(tolua_S);
    lua_register_cocos2dx_FiniteTimeAction(tolua_S);
    lua_register_cocos2dx_ActionInstant(tolua_S);
    lua_register_cocos2dx_Hide(tolua_S);
    lua_register_cocos2dx_ParticleSystem(tolua_S);
    lua_register_cocos2dx_ParticleSystemQuad(tolua_S);
    lua_register_cocos2dx_ParticleSpiral(tolua_S);
    lua_register_cocos2dx_GridBase(tolua_S);
    lua_register_cocos2dx_AnimationCache(tolua_S);
    lua_register_cocos2dx_ActionInterval(tolua_S);
    lua_register_cocos2dx_ActionCamera(tolua_S);
    lua_register_cocos2dx_ProgressFromTo(tolua_S);
    lua_register_cocos2dx_MoveBy(tolua_S);
    lua_register_cocos2dx_MoveTo(tolua_S);
    lua_register_cocos2dx_JumpBy(tolua_S);
    lua_register_cocos2dx_EventListener(tolua_S);
    lua_register_cocos2dx_EventListenerKeyboard(tolua_S);
    lua_register_cocos2dx_EventListenerMouse(tolua_S);
    lua_register_cocos2dx_TransitionRotoZoom(tolua_S);
    lua_register_cocos2dx_Event(tolua_S);
    lua_register_cocos2dx_EventController(tolua_S);
    lua_register_cocos2dx_Director(tolua_S);
    lua_register_cocos2dx_Scheduler(tolua_S);
    lua_register_cocos2dx_ActionEase(tolua_S);
    lua_register_cocos2dx_EaseElastic(tolua_S);
    lua_register_cocos2dx_EaseElasticOut(tolua_S);
    lua_register_cocos2dx_EaseQuadraticActionInOut(tolua_S);
    lua_register_cocos2dx_EaseBackOut(tolua_S);
    lua_register_cocos2dx_Texture2D(tolua_S);
    lua_register_cocos2dx_TransitionSceneOriented(tolua_S);
    lua_register_cocos2dx_TransitionFlipX(tolua_S);
    lua_register_cocos2dx_CameraBackgroundBrush(tolua_S);
    lua_register_cocos2dx_CameraBackgroundDepthBrush(tolua_S);
    lua_register_cocos2dx_CameraBackgroundColorBrush(tolua_S);
    lua_register_cocos2dx_GridAction(tolua_S);
    lua_register_cocos2dx_TiledGrid3DAction(tolua_S);
    lua_register_cocos2dx_FadeOutTRTiles(tolua_S);
    lua_register_cocos2dx_FadeOutUpTiles(tolua_S);
    lua_register_cocos2dx_FadeOutDownTiles(tolua_S);
    lua_register_cocos2dx_StopGrid(tolua_S);
    lua_register_cocos2dx_Technique(tolua_S);
    lua_register_cocos2dx_SkewTo(tolua_S);
    lua_register_cocos2dx_SkewBy(tolua_S);
    lua_register_cocos2dx_EaseQuadraticActionOut(tolua_S);
    lua_register_cocos2dx_TransitionProgress(tolua_S);
    lua_register_cocos2dx_TransitionProgressVertical(tolua_S);
    lua_register_cocos2dx_Layer(tolua_S);
    lua_register_cocos2dx_TMXTiledMap(tolua_S);
    lua_register_cocos2dx_Grid3DAction(tolua_S);
    lua_register_cocos2dx_BaseLight(tolua_S);
    lua_register_cocos2dx_SpotLight(tolua_S);
    lua_register_cocos2dx_FadeTo(tolua_S);
    lua_register_cocos2dx_FadeIn(tolua_S);
    lua_register_cocos2dx_DirectionLight(tolua_S);
    lua_register_cocos2dx_EventListenerCustom(tolua_S);
    lua_register_cocos2dx_FlipX3D(tolua_S);
    lua_register_cocos2dx_FlipY3D(tolua_S);
    lua_register_cocos2dx_EaseSineInOut(tolua_S);
    lua_register_cocos2dx_TransitionFlipAngular(tolua_S);
    lua_register_cocos2dx_EaseElasticInOut(tolua_S);
    lua_register_cocos2dx_EaseBounce(tolua_S);
    lua_register_cocos2dx_Show(tolua_S);
    lua_register_cocos2dx_FadeOut(tolua_S);
    lua_register_cocos2dx_CallFunc(tolua_S);
    lua_register_cocos2dx_EventMouse(tolua_S);
    lua_register_cocos2dx_GLView(tolua_S);
    lua_register_cocos2dx_EaseBezierAction(tolua_S);
    lua_register_cocos2dx_ParticleFireworks(tolua_S);
    lua_register_cocos2dx_MenuItem(tolua_S);
    lua_register_cocos2dx_MenuItemSprite(tolua_S);
    lua_register_cocos2dx_MenuItemImage(tolua_S);
    lua_register_cocos2dx_AutoPolygon(tolua_S);
    lua_register_cocos2dx_ParticleSmoke(tolua_S);
    lua_register_cocos2dx_TransitionZoomFlipAngular(tolua_S);
    lua_register_cocos2dx_EaseRateAction(tolua_S);
    lua_register_cocos2dx_EaseIn(tolua_S);
    lua_register_cocos2dx_EaseExponentialInOut(tolua_S);
    lua_register_cocos2dx_CardinalSplineTo(tolua_S);
    lua_register_cocos2dx_CatmullRomTo(tolua_S);
    lua_register_cocos2dx_Waves3D(tolua_S);
    lua_register_cocos2dx_EaseExponentialOut(tolua_S);
    lua_register_cocos2dx_Label(tolua_S);
    lua_register_cocos2dx_Application(tolua_S);
    lua_register_cocos2dx_DelayTime(tolua_S);
    lua_register_cocos2dx_LabelAtlas(tolua_S);
    lua_register_cocos2dx_EaseCircleActionOut(tolua_S);
    lua_register_cocos2dx_SpriteBatchNode(tolua_S);
    lua_register_cocos2dx_TMXLayer(tolua_S);
    lua_register_cocos2dx_AsyncTaskPool(tolua_S);
    lua_register_cocos2dx_ParticleSnow(tolua_S);
    lua_register_cocos2dx_EaseElasticIn(tolua_S);
    lua_register_cocos2dx_EaseCircleActionInOut(tolua_S);
    lua_register_cocos2dx_TransitionFadeTR(tolua_S);
    lua_register_cocos2dx_EaseQuarticActionOut(tolua_S);
    lua_register_cocos2dx_EventAcceleration(tolua_S);
    lua_register_cocos2dx_EaseCubicActionIn(tolua_S);
    lua_register_cocos2dx_TextureCache(tolua_S);
    lua_register_cocos2dx_ActionTween(tolua_S);
    lua_register_cocos2dx_TransitionFadeDown(tolua_S);
    lua_register_cocos2dx_ParticleSun(tolua_S);
    lua_register_cocos2dx_TransitionProgressHorizontal(tolua_S);
    lua_register_cocos2dx_ParticleFire(tolua_S);
    lua_register_cocos2dx_FlipX(tolua_S);
    lua_register_cocos2dx_FlipY(tolua_S);
    lua_register_cocos2dx_EventKeyboard(tolua_S);
    lua_register_cocos2dx_TransitionSplitCols(tolua_S);
    lua_register_cocos2dx_Timer(tolua_S);
    lua_register_cocos2dx_RepeatForever(tolua_S);
    lua_register_cocos2dx_Place(tolua_S);
    lua_register_cocos2dx_EventListenerAcceleration(tolua_S);
    lua_register_cocos2dx_TiledGrid3D(tolua_S);
    lua_register_cocos2dx_EaseBounceOut(tolua_S);
    lua_register_cocos2dx_RenderTexture(tolua_S);
    lua_register_cocos2dx_TintBy(tolua_S);
    lua_register_cocos2dx_TransitionShrinkGrow(tolua_S);
    lua_register_cocos2dx_ClippingNode(tolua_S);
    lua_register_cocos2dx_ActionFloat(tolua_S);
    lua_register_cocos2dx_ParticleFlower(tolua_S);
    lua_register_cocos2dx_EaseCircleActionIn(tolua_S);
    lua_register_cocos2dx_Image(tolua_S); // 加载图片资源
    lua_register_cocos2dx_LayerMultiplex(tolua_S);
    lua_register_cocos2dx_Blink(tolua_S);
    lua_register_cocos2dx_ShaderCache(tolua_S);
    lua_register_cocos2dx_JumpTo(tolua_S);
    lua_register_cocos2dx_ParticleExplosion(tolua_S);
    lua_register_cocos2dx_TransitionJumpZoom(tolua_S);
    lua_register_cocos2dx_Pass(tolua_S);
    lua_register_cocos2dx_Touch(tolua_S);
    lua_register_cocos2dx_CardinalSplineBy(tolua_S);
    lua_register_cocos2dx_CatmullRomBy(tolua_S);
    lua_register_cocos2dx_NodeGrid(tolua_S);
    lua_register_cocos2dx_TMXLayerInfo(tolua_S);
    lua_register_cocos2dx_EaseSineIn(tolua_S);
    lua_register_cocos2dx_EaseBounceIn(tolua_S);
    lua_register_cocos2dx_Camera(tolua_S);
    lua_register_cocos2dx_TMXObjectGroup(tolua_S);
    lua_register_cocos2dx_FastTMXTiledMap(tolua_S);
    lua_register_cocos2dx_ParticleGalaxy(tolua_S);
    lua_register_cocos2dx_Twirl(tolua_S);
    lua_register_cocos2dx_MenuItemLabel(tolua_S);
    lua_register_cocos2dx_EaseQuinticActionIn(tolua_S);
    lua_register_cocos2dx_LayerColor(tolua_S);
    lua_register_cocos2dx_FadeOutBLTiles(tolua_S);
    lua_register_cocos2dx_LayerGradient(tolua_S);
    lua_register_cocos2dx_EventListenerTouchAllAtOnce(tolua_S);
    lua_register_cocos2dx_GLViewImpl(tolua_S);
    lua_register_cocos2dx_ToggleVisibility(tolua_S);
    lua_register_cocos2dx_Repeat(tolua_S);
    lua_register_cocos2dx_TransitionFlipY(tolua_S);
    lua_register_cocos2dx_TurnOffTiles(tolua_S);
    lua_register_cocos2dx_TintTo(tolua_S);
    lua_register_cocos2dx_EaseBackInOut(tolua_S);
    lua_register_cocos2dx_TransitionFadeBL(tolua_S);
    lua_register_cocos2dx_TargetedAction(tolua_S);
    lua_register_cocos2dx_DrawNode(tolua_S);
    lua_register_cocos2dx_TransitionTurnOffTiles(tolua_S);
    lua_register_cocos2dx_RotateTo(tolua_S);
    lua_register_cocos2dx_TransitionSplitRows(tolua_S);
    lua_register_cocos2dx_Device(tolua_S);
    lua_register_cocos2dx_TransitionProgressRadialCCW(tolua_S);
    lua_register_cocos2dx_ScaleTo(tolua_S);
    lua_register_cocos2dx_TransitionPageTurn(tolua_S);
    lua_register_cocos2dx_RenderState(tolua_S);
    lua_register_cocos2dx_Properties(tolua_S);
    lua_register_cocos2dx_BezierBy(tolua_S);
    lua_register_cocos2dx_BezierTo(tolua_S);
    lua_register_cocos2dx_ParticleMeteor(tolua_S);
    lua_register_cocos2dx_SpriteFrame(tolua_S);
    lua_register_cocos2dx_Liquid(tolua_S);
    lua_register_cocos2dx_UserDefault(tolua_S);
    lua_register_cocos2dx_FastTMXLayer(tolua_S);
    lua_register_cocos2dx_TransitionZoomFlipX(tolua_S);
    lua_register_cocos2dx_EventFocus(tolua_S);
    lua_register_cocos2dx_TransitionFade(tolua_S);
    lua_register_cocos2dx_EaseQuinticActionInOut(tolua_S);
    lua_register_cocos2dx_SpriteFrameCache(tolua_S);
    lua_register_cocos2dx_PointLight(tolua_S);
    lua_register_cocos2dx_TransitionCrossFade(tolua_S);
    lua_register_cocos2dx_Ripple3D(tolua_S);
    lua_register_cocos2dx_Lens3D(tolua_S);
    lua_register_cocos2dx_EventListenerFocus(tolua_S);
    lua_register_cocos2dx_Spawn(tolua_S);
    lua_register_cocos2dx_EaseQuarticActionInOut(tolua_S);
    lua_register_cocos2dx_ShakyTiles3D(tolua_S);
    lua_register_cocos2dx_PageTurn3D(tolua_S);
    lua_register_cocos2dx_PolygonInfo(tolua_S);
    lua_register_cocos2dx_TransitionSlideInL(tolua_S);
    lua_register_cocos2dx_TransitionSlideInT(tolua_S);
    lua_register_cocos2dx_Grid3D(tolua_S);
    lua_register_cocos2dx_EventListenerController(tolua_S);
    lua_register_cocos2dx_TransitionProgressInOut(tolua_S);
    lua_register_cocos2dx_EaseCubicActionInOut(tolua_S);
    lua_register_cocos2dx_ParticleData(tolua_S);
    lua_register_cocos2dx_EaseBackIn(tolua_S);
    lua_register_cocos2dx_SplitRows(tolua_S);
    lua_register_cocos2dx_Follow(tolua_S);
    lua_register_cocos2dx_Animate(tolua_S);
    lua_register_cocos2dx_ShuffleTiles(tolua_S);
    lua_register_cocos2dx_CameraBackgroundSkyBoxBrush(tolua_S);
    lua_register_cocos2dx_ProgressTimer(tolua_S);
    lua_register_cocos2dx_EaseQuarticActionIn(tolua_S);
    lua_register_cocos2dx_Menu(tolua_S);
    lua_register_cocos2dx_EaseInOut(tolua_S);
    lua_register_cocos2dx_TransitionZoomFlipY(tolua_S);
    lua_register_cocos2dx_ScaleBy(tolua_S);
    lua_register_cocos2dx_EventTouch(tolua_S);
    lua_register_cocos2dx_Animation(tolua_S);
    lua_register_cocos2dx_TMXMapInfo(tolua_S);
    lua_register_cocos2dx_EaseExponentialIn(tolua_S);
    lua_register_cocos2dx_ReuseGrid(tolua_S);
    lua_register_cocos2dx_EaseQuinticActionOut(tolua_S);
    lua_register_cocos2dx_EventDispatcher(tolua_S);
    lua_register_cocos2dx_MenuItemAtlasFont(tolua_S);
    lua_register_cocos2dx_ActionManager(tolua_S);
    lua_register_cocos2dx_OrbitCamera(tolua_S);
    lua_register_cocos2dx_ClippingRectangleNode(tolua_S);
    lua_register_cocos2dx_EventCustom(tolua_S);
    lua_register_cocos2dx_ParticleBatchNode(tolua_S);
    lua_register_cocos2dx_Component(tolua_S);
    lua_register_cocos2dx_EaseCubicActionOut(tolua_S);
    lua_register_cocos2dx_EventListenerTouchOneByOne(tolua_S);
    lua_register_cocos2dx_Renderer(tolua_S);
    lua_register_cocos2dx_ParticleRain(tolua_S);
    lua_register_cocos2dx_Waves(tolua_S);
    lua_register_cocos2dx_ComponentLua(tolua_S);
    lua_register_cocos2dx_MotionStreak3D(tolua_S);
    lua_register_cocos2dx_EaseOut(tolua_S);
    lua_register_cocos2dx_MenuItemFont(tolua_S);
    lua_register_cocos2dx_TransitionFadeUp(tolua_S);
    lua_register_cocos2dx_LayerRadialGradient(tolua_S);
    lua_register_cocos2dx_EaseSineOut(tolua_S);
    lua_register_cocos2dx_JumpTiles3D(tolua_S);
    lua_register_cocos2dx_MenuItemToggle(tolua_S);
    lua_register_cocos2dx_RemoveSelf(tolua_S);
    lua_register_cocos2dx_SplitCols(tolua_S);
    lua_register_cocos2dx_ProtectedNode(tolua_S);
    lua_register_cocos2dx_MotionStreak(tolua_S);
    lua_register_cocos2dx_RotateBy(tolua_S);
    lua_register_cocos2dx_FileUtils(tolua_S);
    lua_register_cocos2dx_Sprite(tolua_S);
    lua_register_cocos2dx_ProgressTo(tolua_S);
    lua_register_cocos2dx_TransitionProgressOutIn(tolua_S);
    lua_register_cocos2dx_AnimationFrame(tolua_S);
    lua_register_cocos2dx_Sequence(tolua_S);
    lua_register_cocos2dx_Shaky3D(tolua_S);
    lua_register_cocos2dx_TransitionProgressRadialCW(tolua_S);
    lua_register_cocos2dx_EaseBounceInOut(tolua_S);
    lua_register_cocos2dx_TransitionSlideInR(tolua_S);
    lua_register_cocos2dx_AmbientLight(tolua_S);
    lua_register_cocos2dx_ParallaxNode(tolua_S);
    lua_register_cocos2dx_EaseQuadraticActionIn(tolua_S);
    lua_register_cocos2dx_WavesTiles3D(tolua_S);
    lua_register_cocos2dx_TransitionSlideInB(tolua_S);
    lua_register_cocos2dx_Speed(tolua_S);
    lua_register_cocos2dx_ShatteredTiles3D(tolua_S);
 
    tolua_endmodule(tolua_S);
    return 1;
}

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

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
29
int lua_register_cocos2dx_Image(lua_State* tolua_S)
{
    tolua_usertype(tolua_S,"cc.Image");
    tolua_cclass(tolua_S,"Image","cc.Image","cc.Ref",nullptr);
 
    tolua_beginmodule(tolua_S,"Image");
        tolua_function(tolua_S,"new",lua_cocos2dx_Image_constructor);
        tolua_function(tolua_S,"hasPremultipliedAlpha",lua_cocos2dx_Image_hasPremultipliedAlpha);
        tolua_function(tolua_S,"reversePremultipliedAlpha",lua_cocos2dx_Image_reversePremultipliedAlpha);
        tolua_function(tolua_S,"isCompressed",lua_cocos2dx_Image_isCompressed);
        tolua_function(tolua_S,"hasAlpha",lua_cocos2dx_Image_hasAlpha);
        tolua_function(tolua_S,"getPixelFormat",lua_cocos2dx_Image_getPixelFormat);
        tolua_function(tolua_S,"getHeight",lua_cocos2dx_Image_getHeight);
        tolua_function(tolua_S,"premultiplyAlpha",lua_cocos2dx_Image_premultiplyAlpha);
        tolua_function(tolua_S,"initWithImageFile",lua_cocos2dx_Image_initWithImageFile); // 初始化图片文件
        tolua_function(tolua_S,"getWidth",lua_cocos2dx_Image_getWidth);
        tolua_function(tolua_S,"getBitPerPixel",lua_cocos2dx_Image_getBitPerPixel);
        tolua_function(tolua_S,"getFileType",lua_cocos2dx_Image_getFileType);
        tolua_function(tolua_S,"getFilePath",lua_cocos2dx_Image_getFilePath);
        tolua_function(tolua_S,"getNumberOfMipmaps",lua_cocos2dx_Image_getNumberOfMipmaps);
        tolua_function(tolua_S,"saveToFile",lua_cocos2dx_Image_saveToFile);
        tolua_function(tolua_S,"setPVRImagesHavePremultipliedAlpha", lua_cocos2dx_Image_setPVRImagesHavePremultipliedAlpha);
        tolua_function(tolua_S,"setPNGPremultipliedAlphaEnabled", lua_cocos2dx_Image_setPNGPremultipliedAlphaEnabled);
    tolua_endmodule(tolua_S);
    std::string typeName = typeid(cocos2d::Image).name();
    g_luaType[typeName] = "cc.Image";
    g_typeCast["Image"] = "cc.Image";
    return 1;
}

lua_cocos2dx_Image_initWithImageFile加载了我们的图片文件

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
int lua_cocos2dx_Image_initWithImageFile(lua_State* tolua_S)
{
    int argc = 0;
    cocos2d::Image* cobj = nullptr;
    bool ok  = true;
 
#if COCOS2D_DEBUG >= 1
    tolua_Error tolua_err;
#endif
 
 
#if COCOS2D_DEBUG >= 1
    if (!tolua_isusertype(tolua_S,1,"cc.Image",0,&tolua_err)) goto tolua_lerror;
#endif
 
    cobj = (cocos2d::Image*)tolua_tousertype(tolua_S,1,0);
 
#if COCOS2D_DEBUG >= 1
    if (!cobj)
    {
        tolua_error(tolua_S,"invalid 'cobj' in function 'lua_cocos2dx_Image_initWithImageFile'", nullptr);
        return 0;
    }
#endif
 
    argc = lua_gettop(tolua_S)-1;
    if (argc == 1)
    {
        std::string arg0;
 
        ok &= luaval_to_std_string(tolua_S, 2,&arg0, "cc.Image:initWithImageFile");
        if(!ok)
        {
            tolua_error(tolua_S,"invalid arguments in function 'lua_cocos2dx_Image_initWithImageFile'", nullptr);
            return 0;
        }
        bool ret = cobj->initWithImageFile(arg0);
        tolua_pushboolean(tolua_S,(bool)ret);
        return 1;
    }
    luaL_error(tolua_S, "%s has wrong number of arguments: %d, was expecting %d \n", "cc.Image:initWithImageFile",argc, 1);
    return 0;
 
#if COCOS2D_DEBUG >= 1
    tolua_lerror:
    tolua_error(tolua_S,"#ferror in function 'lua_cocos2dx_Image_initWithImageFile'.",&tolua_err);
#endif
 
    return 0;
}

下面看cobj->initWithImageFile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool Image::initWithImageFile(const std::string& path)
{
    bool ret = false;
    _filePath = FileUtils::getInstance()->fullPathForFilename(path);
 
    Data data = FileUtils::getInstance()->getDataFromFile(_filePath); // 获取文件数据
 
    if (!data.isNull())
    {
        ret = initWithImageData(data.getBytes(), data.getSize()); // 解码/解密 处理文件数据
    }
 
    return ret;
}

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

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
bool Image::initWithImageData(const unsigned char * data, ssize_t dataLen)
{
    bool ret = false;
 
    do
    {
        CC_BREAK_IF(! data || dataLen <= 0);
 
        unsigned char* unpackedData = nullptr;
        ssize_t unpackedLen = 0;
 
        //detect and unzip the compress file
        if (ZipUtils::isCCZBuffer(data, dataLen))
        {
            unpackedLen = ZipUtils::inflateCCZBuffer(data, dataLen, &unpackedData);
        }
        else if (ZipUtils::isGZipBuffer(data, dataLen))
        {
            unpackedLen = ZipUtils::inflateMemory(const_cast<unsigned char*>(data), dataLen, &unpackedData);
        }
        else
        {
            unpackedData = const_cast<unsigned char*>(data);
            unpackedLen = dataLen;
        }
 
        _fileType = detectFormat(unpackedData, unpackedLen);
 
        switch (_fileType)
        {
        case Format::PNG:
            ret = initWithPngData(unpackedData, unpackedLen);
            break;
        case Format::JPG:
            ret = initWithJpgData(unpackedData, unpackedLen);
            break;
        case Format::WEBP:
            ret = initWithWebpData(unpackedData, unpackedLen);
            break;
        case Format::PVR:
            ret = initWithPVRData(unpackedData, unpackedLen);
            break;
        case Format::ETC:
            ret = initWithETCData(unpackedData, unpackedLen);
            break;
        case Format::S3TC:
            ret = initWithS3TCData(unpackedData, unpackedLen);
            break;
        case Format::ATITC:
            ret = initWithATITCData(unpackedData, unpackedLen);
            break;
        default:
            {
                // load and detect image format
                tImageTGA* tgaData = tgaLoadBuffer(unpackedData, unpackedLen);
 
                if (tgaData != nullptr && tgaData->status == TGA_OK)
                {
                    ret = initWithTGAData(tgaData);
                }
                else
                {
                    CCLOG("cocos2d: unsupported image format!");
                }
 
                free(tgaData);
                break;
            }
        }
 
        if(unpackedData != data)
        {
            free(unpackedData);
        }
    } while (0);
 
    return ret;
}

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

 

img

图片资源解密

快速定位

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

 

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

 

image-20210724211445149

 

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

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
cocos2d *__fastcall cocos2d::Image::decodePngData(cocos2d::Image *this, cocos2d::Data *a2)
{
  _BYTE *v4; // r7
  int v5; // r5
  signed int v6; // r0
  bool v7; // cc
  int v8; // r4
  void *v9; // r0
  int v10; // r2
  int v12; // r6
  int v13; // r4
  int v14; // r9
  int v15; // r2
  int v16; // r0
  int v17; // r3
  _BYTE *v18; // r5
  unsigned int *v19; // r2
  int v20; // r3
  int v21; // [sp+8h] [bp-28h] BYREF
 
  v4 = (_BYTE *)cocos2d::Data::getBytes(a2);
  v5 = cocos2d::Data::getSize(a2);
  sub_FBDCA8(&v21, (int *)this + 47);
  v6 = sub_FBC854((int)&v21, ".png", 0, 4u);
  v7 = v6 <= -1;
  if ( v6 != -1 )
    v7 = v5 <= 24;
  v8 = !v7;
  v9 = (void *)(v21 - 12);
  if ( (int *)(v21 - 12) != &dword_12F6978 )
  {
    if ( &pthread_create )
    {
      v19 = (unsigned int *)(v21 - 4);
      __dmb(0xFu);
      do
        v20 = __ldrex(v19);
      while ( __strex(v20 - 1, v19) );
      __dmb(0xFu);
    }
    else
    {
      v20 = *(_DWORD *)(v21 - 4);
      *(_DWORD *)(v21 - 4) = v20 - 1;
    }
    if ( v20 <= 0 )
      operator delete(v9);
  }
  if ( v8 && memcmp(&unk_11CBE0C, v4, 8u) )     // PNG Header
  {
    v12 = v5 - 4;
    v13 = v5 - 8;
    v14 = cocos2d::hexToDecimal((cocos2d *)&v4[v5 - 4], (unsigned __int8 *)&byte_4, v10);
    v16 = cocos2d::hexToDecimal((cocos2d *)&v4[v5 - 8], (unsigned __int8 *)&byte_4, v15);
    if ( v5 >= v16 )
      cocos2d::decodePng(v4, (unsigned __int8 *)(v14 - 2048), v16, v17);
    v18 = &v4[v5];
    *(v18 - 1) = 130;
    *(v18 - 2) = 96;
    *(v18 - 3) = 66;
    v4[v12] = 174;
    *(v18 - 5) = 68;
    *(v18 - 6) = 78;
    *(v18 - 7) = 69;
    v4[v13] = 73;
  }
  return (cocos2d *)v4;
}

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

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
_BYTE *__fastcall cocos2d::decodePng(_BYTE *this, unsigned __int8 *a2, int a3, int a4)
{
  char v4; // r12
  unsigned int v5; // r1
  int v6; // r12
  char v7[4]; // [sp+0h] [bp-10h]
 
  v7[1] = BYTE2(a2);
  v4 = BYTE1(a2);
  v7[3] = (char)a2;
  v5 = (unsigned int)a2 >> 24;
  v7[2] = v4;
  v6 = 0;
  v7[0] = v5;
  if ( a3 > 0 )
  {
    while ( 1 )
    {
      ++v6;
      *this++ ^= v5;
      if ( v6 == a3 )
        break;
      LOBYTE(v5) = v7[v6 % 4];
    }
  }
  return this;
}

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

 

同样的,我就不Hook了

算法还原

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
def _hexToDecimal(data, offset):
    if offset <= 0:
        return 0
    return struct.unpack(">I", data[:offset])[0]
 
 
def LOBYTE(d):
    return d & 0xff
 
 
def _decodePng(data, magic, rounds):
    dthis = bytearray(data)
 
    v7 = [0] * 4
    a2 = struct.pack("<i", magic)
    v7[1] = a2[2]
    v7[3] = a2[0]
    v7[0] = v5 = magic >> 24
    v7[2] = a2[1]
 
    i = 0
    if rounds > 0:
        while 1:
            i += 1
            dthis[i-1] ^= v5
            if i == rounds:
                break
            v5 = LOBYTE(v7[i % 4])
    return dthis
 
 
def decodePng(data, size):
    v14 = _hexToDecimal(data[-4:], 4)
    v16 = _hexToDecimal(data[-8:], 4)
    if size >= v16:
        ret = _decodePng(data, v14 - 2048, v16)
        ret[-1] = 130
        ret[-2] = 96
        ret[-3] = 66
        ret[-4] = 174
        ret[-5] = 68
        ret[-6] = 78
        ret[-7] = 69
        ret[-8] = 73
    return ret

解密结果

 

image-20210724212037493

总结

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


【公告】看雪团队招聘安全工程师,将兴趣和工作融合在一起!看雪20年安全圈的口碑,助你快速成长!

最后于 2021-7-24 22:21 被sunfishi编辑 ,原因:
收藏
点赞6
打赏
分享
打赏 + 51.00
打赏次数 2 金额 + 51.00
 
赞赏  闪星星   +50.00 2021/08/18 大佬我也要加你QQ
赞赏  wx_N_495   +1.00 2021/08/02 宁哥哥的爱
最新回复 (11)
雪    币: 2891
活跃值: 活跃值 (1273)
能力值: ( LV13,RANK:240 )
在线值:
发帖
回帖
粉丝
IamHuskar 活跃值 4 2021-7-25 09:52
2
0
以前大都是xxtea那种形式  源码加密。很多变成了luajit的字节码 可就麻烦了。虽然也有还原的。但是还原度很差
雪    币: 62
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
MrSean 活跃值 2021-7-26 12:34
3
0
mason大佬tql
雪    币: 11531
活跃值: 活跃值 (2829)
能力值: (RANK:200 )
在线值:
发帖
回帖
粉丝
LowRebSwrd 活跃值 4 2021-7-26 19:46
4
0
tql
雪    币: 164
活跃值: 活跃值 (61)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
gotyou 活跃值 2021-7-26 21:22
5
0
像大佬学习了
雪    币: 303
活跃值: 活跃值 (80)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
图灵真主护佑 活跃值 2021-8-2 09:25
6
0
太强了
雪    币: 439
活跃值: 活跃值 (777)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
pareto 活跃值 2021-8-2 10:05
7
0
luajit 反汇编感觉没啥比较好的工具
雪    币: 1230
活跃值: 活跃值 (334)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
挥一挥衣袖 活跃值 2021-8-2 10:11
8
0
cocos2dx-lua加载流程讲解这么细,谢谢大佬分享
雪    币: 6881
活跃值: 活跃值 (313)
能力值: (RANK:1290 )
在线值:
发帖
回帖
粉丝
玩命 活跃值 31 2021-8-2 19:41
9
0
这玩意最早应该是我给他们的方案。大概是6,7年前吧。 当时还做了一个魔改的lua解释器给dota传奇做保护。想想时间过的好快。  
雪    币: 381
活跃值: 活跃值 (265)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
闪星星 活跃值 2021-8-18 01:04
10
0
大佬,我也要加你QQ
雪    币: 20
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
万里星河 活跃值 2021-8-18 06:39
11
0
支持一下
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_cbpwxrde 活跃值 2021-8-25 15:20
12
0
我这里有个修改了opcode的始终搞不定,有能力的来,Q 68056971,有尝,怎么联系博主
游客
登录 | 注册 方可回帖
返回