Android⼿游lua脚本的加密与解密
2018.05.02更新
这段时间在翻备份的硬盘,突然发现了以前的分析项⽬和代码,从⾥⾯提取了之前附件的内容,现在上传给⼤家,真是柳暗花明⼜⼀村啊。附件包括201703版本的梦幻⼿游⾥⾯提取的so⽂件和⼀些加密后的资源⽂件(包括lua脚本),并包括了2个扑鱼APK⽂件,最后还打包了解密代码,供⼤家参考。
附件太⼤,快100MB,上传不来论坛,我⼜放到百度⽹盘了......
2018.04.09更新
附件是真的不到了, ⼤家主要理解思路吧。百度⽹盘的附件好多朋友都下载和保存过,能不能发⼀份到论坛上传?感谢感谢~
2017.04.15更新
1. 在编辑过程中,5.1后半段内容(解密和反编译部分)被删除了,现在补上。
2. 在
3.3⾥说到,“修改lua项⽬中的opcode后,编译⽣成再替换到反编译⽬录下,就可以反编译”,这⼀句是错误的,正确
是“修改lua项⽬中的opcode后,重新编译反编译⼯具luadec51项⽬,就可以反编译了”,已经修改。
0.前⾔
这篇⽂章是本⼈在学习android⼿游安全时总结的⼀篇关于lua的⽂章,不⾜之处欢迎指正,也欢迎⼤⽜前来交流。本⽂⽬录如下:
⽬录
0. 前⾔
1. lua脚本在⼿游中的现状
2. lua、luac、luaJIT三种⽂件的关系
3. lua脚本的保护
3.1 普通的对称加密,在加载脚本之前解密
3.2 将lua脚本编译成luaJIT字节码并且加密打包
3.3 修改lua虚拟机中opcode的顺序
4. 获取lua代码的⼀般⽅法
4.1 静态分析so解密⽅法
4.2 动态调试:ida + idc + dump
4.3 hook so
4.4 分析lua虚拟机的opcode的顺序
5. 三个游戏的lua脚本解密过程
5.1 54捕鱼
5.2 捕鱼达⼈4
5.3 梦幻西游⼿游
6. 总结
参考⽂章
主要⽤到的⼯具和环境:
1 win7系统⼀枚
2 quick-cocos2d-x的开发环境(弄⼀个开发环境⽅便学习,⽽且⼤部分lua⼿游都是⽤的cocos2d-x框架,还有⼀个好处,可以查看源码关键函数中的特征字符串,然后在IDA定位到关键函数,⾮常⽅便)
3 IDA6.8(分析so⽂件+动态调试so)
4 vs2015(编写解密代码)这⾥建议⽤vs2013来编译运⾏cocos2d-x,vs2015太多坑要填了.....
5 AndroidKiller 1.3.1(反编译apk,其中是最新版)
6 luadec51(反编译luac)
7 luajit-decomp(反编译luaJIT)
等等...
1.lua脚本在⼿游中的现状
略。
2.lua、luac、luaJIT三种⽂件的关系
在学习lua⼿游过程中,本⼈遇到的lua⽂件⼤部分是这3种。其中lua是明⽂代码,直接⽤记事本就能打开,luac是lua编译后的字节码,⽂件头为0x1B 0x4C 0x75 0x61 0x51,lua虚拟机能够直接解析lua和luac脚本⽂件,⽽luaJIT是另⼀个lua的实现版本(不是原作者写的),JIT是指Just-In-Time(即时解析运⾏),luaJIT相⽐lua和luac更加⾼效,⽂件头是0x1B 0x4C 0x4A。
luac:
luajit:
3.lua脚本的保护
⼀般有安全意识的游戏⼚商都不会直接把lua源码脚本打包到APK中发布,所以⼀般对lua脚本的保护有下⾯3种:
3.1 普通的对称加密,在加载脚本之前解密
这种情况是指打包在APK中的lua代码是加密过的,程序在加载lua脚本时解密(关键函数luaL_loadbuffer ),解密后就能够获取lua源码。如果解密后获取的是luac字节码的话,也可以通过反编译得到lua源码,反编译主要⽤的⼯具有unluac和luadec51,后⾯会具体分析。
3.2 将lua脚本编译成luaJIT字节码并且加密打包
因为反编译的结果并不容易查看,所以这种情况能够较好的保护lua源码。这个情况主要是先解密后反编译,反编译主要是通过luajit-decomp项⽬,它能够将luajit字节码反编译成伪lua代码。
3.3 修改lua虚拟机中opcode的顺序
这种情况主要是修改lua虚拟机源码,再通过修改过的虚拟机将lua脚本编译成luac字节码,达到保护的⽬的。这种情况如果直接⽤上⾯的反编译⼯具是不能将luac反编译的,需要在程序中分析出相对应的opcode,然后修改lua项⽬的opcode的顺序并重新编译⽣成反编译⼯具,就能反编译了,后⾯会具体分析。
⼀般上⾯的情况都会交叉遇到。
4.获取lua源码的⼀般⽅法
这⾥主要介绍4种⽅法,都会在第5节中⽤实例说明。
4.1 静态分析so解密⽅法
这种⽅法需要把解密的过程全部分析出来,⽐较费时费⼒,主要是通过ida定位到luaL_loadbuffer函数,然后往上回溯,分析出解密的过程。
4.2 动态调试:ida + idc + dump
这⾥主要通过ida动态调试so⽂件,然后是定位到luaL_loadbuffer地址,游戏会在启动的时候通过调⽤luaL_loadbuffer函数加载必要的lua脚本,通过在luaL_loadbuffer下断点 ,断下后就可以运⾏idc脚本将lua代码导出(程序调⽤⼀次luaL_loadbuffer加载⼀个lua脚本,不写idc脚本的话需要⼿动导N多遍.....)。
4.3 hook so
跟4.2原理⼀样,就是通过hook函数luaL_loadbuffer地址,将代码保存,相⽐4.2的好处是有些lua脚本需要在玩游戏的过程中才加载,如果⽤了4.2的⽅法,游戏过程中 中断⼀次就需要⼿动运⾏⼀次idc脚本,⽽且往往每次只加载⼀个lua⽂件,如果是hook的话,就不需要那么⿇烦,直接玩⼀遍游戏,全部lua脚本就已经保存好了。
4.4 分析lua虚拟机的opcode的顺序
这⾥主要是opcode的顺序被修改了,需要⽤ida定位到虚拟机执⾏luac字节码的地⽅,然后对⽐原来lua虚拟机的执⾏过程,获取修改后的opcode顺序,最后还原lua脚本。
5.三个游戏的lua脚本解密实例
好了,下⾯⽤3个例⼦来说明上⾯的情况。
5.1 54捕鱼
⾸先⽤AndroidKiller 加载,然后查看lib⽬录下的so⽂件,发现libcocos2dlua.so⽂件,基本可以确定是lua脚本编写的了。这⾥有个⼩技巧,当有很多so⽂件的时候,⼀般最⼤的⽂件是我们的⽬标(⽂件⼤是因为集成了lua引擎)。既然有lua引擎,肯定有lua脚本了,接着lua脚本。资源⽂件和lua脚本⽂件都是在assets⽬录下。发现游戏的资源⽂件和配置⽂件都是明⽂,这⾥直接修改游戏的配置⽂件就可
以作弊(⽐如修改升级炮台所需的⾦币和钻⽯,就可以达到快速升级炮台的⽬的),然后并没有发现类似lua脚本的⽂件。
顺⼿解压了⼀下res⽬录下的liveupdate_precompiled.zip,发现解压失败,看来是加密了(看名字就知道是更新游戏的代码)这⾥说明⼀下,⼀般遇到xxxx_precompiled.zip的这种⽂件,都是quick-cocos2d-x框架(quick简单来说就是对lua的拓展实现),在quick-cocos2d-x框架下可以⽤compile_scripts命令将lua⽂件加密打包成xxxx_precompiled.zip,游戏运⾏时再解密加载。注意,这种⽅式打包的lua脚本⼀般都会被编译成luaJIT,加载的关键函数是loadChunksFromZIP,可以在IDA中直接搜索该函数,如果不到可以搜索字符串luaLoadChunksFromZIP来定位到函数
OK,了解了原理接下来开始动⼿分析,将libcocos2dlua.so拖到IDA中加载,函数中直接搜索loadChunksFromZIP,定位后F5。
⼀直向上回溯(交叉引⽤ ),来到下图,发现解密的密钥和签名,其中xiaoxian为密钥,XXFISH为签名
进去函数⾥⾯看看,其实会发现调⽤了XXTea算法,这⾥我们也可以直接分析loadChunksFromZIP函数的源码(所以配置⼀个cocos2d的开发环境还是⾮常有必要的)。查看源码⾥的lua_loadChunksFromZIP函数的原型:
1
2
3
4
5
6
7
8
9
10
11
12int  CCLuaStack::lua_loadChunksFromZIP(lua_State *L){    if  (lua_gettop(L) < 1)    {    // 这⾥可以发现⽤字符串也可以定位到⽬标函数        CCLOG("lua_loadChunksFromZIP() - invalid arguments");        return  0;    }...        if  (isXXTEA)        {            // decrypt XXTEA
13 14 15 16 17 18 19 20 21 22 23 24            // 这⾥调⽤了解密函数
xxtea_long len = 0;
buffer = xxtea_decrypt(zipFileData + stack->m_xxteaSignLen,
(xxtea_long)size - (xxtea_long)stack->m_xxteaSignLen,                                  (unsigned char*)stack->m_xxteaKey,
(xxtea_long)stack->m_xxteaKeyLen,
&len);
delete[]zipFileData;
zipFileData = NULL;
zip = CCZipFile::createWithBuffer(buffer, len);
}
...
}
接下来直接写解密函数(在cocos2d-x项⽬⾥⾯写的解密函数,很多⼯具直接可以调⽤)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33void decryptZipFile_54BY(string strZipFilePath)
{
CCFileUtils *utils = CCFileUtils::sharedFileUtils();
unsigned long lZipFileSize = 0;
unsigned char*szBuffer = NULL;
unsigned char*zipFileData = utils->getFileData(strZipFilePath.c_str(), "rb", &lZipFileSize);        xxtea_long xxBufferLen = 0;
szBuffer = xxtea_decrypt(zipFileData + 6,          //6为签名XXFISH的长度
android最新版
(xxtea_long)lZipFileSize - (xxtea_long)6,    //减去签名的长度
(unsigned char*)"xiaoxian",                  //xiaoxian为密钥
(xxtea_long)8,                              //密钥的长度
&xxBufferLen);
//获取zip⾥⾯的所有⽂件
CCZipFile *zipFile = CCZipFile::createWithBuffer(szBuffer, xxBufferLen);
int count = 0;
string strFileName = zipFile->getFirstFilename();
while(strFileName.length())
{
cout << "filename:"<< strFileName << endl;
unsigned long lFileBufferSize = 0;
unsigned char*szFileBuffer = zipFile->getFileData(strFileName.c_str(), &lFileBufferSize);              if(lFileBufferSize)
{
++count;
ofstream ffout(strFileName, ios::binary);
ffout.write((char*)szFileBuffer, sizeof(char) * (lFileBufferSize));
ffout.close();
delete[] szFileBuffer;
}
strFileName = zipFile->getNextFilename();
}
delete[] zipFileData;
}
解密后的⽂件如下:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。