Lua是一种小巧的脚本语言,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数。几乎在所有操作系统和平台上都可以编译、运行。市面上很多流行的手游就是采用Lua编写关键代码。

Lua、Luac、LuaJIT

Lua、Luac、LuaJIT是平时较为常见的Lua文件类型,其中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文件头:
在这里插入图片描述

Lua防护措施

一般有安全意识的厂商都不会直接把Lua脚本源码apk落盘,一般防护措施有下面几种:

  • 对称加密,在加载脚本之前解密 :这种情况是指apk中的lua代码是加密过的,程序在加载Lua脚本时解密(关键函数luaL_loadbuffer ),解密后就能够获取lua源码。如果解密后获取的是luac字节码的话,也可以通过反编译得到lua源码,反编译主要用的工具有unluac和luadec51。
  • 将lua脚本编译成luaJIT字节码并加密打包 :因为反编译的结果并不容易查看,所以这种情况能够较好的保护Lua源码。这个情况主要是先解密后反编译,反编译主要是通过luajit-decomp项目,它能够将luajit字节码反编译成伪lua代码。
  • 修改lua虚拟机中opcode的顺序 :这种情况主要是修改lua虚拟机源码,再通过修改过的虚拟机将lua脚本编译成luac字节码,达到保护的目的。这种情况如果直接用上面的反编译工具是不能将luac反编译的。

逆向Lua源码的一般方案

要逆向获取Lua源码一般采用以下方案:

  • 静(动)态分析so :这种方法需要把解密的过程全部分析出来,主要是通过ida定位到luaL_loadbuffer(相关)函数,分析出解密的过程。
  • dump :通过ida动态调试so文件,然后是定位到luaL_loadbuffer地址,在加载必要的Lua脚本的地方下断点 ,接着针对每个Lua文件使用 idc脚本 + dump 拿到源码。
  • hook dump方案 需要一个个文件断点再通过脚本拿到源码,如果采用hook方案则只需要跑一遍流程,hook函数自动拿到全部源码。
  • 分析lua虚拟机的opcode的顺序 :这种方案主要针对修改lua虚拟机中opcode的顺序的防护。需要ida定位到虚拟机执行luac字节码的地方,然后对比原来lua虚拟机的执行过程,获取修改后的opcode顺序,最后还原lua脚本。

本次演示的案例是一款使用了Lua脚本作为关键代码的直播软件。首先抓包可以看到其每次都是动态下载Lua文件zip包:
在这里插入图片描述
看来是通过服务器下发带Lua文件信息的json,再通过其中的url下载(最新)zip包,下载到本地分析:
在这里插入图片描述
打开lua,发现都是加密的:
在这里插入图片描述
要解密lua就要从读取lua的地方入手,打开其apk结构,发现lib包下明显有负责读取lua的so库。
在这里插入图片描述
ida静态分析,发现这几个load函数应该是用来读取lua文件的。
在这里插入图片描述
查看luaL_loadEncryptedfile函数:
在这里插入图片描述
伪代码如下,后面的注释是根据函数名猜测添加的。

signed int __fastcall luaL_loadEncryptedfile(int a1, const char *a2)
  const char *v2; // r7
  size_t v3; // r4
  int v4; // r5
  int *v5; // r0
  char *v6; // r0
  void *v7; // r6
  char *v8; // r5
  int v9; // r4
  int v11; // [sp+Ch] [bp-8Ch]
  void *ptr; // [sp+10h] [bp-88h]
  int v13; // [sp+14h] [bp-84h]
  char v14; // [sp+18h] [bp-80h]
  size_t v15; // [sp+48h] [bp-50h]
  v11 = a1;
  v2 = a2;
  if ( stat(a2, &v14) < 0 )
    return -1;
  v3 = v15;
  printf("filesize; %d\n", v15);
  v4 = open(v2, 0);
  if ( !v4 )
    v5 = _errno();
    v6 = strerror(*v5);
    printf("open file error, %s\n", v6);
    exit(0);
  v7 = malloc(v3);
  memset(v7, 0, v3);
  read(v4, v7, v3); // 读取加密文件
  close(v4);
  v13 = 0;
  v8 = tinydes_generateKey();
  tinydes_decoding(&ptr, &v13, v7, v3, v8); // 解密文件
  printf("decoded: \n%s\n", ptr);
  v9 = luaL_loadbuffer(v11, ptr, v13); // buffer流载入解密文件
  free(v8);
  free(v7);
  free(ptr);
  return v9;

