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