lua热更新
很多游戏和软件都是用了lua热更新技术,以前对lua热更新不太熟悉,前段时间刚好帮同事个小忙就研究了一下,很多都是比较浅显,有错误还要读者指出。lua作为热更新包还是有很多优势的,首先lua性能算是比较高的,虽然比着C的性能还差一些,但是相当不错。其次lua比较好写,对于迭代比较高的一些游戏业务或者其他需要反复开发的业务都很适合。另外lua很容易内嵌到C语言中,简直是无缝结合,所以很多厂商用lua脚本来写那些需要快速迭代的业务。
lua、luac、luaJIT三种文件的关系
基于lua的更新包一般有三种文件:lua、luac、luaJIT。lua是明文代码,直接用记事本就能打开;luac是lua编译后的字节码,文件头为
0x1B 0x4C 0x75 0x61 0x51
lua虚拟机能够直接解析lua和luac脚本文件;而luaJIT是另一个lua的实现版本,JIT是指Just-In-Time(即时解析运行),luaJIT比lua和luac更加高效,文件头是
0x1B 0x4C 0x4A。
一般情况加,厂商是不会直接来把lua脚本明文加载到apk包内的,因为apk包很容易解开拿到lua明文。厂商一般会使用三种方式来加密lua脚本
- 加密lua脚本,在加载lua脚本前来解密lua脚本,最后运行lua脚本。
- 首先编译lua脚本生成luac字节码,然后再加密字节码,最后在加载前解密然后加载字节码。
- 最后是修改lua虚拟机中的opcode的顺序,有点像重新映射lua虚拟机执行指令。
好啦!基本层面介绍完开始实战。首先拿游戏开刀因为很多游戏都是用cocos2d开发的,cocos2d自带lua接口方便开发,所以很多游戏的业务逻辑都是在lua脚本里面,所以第一时间拿到lua脚本基本就是成功了一大半。
逆向
1. 解压apk
下载待解密apk包,具体哪款我就不具体指出了,简单粗暴把apk包用解压工具直接解压,一般情况下会得到文件夹结构
一般lib目录里面是存放的打包的整个游戏的动态库,如图所示:
下一步就要对这个动态库进行挖矿了,里面有所有想要的东西。
2. 反编译工具
反编译工具还是挺多的,我使用的IDAQ这个工具,很好用推荐。直接可以将静态库和动态库进行反编译,得到内部各个段的数据,同时IDAQ工具可以将反编译的汇编代码逆编译到C语言,虽然只是部分近似C语言代码,但是对于逆向已经是非常完美的工具。
-
对于找到的lua动态库进行反编译。使用这个工具是很简单的。教程不细说了,google一下。反编译后的汇编界面如下图所示:
3.分析待破解文件
在apk的解压包内开始找apk可能加载的lua文件。一般apk的文件会在assets目录下,尽情的找吧!首先找到了一些.lua后缀的文件,很明显这可能是主要的lua程序入口。
继续翻找又找到了一些package包,看起来很想游戏的加载包。
首先我们尝试打开.lua文件。
不出所料加密了,很沮丧,没事加密了才有挑战。我们尝试打开package包看看,
同样加密了,但是我们对比了多个package包,发现首字母都是]6这两个字符,这说明有可能说明未加密的数据前面是有一个固定的头的,这让我想起了压缩包格式,常见的zip,tar,tar.gz,rar等压缩包其实都有固定的标识。当然很多厂商也确实会把文件先打成压缩包,毕竟会节省一部分空间。现在就比较尴尬了。我们是不知道这个文件是用的什么算法加密的,秘钥也不知道。我们该想办法得到加密方式和秘钥。
4.逆向分析
前面讲过,lua加载无外乎哪几种方式,但是不管什么方式最后在运行时都要lua代码或者字节码。所以我们要想办法找到解密文件的地方。
首先我们要了解cocos2d引擎加载lua脚本的过程,这样我们才能顺着整个路径走。google到cocos启动的入口函数和普通常规代码在AppDelegate.cpp文件下如下:
bool AppDelegate::applicationDidFinishLaunching()
{
// set default FPS
Director::getInstance()->setAnimationInterval(1.0 / 60.0f);
// register lua module
auto engine = LuaEngine::getInstance();
ScriptEngineManager::getInstance()->setScriptEngine(engine);
lua_State* L = engine->getLuaStack()->getLuaState();
lua_module_register(L);
register_all_packages();
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 (COCOS2D_DEBUG > 0) && (CC_CODE_IDE_DEBUG_SUPPORT > 0)
// NOTE:Please don't remove this call if you want to debug with Cocos Code IDE
auto runtimeEngine = RuntimeEngine::getInstance();
runtimeEngine->addRuntime(RuntimeLuaImpl::create(), kRuntimeEngineLua);
runtimeEngine->start();
#else
//默认调用src/main.lua代码
if (engine->executeScriptFile("src/main.lua"))
{
return false;
}
#endif
return true;
}
逻辑通熟易懂,可以看到入口函数applicationDidFinishLaunching 中完成初始化lua虚拟机完成后,然后调用了main.lua进入业务逻辑。所以可以顺着这条路走下去。 在IDAQ的函数栏查找applicationDidFinishLaunching函数。然后反编译生成代码。
好了就要按着耐心查找入口代码了,先分析MainManagerCpp::setupLuaBinding(v13, v11);这句话还是在初始化lua引擎的工作,说明加载理论上用在在这句话后面,好吧小哥开始找吧!最终找到了这个函数:这个是设置秘钥的函数,后面的长字符串就是秘钥。
有了秘钥肯定有地方调用解密,好吧,继续查找发现了这个函数
继续点击进去发现了这个关键字rc4:
到这地方基本就很清楚了,该代码在加载lua文件的时候先解密了文件,使用了rc4加解密算法,秘钥是上面的字符串,是不是很激动。
5.解密文件
1. 解密lua文件
有了秘钥和加密方式,那就开始左手解密,rc4流加密算法介绍可以看一下,秘钥长度可变,上面获取的秘钥是128位的秘钥。rc4的加解密算法网上可以拿到实现,rc4加密算法,加密一次是加密,再次加密就是解密,很有意思,拿到算法实现直接写一个加解密工具。对上面的文件进行解密。获取到main.lua文件解密后的代码截图:
有没有很激动。
2. 解密package包
同样对pakeage包进行解密,获得原始二进制文件,打开文件还是乱码,如下图:
但是注意箭头处的两个字符PK这两个字符,ASCII字符值为0x50,0x4B,通过查询文件压缩头发现zip压缩的头表示符为0x04034b50,zip文件格式,随即确定为zip压缩格式,直接按zip文件解压得到了一下文件结构:
作为lua文件直接打开得到包内核心lua业务代码,转账银行代码:
好了至此,全部文件解密,可以一窥核心逻辑代码,想怎么修改就怎么修改。
小结
本文详细介绍了如果逆向一个基于lua热更新游戏的包,获取了整个解密后的lua代码。如果你收本文启发可以尝试破解其他游戏,关闭碰撞检测来个子弹穿墙没问题,哈哈,切记任何破解违法行为与本作者无关,只供交流学习。