記一次棋牌遊戲 cocos2d-x LUA指令碼解密
1.如何檢視luac的key和sign
對一棋牌遊戲反編譯後,可以在其assets目錄下面發現*.luac的指令碼,開啟下面的luac指令碼,可以看到luac是加密的,如下圖:
可以看到加密的luac檔案的頭幾個位元組,都是一個固定字串值,而這個字串值就是利用官方加解密Xxtea演算法用的sign值,找到sign後,接下來找KEY,用IDA載入libcocos2dlua.so,搜尋剛剛找到的固定字串zhongrdhsakhfjasf,可以看到KEY位於其附近。
2.xxtea加解密實現
由於lua本身是開源的,其官方加解密實現方式很容易就能查詢資料得到,根據得到key和sign就可以利用xxtea演算法來對指令碼進行解密,本人用c++實現了xxtea演算法的解密過程,這個程式只需要三個引數:檔案路徑,sign, key即可解密,附上程式碼:
main.cpp
xxtea.cpp#include "stdafx.h" #include "xxtea.h" #include <fstream> int main(int argc, char *argv[]) { if (argc <= 1) { printf("usage .\ConsoleApplication1 <filePath> <sign> <key>\n"); return 0; } printf("filePath:%s sign:%s key:%s", argv[1], argv[2], argv[3]); std::string filePath = argv[1]; std::string outFilePath = filePath; outFilePath.resize(filePath.rfind("\\")+ 1); size_t pos = filePath.rfind("\\"); size_t pos2 = filePath.rfind("."); std::string temp = filePath.substr(pos + 1, pos2 - pos -1); outFilePath += "jiemi\\"; outFilePath += temp; outFilePath += ".lua"; int signLen = strlen(argv[2]); int keyLen = strlen(argv[3]); std::ifstream ifs(argv[1], std::ios::binary); unsigned char *content = NULL; int length = 0; if (ifs) { ifs.seekg(0, std::ios::end); length = ifs.tellg(); ifs.seekg(0, std::ios::beg); content = new unsigned char[length]; memset(content, 0, length); ifs.read((char* )content, length); } ifs.close(); xxtea_long retLen = 0; unsigned char * data = xxtea_decrypt(content + signLen, length - signLen, (unsigned char*)argv[3], keyLen, &retLen); if (data == NULL) { printf("%s decrypt fail\n", argv[1]); return -1; } std::ofstream ofs(outFilePath.c_str(), std::ios::binary); if (ofs) { ofs.write((char*)data, retLen); } ofs.close(); printf("%s->%s decrypt success\n", filePath.c_str(), outFilePath.c_str()); delete[] content; return 0; }
XXTEA,又稱Corrected Block TEA,是XTEA的升級版,設計者是Roger Needham, David Wheeler/*********************************************************************** Copyright 2006-2009 Ma Bingyao Copyright 2013 Gao Chunhui, Liu Tao These sources is free software. Redistributions of source code must retain the above copyright notice. Redistributions in binary form must reproduce the above copyright notice. You can redistribute it freely. You can use it with any free or commercial software. These sources is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY. Without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. github: https://github.com/liut/pecl-xxtea *************************************************************************/ #include "xxtea.h" #include <memory.h> #include <stdlib.h> static void xxtea_long_encrypt(xxtea_long *v, xxtea_long len, xxtea_long *k) { xxtea_long n = len - 1; xxtea_long z = v[n], y = v[0], p, q = 6 + 52 / (n + 1), sum = 0, e; if (n < 1) { return; } while (0 < q--) { sum += XXTEA_DELTA; e = sum >> 2 & 3; for (p = 0; p < n; p++) { y = v[p + 1]; z = v[p] += XXTEA_MX; } y = v[0]; z = v[n] += XXTEA_MX; } } static void xxtea_long_decrypt(xxtea_long *v, xxtea_long len, xxtea_long *k) { xxtea_long n = len - 1; xxtea_long z = v[n], y = v[0], p, q = 6 + 52 / (n + 1), sum = q * XXTEA_DELTA, e; if (n < 1) { return; } while (sum != 0) { e = sum >> 2 & 3; for (p = n; p > 0; p--) { z = v[p - 1]; y = v[p] -= XXTEA_MX; } z = v[n]; y = v[0] -= XXTEA_MX; sum -= XXTEA_DELTA; } } static unsigned char *fix_key_length(unsigned char *key, xxtea_long key_len) { unsigned char *tmp = (unsigned char *)malloc(16); memcpy(tmp, key, key_len); memset(tmp + key_len, '\0', 16 - key_len); return tmp; } static xxtea_long *xxtea_to_long_array(unsigned char *data, xxtea_long len, int include_length, xxtea_long *ret_len) { xxtea_long i, n, *result; n = len >> 2; n = (((len & 3) == 0) ? n : n + 1); if (include_length) { result = (xxtea_long *)malloc((n + 1) << 2); result[n] = len; *ret_len = n + 1; } else { result = (xxtea_long *)malloc(n << 2); *ret_len = n; } memset(result, 0, n << 2); for (i = 0; i < len; i++) { result[i >> 2] |= (xxtea_long)data[i] << ((i & 3) << 3); } return result; } static unsigned char *xxtea_to_byte_array(xxtea_long *data, xxtea_long len, int include_length, xxtea_long *ret_len) { xxtea_long i, n, m; unsigned char *result; n = len << 2; if (include_length) { m = data[len - 1]; if ((m < n - 7) || (m > n - 4)) return NULL; n = m; } result = (unsigned char *)malloc(n + 1); for (i = 0; i < n; i++) { result[i] = (unsigned char)((data[i >> 2] >> ((i & 3) << 3)) & 0xff); } result[n] = '\0'; *ret_len = n; return result; } static unsigned char *do_xxtea_encrypt(unsigned char *data, xxtea_long len, unsigned char *key, xxtea_long *ret_len) { unsigned char *result; xxtea_long *v, *k, v_len, k_len; v = xxtea_to_long_array(data, len, 1, &v_len); k = xxtea_to_long_array(key, 16, 0, &k_len); xxtea_long_encrypt(v, v_len, k); result = xxtea_to_byte_array(v, v_len, 0, ret_len); free(v); free(k); return result; } static unsigned char *do_xxtea_decrypt(unsigned char *data, xxtea_long len, unsigned char *key, xxtea_long *ret_len) { unsigned char *result; xxtea_long *v, *k, v_len, k_len; v = xxtea_to_long_array(data, len, 0, &v_len); k = xxtea_to_long_array(key, 16, 0, &k_len); xxtea_long_decrypt(v, v_len, k); result = xxtea_to_byte_array(v, v_len, 1, ret_len); free(v); free(k); return result; } unsigned char *xxtea_encrypt(unsigned char *data, xxtea_long data_len, unsigned char *key, xxtea_long key_len, xxtea_long *ret_length) { unsigned char *result; *ret_length = 0; if (key_len < 16) { unsigned char *key2 = fix_key_length(key, key_len); result = do_xxtea_encrypt(data, data_len, key2, ret_length); free(key2); } else { result = do_xxtea_encrypt(data, data_len, key, ret_length); } return result; } unsigned char *xxtea_decrypt(unsigned char *data, xxtea_long data_len, unsigned char *key, xxtea_long key_len, xxtea_long *ret_length) { unsigned char *result; *ret_length = 0; if (key_len < 16) { unsigned char *key2 = fix_key_length(key, key_len); result = do_xxtea_decrypt(data, data_len, key2, ret_length); free(key2); } else { result = do_xxtea_decrypt(data, data_len, key, ret_length); } return result; } /* }}} */
加密過程:
其核心是:XXTEA演算法使用128bit的金鑰對以32bit為單位的資訊塊進行加密。
3.非官方xxtea加解密爆破方案
剛上述操作幾乎無障礙的就可以輕易獲得key和sign進行解密,所以比較的好的加固方案就是不使用官方加解密,並且對key字串做混淆,那如果碰上這種加固方案,那如何進行爆破呢,答案是爆破libcocos2dlua.so裡面的luaL_loadbuffer函式。Cocos引擎的lua載入器為cocos2dx_lua_loader,最終都是呼叫luaL_loadbuffer函式來載入,一般廠商會在這層上面對lua指令碼進行解密,即是在luaL_loadbuffer函式獲取buff引數可得到解密後的lua指令碼。
lua引擎載入lua指令碼最底層是到lua_reader函式。該函式負責最底層的指令碼buff遍歷,因此在此處dump出來的lua指令碼是最純正的lua指令碼,所有加密都已經被去除(修改lua opcode或者引擎邏輯除外)。不過這個點的獲取不到足夠的檔案資訊(檔名、buff index等),需要配合上層函式拼湊lua指令碼。
參考連結:
Lua遊戲逆向及破解方法介紹: http://www.freebuf.com/articles/system/103388.html
淺析android手遊lua指令碼的加密與解密: https://bbs.pediy.com/thread-216969.htm