首先查看tinydes_generateKey函数,不难发现,其中的key_map是破解关键。

void *tinydes_generateKey()
  const char **v0; // r4
  _BYTE *v1; // r6
  signed int v2; // r5
  const char *v3; // r7
  signed int v4; // ST04_4
  int v5; // r1
  void *v7; // [sp+0h] [bp-20h]
  v7 = malloc(0x11u);
  memset(v7, 0, 0x11u);
  v0 = key_map;  // 解密算法中的key
  v1 = v7;
  v2 = 3;
    v3 = *v0;
    ++v0;
    v4 = strlen(v3);
    *v1 = v3[v4 % v2];
    v5 = v4 % (v2 + 1);
    v2 += 2;
    v1[1] = v3[v5];
    v1 += 2;
  while ( v2 != 19 );
  return v7;

貌似key_map初始化了一些字符串常量,不过放在.data段,也存在中途被修改的可能,还需要动态调试一下。
在这里插入图片描述
tinydes_decoding,猜测参数应该包含解密的文件内容指针、key、文件长度

int __fastcall tinydes_decoding(char *s, _DWORD *a2, int a3, size_t a4, char *sa)
  char *v5; // r4
  size_t v6; // r5
  _DWORD *v7; // r7
  signed int v8; // r6
  void *v9; // r0
  int i; // r4
  int v11; // r2
  int v13; // [sp+0h] [bp-20h]
  int v14; // [sp+4h] [bp-1Ch]
  v5 = s;
  v6 = a4;
  v7 = a2;
  v14 = a3;
  v8 = strlen(sa);
  v9 = malloc(v6);
  *v5 = v9;
  memset(v9, 0, v6);
  v13 = *v5;
  for ( i = 0; i != v6; ++i )
    v11 = sa[(i + v8) % v8];
    *(v13 + i) = (((v11 + *(v14 + i)) & 0xFFu) >> 3) | 32 * ((v11 + *(v14 + i)) & 7);
  *v7 = i;
  return 0;

开始动态调试。
在这里插入图片描述
在这里插入图片描述
找到函数入口。
在这里插入图片描述
tinydes_generateKey开始结束分别下断。
在这里插入图片描述
码表居然没被二次赋值过,那上面的流程用java写出来就是:

