apktool 解包,jdgui查看jar發現代碼很是少。查看代碼發現它動態加載了一個he.jar。這個jar其實是一個dex文件。這個dex文件用dex2jar反編譯後發現裏面代碼仍然很是的少。算法
從新打包後打manifest.xml裏面設置成debug標誌爲true。api
從新打包後發現一直出現簽名校驗錯誤。提示到官網下載apk。因而採用ida調試so文件。最重要的so文件是libhegame.so。最重要的東西都是在這個so裏面。用ida在裏面跟蹤,被搞的很慘,由於繞來繞去老是在lua裏面。度娘後才知道lua是一個腳本語言。原來在IDA裏繞的都是lua的運行平臺。在解壓縮包裏發現assets\src裏面發現大量.lua加密過的文件。看來真正的遊戲是運行在lua環境裏面的。在IDA裏調試的時候,一個偶然的機會發現一個重要的函數 load_lua。(其實也不是偶然,由於lua內容加載有幾個函數,有從文件加載的,有從內存加載的,在從內存加載的地方斷點,便可找到load_lua函數),這個函數是破解的關鍵。它用於加載全部加密的assets\src下面的全部lua加密文件。ida的f5查看C代碼,再加上靜態分析手段。基本上能看到它循環讀取文件,並使用密鑰解密lua密文的過程。它採用的是openssl的EVP_aes_128_cbc加密方式。密鑰靜態分析都能獲得。再加上動態調試,能夠拿到全部lua文件的明文。數組
#include "stdafx.h" #include "ZipUtils.h" #include "io.h" #include <stdio.h> #include <string.h> #include <errno.h> #include <winsock2.h> #include <Iphlpapi.h> #include <stdlib.h> #include <openssl/ssl.h> #include <openssl/err.h> #include <openssl/evp.h> #include <openssl/x509.h> #pragma comment( lib, "zlibwapi.lib") #define DATA_LEN 32 #define EVP_MAX_KEY_LENGHT 64 extern int ccInflateMemoryWithHint(unsigned char *in, unsigned int inLength, unsigned char **out, unsigned int *outLength, unsigned int outLenghtHint); //初始化密鑰 void keyInit(unsigned char* key) { int i = 0; key[i++] = -23; key[i++] = 116; key[i++] = 125; key[i++] = -110; key[i++] = -52; key[i++] = 50; key[i++] = 46; key[i++] = 125; key[i++] = 17; key[i++] = 46; key[i++] = 124; key[i++] = 52; key[i++] = 81; key[i++] = -41; key[i++] = -77; key[i++] = 106; } #define MAXLEN (1024 * 1024) int readFile(char * fileName, unsigned char ** buf) { FILE *fp; int count; if((fp=fopen(fileName,"rb"))==NULL){ printf("\nCannot open file strike any key exit!"); exit(1); } fseek(fp,0,SEEK_END); //定位到文件末 count = ftell(fp); //文件長度 fseek(fp,0,SEEK_SET); (*buf) = (unsigned char*)malloc(count); count = fread((*buf), sizeof(unsigned char), count, fp); fclose(fp); return count; } int writeFile(unsigned char * dest, int count, char * fileName) { FILE *fp; if((fp=fopen(fileName,"wb"))==NULL){ exit(1); } count = fwrite((void *)dest, count, 1, fp); fclose(fp); return count; } int decryptAFile(char * path) { EVP_CIPHER_CTX ctx; unsigned char key[16]; unsigned char out[1024] = {0}; int outl, tmp, i; unsigned int len; unsigned char * msg; int count; const EVP_CIPHER * type; unsigned char * inBuf; unsigned char * decode; char outPath[256]; int rv; keyInit(key); count = readFile(path, &inBuf); msg = inBuf + 16; //OpenSSL_add_all_algorithms(); EVP_CIPHER_CTX_set_padding(&ctx, 0x07); EVP_CIPHER_CTX_init(&ctx); count -= 16; unsigned char * dest = (unsigned char *)operator new[](count); type = EVP_aes_128_cbc(); rv = EVP_DecryptInit_ex(&ctx, type, 0, key, inBuf); if(rv != 1) { printf("Error"); return -1; } outl = 0; rv = EVP_DecryptUpdate(&ctx, dest, &outl, msg, count); if(rv != 1) { printf("Error"); EVP_CIPHER_CTX_cleanup(&ctx); //清除EVP加密上下文環境 return -1; } rv = EVP_DecryptFinal_ex(&ctx, dest + outl, &tmp); outl = outl + tmp; ccInflateMemoryWithHint(dest, outl, &decode, &len, 1024*1024); EVP_CIPHER_CTX_cleanup(&ctx); //清除EVP加密上下文環境 strcpy(outPath, path); path = strcat(outPath, ".c"); writeFile(decode, len, path); printf("rn"); return 0; } /* * key:加密密鑰 * iv:加密初始向量 * in_enc:明文數組,輸入數組 * out_enc:加密後的數組,輸出密文數組 * in_len:明文長度 * out_len:密文長度 * */ unsigned char outBuffer[MaxLen]; int EncryptBuffer(unsigned char * key,unsigned char *iv,unsigned char * in_enc, unsigned char ** out_enc,int in_len,int *out_len) { int outl; //第一次使用update加密的數據長度 int outl2; //剩餘的字段,通過final填充後的長度 int inl; int rv; EVP_CIPHER_CTX ctx; //OpenSSL_add_all_algorithms(); EVP_CIPHER_CTX_set_padding(&ctx, 0x07); EVP_CIPHER_CTX_init(&ctx); //初始化ctx rv = EVP_EncryptInit_ex(&ctx, EVP_aes_128_cbc(), NULL, key, iv); //設置密碼算法、key和iv if(rv != 1) { printf("Err\n"); return -1; } inl = in_len; rv = EVP_EncryptUpdate(&ctx, (unsigned char *)outBuffer, &outl, in_enc, in_len);//加密 if(rv != 1) { printf("Err\n"); return -1; } //加密結束 rv = EVP_EncryptFinal_ex(&ctx, (unsigned char *)(outBuffer+outl), &outl2); if(rv != 1) { EVP_CIPHER_CTX_cleanup(&ctx); return -1; } *out_len=outl+outl2; (*out_enc) = (unsigned char *) malloc(*out_len); memset((*out_enc), 0, *out_len); memcpy((void *)(*out_enc), (void *)outBuffer, *out_len); EVP_CIPHER_CTX_cleanup(&ctx); //清除EVP加密上下文環境 printf("加密已完成\n"); } /*launcher的iv void ivInit(unsigned char* iv) { int i = 0; iv[i++] = 0xae; iv[i++] = 0x4e; iv[i++] = 0xec; iv[i++] = 0x18; iv[i++] = 0x1c; iv[i++] = 0x95; iv[i++] = 0x97; iv[i++] = 0x3d; iv[i++] = 0x0d; iv[i++] = 0x40; iv[i++] = 0xf5; iv[i++] = 0xa2; iv[i++] = 0x57; iv[i++] = 0x34; iv[i++] = 0x3d; iv[i++] = 0x33; }*/ //LevelInfoPanel.a39d133180fc618a3a1964ae1f089c2a.lua void ivInit(unsigned char* iv) { int i = 0; iv[i++] = 0xb9; iv[i++] = 0xec; iv[i++] = 0x39; iv[i++] = 0x96; iv[i++] = 0x57; iv[i++] = 0x69; iv[i++] = 0x75; iv[i++] = 0xbe; iv[i++] = 0xa9; iv[i++] = 0x9b; iv[i++] = 0x93; iv[i++] = 0x54; iv[i++] = 0xcb; iv[i++] = 0x6d; iv[i++] = 0xf9; iv[i++] = 0x17; } int gzcompress(unsigned char *data, int ndata, unsigned char ** zdata, int*nzdata); void encryptAFile(char * path) { unsigned char * inFile; unsigned char * zipBuf = NULL; unsigned char * out_enc = NULL; int zipLen; char outPath[256]; int decode; unsigned int len; unsigned char * decodebuf; int inLen, out_len; unsigned char key[16]; unsigned char iv[16]; keyInit(key); ivInit(iv); inLen = readFile(path, &inFile); gzcompress(inFile, inLen, (unsigned char ** )&zipBuf, &zipLen); //ccInflateMemoryWithHint(zipBuf, zipLen, &decodebuf, &len, 1024*1024); EncryptBuffer((unsigned char *)key, iv,(unsigned char *)zipBuf, &out_enc, zipLen, &out_len); strcpy(outPath, path); path = strcat(outPath, ".zp.out"); writeFile(out_enc, out_len, path); } const int PATH_MAXLEN = 1024;//定義最大目錄長度 unsigned long FILECOUNT = 0; //記錄文件數量 void ListDir(const char* pchData) { _finddata_t fdata; //定義文件查找結構對象 long done; char tempdir[PATH_MAXLEN] = {0}; //定義一個臨時字符數組 strcat(tempdir, pchData); //鏈接字符串 strcat(tempdir, "\\*"); //鏈接字符串(搜索以lua結尾的文件) //cout << tempdir << endl; done = _findfirst(tempdir, &fdata); //開始查找文件 if(done!=-1) //是否查找成功 { int ret = 0; while(ret!=-1) //定義一個循環 { if(fdata.attrib != _A_SUBDIR) //判斷文件屬性 { //cout << fdata.name << endl; if(strcmp(fdata.name, "...")!=0 && strcmp(fdata.name, "..") != 0 && strcmp(fdata.name, ".") != 0) //過濾 { char dir[PATH_MAXLEN] = {0}; //定義字符數組 strcat(dir, pchData); //鏈接字符串 strcat(dir, "\\"); strcat(dir, fdata.name); if(strstr(dir, ".lua") && strstr(dir, ".out") == false) { decryptAFile(dir); } //cout << dir << endl; //輸出查找到的文件名 FILECOUNT++; //累加文件個數 } } ret = _findnext(done, &fdata); //查找下一個文件 if(fdata.attrib == _A_SUBDIR && ret != -1) //判斷文件屬性,若是是目錄,則遞歸調用 { if(strcmp(fdata.name, "...") != 0 && strcmp(fdata.name, "..") != 0 && strcmp(fdata.name, ".") != 0) //過濾 { char pdir[PATH_MAXLEN] = {0}; //定義字符數組 strcat(pdir, pchData); //鏈接字符串 strcat(pdir, "\\"); strcat(pdir, fdata.name); //cout << pdir << endl; //輸出要遞歸調用的目錄名 ListDir(pdir); //遞歸調用 } } } } } int main() { char * path = "LevelInfoPanel.a39d133180fc618a3a1964ae1f089c2a.lua.c"; //ListDir(path); encryptAFile(path); return 0; }
ZipUtils.cpp這個裏面是壓縮和解壓的代碼。解壓是用cocos2d的源代碼扣出來的解壓函數。根據解壓又寫了壓縮用的函數。麻煩的是要集成zlib庫和openssl庫。函數
/**************************************************************************** Copyright (c) 2010 cocos2d-x.org http://www.cocos2d-x.org Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "stdafx.h" #include "zlib.h" #include <assert.h> #include <stdlib.h> #include "ZipUtils.h" #include <map> unsigned int s_uEncryptedPvrKeyParts[4] = {0,0,0,0}; unsigned int s_uEncryptionKey[1024]; bool s_bEncryptionKeyIsValid = false; // --------------------- ZipUtils --------------------- inline void ccDecodeEncodedPvr(unsigned int *data, int len) { const int enclen = 1024; const int securelen = 512; const int distance = 64; // create long key if(!s_bEncryptionKeyIsValid) { unsigned int y, p, e; unsigned int rounds = 6; unsigned int sum = 0; unsigned int z = s_uEncryptionKey[enclen-1]; do { #define DELTA 0x9e3779b9 #define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (s_uEncryptedPvrKeyParts[(p&3)^e] ^ z))) sum += DELTA; e = (sum >> 2) & 3; for (p = 0; p < enclen - 1; p++) { y = s_uEncryptionKey[p + 1]; z = s_uEncryptionKey[p] += MX; } y = s_uEncryptionKey[0]; z = s_uEncryptionKey[enclen - 1] += MX; } while (--rounds); s_bEncryptionKeyIsValid = true; } int b = 0; int i = 0; // encrypt first part completely for(; i < len && i < securelen; i++) { data[i] ^= s_uEncryptionKey[b++]; if(b >= enclen) { b = 0; } } // encrypt second section partially for(; i < len; i += distance) { data[i] ^= s_uEncryptionKey[b++]; if(b >= enclen) { b = 0; } } } inline unsigned int ccChecksumPvr(const unsigned int *data, int len) { unsigned int cs = 0; const int cslen = 128; len = (len < cslen) ? len : cslen; for(int i = 0; i < len; i++) { cs = cs ^ data[i]; } return cs; } // memory in iPhone is precious // Should buffer factor be 1.5 instead of 2 ? #define BUFFER_INC_FACTOR (2) int ccInflateMemoryWithHint(unsigned char *in, unsigned int inLength, unsigned char **out, unsigned int *outLength, unsigned int outLenghtHint) { /* ret value */ int err = Z_OK; int bufferSize = outLenghtHint; *out = new unsigned char[bufferSize]; z_stream d_stream; /* decompression stream */ d_stream.zalloc = (alloc_func)0; d_stream.zfree = (free_func)0; d_stream.opaque = (voidpf)0; d_stream.next_in = in; d_stream.avail_in = inLength; d_stream.next_out = *out; d_stream.avail_out = bufferSize; /* window size to hold 256k */ if( (err = inflateInit2(&d_stream, 15 + 32)) != Z_OK ) return err; for (;;) { err = inflate(&d_stream, Z_NO_FLUSH); if (err == Z_STREAM_END) { break; } switch (err) { case Z_NEED_DICT: err = Z_DATA_ERROR; case Z_DATA_ERROR: case Z_MEM_ERROR: inflateEnd(&d_stream); return err; } // not enough memory ? if (err != Z_STREAM_END) { *out = (unsigned char*)realloc(*out, bufferSize * BUFFER_INC_FACTOR); /* not enough memory, ouch */ if (! *out ) { // CCLOG("cocos2d: ZipUtils: realloc failed"); inflateEnd(&d_stream); return Z_MEM_ERROR; } d_stream.next_out = *out + bufferSize; d_stream.avail_out = bufferSize; bufferSize *= BUFFER_INC_FACTOR; } } *outLength = bufferSize - d_stream.avail_out; err = inflateEnd(&d_stream); return err; } int ccInflateMemoryWithHint(unsigned char *in, unsigned int inLength, unsigned char **out, unsigned int outLengthHint) { unsigned int outLength = 0; int err = ccInflateMemoryWithHint(in, inLength, out, &outLength, outLengthHint); if (err != Z_OK || *out == NULL) { if (err == Z_MEM_ERROR) { // CCLOG("cocos2d: ZipUtils: Out of memory while decompressing map data!"); } else if (err == Z_VERSION_ERROR) { // CCLOG("cocos2d: ZipUtils: Incompatible zlib version!"); } else if (err == Z_DATA_ERROR) { //CCLOG("cocos2d: ZipUtils: Incorrect zlib compressed data!"); } else { // CCLOG("cocos2d: ZipUtils: Unknown error while decompressing map data!"); } delete[] *out; *out = NULL; outLength = 0; } return outLength; } unsigned char outData[MaxLen]; //void encodeZip(const unsigned char * buffer, int inLen, unsigned char ** zipBuf, int* zipLen) /* Compress gzip data */ /* data 原數據 ndata 原數據長度 zdata 壓縮後數據 nzdata 壓縮後長度 */ int gzcompress(unsigned char *data, int ndata, unsigned char ** zdata, int*nzdata) { z_stream c_stream; int err = 0; if(data && ndata > 0) { c_stream.zalloc = NULL; c_stream.zfree = NULL; c_stream.opaque = NULL; //只有設置爲MAX_WBITS + 16才能在在壓縮文本中帶header和trailer if(deflateInit2(&c_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, MAX_WBITS, 8, Z_DEFAULT_STRATEGY) != Z_OK) return -1; c_stream.next_in = data; c_stream.avail_in = ndata; c_stream.next_out = (unsigned char *)outData; c_stream.avail_out = MaxLen; while(c_stream.avail_in != 0 && c_stream.total_out < *nzdata) { if(deflate(&c_stream, Z_NO_FLUSH) != Z_OK) return -1; } if(c_stream.avail_in != 0) return c_stream.avail_in; for(;;) { if((err = deflate(&c_stream, Z_FINISH)) == Z_STREAM_END) break; if(err != Z_OK) return -1; } if(deflateEnd(&c_stream) != Z_OK) return -1; *nzdata = c_stream.total_out; (*zdata) = (unsigned char *)malloc(c_stream.total_out); memset((void*)(*zdata), 0, c_stream.total_out); memcpy((void*)(*zdata), (void*)outData, c_stream.total_out); return 0; } return -1; }
代碼沒有仔細整理。ui
有一點須要特別注意的是解密時使用的,iv初始向量。它位於lua密文的前16個字節。這個不用修改。密文內容從第16個字節偏移纔是真正的加密內容。openssl解密後仍然是亂碼。跟蹤後發現是正確的。不過是壓縮過了。解壓縮便可。this
使用上面的函數遍歷一遍lua目錄,便可拿到全部lua腳本。改lua腳本就比較快了。對比簽名是在SignatureUtil.lua裏面,讓verifysignature全返回true便可。lua修改後須要先壓縮,壓縮完了再加密,加密完成後再把解密前密文的前16個字節,是初始向量添加在文件的開頭。再從新打包簽名,便可。加密
另外附上工程lua
http://files.cnblogs.com/files/chyl411/openssl.zipspa
http://files.cnblogs.com/files/chyl411/libhegame.7zdebug