00003 不思议迷宫.0001:解密Lua脚本



00003 不思议迷宫.0001:解密Lua脚本

《不思议迷宫》是我想要重点研究的游戏之一。之所以说是重点,是因为我最近一直在玩它,并且花的时间还比较多。虽然严格来说,它并不怎么样。主要的原因是,我自己手贱,在这个游戏上花钱了。这对我这个吊死来说简直不可思议。既然花钱了那就继续玩吧。玩了几天就发现特累,每次副本都得n个小时,而且收获感觉也不怎么样。这个游戏号称能让人“肝到爆”,也就体现在这个副本时间上了。游戏有其不错的地方,但也有不少槽点,甚至是常识性错误。当我感觉到累和不爽之后,就开始研究它,看能不能改改。结果才发现,这居然是个网络游戏、网络游戏、网络游戏。玩家数据这就没办法直接改了,那就继续研究看看吧,也许能够找到什么bug来间接改呢。我的这个愿望落空了,但是我还是想把一些过程和经验给分享出来。另外,我会试图修正游戏中的那些槽点,然后将修正后的文件共享给大家。修正槽点将会消耗较长的时间,中途可能会出现各种意外,游戏停服、游戏大版本更新并加强了程序保护、我的技术储备不够改不了、游戏我腻得透透的不玩也就算了甚至连看一眼都恶心、我失去耐心不改了。总之,对修正槽点大家不要抱太大的期望就是了。

弄出“不思议迷宫.ipa”,解压,到“Payload\slime.app\”目录,我一眼就看到了“res”和“src”这两个目录。我知道cocos2d捣鼓出来的游戏默认会把资源放到res目录中。src从名字看就是源码之类的,进入一看,果然是。

源码是luac文件,也就是说,游戏是cocos2dx-lua开发的。随便打开一个luac文件,一看,意料之中的情况——不是明文:

00003 不思议迷宫.0001:解密Lua脚本_第1张图片

像乱码又不是乱码——被某种形式地加密了。老方法,下载安卓版游戏,解压,找到libcocos2dlua.so,祭出ida反编译。

cocos2dx-lua在加载lua脚本时,要么使用默认的cocos2dx_lua_loader函数(包括用户扩展版),要么自己创建一个。

先试试查找cocos2dx_lua_loader函数,居然就找到了……来得太容易了,都不怎么真实。游戏公司大概是觉得这个网络游戏玩家是没办法直接修改玩家数据的,也就不怎么在意了吧。直接就找到了固然是幸福的,但万一没找到呢,该怎么办?

好在默认的cocos2dx_lua_loader函数会引用文件名后缀“.lua”和“.luac”。正常情况下,前者表示普通的lua文件,后者表示lua字节码文件(LuaJIT生成)。万一通过函数名称找不到,那就可以试着跟踪字符串“.lua”和“.luac”的引用。引用可能会非常多,这个时候就需要耐心了,或者根据具体情况,看看能不能从其他的字符串入手。

得到的cocos2dx_lua_loader伪码很长,但我们还是一眼就定位了一个非常可疑的函数调用,就在局部变量之后的第二行,想不发现都难:

signed int__fastcall cocos2dx_lua_loader(int a1)

