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_SIGNATURElua.h中有定义,#define LUA_SIGNATURE "\033Lua"\033 指[ESC] 键,

LUAC_VERSIONlundump.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文件,分析头部的内容,以便进行合理的操作。