赞
踩
声明:案例分析仅供学习交流使用,勿用于任何非法用途。如学习者进一步逆向并对版权方造成损失,请自行承担法律后果,本人概不负责。
Lua是一种小巧的脚本语言,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数。几乎在所有操作系统和平台上都可以编译、运行。市面上很多流行的手游就是采用Lua编写关键代码。
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脚本源码apk落盘,一般防护措施有下面几种:
要逆向获取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;
do
{
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文件解密后的样子:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。