lua字節碼混淆與反編譯

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文件,分析頭部的內容,以便進行合理的操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章