长亭百川云 - 文章详情

[转]Cocos2dlua手游 Lua解密与资源解密实战 - sevck

博客园 - sevck

58

2024-07-19

来自看雪:Cocos2dlua手游 Lua解密与资源解密实战

https://mp.weixin.qq.com/s/WeYxlXZvCPv\_3nGgeKdunw

Cocos2dlua 逆向相关学习,略有删减,直接进入正文

APK大致如下:

.
├── 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的文件头有点奇怪。

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

 综上,我们需要实现的目标:1、解密.lua文件;2、解密.png文件。

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

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

Cocos2d-x环境搭建:参考官方Docs,搭建所需环境:

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

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
 
    }
}

这个方法很简单,就只调用了父类Cocos2dxActivity的onCreate方法:

@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是工具辅助类,不是重点。

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:

@Override
    public void onSurfaceCreated(final GL10 GL10, final EGLConfig EGLConfig) {
        Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
        this.mLastTickInNanoSeconds = System.nanoTime();
        mNativeInitCompleted \= true;
    }

nativeInit是一个Native函数:

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

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

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()。

int Application::run()
{
    // Initialize instance and cocos2d.
    if (! applicationDidFinishLaunching())
    {
        return 0;
    }
 
    return -1;
}

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

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初始化过程:

LuaEngine\* LuaEngine::getInstance(void)
{
    if (!\_defaultEngine)
    {
        \_defaultEngine \= new (std::nothrow) LuaEngine();
        \_defaultEngine\->init();
    }
    return \_defaultEngine;
}

接下来是_defaultEngine->init():

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;
}

下面是stack->init():

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):

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为何可以使用 "." 来设定文件路径了,比如:

require "cocos.cocos2d.Cocos2d"
require "cocos.cocos2d.Cocos2dConstants"
require "cocos.cocos2d.functions"

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

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文件主要加载流程图

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

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

 定位至cocos2dx_lua_loader:

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

下面是伪代码:

\_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:

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反汇编即可。

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

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:

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加载了我们的图片文件。

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:

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

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;
}

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

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

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

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

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:

\_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了。算法还原

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

解密结果:

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2