好久没发文章了,并不是我停止了研究,而是一直在忙些修复bug的工作。今天在研究精英服的时候,本打算对比一下和正式服的代码的,但无奈时间不足,就不多说了。这儿简单点,向大伙介绍一下对游戏的Android版本中的的luac文件的解密研究的过程和成果。
在上一篇文章中(好吧,按时间上不是上一篇,按标题才是),我已介绍过,游戏Android版中的luac文件是被xxtea加密的,并且解说了如何获得xxtea的key。如果有人继续研究下去,会发现解密出来的文件还是加密的:
当然,这并不是真的加密,它其实是lua的字节码。但是呢,它不是普通的lua字节码,而是一个叫做LuaJIT的东东生成的变种lua字节码。知道它是字节码就基本可以放心了:java字节码,被人破解了;C#字节码,结果也被人破解了;那么lua字节码,会幸免吗?
我就从网上搜,找找各种反编译LuaJIT/Lua字节码的工具,还真找到那么几个。在这个过程中,还发现有其他的变种lua字节码(具体名字我已忘了,哎,瞅瞅我这破记性)。
抛开不能用的、反编译lua原版字节码的不谈,我找到的确定能用的、反编译LuaJIT字节的工具有两个(严格来说只是一个):luajit-decomp和ljd。
luajit-decomp这个东西有点差:下载下来的exe在我的电脑上不能运行(我猜测可能是需要将字节码文件拷贝在某个特定目录。它告诉我说:“If you have any questions on how to use it then read the wiki”。可是鬼才知道这个wiki在哪儿)。经研究呢,这个工具反编译并没有干啥,也只是利用LuaJIT自身的命令而已:
luajit -bl in.luac out.txt
这个能得到啥?一种类似汇编代码的东西,可读性也就比字节码好上一点:
-- BYTECODE -- UIDungeonMgr.lua:18-26
0001 GGET 0 0 ; "DungeonM"
0002 TGETS 0 0 1 ; "getDungeonId"
0003 CALL 0 2 1
0004 GGET 1 2 ; "DungeonAreaM"
0005 TGETS 1 1 3 ; "getParentId"
0006 MOV 2 0
0007 CALL 1 2 2
0008 GGET 2 2 ; "DungeonAreaM"
0009 TGETS 2 2 4 ; "isAllSubAreaPassed"
0010 GGET 3 5 ; "ME"
0011 TGETS 3 3 6 ; "user"
0012 MOV 4 1
0013 CALL 2 2 3
0014 ISF 2
0015 JMP 3 => 0036
找本luac指令手册,对照着看,应当可以看懂。但是,谁闲得蛋疼去这么看?
我幸运地找到了第二个能用的工具。
将ljd从git上下来之后,按照它的说明,执行:
python main.py UIDungeonMgr.luac > UIDungeonMgr.txt
错误了:
complex_constants.append(string.decode("ascii"))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6 in position 0: ordinal
not in range(128)
明明是utf-8的,你给我说ascii?也许是UIDungeonMgr.luac没有BOM头?加上BOM之后,结果又说:
Invalid magic, not a LuaJIT format
Failed to read raw-dump header
看看magic是啥:
def _check_magic(state):
if state.stream.read_bytes(3) != _MAGIC:
errprint("Invalid magic, not a LuaJIT format")
return False
return True
我是很不喜欢python的,居然用空格或者tab的数量作为块控制。上面的这个函数定义怎么看都像是少了个end或者},真是让人不习惯。回头看一下_MAGIC:
_MAGIC = b'\x1bLJ'
和原始的UIDungeonMgr.luac比较一下,发现是相同的。那看来文件是没错的,回头看看“UnicodeDecodeError: 'ascii' codec can't decode byte 0xe6”这个错:
def _read_complex_constants(parser, complex_constants):
i = 0
while i < parser.complex_constants_count:
constant_type = parser.stream.read_uleb128()
if constant_type >= BCDUMP_KGC_STR:
length = constant_type - BCDUMP_KGC_STR
string = parser.stream.read_bytes(length)
complex_constants.append(string.decode("ascii"))
……
原来是这个地方定死的ascii,而没有自动检测。那我定死成utf-8吧:
#complex_constants.append(string.decode("ascii"))
complex_constants.append(string.decode("utf-8"))
再试试:python main.py UIDungeonMgr.luac > UIDungeonMgr.txt
ok,看到了生成的文件:
require("game/ui/form/dungeon/UIDungeonMain")
require("game/ui/form/combat_stat/UICombatStat")
module("UIDungeonMgr", package.seeall)
local uiLevel = nil
local SEPERATOR_FORM_NAME = "SeperatorForm"
local BOSS_LOCK_SCREEN_TIME = 7.5
EventMgr.register("UIDungeonMgr", event.DUNGEON_PASS, function ()
local dungeonId = DungeonM.getDungeonId()
local parentId = DungeonAreaM.getParentId(dungeonId)
if DungeonAreaM.isAllSubAreaPassed(ME.user, parentId) then
local nextMainAreaId = DungeonAreaM.getNextMainArea(parentId)
ME.user.dbase:setTemp("new_passed_area", parentId)
ME.user.dbase:setTemp("new_unlock_area", nextMainAreaId)
end
return
end)
……
大功告成!——好吧,这个工具还不够完善,某些情况会无法正常反编译为源码,这个时候就只能人肉翻译“luajit -bl”产生的“汇编代码”了。