{

  ……

  一大堆局部变量

  ……

 

  v1 = a1;

  buildEncryptMap();

  if ( !(byte_9B4E5C & 1) &&j_j___cxa_guard_acquire(&byte_9B4E5C))

  {

    sub_63E9BC((int)&dword_9B4E58,(int)".luac", (int)&v57);

    j_j___cxa_atexit(sub_7856B8,&dword_9B4E58, &unk_9A3000);

    j_j___cxa_guard_release(&byte_9B4E5C);

  }

  if ( !(byte_9B4E64 & 1) &&j_j___cxa_guard_acquire(&byte_9B4E64) )

  {

    sub_63E9BC((int)&dword_9B4E60,(int)".lua", (int)&v56);

    j_j___cxa_atexit(sub_7856B8,&dword_9B4E60, &unk_9A3000);

    j_j___cxa_guard_release(&byte_9B4E64);

  }

  v2 = j_j_luaL_checklstring(v1, 1, 0);

……

真的,幸福来得太容易了,太不真实了。

跟进buildEncryptMap看看:

void *buildEncryptMap()

{

  void *result; // r0@1

  unsigned int v1; // r1@1

 

  result = (void *)0xFFFFB65C;

  v1 = 0;

  if ( !charMapInited )

  {

    do

    {

      charMapList[charMaps[v1 + 1]] = charMaps[v1];

      v1 += 2;

    }

    while ( v1 < 0x90 );

    result = &charMapInited;

    charMapInited = 1;

  }

  return result;

}

这还需要多解释吗?我们只需要弄清楚charMaps就可以了。

跟进,查看charMaps的定义,一点保护的手段都没有:

.data:009A3018   EXPORT charMaps

.data:009A3018 ; _BYTE charMaps[144]

.data:009A3018 charMaps    DCB 0x61, 0x67, 0x62, 0x78, 0x63, 0x77,0x64, 0x76, 0x65

.data:009A3018   ; DATA XREF: .got:charMaps_ptro

.data:009A3018   DCB 0x66, 0x66, 0x75, 0x67, 0x74, 0x68, 0x7A, 0x69, 0x73

.data:009A3018   DCB 0x6A, 0x68,0x6B, 0x65, 0x6C, 0x69,0x6D, 0x79, 0x6E

.data:009A3018   DCB 0x72, 0x6F, 0x64,0x70, 0x63, 0x71, 0x71, 0x72, 0x62

.data:009A3018   DCB 0x73, 0x6A, 0x74,0x70, 0x75, 0x6B, 0x76, 0x61, 0x77

.data:009A3018   DCB 0x6C, 0x78,0x6D, 0x79, 0x6F, 0x7A, 0x6E, 0x41, 0x4F

.data:009A3018   DCB 0x42, 0x4D, 0x43, 0x45, 0x44, 0x4E, 0x45, 0x50, 0x46

.data:009A3018   DCB 0x44, 0x47, 0x51, 0x48, 0x4B, 0x49, 0x43, 0x4A, 0x4C

.data:009A3018   DCB 0x4B, 0x53, 0x4C,0x55, 0x4D, 0x4A, 0x4E,0x42, 0x4F

.data:009A3018   DCB 0x52, 0x50, 0x54, 0x51, 0x41, 0x52, 0x49, 0x53, 0x47

.data:009A3018   DCB 0x54, 0x46, 0x55, 0x5A,0x56, 0x58, 0x57, 0x57, 0x58

.data:009A3018   DCB 0x56, 0x59, 0x48, 0x5A,0x59, 0x30, 0x33, 0x31, 0x39

.data:009A3018   DCB 0x32, 0x35, 0x33, 0x32, 0x34, 0x34, 0x35, 0x30, 0x36

.data:009A3018   DCB 0x37, 0x37, 0x31, 0x38, 0x36, 0x39, 0x38, 0x7B, 0x28

.data:009A3018   DCB 0x7D, 0x29, 0x28, 0x3A,0x29, 0x7B, 0x2E, 0x7D, 0x3A

.data:009A3018   DCB 0x20, 0x20, 0x5F,0x5F, 0x2C, 0x2C, 0xA, 0xA, 0x2E

接下来干什么?打开VC建工程、编写自己的buildEncryptMap,然后解密。——等等,src目录下的luac文件是utf8编码的,并且没有BOM头,还是换成VC#好了。

        privatestaticbyte[]charMaps = new byte[144]

        {

           0x61, 0x67, 0x62, 0x78, 0x63, 0x77, 0x64, 0x76, 0x65

           ,0x66, 0x66, 0x75, 0x67, 0x74, 0x68, 0x7A, 0x69, 0x73

           ,0x6A, 0x68,0x6B, 0x65, 0x6C, 0x69,0x6D, 0x79, 0x6E

           ,0x72, 0x6F, 0x64,0x70, 0x63, 0x71, 0x71, 0x72, 0x62

           ,0x73, 0x6A, 0x74,0x70, 0x75, 0x6B, 0x76, 0x61, 0x77

           ,0x6C, 0x78,0x6D, 0x79, 0x6F, 0x7A, 0x6E, 0x41, 0x4F

           ,0x42, 0x4D, 0x43, 0x45, 0x44, 0x4E, 0x45, 0x50, 0x46

           ,0x44, 0x47, 0x51, 0x48, 0x4B, 0x49, 0x43, 0x4A, 0x4C

           ,0x4B, 0x53, 0x4C,0x55, 0x4D, 0x4A, 0x4E,0x42, 0x4F

           ,0x52, 0x50, 0x54, 0x51, 0x41, 0x52, 0x49, 0x53, 0x47

           ,0x54, 0x46, 0x55, 0x5A,0x56, 0x58, 0x57, 0x57, 0x58

           ,0x56, 0x59, 0x48, 0x5A,0x59, 0x30, 0x33, 0x31, 0x39

           ,0x32, 0x35, 0x33, 0x32, 0x34, 0x34, 0x35, 0x30, 0x36

            ,0x37,0x37, 0x31, 0x38, 0x36, 0x39, 0x38, 0x7B, 0x28

           ,0x7D, 0x29, 0x28, 0x3A,0x29, 0x7B, 0x2E, 0x7D, 0x3A

           ,0x20, 0x20, 0x5F,0x5F, 0x2C, 0x2C, 0xA, 0xA, 0x2E

        };

        privatestaticboolcharMapInited = false;

        privatestaticbyte[]encodedToPlain = new byte[256];

        privatestaticbyte[]plainToEncoded = new byte[256];

        privatestaticvoidbuildEncryptMap()

        {

            if(!charMapInited)

            {

               int v1 = 0;

                do

               {

                   encodedToPlain[charMaps[v1 + 1]] = charMaps[v1];

                   plainToEncoded[charMaps[v1]] = charMaps[v1 + 1];

                   v1 += 2;

               }

               while (v1 < 0x90);

                charMapInited = true;

            }

        }

        privatestaticcharToPlain(char v)

        {

           buildEncryptMap();

            if(v >= 256) return v;

            if(encodedToPlain[v] == 0) return v;

            return(char)encodedToPlain[v];

        }

        privatestaticcharToEncoded(char v)

        {

           buildEncryptMap();

            if(v >= 256) return v;

            if(plainToEncoded[v] == 0) return v;

            return(char)plainToEncoded[v];

        }

        privatestaticstringToPlain(string v)

        {

            char[]r = new char[v.Length];

            for(inti = 0; i < v.Length;++i )r[i]= ToPlain(v[i]);

            returnnewstring(r);

        }

        privatestaticstringToEncoded(string v)

        {

            char[]r = new char[v.Length];

            for(inti = 0; i < v.Length; ++i) r[i] = ToEncoded(v[i]);

            returnnewstring(r);

    }

然后,利用上面的几个函数批量转换整个src目录。打开解密后的Config.luac看看内容。用“记事本”打开,得,没有换行;用“写字板”打开,我去,中文乱码;VS,唉,转换时没有写入BOM头,VS不认识,当二进制显示了。Ultra Edit吧。

-- Config

-- create by zouyb

-- 服务器配置

 

-- SERVER_IP = "127.0.0.1";

-- SERVER_PORT = "8001";

 

CC_IP   =  CC_IP or "gumballslogin.leiting.com"

CC_PORT =  CC_PORTor "8050"

 

-- 默认区组id

DEFAULT_AAA_ID = 1;

 

-- dislist下载链接

DISLIST_URL_ARR = {

   "http://update.leiting.com/gumballs/leiting/ios/dislist/dislist_ios.json",

    "http://10.2.49.75:8080/slime/leiting/ios/dislist/dislist.json",

    "",

    "",

    "",

}

 

-- dislist测试标签

DISLIST_TEST_TAG = "0.5.161125.05"

 

-- 公告下载链接

OUT_BOARD_URL ="http://update.leiting.com/gumballs/leiting/ios/dislist/out_board.json"

 

-- WEB公告下载链接

WEB_BOARD_URL ="http://update.leiting.com/gumballs/leiting/ios/web_board"

 

-- 文件缺失上报地址

MISSING_FILE_URL ="http://update.leiting.com/gumballs/missing_report/missing.php"

 

-- 更新失败上报地址

DOWNLOAD_ERROR_REPORT_URL ="http://update.leiting.com/gumballs/download_report/error.php"

 

-- 快捷登录

--EXPRESS_LOGIN = 1

 

-- 调试模式

DEBUG_MODE = 0

 

-- 是否允许print

ENABLE_PRINT = 0

 

-- release日志输出开关

RELEASE_LOG_ENABLED = 0

 

-- 支持版本号(由version.py自动生成)

SUPPORT_VERSION = "0.5.161120.05"

 

-- 需求引擎版本号

REQUIRE_ENGINE_VERSION = "2"

 

-- 是否测试怪物模型

TEST_MONSTER_MODEL = 0;

 

-- 是否略过炼金引导

SKIP_WORKSHOP_GUIDE = 0;

 

-- 是否略过新手关卡引导

SKIP_DUNGEON_GUIDE = 0;

 

-- 功能全开

ALL_FEATURE_OPEN = 0;

 

-- 显示AUTOPATCHLOG

-- SHOW_AUTO_PATCH_LOG = 0;

 

-- 允许切换语言版本

ALLOW_TO_SWITCH_LANG = 0;

 

-- 获取不到系统语言后/默认语言

DEFAULT_LANGUAGE = "en";

 

-- 强制更新完整包

FORCE_UPDATE_FULL_PKG = 0;

 

-- 运营平台

PUBLISH_PLATFORM = "lt"

src目录下的的luac文件有4000多个。我们需要关心的是game目录下的那些,不过也还是有3940多个。看起来很多,其实按照目录去理解,也就那么几个了。

l        base:一些基本工具,一般无需过问

l        cmd:和服务器的通信回调之类的

l        csv:这个不用说了吧,表格型数据都在这儿了

l        formula:各种计算

l        loc:语言包

l        logic:逻辑控制

l        operation:向服务器发送消息

l        scene:几种场景,基本用不上,想象成界面的容器就对了

l        sdk:这个不用管

l        ui:一些具体的界面

game目录下还有3个文件:

l        dislist.jsonc:一个json文件,一些起来了没用的发布信息?

l        PreloadList.luac:看说明:配置在客户端启动后,需要预先加载的内容。这样在后续就可以避免大量的IO读取。需要注意的是,预先加载时应该在比较闲空的页面(目前是准备开始游戏界面和登录界面)

l        StartGame.luac:看名字就知道了。

本章先介绍到这里。下一章我们介绍如何修改luac文件,来达到过过眼瘾的目的。比如,签到获得10万钻石。

你可能感兴趣的:(游戏破解技术研究,不思议迷宫)