对于游戏开发人员,有时候希望从一些游戏apk中反编译出源代码,进行学习,但是如果你触碰到法律边缘,那么你要非常小心。
这篇文章,我针对一些用lua写客户端或者服务器的编译过的luac文件进行反编译,获取其源代码的过程。
这里我不赘述如何反编译解压apk包的过程了,只说重点,在解压获取luac文件后,你应该是可以看到类似于这样的目录:
编辑
这些文件是经过lua或者luajit编译后生成的字节码文件(后边我们会分析下它到底是经过lua还是luajit处理的)
我们需要知道的是lua手游有三种文件:lua,luac,luajit。
lua是明文代码,直接用记事本就能打开。
luac是lua编译后的字节码。
luajit是用的另一种对lua加密。
Luac文件格式
一个luac文件包含两部分:文件头和函数体
文件头格式
typedef struct { char signature[4]; //".lua" uchar version; uchar format; uchar endian; uchar size_int; uchar size_size_t; uchar size_Instruction; uchar size_lua_Number; uchar lua_num_valid; uchar luac_tail[0x6]; } GlobalHeader;
第一个字段**signature**在lua.h头文件中有定义,它是LUA_SIGNATURE,取值为“\033Lua",其中,\033表示按键。LUA_SIGNATURE作为Luac文件开头的4字节,它是Luac的Magic Number,用来标识它为Luac字节码文件。Magic Number在各种二进制文件格式中比较常见,通过是特定文件的前几个字节,用来表示一种特定的文件格式。
version 字段表示Luac文件的格式版本,它的值对应于Lua编译的版本,对于5.2版本的Lua生成的Luac文件,它的值为0x52。
**format**字段是文件的格式标识,取值0代表official,表示它是官方定义的文件格式。这个字段的值不为0,表示这是一份经过修改的Luac文件格式,可能无法被官方的Lua虚拟机正常加载。
**endian**表示Luac使用的字节序。现在主流的计算机的字节序主要有小端序LittleEndian与大端序BigEndian。这个字段的取值为1的话表示为LittleEndian,为0则表示使用BigEndian。
**size_int**字段表示int类型所占的字节大小。size_size_t字段表示size_t类型所占的字节大小。这两个字段的存在,是为了兼容各种PC机与移动设备的处理器,以及它们的32位与64位版本,因为在特定的处理器上,这两个数据类型所占的字节大小是不同的。
**size_Instruction**字段表示Luac字节码的代码块中,一条指令的大小。目前,指令Instruction所占用的大小为固定的4字节,也就表示Luac使用等长的指令格式,这显然为存储与反编译Luac指令带来了便利。
**size_lua_Number**字段标识lua_Number类型的数据大小。lua_Number表示Lua中的Number类型,它可以存放整型与浮点型。在Lua代码中,它使用LUA_NUMBER表示,它的大小取值大小取决于Lua中使用的浮点数据类型与大小,对于单精度浮点来说,LUA_NUMBER被定义为float,即32位大小,对于双精度浮点来说,它被定义为double,表示64位长度。目前,在macOS系统上编译的Lua,它的大小为64位长度。
**lua_num_valid**字段通常为0,用来确定lua_Number类型能否正常的工作。
**luac_tail**字段用来捕捉转换错误的数据。在Lua中它使用LUAC_TAIL表示,这是一段固定的字符串内容:"\x19\x93\r\n\x1a\n"。
在文件头后面,紧接着的是函数体部分。一个Luac文件中,位于最上面的是一个顶层的函数体,函数体中可以包含多个子函数,子函数可以是嵌套函数、也可以是闭包,它们由常量、代码指令、Upvalue、行号、局部变量等信息组成。
HxD查看lua是否经过加密编译
luac和luajit同样是.luac后缀,但是文件头不同,对其所使用的反编译方法也不同,所以需要特别注意。
luac文件头为:0x1B 0x4C 0x75 0x61 0x51
luacjit文件头为:文件头是0x1B 0x4C 0x4A
打开so文件(一般是文件大小最大的那个(不做更改的话是libcocos2dlua.so),在so文件里搜字符串有无lua就知道是不是)
比如我们使用objdump反汇编出来重定向到文件1中: objdump -S libcocos2dlua.so >1
然后搜索你应该是能看到luaopen_jit字符,说明是用luajit编译的
编辑
我们选取其中的一个main.luac.32,使用工具HxD打开,
编辑
注意看,你的头部信息是这种0x1B 0x4C 0x4A开头的,所以我们推断他是luajit处理的文件。
接下来,我们可以直接使用python工具来导出对应的源码的
使用工具ljd
准备python3的环境,下载GitHub - zzwlpx/ljd: LuaJIT raw-bytecode decompiler
下载本地后,直接按照手册说明运行, 比如我想反编译main.luac.32
编辑 同样的其他文件也是类似这样的方式来反编译。
最终反编译所有的源码,便可以运行了。
有些文件在反编译过程中会报错如下:
$ python main.py code/layer/GuideLayer.luac.32
Traceback (most recent call last):
File "main.py", line 126, in <module>
retval = main()
File "main.py", line 105, in main
ljd.ast.unwarper.unwarp(ast)
File "C:\Users\PC\Downloads\ljd-master\ljd-master\ljd\ast\unwarper.py", line 36, in unwarp
_run_step(_unwarp_ifs, node)
File "C:\Users\PC\Downloads\ljd-master\ljd-master\ljd\ast\unwarper.py", line 43, in _run_step
statements.contents = step(statements.contents, **kargs)
File "C:\Users\PC\Downloads\ljd-master\ljd-master\ljd\ast\unwarper.py", line 161, in _unwarp_ifs
_unwarp_if_statement(start, body, end, end)
File "C:\Users\PC\Downloads\ljd-master\ljd-master\ljd\ast\unwarper.py", line 978, in _unwarp_if_statement
assert isinstance(warp_out, nodes.UnconditionalWarp)
AssertionError
这个是由于debug模式下断言报错,我们可以加上-O参数来禁止debug模式:
$ python -O main.py code/layer/GuideLayer.luac.32
绝大多数文件都可以被成功转换,但部分文件仍然会收到如下报错:
Traceback (most recent call last):
File "main.py", line 123, in <module>
retval = main()
File "main.py", line 77, in main
header, prototype = ljd.rawdump.parser.parse(file_in)
File "C:\Users\PC\Downloads\ljd-master\ljd\ljd\rawdump\parser.py", line 34, in parse
r = r and _read_prototypes(parser, parser.prototypes)
File "C:\Users\PC\Downloads\ljd-master\ljd\ljd\rawdump\parser.py", line 72, in _read_prototypes
if not ljd.rawdump.prototype.read(state, prototype):
File "C:\Users\PC\Downloads\ljd-master\ljd\ljd\rawdump\prototype.py", line 51, in read
r = r and _read_instructions(parser, prototype)
File "C:\Users\PC\Downloads\ljd-master\ljd\ljd\rawdump\prototype.py", line 127, in _read_instructions
instruction = ljd.rawdump.code.read(parser)
File "C:\Users\PC\Downloads\ljd-master\ljd\ljd\rawdump\code.py", line 179, in read
instruction_class = instructions.UNKN # @UndefinedVariable
AttributeError: module 'ljd.bytecode.instructions' has no attribute 'UNKN'
使用luajit-decompiler
https://download.csdn.net/download/pbymw8iwm/88222584
该工具的使用方法和ljd相差无几,基本上所有的luac文件都可以被还原lua文件。