浅析android手游lua脚本的加密与解密
2018.05.02更新
这段时间在翻备份的硬盘,突然发现了以前的分析项目和代码,从里面提取了之前附件的内容,现在上传给大家,真是柳暗花明又一村啊。附件包括201703版本的梦幻手游里面提取的so文件和一些加密后的资源文件(包括lua脚本),并包括了2个扑鱼APK文件,最后还打包了解密代码,供大家参考。
附件太大,快100MB,上传不来论坛,我又放到百度网盘了......
链接:https://pan.baidu.com/s/1DVgH0qHYPkiHBIiV2UsU7g 密码:ipt3
2018.04.09更新
附件是真的找不到了, 大家主要理解思路吧。百度网盘的附件好多朋友都下载和保存过,能不能发一份到论坛上传?感谢感谢~
2017.04.15更新
1. 在编辑过程中,5.1后半段内容(解密和反编译部分)被删除了,现在补上。
2. 在3.3里说到,“修改lua项目中的opcode后,编译生成lua.exe再替换到反编译目录下,就可以反编译”,这一句是错误的,正确是“修改lua项目中的opcode后,重新编译反编译工具luadec51项目,就可以反编译了”,已经修改。
0.前言
主要用到的工具和环境:
在学习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。
luajit:
一般有安全意识的游戏厂商都不会直接把lua源码脚本打包到APK中发布,所以一般对lua脚本的保护有下面3种:
这种情况是指打包在APK中的lua代码是加密过的,程序在加载lua脚本时解密(关键函数luaL_loadbuffer ),解密后就能够获取lua源码。如果解密后获取的是luac字节码的话,也可以通过反编译得到lua源码,反编译主要用的工具有unluac和luadec51,后面会具体分析。
因为反编译的结果并不容易查看,所以这种情况能够较好的保护lua源码。这个情况主要是先解密后反编译,反编译主要是通过luajit-decomp项目,它能够将luajit字节码反编译成伪lua代码。
这种情况主要是修改lua虚拟机源码,再通过修改过的虚拟机将lua脚本编译成luac字节码,达到保护的目的。这种情况如果直接用上面的反编译工具是不能将luac反编译的,需要在程序中分析出相对应的opcode,然后修改lua项目的opcode的顺序并重新编译生成反编译工具,就能反编译了,后面会具体分析。
一般上面的情况都会交叉遇到。
这里主要介绍4种方法,都会在第5节中用实例说明。
这种方法需要把解密的过程全部分析出来,比较费时费力,主要是通过ida定位到luaL_loadbuffer函数,然后往上回溯,分析出解密的过程。
这里主要通过ida动态调试so文件,然后是定位到luaL_loadbuffer地址,游戏会在启动的时候通过调用luaL_loadbuffer函数加载必要的lua脚本,通过在luaL_loadbuffer下断点 ,断下后就可以运行idc脚本将lua代码导出(程序调用一次luaL_loadbuffer加载一个lua脚本,不写idc脚本的话需要手动导N多遍.....)。
跟4.2原理一样,就是通过hook函数luaL_loadbuffer地址,将代码保存,相比4.2的好处是有些lua脚本需要在玩游戏的过程中才加载,如果用了4.2的方法,游戏过程中 中断一次就需要手动运行一次idc脚本,而且往往每次只加载一个lua文件,如果是hook的话,就不需要那么麻烦,直接玩一遍游戏,全部lua脚本就已经保存好了。
这里主要是opcode的顺序被修改了,需要用ida定位到虚拟机执行luac字节码的地方,然后对比原来lua虚拟机的执行过程,获取修改后的opcode顺序,最后还原lua脚本。
好了,下面用3个例子来说明上面的情况。
一直向上回溯(交叉引用 ),来到下图,发现解密的密钥和签名,其中xiaoxian为密钥,XXFISH为签名
int CCLuaStack::lua_loadChunksFromZIP(lua_State *L)
{if (lua_gettop(L) < 1){ // 这里可以发现用字符串也可以定位到目标函数CCLOG("lua_loadChunksFromZIP() - invalid arguments");return 0;}
...if (isXXTEA){// decrypt XXTEA// 这里调用了解密函数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项目里面写的解密函数,很多工具直接可以调用)
void 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的长度(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; }
解密后的文件如下:
lua版本为5.1
luajit版本为2.1.0
反编译本人用到的是luajit-decomp,这里需要注意,luajit-decomp默认的lua版本为5.1,luajit版本为2.0.2,我们需要下载对应lua和luajit的版本,编译后替换luajit-decomp下的lua51.dll、luajit.exe、jit文件夹。反编译时需要注意的文件和文件夹:
这里需要下载版本为2.1.0-beta2的luajit,并且编译生成文件后,复制LuaJIT-2.1.0-beta2\src路径下的lua51.dll、luajit.exe文件和jit文件夹覆盖到luajit-decomp目录中。luajit-decomp用的是autolt3语言,原脚本默认是只反编译当前目录下的test.lua文件,所以需要改一下decoder.au3文件的代码。修改后的代码另存为jitdecomp.au3文件,编译后为jitdecomp.exe。并且增加了data目录,目录下有3个文件夹,分别为:
将解密后的文件放到luajit文件夹,运行 jitdecomp.exe,反编译的结果在out目录下,结果如下:
5.2 捕鱼达人4
接着,ida加载libcocos2dlua.so文件,定位到函数luaL_loadbuffer,可以在函数中直接搜索,也可以字符串搜索"[LUA ERROR]"来定位到函数中,函数分析如下:
所以在ARM汇编中,参数R0为lua_State指针,参数R1为脚本内容,R2为脚本大小,R3为脚本的名称,写一段IDC脚本dump数据即可:
#include <idc.idc> static main() {auto code, bp_addrese,fp,strPath,strFileName;bp_addrese = 0x7573022C; // luaL_loadbuffer函数地址 ,静态分析获取的函数地址+so文件的地址得到AddBpt(bp_addrese); // 下断点,也可以手动下断while(1){code = GetDebuggerEvent(WFNE_SUSP|WFNE_CONT, 15); // 等待断点发生,等待时间为15秒if ( code <= 0 ){Warning("错误代码:%d",code);return 0;}Message ("地址:%a, 事件id:%x\n", GetEventEa(), GetEventId()); // 断点发生,打印消息strFileName = GetString(GetRegValue("R3"),-1,0); // 获取文件路径名strFileName = substr(strFileName,strrstr(strFileName,"/")+1,-1); // 获取最后一个‘/’后面的名字(文件的名字)去掉路径strPath = sprintf("c:\\lua\\%s",strFileName); // 保存lua的本地路径fp = fopen(strPath,"wb");savefile(fp,0,GetRegValue("R1"),GetRegValue("R2"));fclose(fp);Message("保存文件成功: %s\n",strPath);} } //字符串查找函数,从后面向前查找,返回第一次查找的字符串下标 static strrstr(str,substr1) {auto i,index;index = -1;while (1){i = strstr(str,substr1);if (-1 == i) return index;str = substr(str,i+1,-1);index = index+i+1;}; }
ida动态调试so文件网上有很多文章,这里就不详细说明了。通过idc脚本获取的部分数据如下:
5.3.梦幻西游手游
这里需要实现Lrc4解密的相关函数,还有Lzma解压函数需要自己实现,其他几个都是cocos2d平台自带的函数,直接调用就可以了。上面的流程图实现的函数如下:
bool decryptLua_Mhxy(string strFilePath, string strSaveDir) {bool bResult = false;char *szBuffer = NULL;int nBufferSize = 0;CCFileUtils *utils = CCFileUtils::sharedFileUtils();unsigned long ulFileSize = 0;char *szFileData = (char*)utils->getFileData(strFilePath.c_str(), "rb", &ulFileSize);if (strncmp(szFileData, "L:grxx", 6)){if (!strncmp(szFileData, "__sign_of_g18_enc__", 0x13)){szBuffer = szFileData + 0x13;nBufferSize = ulFileSize - 0x13;bResult = decrypt((unsigned char*)szBuffer, nBufferSize);}}else if (!strncmp(szFileData + 6, "__sign_of_g18_enc__", 0x13)){unsigned char *pData = (unsigned char *)szFileData + 0x19;int nLen = ulFileSize - 0x19;bResult = decrypt(pData, nLen);if (ZipUtils::isGZipBuffer(pData, nLen)){nBufferSize = ZipUtils::ccInflateMemory(pData, nLen, (unsigned char**)&szBuffer);}else if (ZipUtils::isCCZBuffer(pData, nLen)){nBufferSize = ZipUtils::inflateCCZBuffer(pData, nLen, (unsigned char**)&szBuffer);}else if (LzmaUtils::isLzmaBuffer(pData, nLen)){nBufferSize = LzmaUtils::inflateLzmaBuffer(pData, nLen, (unsigned char**)&szBuffer);}else{bResult = false;}}if(bResult)saveLuaData(szBuffer, nBufferSize, strSaveDir);return bResult; }
解密函数过程如下:
decrypt()实现代码如下:
bool decrypt(unsigned char *pData, int nLen) {Lrc4 *pLrc4 = new Lrc4;Lrc4_lrc4(pLrc4);Lrc4_s(pLrc4, pData, nLen);return true; }
Lrc4结构如下:
#define DATA_SIZE 256 struct Lrc4 {unsigned char pData[DATA_SIZE]; //初始化时计算得到的256个字节int nIndex; //记录下标int nPreIndex; //记录前一个下标 };
其他函数的具体实现请看DecryptData_Mhxy.cpp文件,这里就不贴代码了。解密后的文件如下:
可以看出,解密后的文件为luac字节码,但是这里直接用反编译工具是不能反编译luac字节码的,因为游戏的opcode被修改过了,我们需要找到游戏opcode的顺序,然后生成一个对应opcode的luadec.exe文件才能反编译。下表为修改前后的opcode:
lua虚拟机的相关内容就不说明了,百度很多,这里说明下如何还原opcode的顺序。首先需要定位到opmode的地方,IDA搜索字符串"LOADK",定位到opname的地方,交叉引用到代码,找到opmode:
off_B02CEC为opname的地址,byte_A67C00为opmode的地址,进入opmode地址查看:
源码用了宏,计算出来的结果就是上表中opmode的结果。这里对比opmode就可以快速对比出opcode,因为opmode不相等,那么opcode也肯定不相等,到这一步,已经能还原部分opcode了,因为有一些opmode是唯一的。比如下面几个:
接下来就需要定位到luaV_execute函数,然后对比源码来还原其他的opcode,直接IDA搜索字符串"initial value must be a number"可以定位到luaV_execute 函数,再F5一下。接着打开lua源码中的lvm.c文件,找到luaV_execute函数,就可对比还原了。lua源码和IDA F5后的代码其实差别还是有的,而且源码用了大量的宏,所以源码只是用来参考、理解lua虚拟机的解析过程,本人在还原的过程中,会再打开一个没有修改opcode的libcocos2dlua.so文件,这样对比查找就方便多了。
最后修改lua源码 lopcodes.h中的opcode、lopcodes.c的opname和opmode,重新编译并生成luadec51 .exe(需要将lua源码中的src目录放到luadec51的lua目录下才能编译),就OK了,写个批处理文件就可以批量反编译。一个文件反编译的结果:
6.总结
总结一下解密lua的流程,拿到APK,首先反编译,查看lib目录下是否有libcocos2dlua.so,存在的话很大可能这个游戏就是lua编写,lib目录下文件最大的就是目标so文件,一般情况就是libcocos2dlua.so。接着再看assets文件夹有没有可疑的文件,cocos2dx框架都会把游戏的资源文件放到这个文件夹下,包括lua脚本。其次分析lua加密的方式并选择解密脚本的方式,如果可以ida动态调试,本人一般都会选择用idc脚本dump代码。最后如果得到的不是lua明文,还需要再反编译一下。
不足之处:第一个是此文是本人逆向lua手游时的总结,而且本人逆向的手游可能不是很多,所以有些观点比较片面,不足之处请指正。第二个就是文章是事后写的,并且写文章的时间比较仓促,所以有些步骤写得可能不详细,欢迎讨论。如果有必要,会写一篇《如何一步一步还原梦幻手游opcode》,但是如果看过lua源码,对lua比较熟悉的话,找出来我想应该不是问题的。第三个就是luajit的反编译并不完美,用的是luajit-decomp反编译工具,工具作者也说只是满足了他自己的需求,所以如果可以的话,想自己实现一个luajit的反编译工具,而且梦幻luac的反编译好像部分代码也反编译失败了,可能自己遗漏了点什么吧,就先这样吧.....(2018/07/10 增加:梦幻西游手游lua代码反编译失败的修复 请点这里)
参考文章
Kaitiren的专栏《Quick-cocos2d-x 与Cocos2dx 区别》http://blog.csdn.net/kaitiren/article/details/35276177
littleNA《梦幻手游部分Luac反编译失败的解决方法》 https://litna.top/2018/07/08/梦幻手游部分Luac反编译失败的解决方法/
转载于:https://www.cnblogs.com/dmeng2009/p/11329258.html
浅析android手游lua脚本的加密与解密相关推荐
- 浅析android手游lua脚本的加密与解密(番外篇之反编译的对抗)
前言 去年在看雪论坛写了一篇<浅析android手游lua脚本的加密与解密>的精华文章,今年写一篇番外篇,将一些lua反编译对抗的内容整合一起,并以3个实例作为说明(包括2018腾讯游 ...
- 血族手游Lua脚本及资源文件解密
之前一直和朋友在玩手游血族.有一天朋友问我能不能把里面某个角色的立绘拿下来.当时没多想就答应了,以为只要解压找到图片就行了.但是万万没想到,图片竟然打不开(被加密了). 快速分析 下载最新的血族apk ...
- lua脚本的加密与解密简单介绍
1.lua脚本在手游中的现状 略. 2.lua.luac.luaJIT三种文件的关系 在学习lua手游过程中,本人遇到的lua文件大部分是这3种.其中lua是明文代码,直接用记事本就能打开,luac是 ...
- Android手游SDK那点事(四)聚合打包
更新: 聚合SDK 我们的SDK其实也可以称为聚合SDK,所谓聚合,就是CP游戏方接入聚合SDK,然后通过打包工具将游戏分发到更多渠道上,我们的SDK也有这个能力,相对渠道少一点 分发渠道包 以下操作 ...
- Android手游《》斗地主完整的源代码(支持单机和网络对战)
Android手游<斗地主>完整的源代码(支持单机和网络对战)下载.一个很不错的源代码. 斗地主掌游是一个独特的国内社会斗地主棋牌游戏,之后玩家可以下载网上斗地主和全世界.掌游斗地主特点: ...
- 分享一个蓝月传奇手游辅助脚本,想回味传奇游戏又不想费太多时间练级的可以试试
分享一个蓝月传奇手游辅助脚本,想回味传奇游戏又不想费太多时间练级的可以试试 蓝月传奇手游这个游戏等级非常重要,用工具红手指云手机来多开.自动练级完成任务,这样能够让你快速形成等级优势,在别人睡觉的时候 ...
- 三国志手游挂机脚本 三国志辅助玩法介绍
期待已久的手游三国志战略版即将公测,这款手机游戏吸引了大批玩家的关注,下面为大家介绍一下三国志手游辅助脚本及基本玩法介绍 1首先进入游戏,可以看到菜单选择,游戏包含统一中国为目标的统一中国剧本和其他剧 ...
- 安卓修改大师揭秘Android手游破解全过程
由于安卓修改大师的零门槛学习成本,让安卓应用程序的破解和二次开发变得相当简单,也正因如此,手机APP遭受破解和盗版问题长期存在,且愈演愈烈.尤其是手游行业,如刀塔传奇.植物大战僵尸.2048等知名游戏 ...
- 关于android手游Sdk开发的专题(一)
随着手游行业的蓬勃发展,以及各大IP加入,对于Android手游插件sdk的开发也多了起来. 下面我将给大家分享手游插件的开发细节 1.sdk开发与传统的app开发的不同点 首先做为sdk由于要兼容e ...
最新文章
- MySQL Commons
- C++编程连接string字符串和int数字的好方法
- portal启用自动 JSP 重新装入
- 虚拟机中模拟uboot启动
- 2017.10.7 QBXT 模拟赛
- 业务用例模型涉及的主要概念
- android 3gpp 播放,Android Market:MoboPlayer 最強悍的免費影片播放程式
- 抖音、快手无水印视频下载【现已支持所有平台】
- php eclipse 版本_PHPEclipse官方下载
- 龙哥手把手教你学LabVIEW视觉-深度学习简明教程【halcon篇】
- 2000坐标系xy坐标几位_2000国家大地坐标系简介
- linux xxx命令,linux命令ps aux|grep xxx详解
- 直播预告 | 端云协同的淘宝机器学习系统
- 课堂纪律一团糟老师应该怎么办?
- ae中计算机打字预设,Typewriter Pro(AE电脑打字动画特效预设)
- MSDN如何找到和打开
- dnf强化卷代码_DNF4.9日魔盒更新内容详解_全强化卷概率一览_52pk
- 万能播放器KMPlayer
- 供应python少儿编程课_供的解释|供的意思|汉典“供”字的基本解释
- 回复徐老师暨不成功教学的反思
热门文章
- 七夕第一波狗粮来啦!魏晨晒婚纱照,与妻子爱情长跑十年成眷属
- python格式化字符%e_用%格式化Python字符串
- linux关闭内存插槽,linux 统管理中的查看内存插槽数、最大容量和频率
- 快讯丨用于zData一体机的两款服务器率先通过3C认证
- Alphapose - Windows下Alphapose(Pytorch 1.1+)版本2021最新环境配置步骤以及踩坑说明
- C语言sopc蜂鸣器按键弹奏中音,SOPC乐曲演奏大作业.doc
- stream之List转Map---Collectors.toMap()介绍
- 安装burp2022 --illegal-access=permit
- JAVA版村庄哨塔种子_我的世界:5个奇特且罕见的种子,相邻的哨塔,循环的地形图...
- JZOJ100029 陪审团