某android游戏逆向小记

apktool 解包,jdgui查看jar发现代码非常少。查看代码发现它动态加载了一个he.jar。这个jar实际上是一个dex文件。这个dex文件用dex2jar反编译后发现里面代码仍然非常的少。

重新打包后打manifest.xml里面设置成debug标志为true。

  重新打包后发现一直出现签名校验错误。提示到官网下载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;
}

代码没有仔细整理。

有一点需要特别注意的是解密时使用的,iv初始向量。它位于lua密文的前16个字节。这个不用修改。密文内容从第16个字节偏移才是真正的加密内容。openssl解密后仍然是乱码。跟踪后发现是正确的。不过是压缩过了。解压缩即可。

使用上面的函数遍历一遍lua目录,即可拿到所有lua脚本。改lua脚本就比较快了。对比签名是在SignatureUtil.lua里面,让verifysignature全返回true即可。lua修改后需要先压缩,压缩完了再加密,加密完成后再把解密前密文的前16个字节,是初始向量添加在文件的开头。再重新打包签名,即可。

另外附上工程

 https://files.cnblogs.com/files/chyl411/openssl.zip

https://files.cnblogs.com/files/chyl411/libhegame.7z

posted on 2015-04-28 17:53  各各他  阅读(2511)  评论(1编辑  收藏  举报

导航