分析目标

  • 少年三国志,包名: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 *) 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 copy
int (*luaL_loadbuffer_orig)(void *L,int size,const char *name) = NULL;

//local function
int luaL_loadbuffer_mod(void *L,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
              pri
01-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: LJA
01-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: LJP
01-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 decryptUF
int (*decryptUF_orig)(void *pInBuff,int *n,int *poutlen,char *name) = NULL;
int decryptUF_mod(void *pInBuff,char *name) {
    int ret = decryptUF_orig(pInBuff,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-8
import os

for i in range(1,10000):
    cmd = 'adb pull /data/data/com.youzu.android.snsgz/cache/' + str(i) +'.png' + ' e:\\test'
    os.system(cmd)


其实通过上面的分析就可以知道,图片资源的解密和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: LuaQ
01-05 20:04:16.574 17729-17886/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: Modal.lua lua: LuaQ
01-05 20:04:16.574 17729-17886/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: UIDefine.lua lua: LuaQ
01-05 20:04:16.574 17729-17886/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: MessageBox.lua lua: LuaQ
01-05 20:04:16.579 17729-17886/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: SoundManager.lua lua: LuaQ
01-05 20:04:16.579 17729-17886/? D/SUBSTRATEHOOK: [dumplua] luaL_loadbuffer name: SoundConfig.lua lua: LuaQ

cocos2dx-Lua引擎游戏脚本及图片资源解密与DUMP的更多相关文章

  1. 解析html5 canvas实现背景鼠标连线动态效果代码

    流行的动态背景连线特效。今天小编通过实例代码给大家解析html5 canvas实现背景鼠标连线动态效果,感兴趣的朋友一起看看吧

  2. 手把手教你swift项目添加cocos2dx-lua

    去倒杯水吧,这个过程会很久。。。至此,工程已经全部配置完毕,你已经解决一个大Boss了;今天就到这里吧,之后我们再续如何用swift调用lua手把手教你swift项目添加cocos2dx-lua

  3. 在android中引用javax.script.ScriptEngine或者评估一个javascript表达式

    如果没有,那么有可能在android中评估一个javascript表达式?

  4. PHP 脚本:随心所欲的代码逐渐流行

    有些人认为PHP代表“个人主页”。有些人认为PHP是其发明者姓名的首字母缩写。他公开发布了最初的超文本预处理器源代码,以帮助其它的Web开发人员对在线内容执行类似的操作。由于PHP的入门级这一特性,一个开放源代码社区围绕着该语言成长起来,以帮助PHP开发变得更加简单。所有这些结果表明在最近3年里PHP的使用得到了爆炸性的增长。

  5. SpringBoot+Redis+Lua分布式限流的实现

    本文主要介绍了SpringBoot+Redis+Lua分布式限流的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  6. 用node.js写一个jenkins发版脚本

    这篇文章主要介绍了用node.js写一个jenkins发版脚本,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  7. SpringBoot应用线上重启脚本的命令详解

    这篇文章主要介绍了SpringBoot应用线上重启脚本,通过查找应用进程PID,杀死应用进程PID,运行启动脚本,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  8. JSP简明教程:令人兴奋的脚本编程

    另外,通过使用JSP的指令,还可以包含非Java代码模块,比如来自其他文件的HTML文本。它们是page,include和taglib,必须写在JSP页的第一行。taglib指令用于扩充标准的JSP标签集,这超出了本文的讨论范围。注意,声明并不在JSP页内产生任何输出。要生成输出结果,你应该用JSP表达式或脚本片断。另一个简单的例子在下面的例子中,我们来看一看一个表单和它的JSP表单句柄之间的交互过程。

  9. 分享五个超实用Python脚本,减少垃圾软件负担

    今天给大家带来几个实用的python脚本工具,可以实现系统提示工具、文件夹清理工具等,这样就再也不用担心女朋友在电脑里安装垃圾软件啦

  10. 建立文件交换功能的脚本(一)

    因为工作的原因需要在局域网上安装一个文件交换的东西,也可以作成一个文件上下载的功能块。用的是php,mysql,apache现将程序编写过程贴出来,因为作这个参考来oso的一些文章,也算是一个补充,也表达我对前辈们的敬意。准备工作在你的mysql的yourdatabase库中建一个表upfile/*上传文件表CREATETABLEupfile*///这是该程序的主页面,用来显示上载的文件。//index.php文件交换

随机推荐

  1. 【cocos2d-x 3.x 学习笔记】对象内存管理

    Cocos2d-x的内存管理cocos2d-x中使用的是上面的引用计数来管理内存,但是又增加了一些自己的特色。cocos2d-x中通过Ref类来实现引用计数,所有需要实现内存自动回收的类都应该继承自Ref类。下面是Ref类的定义:在cocos2d-x中创建对象通常有两种方式:这两中方式的差异可以参见我另一篇博文“对象创建方式讨论”。在cocos2d-x中提倡使用第二种方式,为了避免误用第一种方式,一般将构造函数设为protected或private。参考资料:[1]cocos2d-x高级开发教程2.3节[

  2. 利用cocos2dx 3.2开发消灭星星六如何在cocos2dx中显示中文

    由于编码的不同,在cocos2dx中的Label控件中如果放入中文字,往往会出现乱码。为了方便使用,我把这个从文档中获取中文字的方法放在一个头文件里面Chinese.h这里的tex_vec是cocos2dx提供的一个保存文档内容的一个容器。这里给出ChineseWords,xml的格式再看看ChineseWord的实现Chinese.cpp就这样,以后在需要用到中文字的地方,就先include这个头文件然后调用ChineseWord函数,获取一串中文字符串。

  3. 利用cocos2dx 3.2开发消灭星星七关于星星的算法

    在前面,我们已经在GameLayer中利用随机数初始化了一个StarMatrix,如果还不知道怎么创建星星矩阵请回去看看而且我们也讲了整个游戏的触摸事件的派发了。

  4. cocos2dx3.x 新手打包APK注意事项!

    这个在编译的时候就可以发现了比较好弄这只是我遇到的,其他的以后遇到再补充吧。。。以前被这两个问题坑了好久

  5. 利用cocos2dx 3.2开发消灭星星八游戏的结束判断与数据控制

    如果你看完之前的,那么你基本已经拥有一个消灭星星游戏的雏形。开始把剩下的两两互不相连的星星消去。那么如何判断是GameOver还是进入下一关呢。。其实游戏数据贯穿整个游戏,包括星星消除的时候要加到获得分数上,消去剩下两两不相连的星星的时候的加分政策等,因此如果前面没有做这一块的,最好回去搞一搞。

  6. 利用cocos2dx 3.2开发消灭星星九为游戏添加一些特效

    needClear是一个flag,当游戏判断不能再继续后,这个flag变为true,开始消除剩下的星星clearSumTime是一个累加器ONE_CLEAR_TIME就是每颗星星消除的时间2.连击加分信息一般消除一次星星都会有连击信息和加多少分的信息。其实这些combo标签就是一张图片,也是通过控制其属性或者runAction来实现。源码ComboEffect.hComboEffect.cpp4.消除星星粒子效果消除星星时,为了实现星星爆裂散落的效果,使用了cocos2d提供的粒子特效引擎对于粒子特效不了

  7. 02 Cocos2D-x引擎win7环境搭建及创建项目

    官网有搭建的文章,直接转载记录。环境搭建:本文介绍如何搭建Cocos2d-x3.2版本的开发环境。项目创建:一、通过命令创建项目前面搭建好环境后,怎样创建自己的Cocos2d-x项目呢?先来看看Cocos2d-x3.2的目录吧这就是Cocos2d-x3.2的目录。输入cocosnew项目名–p包名–lcpp–d路径回车就创建成功了例如:成功后,找到这个项目打开proj.win32目录下的Hello.slnF5成功了。

  8. 利用cocos2dx 3.2开发消灭星星十为游戏添加音效项目源码分享

    一个游戏,声音也是非常的重要,其实cocos2dx里面的简单音效引擎的使用是非常简单的。我这里只不过是用一个类对所有的音效进行管理罢了。Audio.hAudio.cpp好了,本系列教程到此结束,第一次写教程如有不对请见谅或指教,谢谢大家。最后附上整个项目的源代码点击打开链接

  9. 03 Helloworld

    程序都有一个入口点,在C++就是main函数了,打开main.cpp,代码如下:123456789101112131415161718#include"main.h"#include"AppDelegate.h"#include"cocos2d.h"USING_NS_CC;intAPIENTRY_tWinMain{UNREFERENCED_ParaMETER;UNREFERENCED_ParaMETER;//createtheapplicationinstanceAppDelegateapp;return

  10. MenuItemImage*图标菜单创建注意事项

    学习cocos2dx,看的是cocos2d-x3.x手游开发实例详解,这本书错误一大把,本着探索求知勇于发现错误改正错误的精神,我跟着书上的例子一起调试,当学习到场景切换这个小节的时候,出了个错误,卡了我好几个小时。

返回
顶部