* lua码表 private final static String[] luaKeyArray = { "6f8f57715090da2632453988d9a1501b", "d95679752134a2d9eb61dbd7b91c4bcc", "4b43b0aee35624cd95b910189b3dc231", "e1671797c52e15f763380b45e841ec32", "e358efa489f58062f10dd7316b65649e", "9e3669d19b675bd57058fd4664205d2a", "9d4d6204ee943564637f06093236b181", "d88468fb83a6d5675fcd2bdcb8fa57bf", }; * 生成lua密匙 * @param luaKeyArray * lua码表 * @return private static String generateLuaKey(String[] luaKeyArray) { StringBuilder keyBuilder = new StringBuilder(); int cout = 3; final int arrlength = luaKeyArray.length; final int strLength = luaKeyArray[0].length(); for (int i = 0; i < arrlength; i++) { keyBuilder.append(luaKeyArray[i].charAt(strLength % cout)) .append(luaKeyArray[i].charAt((strLength % (cout + 1)))); cout += 2; return keyBuilder.toString(); * 解密lua文件中的数据 * @param encFile * @return private static String desDecLuaFile(File encFile) { StringBuilder decBuilder = new StringBuilder(); if (encFile == null || !encFile.exists() || encFile.length() <= 0) { return "无文件数据"; String key = generateLuaKey(luaKeyArray); int keyLength = key.length(); byte[] encDate = FileUtils.binaryFileRead(encFile.getAbsolutePath()); int[] encArray = ByteUtils.byteArrayToIntArray(encDate); int coutKey = 0; int coutDate = 0; char decDate = 0; for (int i = 0; i < encDate.length; i++) { coutKey = key.charAt(ByteUtils.loopPoint(keyLength, i)); coutDate = encArray[i]; decDate = (char) ((((coutKey + coutDate) & 0xFF) >> 3) | 32 * (coutKey + coutDate & 7)); decBuilder.append(decDate); return decBuilder.toString(); //------上面用到的工具类-----// * 循环指针下标 * @param o * @param i * @return public static int loopPoint(int arrayLen, int i) { return (i + arrayLen) % arrayLen; * byte[]转int[] * @param byteArray * @return public static int[] byteArrayToIntArray(byte[] byteArray) { int[] intArray = new int[byteArray.length]; for (int i = 0; i < intArray.length; i++) { intArray[i] = byteToInt(byteArray[i]); return intArray;

在这里插入图片描述
最后附上Lua文件解密后的样子:
在这里插入图片描述

浅析android手游lua脚本的加密与解密
Lua游戏逆向及破解方法介绍

简介Lua是一种小巧的脚本语言,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数。几乎在所有操作系统和平台上都可以编译、运行。市面上很多流行的手游就是采用Lua编写关键代码。Lua、Luac、LuaJITLua、Luac、LuaJIT是平时较为常见的Lua文件类型,其中lua是明文代码,直接用记事本就能打开,luac是lua编译后的字节码,文件头为0x1B 0x4C 0x75 0x61 # for life capture $ tshark -q -X lua_script:nfs.lua -f " port 2049 " # or if nfs trafic is not on a standard port ( pNFS DS ) $ tshark -q -X lua_script:nfs.lua -f " port 32049 " -d tcp.port==32049,rpc # for read from existing capture file capture file $ tshark -q -r nfs.dump -X lua_script:nfs.lua # or if you need to avoid temp files $ dumpca
热更新一直都是大多数团队的硬需求,而目前使用Lua作为热更新方案又占了绝大多数,在使用上也越来越重度,随之而来的维护、调试、优化成本也越来越高。因此,我们在本地资源检测服务中增加了Lua检测,来帮助开发团队方便地检测Lua代码不规范的问题,提高代码质量,从而提高开发的效率。 不多赘述,先秀一段演示Demo吧 下面,我们来详细介绍下这个Feature。 Luacheck是什么 Luacheck是一个静态Lua代码分析器,用于检测Lua脚本中的各种问题,诸如:使用未定义的全局变量、未使用的变量或值、
文章目录前言分析 前段时间的O泡果奶病毒火了一把,虽然平时很少看android,却也来了兴趣,大致看了一下,也搜了搜,知道了关键点在lua上,可惜之前没接触过lua,当时并没有把脚本解出来。今天看到了一个lua解密的帖子,就试了下,没想到真的成功了,在此作一下记录吧 android目录结构 关键的lua逻辑目录 关键文件是main.lua 使用编辑器看也不是luac文件 当我看到这个帖子时,翻看了下android的目录,果然看到了libluajava.so文件 又了解到libluajav
原文地址:http://justin-ray.iteye.com/blog/1156591 1. 下载离线安装 WindowBuilder 插件,url地址:http://www.eclipse.org/windowbuilder/download.php; 根据自己使用的 Eclipse 版本下载对应的插件; 2. 打开 Eclipse 离线安装插件,进入 Eclipse-->In...
2018.05.02更新 这段时间在翻备份的硬盘,突然发现了以前的分析项目和代码,从里面提取了之前附件的内容,现在上传给大家,真是柳暗花明又一村啊。附件包括201703版本的梦幻手游里面提取的so文件和一些加密后的资源文件(包括lua脚本),并包括了2个扑鱼APK文件,最后还打包了解密代码,供大家参考。 附件太大,快100MB,上传不来论坛,我又放到百度网盘了...... 链接:https://pan.baidu.com/s/1DVgH0qHYPkiHB...
Lua脚本解密工具是一种能够解密多种加密方式的工具。由于Lua脚本的开源性和便捷性,很多使用Lua编写的软件都被加密,使得一些用户无法修改和定制自己需要的功能。而Lua脚本解密工具的出现解决了这一问题。 这种工具能够解密常见的Lua加密方式,比如Tea和Xor等。当用户通过该工具解密加密的Lua脚本后,就可以对其进行修改、添加或删除所需的功能。 使用Lua脚本解密工具的具体方法非常简单,只需要将需要解密Lua脚本拷贝到工具中,选择对应的加密方式,即可得到解密后的Lua脚本。该工具不仅支持单个文件的解密,还可以批量解密多个文件,以便用户快速处理大量的文件。 总之,Lua脚本解密工具为Lua脚本的开发者和用户提供了非常便利的解决方案,并且它的使用方法简单易懂,可以让用户轻松地完成解密操作。