作者:
预估稿费:600RMB
投稿方式:发送邮件至linwei#http://360.cn,或登陆网页版在线投稿
简介
Lua语言对于游戏开发与相关逆向分析的人来说并不陌生。Lua语言凭借其高效、简洁与跨平台等多种特性,一直稳立于游戏、移动APP等特定的开发领域中。
目前Lua主要有5.1、5.2、5.3共三个版本。5.1版本的Lua之所以目前仍然被广泛使用的原因之一,是由于另一个流行的项目LuaJit采用了该版本Lua的内核。单纯使用Lua来实现的项目中,5.2与5.3版本的Lua则更加流行。这里主要以Lua版本5.2为例,通过分析它生成的Luac字节码文件,完成Lua程序的初步分析,为以后更深入的反汇编、字节码置换与重组等技能打下基础。
Lua与Luac
Lua与Python一样,可以被定义为脚本型的语言,与Python生成pyc字节码一样,Lua程序也有自己的字节码格式luac。Lua程序在加载到内存中后,Lua虚拟机环境会将其编译为Luac(下面文中Luac与luac含义相同)字节码,因此,加载本地的Luac字节码与Lua源程序一样,在内存中都是编译好的二进制结构。
为了探究Luac的内幕,我们需要找到合适的资料与工具来辅助分析Luac文件。最好的资料莫过于Lua的源码,它包含了Lua相关知识的方方面面,阅读并理解Luac的构造与Lua虚拟机加载字节码的过程,便可以通透的了解Luac的格式。但这里并不打算这么做,而采取阅读第三方Lua反编译工具的代码。主要原因是:这类工具的代码往往更具有针对性,代码量也会少很多,分析与还原理解Luac字节码文件格式可以省掉不少的时间与精力。
luadec与unlua是最流行的Luac反汇编与反编译工具,前者使用C++语言开发,后者使用Java语言,这两个工具都能很好的还原与解释Luac文件,但考虑到Lua本身采用C语言开发,并且接下来打算编写010 Editor编辑器的Luac.bt文件格式模板,010 Editor的模板语法类似于C语言,为了在编码时更加顺利,这里分析时主要针对luadec。
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、行号、局部变量等信息组成。
在Lua中,函数体使用`Proto`结构体表示,它的声明如下:
typedef struct {
//header
ProtoHeader header;
//code
Code code;
// constants
Constants constants;
// functions
Protos protos;
// upvalues
Upvaldescs upvaldescs;
// string
SourceName src_name;
// lines
Lines lines;
// locals
LocVars loc_vars;
// upvalue names
UpValueNames names;
} Proto;
ProtoHeader是Proto的头部分。它的定义如下:
typedef struct {
uint32 linedefined;
uint32 lastlinedefined;
uchar numparams;
uchar is_vararg;
uchar maxstacksize;
} ProtoHeader;
ProtoHeader在Lua中使用lua_Debug表示,lua_Debug的作用是调试时提供函数的行号,函数与变量名等信息,只是它部分字段的信息在生成Luac字节码时,最终没有写入Luac文件中。`linedefined`与`lastlinedefined`是定义的两个行信息。`numpa