点击上方↑↑↑蓝字[协议分析与还原]关注我们
“ 介绍使用libcocos2dlua.so库的游戏的解密分析方法。”
Cocos2dlua是一款流行的游戏引擎,常用于开发棋牌游戏。为了保护游戏代码,Cocos2dlua通常会对游戏脚本lua文件进行加密,生成Luac文件,内容一般长这样,前面会有几个固定字符串,对比几个文件就能确定它的内容:
上面这个luac文件的固定字符串就是tianlianmjXXTEA。
对cocos2dlua游戏进行分析,获取里面的算法或者一些参数,首先需要做的是对这个文件进行解密。
01
—
找密钥
使用Android Killer打开APK,看下文件的结构,很容易lib目录下看到armeabi目录,里面有几个so文件,其中,libcocos2dlua.so就是luac文件解密的关键所在,我们需要从这个so里面搞定加密密钥和算法。
如果这个APK使用的密钥是在cocos2d里面常规配置的,则很容易在so文件里面找到,使用IDA打开so,直接搜前面看到的luac文件前面的固定字符串,它是文件的签名,我们把它叫sign,这个例子里面是:tianlianmjXXTEA。前后看看,一般如果有一串比较长的无规律字符串,我们需要找的密钥key就是它了。但是,有的时候这个密钥key找不到,因为它不是常规配置的,这个时候密钥key的取法需要知道加密算法,我们后面再说,这里先说加密算法。
虽然so分析很耗时且繁琐,这里就直接上结论,cocos2d对lua文件的加密使用的是xxtea算法,在ida对so自动分析完之后,直接搜xxtea_decrypt即可定位函数位置,看下算法,不用担心,这就是你想要的解密算法了,你不用去抠它,它的核心是常规的xxtea,大部分编程语言都有很好的实现供你使用,看函数的上下文,读取luac文件,对sign进行对比,对文件去sign之后的内容使用密钥key进行xxtea解密,对解密结果进行处理。这个解密,不仅仅解密luac文件,还解密各种其它文件。
一个简单的python解密函数实现如下:
dec_data = xxtea.decrypt(data=data[len(sign):], key=key, padding=False)
接下来,需要的是解密的密钥key,当在so里面不容易找到的时候,我们需要使用hook来找了,我们可以使用frida来实现hook,如果不会,上frida官网去学习学习,基础的很简单,我们不需要学习复杂的花哨知识,够用就行,百分之八十的人只需要掌握基础的百分之八十就可以了。
我们需要hook的是libcocos2dlua.so这个文件里面的xxtea_decrypt,我们在前面已经找到了它,知道了它在ida里面的偏移地址,这里分析的样本应用的地址是0x4E1ECA,但是我们还要注意一点Thumb指令集模式和Arm指令集模式的区别,如果是Thumb指令集,inline hook的偏移地址需要进行+1,很巧,样本应用就是Thumb指令集,所以我们的偏移地址是0x4E1ECB,hook跑起来,刷刷的刷屏,这就是我们需要的东西:
上面的arg2里面就是我们我们需要的解密密钥key,arg3是key的长度,在打印出的内存里面,紧跟着key的内容是sign,很容易识别它。
至此,解密算法,解密的sign,key都找到了,接下来就是写程序实现算法批量解密luac文件了。
02
—
文件解密
现在回来继续关注我们要解密的luac文件,从apk里面解出的目录下,有很多luac,我们需要实现的是自动化的解密,而不是将luac文件一个个找出来解密输出。这里当然是使用python来实现解密。
首先需要实现的是单个luac文件的解密,读取文件,解密,输出解密结果。
def read_jsc_file(path):f = open(path, "rb")data = f.read()f.close()return data
def decrypt(filePath, key,sign):data = read_jsc_file(path=filePath)if data.startswith(sign):if(len(data)>len(sign)):dec_data = xxtea.decrypt(data=data[len(sign):], key=key, padding=False)else:return b''else:dec_data = xxtea.decrypt(data=data, key=key, padding=False)if dec_data[:2] == b"PK":fio = BytesIO(dec_data)fzip = zipfile.ZipFile(file=fio)dec_data = fzip.read(fzip.namelist()[0])elif dec_data[:2] == b"\x1f\x8b":dec_data = bytes(zlib.decompress(dec_data, 16 + zlib.MAX_WBITS))#.decode("utf-8")else:l=dec_data[-4:]l=int.from_bytes(l, byteorder='little', signed=False)dec_data = bytes(dec_data[0:l])return dec_data
接下来,外面再套一层遍历文件夹里的合格文件。
def save_file(fileDir, outData):rootPath = os.path.split(fileDir)[0]try:os.makedirs(rootPath)except OSError:if not os.path.exists(rootPath):raise Exception("Error: create directory %s failed." % rootPath)if fileDir.endswith("c"):file = fileDir[:-1]with open(file, "wb") as fd:fd.write(outData)fd.close()
def dir_decrypt(srcDir, xxtea_key,signkey):if not os.path.exists(srcDir):ColorPrinter.print_white_text("Error:FileNotFound")exit(1)rootDir = os.path.split(srcDir)[0]outDir = rootDirif len(outDir)>0 and outDir[-2:-1] != "\\":outDir += "\\"outDir += "out\\"traveDir.deep_iterate_dir(srcDir)files_list = traveDir.getfileslist()for file_path in files_list:ColorPrinter.print_green_text("Decrypting flie:{0}".format(file_path))decData = decrypt(filePath=file_path, key=xxtea_key,sign=signkey)outFile = outDir + file_path[len(rootDir + os.path.split(srcDir)[1]) + 1:]save_file(fileDir=outFile, outData=decData)print(" Save flie:{0}".format(outFile))
最后,需要写整个功能的入口。
def main():instruct = sys.argv[1]xxtea_key = sys.argv[2]bsignkey=sys.argv[3].encode()srcDir = sys.argv[4]if len(xxtea_key)<16:taillen=16-len(xxtea_key)bxxtea_key=xxtea_key.encode().ljust(16,b'\0')else:bxxtea_key = xxtea_key.encode()[0:16]if instruct[1:2] == "d":dir_decrypt(srcDir=srcDir, xxtea_key=bxxtea_key,signkey=bsignkey)
if __name__ == "__main__":main()
拼拼凑凑,就将luac文件的解密完成了,不难。
03
—
结束
整个luac解密过程的重点是获取解密key,而sign可以用来帮助我们定位它,如果大家有什么需要,可以一起交流交流,high起来。
别忘点“在看”、“赞”和“分享”
新的规则,及时收推文要先给公号星标
别忘了星标一下,不然就错过了
长按进行关注,时刻进行交流。