某三消游戏逆向(1)——lua脚本解密

目前手游游戏所用的引擎,常见的框架有cocos2d,Unity3d,unrealengine等。Lua由于其简单的语言结构和语法,用于很多游戏引擎。
这次以某常见三消游戏为例,来进行逆向工作。

文件分析

确定分析对象

解压apk安装包,进入./lib目录,都是.so的底层库。

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
lib39285EFA.so   //移动安全联盟 OAID SDK 广告服务等           
libsgcore.so //快手
libBugly.so //腾讯Bugly crash监控
libhegame.so //游戏逻辑
libtobEmbedEncrypt.so //bytedance广告
libCtaApiLib.so //极光JVerification Android SDK
libtquic.so //quic协议?
libShanYCore.so //闪验Flutter
libiconv.so //多语言编码
libtraeimp-rtmp.so //腾讯云移动直播
libbuffer_pg.so //字节
libindoor.so //百度室内定位
libtxffmpeg.so //ffmpeg
libc++_shared.so
libkwad_common.so
libtxplayer.so //腾讯云短视频
libcostquic.so //quic协议?
libliteavsdk.so //移动直播sdk
libtxsdl.so //腾讯sdl?
libfile_lock_pg.so
liblocSDK7b.so //百度定位?
libweibosdkcore.so //微博
libmarsstn.so //微信Mars 跨平台基础架构组件
libzbarjni.so //Zbar读取条码
libmarsxlog.so //微信Mars 跨平台基础架构组件
libnms.so //对象检测

用ida打开可疑的libhegame.so 看到函数的名字有大量的lua ,可以知道开心消消乐使用的lua引擎(和网上搜索得到的信息一致)
assert/src 目录下是lua脚本,有经过加密,需要对.so文件进行分析找到密钥。

分析libhegame.so

方法名里搜索“lua”,可以看到都是jni的方法。

查看j_Java_com_happyelements_test_TestService_initLua 方法,一路跟进sub_6EA84

可以看到初始化lua的方法应该是com/happyelements/android/LuaHelper类里的initLuaEnv() 方法.

全文搜索“load_lua”,可以看到方法sub_106B0C(),根据调试信息可以看出此方法为加载lua文件的地方。

其中sub_569864 为读取lua文件,sub_95C00 为解码,sub_97670为解压缩

sub_95C00 -> sub_95C18 从调试信息 推测sub_95C18为openssl的EVP解码算法。

动态调试

frida hook sub_95C00方法

js脚本内容为:

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
function get_func_addr(module, offset) {

var base_addr = Module.findBaseAddress(module);
console.log("base_addr: " + base_addr);

console.log(hexdump(ptr(base_addr), {
length: 16,
header: true,
ansi: true
}))

var func_addr = base_addr.add(offset);
if (Process.arch == 'arm')
return func_addr.add(1); //如果是32位地址+1
else
return func_addr;
}

//var func_addr = get_func_addr('libhegame.so', 0x97670);
var func_addr = get_func_addr('libhegame.so', 0x95C00);
console.log('func_addr: ' + func_addr);

console.log(hexdump(ptr(func_addr), {
length: 16,
header: true,
ansi: true
}))

Interceptor.attach(ptr(func_addr), {
onEnter: function(args) {

console.log("onEnter");
var num1 = args[0];
var num2 = args[1];

console.log("num1: " + num1);
console.log("num2: " + num2);

console.log(hexdump(ptr(num1), {
length: 16,
header: true,
ansi: true
}))

},
onLeave: function(retval) {

console.log("onLeave");
//retval.replace(3); //返回值替换成3
console.log("return: " + retval);

}
});

可以看到密钥为e9 74 7d 92 cc 32 2e 7d 11 2e 7c 34 51 d7 b3 6a

解码lua文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#coding=UTF-8
import Crypto
import zlib
from Crypto.Cipher import AES
def decdata(c):
key=b'\xe9\x74\x7d\x92\xcc\x32\x2e\x7d\x11\x2e\x7c\x34\x51\xd7\xb3\x6a'
iv=c[0:16]
main_data=c[16:]
cryptor = AES.new(key,AES.MODE_CBC,iv)
pad_compress_data=cryptor.decrypt(main_data)
str_len=len(pad_compress_data)
pad=pad_compress_data[-1]
compress_data=pad_compress_data[0:str_len-pad]
plain_text = zlib.decompress(compress_data)
return plain_text