前言
某mips架构的路由器上的lua脚本a.lua,被编译成了luac二进制格式。初步分析发现指定lua使用固件中的共享库liblua.so.5.1.5可运行该luac文件,同架构的官方openwrt则不能运行该luac,所以判断出lialua.so.5.1.5被魔改了。
1 | root@****:~# export LD_LIBRARY_PATH=./ |
静态分析
ida静态分析一下修改后的liblua.so.5.1.5,对照lua源码,发现LOADK字节码的名称字符串不见了。
GDB调试
静态太难分析,难以找到哪些地方被修改,还是在x86上编译一个32位带调试符号的lua加载a.luac吧。。首先根据这篇文章,把openwrt对lua修改的patch打上。
使用make MYCFLAGS='-fPIC -m32 -g3' MYLDFLAGS='-m32' PKG_VERSION='5.1.5' linux
出错,在链接时(gcc -o lua -L. -llua -m32 lua.o -lm -Wl,-E -ldl
),报错符号未定义lua.c:(.text+0x20): undefined reference to 'lua_sethook'
。看了下目标文件都是32位的,怎么会找不到符号?没时间搞,看了这个repo,把目标文件lua.o移到-L指定库路径前(草?),也就是Makefile生成lua和luac的规则改成:
1 | $(LUA_T): $(LUA_O) $(LUA_SO) |
就可以编译32位带调试符号的lua了。
然后gdb挂上去,断点打在error,看下是怎么回事。
1 | gdb-peda$ bt |
还是难以判断,看起来是lua操作码(字节码)无法识别,那去openwrt上opkg安装一下luac吧,指定不同动态库(一个原版,一个魔改),编译同一个.lua成.luac文件,这里用的是luadec里的allopcodes-5.1.lua,尽可能的在二进制里包含所有40个OpCode,然后直接010对比allopcodes.luac。
二进制对比
可以看到,有那么几个字节不同,这里的字节&0x3F就能得到OpCode,很多字节都比原版的少了1,再结合ida静态分析,LOADK操作码名消失了。可以合理猜测删除了LOADK操作码名称字符串(在OpName数组),但这无法解释为什么一部分字节往前移了1(操作码名称字符串删除不代表LOADK操作码被删了,如果删了LOADK,lua虚拟机应该无法正常运行和解释字节码),所以我写了个脚本把不同的字节比对了一遍:
1 | OpCode = ["MOVE", "LOADK", "LOADBOOL", "LOADNIL", "GETUPVAL", "GETGLOBAL", "GETTABLE", "SETGLOBAL", "SETUPVAL", "SETTABLE", "NEWTABLE", "SELF", "ADD", "SUB", "MUL", "DIV", "MOD", "POW", |
1 | 原版 修改版 OpCode |
一目了然,OpName删除了LOADK,所以ida里面无这个字符串,然后调整了OpCode顺序,LOADK被移到20去了,所以1-20全部前移1,之所以要删掉LOADK的OpName,是因为如果OpName和OpCode保持一致的话很容易被人看出来。
因此修改lopcodes.h里的OpCode枚举,lua开发者还温馨的提示要修改两个地方:
1 | ** grep "ORDER OP" if you change these enums |
也就是说OpName也需要调换顺序,我尝试了OpName数组直接删除”LOADK”,然后lua就无法运行了,报错./luac: error while loading shared libraries: liblua.so.5.1.5: wrong ELF class: ELFCLASS64
,但为啥他们的so里面没有”LOADK”这个字符串?先不管了。
接着全局搜索LOADK,全部调换顺序,修改后重新编译lua,luadec就可以用了。。。
总结
最后得出结论,这种简单的调换OpCode顺序还是没啥用。收工。