title: luadec相关的一些总结
背景
在做openwrt相关的路由器时,为了更好的保护lua脚本的代码,通常会选择采用luac进行混淆,这样就需要稍微研究一下如何进行混淆和解密。本文所使用的混淆是lua源代码中带的luac,解密采用的是viruscamp/luadec 的解密工具。
混淆、解析和反编译的前提是对文件格式的定义是一致的,因而一般需要把格式定义在lua解释器的源码中,并在适当的条件下进行修改。
主要内容
luac文件解析
不同版本的lua,对luac的定义是不同的,下文基于(OpenWRT 5.1.5版本 2e115fe26e435e33b0d5c022e4490567,openwrt中lua的代码的节点与正式版本不一致,里面的代码不一致,导致文件头部信息不同)
头部格式
在lua-5.1.5中,并没有像5.2中将头部信息封装为一个结构体,而是通过函数直接定义的:
// lundump.c
void luaU_header (char* h)
{
int x=1;
memcpy(h,LUA_SIGNATURE,sizeof(LUA_SIGNATURE)-1);
h+=sizeof(LUA_SIGNATURE)-1;
*h++=(char)LUAC_VERSION;
*h++=(char)LUAC_FORMAT;
*h++=(char)*(char*)&x; /* endianness */
*h++=(char)sizeof(int);
*h++=(char)sizeof(unsigned int);
*h++=(char)sizeof(Instruction);
*h++=(char)sizeof(lua_Number);
/*
* Last byte of header (0/1 in unpatched Lua 5.1.3):
*
* 0: lua_Number is float or double, lua_Integer not used. (nonpatched only)
* 1: lua_Number is integer (nonpatched only)
*
* +2: LNUM_INT16: sizeof(lua_Integer)
* +4: LNUM_INT32: sizeof(lua_Integer)
* +8: LNUM_INT64: sizeof(lua_Integer)
*
* +0x80: LNUM_COMPLEX
*/
*h++ = (char)(sizeof(lua_Integer)
#ifdef LNUM_COMPLEX
| 0x80
#endif
);
}
LUA_SIGNATURE
在lua.h
中有定义,#define LUA_SIGNATURE "\033Lua"
, \033
指[ESC] 键,
LUAC_VERSION
在 lundump.h
中定义, #define LUAC_VERSION 0x51
,
LUAC_FORMAT
表示是否是标准的luac格式,自定义的最好置为非0, #define LUAC_FORMAT 0
,
后一个字节用于标记大小端:0—大端, 1–小端,
后一个字节表示int类型的大小,32位为4, 64为为8,
后一个字节表示unsigned int类型的大小,
后一个字节表示Luac字节码的代码块中,一条指令的大小,目前,指令Instruction所占用的大小为固定的4字节,也就表示Luac使用等长的指令格式,
后一个字节表示lua_Number类型的数据大小,
最后一个字节表示了lua_Integer的大小,依据lua_Number和平台的大小,有不同的定义。
需要注意的是,因为没有标准的结构体,所以需要手动定义头部的大小,#define LUAC_HEADERSIZE 12
, 按照上文描述,没有改动的情况下,LUAC_HEADERSIZE
的大小为12 。
函数体结构
紧接在文件头后面的内容是函数体部分,采用Proto
结构体表示: 具体内容本文暂不解释
// lobject.h
/*
** Function Prototypes
*/
typedef struct Proto {
CommonHeader;
TValue *k; /* constants used by the function */
Instruction *code;
struct Proto **p; /* functions defined inside the function */
int *lineinfo; /* map from opcodes to source lines */
struct LocVar *locvars; /* information about local variables */
TString **upvalues; /* upvalue names */
TString *source;
int sizeupvalues;
int sizek; /* size of `k' */
int sizecode;
int sizelineinfo;
int sizep; /* size of `p' */
int sizelocvars;
int linedefined;
int lastlinedefined;
GCObject *gclist;
lu_byte nups; /* number of upvalues */
lu_byte numparams;
lu_byte is_vararg;
lu_byte maxstacksize;
} Proto;
解密方法
下载源码
$ git clone https://github.com/viruscamp/luadec.git
$ cd luadec
$ git submodule update --init lua-5.1
git submodule update --init lua-5.1是为了更新标准的lua源码,在这里需要采用openwrt中自带的lua源码,故,直接将target中的源码拷贝过来即可。
编译lua
$ cd lua-5.1
$ make linux
由于openwrt中的lua默认情况下是通过动态链接库进行编译,会出现找不到函数体的错误,参考源码中的设置,将对lua库的连接改为静态:
$(LUA_T): $(LUA_O) $(LUA_A)
$(CC) -o $@ -L. -llua $(MYLDFLAGS) $(LUA_O) $(LUA_A) $(LIBS)
$(LUAC_T): $(LUAC_O) $(LUA_A)
$(CC) -o $@ -L. -llua $(MYLDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS)
编译luadec
$ cd ../luadec
$ make LUAVER=5.1
测试
function test()
print("Hello world")
end
混淆
$ ./luac -o test.luac test.lua
Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: 1B 4C 75 61 51 00 01 04 04 04 08 04 0A 00 00 00 .LuaQ...........
00000010: 40 74 65 73 74 2E 6C 75 61 00 00 00 00 00 00 00 @test.lua.......
00000020: 00 00 00 00 02 02 03 00 00 00 24 00 00 00 07 00 ..........$.....
00000030: 00 00 1E 00 80 00 01 00 00 00 04 05 00 00 00 74 ...............t
00000040: 65 73 74 00 01 00 00 00 00 00 00 00 01 00 00 00 est.............
00000050: 03 00 00 00 00 00 00 02 04 00 00 00 05 00 00 00 ................
00000060: 41 40 00 00 1C 40 00 01 1E 00 80 00 02 00 00 00 A@...@..........
00000070: 04 06 00 00 00 70 72 69 6E 74 00 04 0C 00 00 00 .....print......
00000080: 48 65 6C 6C 6F 20 77 6F 72 6C 64 00 00 00 00 00 Hello.world.....
00000090: 04 00 00 00 02 00 00 00 02 00 00 00 02 00 00 00 ................
000000a0: 03 00 00 00 00 00 00 00 00 00 00 00 03 00 00 00 ................
000000b0: 03 00 00 00 01 00 00 00 03 00 00 00 00 00 00 00 ................
000000c0: 00 00 00 00 ....
反编译
$ ./luadec test.luac
-- Decompiled using luadec 2.2 rev: 895d923 for Lua 5.1 from https://github.com/viruscamp/luadec
-- Command line: test.luac
-- params : ...
-- function num : 0
test = function()
-- function num : 0_0
print("Hello world")
end
注意事项
如果没有改动核心的解释加载部分,仅仅对头部进行改动,并不能真正做到加密,仅仅是对原始文件进行混淆,通过对头文件的分析,是可以通过合理修改代码进行字节码反编译的。
在整个过程中,需要保证对文件的解析规则一致,因此对于混淆和解密,都需要尤其注意,特别是在解析别人混淆的代码时,需要通过分析luac文件,分析头部的内容,以便进行合理的操作。