cocos2dx-Lua引擎游戏脚本及图片资源解密与DUMP

来源:互联网 发布:网络推广主要做什么 编辑:程序博客网 时间:2024/04/20 08:10

分析目标

  • 少年三国志,包名:com.youzu.android.snsgz
  • 全民水浒,包名:com.tencent.Q108

下面分析的主要是少年三国志。

Lua脚本解密与DUMP

  • LuaJit IDA分析调用树:

    1. AppDelegate::applicationDidFinishLaunching(AppDelegate *__hidden this) EXPORT _ZN11AppDelegate29applicationDidFinishLaunchingEv

    2. cocos2d::CCLuaEngine::defaultEngine(cocos2d::CCLuaEngine *__hidden this) EXPORT _ZN7cocos2d11CCLuaEngine13defaultEngineEv

    3. cocos2d::CCLuaEngine::init(cocos2d::CCLuaEngine *__hidden this)
      EXPORT _ZN7cocos2d11CCLuaEngine4initEv

    4. cocos2d::CCLuaStack::create(cocos2d::CCLuaStack *__hidden this)
      EXPORT _ZN7cocos2d10CCLuaStack6createEv

    5. cocos2d::CCLuaStack::init(cocos2d::CCLuaStack *__hidden this)
      EXPORT _ZN7cocos2d10CCLuaStack4initEv

    6. cocos2dx_lua_loader

    7. cocos2d::CCLuaStack::lua_loadbuffer(lua_State , char const, int, char const*)
      EXPORT ZN7cocos2d10CCLuaStack14lua_loadbufferEP9lua_StatePKciS4

cocos2d::CCLuaStack::lua_loadbuffer先调用以下函数解密: cocos2d::extra::CCCrypto::decryptUF(uchar ,int,int ,int *) EXPORT ZN7cocos2d5extra8CCCrypto9decryptUFEPhiPiS3
最后再调用:luaL_loadbuffer

因此可以直接对luaL_loadbuffer进行HOOK,进而DUMP出Lua脚本,网上搜索函数声明:

int luaL_loadbuffer (lua_State *L, const char *buff, size_t sz, const char *name);

进而实现HOOK代码:

//orig function copyint (*luaL_loadbuffer_orig)(void *L, const char *buff, int size, const char *name) = NULL;//local functionint luaL_loadbuffer_mod(void *L, const char *buff, int size, const char *name) {    LOGD("[dumplua] luaL_loadbuffer name: %s lua: %s", name, buff);    return luaL_loadbuffer_orig(L, buff, size, name);}void hook() {    LOGD("[dumplua] hook begin");    void *handle = dlopen("libgame.so", RTLD_NOW);    if (handle == NULL) {        LOGE("[dumplua]dlopen err: %s.", dlerror());        return;    }else{        LOGD("[dumplua] libgame.so dlopen OK!");    }    void *pluaL_loadbuffer = dlsym(handle, "luaL_loadbuffer");    if (pluaL_loadbuffer == NULL){        LOGE("[dumplua] lua_loadbuffer not found!");        LOGE("[dumplua] dlsym err: %s.", dlerror());    }else{        LOGD("[dumplua] luaL_loadbuffer found!");        MSHookFunction(pluaL_loadbuffer, (void *)&luaL_loadbuffer_mod, (void **)&luaL_loadbuffer_orig);    }}

运行后拦截到的输出信息:

01-05 19:29:27.674 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: assets/scripts/main.lua lua:     function __G__TRACKBACK__(errorMessage)      print("----------------------------------------")      print("LUA ERROR: " .. tostring(errorMessage) .. "\n")      local traceback = debug.traceback("", 2)      print(traceback)      print("----------------------------------------")      --只有G_Report初始过后才会对错误日志做处理      if G_Report ~= nil then          G_Report:onTrackBack(errorMessage, traceback)      end      if SHOW_EXCEPTION_TIP and uf_notifyLayer ~= nil then         uf_notifyLayer:getDebugNode():removeChildByTag(10000)        local text = tostring(errorMessage)          require("upgrade.ErrMsgBox").showErrorMsgBox(text)      end    end    function traceMem(desc)      if desc == nil then          desc = "memory:"      end      if CCLuaObjcBridge then          local callStaticMethod = CCLuaObjcBridge.callStaticMethod          local ok, ret = callStaticMethod("NativeProxy", "getUsedMemory", nil)          if ok then              pri01-05 19:29:27.679 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: upgrade.AntiAddictionLayer lua: LJ-01-05 19:29:27.679 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: upgrade.ComSdkUtils lua: LJA01-05 19:29:27.684 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: upgrade.config lua: LJ�    01-05 19:29:27.684 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: upgrade.ConfigLayer lua: LJ]01-05 19:29:27.684 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: upgrade.EffectNode_Upgrade lua: LJ�01-05 19:29:27.684 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: upgrade.ErrMsgBox lua: LJP01-05 19:29:27.684 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: upgrade.game lua: LJ�01-05 19:29:27.684 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: upgrade.NativeCallUtils lua: LJ�01-05 19:29:27.684 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: upgrade.NativeProxy lua: LJ�01-05 19:29:27.684 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: upgrade.Patcher lua: LJ�01-05 19:29:27.689 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: upgrade.SplashLayer lua: LJ-01-05 19:29:27.689 13191-13215/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: upgrade.upgrade lua: LJ6

可见,有些Lua脚本是源码形式,有些是LuaJit编译的,可以改写以上代码把脚本DUMP到文件中再进一步分析,此处略。

资源解密与DUMP

主要函数: cocos2d::CCImage::initWithImageFile调用 cocos2d::CCImage::initWithImageData

但是IDA分析发现initWithImageData会调用cocos2d::extra::CCCrypto::decryptXXTEAcocos2d::extra::CCCrypto::decryptUF进行解密,最后再加载图片资源。以下是initWithImageData部分代码:

if ( s )  {    v12 = (unsigned __int8 *)strlen(s);    v13 = (void *)cocos2d::extra::CCCrypto::decryptXXTEA(v9, v11, (int)s, v12, (int)v24, v22);    v14 = v13;    if ( v13 )      v9 = (cocos2d::extra::CCCrypto *)v13;    goto LABEL_28;  }  v14 = 0;  if ( (signed int)a3 > 3 && *(_BYTE *)this == 85 && *((_BYTE *)this + 1) == 70 )  {    v25 = 0;    cocos2d::extra::CCCrypto::decryptUF(this, (int)a3, (int)&v25, v24, v21);    v15 = v25 >> 4;    if ( (v25 & 0xFu) <= 9 )      *(_DWORD *)(v8 + 36) = v25 & 0xF;    switch ( v15 )    {      case 1:        v16 = 1067030938;        break;      case 2:        v16 = 1068708659;

也即会调用cocos2d::extra::CCCrypto::decryptXXTEA和cocos2d::extra::CCCrypto::decryptUF进行解密操作。我们看下cocos2d::extra::CCCrypto::decryptUF这个函数,通过IDA的F5插件,并不断修改变量名可以获得一个比较清晰的C代码。

int __fastcall cocos2d::extra::CCCrypto::decryptUF(cocos2d::extra::CCCrypto *pInBuff, int nlen, int a3, int *pOutLen, int *name){  cocos2d::extra::CCCrypto *pInBuff2; // r5@1  int *pOutLen2; // r7@1  int v7; // r3@4  int v8; // r6@5  int v9; // r1@6  int result; // r0@9  int v11; // r4@10  int v12; // r6@12  int v13; // r6@15  signed int v14; // r0@19  int v15; // [sp+0h] [bp-28h]@5  int v16; // [sp+0h] [bp-28h]@10  int v17; // [sp+4h] [bp-24h]@5  pInBuff2 = pInBuff;  pOutLen2 = pOutLen;  if ( nlen <= 3 )  {    v14 = 1;    return -v14;  }  if ( *(_BYTE *)pInBuff != 'U' || *((_BYTE *)pInBuff + 1) != 'F' )  {    v14 = 2;    return -v14;  }  *(_DWORD *)a3 = *((_BYTE *)pInBuff + 2);  v7 = *((_BYTE *)pInBuff + 3);  if ( v7 == 1 )  {    v15 = nlen - 5;    v17 = *((_BYTE *)pInBuff + 4);    v8 = 0;    while ( v8 < v15 )    {      v9 = (v8++ + v17) % 0x21;      *(_BYTE *)pInBuff2 = *((_BYTE *)pInBuff2 + 5) ^ byte_6D192C[v9];      pInBuff2 = (cocos2d::extra::CCCrypto *)((char *)pInBuff2 + 1);    }    *pOutLen2 = v15;  }  else  {    result = 0;    if ( v7 != 2 )      return result;    v11 = 0;    v16 = *((_BYTE *)pInBuff2 + 4);    do    {      *((_BYTE *)pInBuff2 + v11) = *((_BYTE *)pInBuff2 + nlen + v11 - 5) ^ byte_6D192C[(v11 + v16) % 33 + 33];      ++v11;    }    while ( v11 != 5 );    v12 = nlen - 10;    if ( nlen - 10 > 95 )      v12 = 95;    v13 = v12 + 4;    while ( v13 >= v11 )    {      *((_BYTE *)pInBuff2 + v11) ^= byte_6D192C[(v11 + v16) % 33 + 33];      ++v11;    }    *pOutLen2 = nlen - 5;  }  return 0;}

其实看到这里应该也是比较容易逆向分析出解密的算法的,应该说比较简单,可以直接写一个脚本来解密assets里的资源。但是为了保证通用性,还是写HOOK代码比较好。

本来分析以为最终都会调用_initWithWebpData、_initWithJpgData、_initWithBpgData、_initWithPngData、_initWithTiffData、_initWithRawData这些函数的,但是实际上分别HOOK后并没有被拦截,所以最后还是HOOK了下cocos2d::extra::CCCrypto::decryptUF

static string g_strDataPath;static int g_nCount = 1;string getNextFilePath(const char *fileExt) {    char buff[100] = {0};    ++g_nCount;    sprintf(buff, "%s/cache/%d%s", g_strDataPath.c_str(), g_nCount, fileExt);    return buff;}bool saveFile(const void* addr, int len, const char *outFileName){    bool bSuccess = false;    FILE* file = fopen(outFileName, "wb+");    if (file != NULL) {        fwrite(addr, len, 1, file);        fflush(file);        fclose(file);        bSuccess = true;        chmod(outFileName, S_IRWXU | S_IRWXG | S_IRWXO);    }else{        LOGE("[%s] fopen failed, error: %s", __FUNCTION__, dlerror());    }    return bSuccess;}//hook decryptUFint (*decryptUF_orig)(void *pInBuff, int len, int *n, int *poutlen, char *name) = NULL;int decryptUF_mod(void *pInBuff, int len, int *n, int *poutlen, char *name) {    int ret = decryptUF_orig(pInBuff, len, n, poutlen, name);    saveFile(pInBuff, *poutlen, getNextFilePath(".png").c_str());    return ret;}void hook() {    //hook decryptUF    void *decryptUF = dlsym(handle, "_ZN7cocos2d5extra8CCCrypto9decryptUFEPhiPiS3_");    if ( decryptUF==NULL ) {        LOGE("[dumplua] _ZN7cocos2d5extra8CCCrypto9decryptUFEPhiPiS3_ (decryptUF) not found!");        LOGE("[dumplua] dlsym err: %s.", dlerror());    }else{        LOGD("[dumplua] _ZN7cocos2d5extra8CCCrypto9decryptUFEPhiPiS3_ (decryptUF) found!");        MSHookFunction(decryptUF, (void *)&decryptUF_mod, (void **)&decryptUF_orig);    }}

我这里图方便把所有解密的数据都DUMP为/data/data/packagename/cache目录下扩展名为PNG的文件了,最后通过脚本从手机中批量提取出解密后的文件:

#coding:utf-8import osfor i in range(1, 10000):    cmd = 'adb pull /data/data/com.youzu.android.snsgz/cache/' + str(i) +'.png' + ' e:\\test'    os.system(cmd)

image
其实通过上面的分析就可以知道,图片资源的解密和Lua的解密都是调用了相同的函数,因此解密出的文件不全是图片,还有写LuaJit的脚本文件,用十六进制编辑器打开就可以看到LJ开头的魔法数字。

全民水浒

全民水浒这个比较简单,资源直接没加密处理,解压缩APK文件就可以在assets目录下查看了。Lua脚本可以通过HOOK函数luaL_loadbuffer获得,而且可以看出只是对编译的Lua脚本做了简单的加密,可以直接DUMP出来,相对少年三国志稍微弱了一些。

01-05 20:04:16.569 17729-17886/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: require "UpdateScene.lua" lua: require "UpdateScene.lua"01-05 20:04:16.574 17729-17886/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: UpdateScene.lua lua: LuaQ01-05 20:04:16.574 17729-17886/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: Modal.lua lua: LuaQ01-05 20:04:16.574 17729-17886/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: UIDefine.lua lua: LuaQ01-05 20:04:16.574 17729-17886/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: MessageBox.lua lua: LuaQ01-05 20:04:16.579 17729-17886/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: SoundManager.lua lua: LuaQ01-05 20:04:16.579 17729-17886/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: SoundConfig.lua lua: LuaQ
1 0
原创粉丝点击