1. 程式人生 > >將APP保衛戰進行到底--為你的APP新增四道防線

將APP保衛戰進行到底--為你的APP新增四道防線

計算機黑客或者說是黑帽子和病毒及木馬是軟體業和網際網路發展的三大癌細胞,靠不停的攫取網際網路的養分茁壯成長,生生不息,無孔不入。近些年智慧手機的飛速發展使其成為替代pc成為新的黑客樂園,因此研究APP安全問題,保衛個人企業的勞動成果,建立防禦戰線必不可少。

要解決app安全問題,APP的攻擊手段不能不知道。無非解包,修改,編輯,打包,一招一式,一攻一守,都要有應對之道。

1.APP攻擊首先是解包,APK本身是一個壓縮包,對於未加密的app來說,使用apktool及其更高階的組合解包工具可以輕鬆獲取APP的資源,smail程式碼,這是第一步攻擊,這種攻擊危害比較小,可能主要目的是獲取我們的APP和資源和實現方法。這一步可以通過一些加固手段以防範,可以是通用的愛加密或者360之類或者自己研發的獨有的,

2,解包後進一步對程式碼進行分析,如還原為JAVA原始碼,這就可以非常方便的修改原碼,學習APP核心實現,把更多的資源用於其他方面。比如去掉原app廣告,加入自己廣告,加入支付寶紅包程式碼,破解一些UI效果或者演算法庫用於自己APP,竊取使用者資訊,收集APP資源。這一步對於所有APP來說危害是最大的,尤其是以演算法為核心的APP如美顏,日曆類危險是致命的。這一步可以通過混淆,通過jni等方法把一些核心的演算法加密,並通過md5檢測安裝檔案完整性。

3重新打包當作新APP釋出或者當作盜版app釋出,這一步危害也不小,比如本身不帶廣告的產品被加入廣告發布,本身不收集使用者資訊的變成了收集資訊,本身有廣告的,被去掉了廣告,對原APP聲譽和掙錢影響非常大,同時也會擠佔官方APP的市場。通過校驗簽名可解決。

4.通過網路通訊抓包解析網路協議,分析協議規律,伺服器漏洞,既可以用於攻擊伺服器,也可以用於盜取伺服器核心資源連結。影響伺服器正常業務,同樣增加了企業維護成本。還能開發盜版APP,自己不需要伺服器,空手套白狼賺錢。還能研究出牛逼的爬蟲對伺服器攻擊,很多網際網路資源提供商都被抓蟲攻擊的休無完膚。這裡可以使用https雙向加密等。

下面通過簽名和MD5校驗APP,使用JNI實現,以防止逆向工程。簽名檢驗可以防重新打包,MD5可以防止修改程式碼,提供兩種方試的MD5校驗,一種是APK,一種是DEX,通過網路校驗可以使用APK,因為apk每次修改,都會導致生成的MD5碼發生改變,本地校驗需要把正確的MD5存在APP中,每次存md5都會導致生成新的MD5,故無法在本地使用,但只校驗DEX則可以,可以把正確的MD5存在JNI中或者資源中,如果害怕破解者修改,可以使用AES、DES加密,也可以使用RSA加密。不過本地校驗其實可以跳過,最好使用網路下發正確的MD5並加密傳輸,或者在請求協議中加入加密的MD5給伺服器校驗,一旦不正確,不下發資料返回錯誤,在客戶端收到錯誤時提醒使用者更新正版APP

char* getSha1(JNIEnv *env, jobject context_object){
    //上下文物件
    jclass context_class = env->GetObjectClass(context_object);

    //反射獲取PackageManager
    jmethodID methodId = env->GetMethodID(context_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    jobject package_manager = env->CallObjectMethod(context_object, methodId);
    if (package_manager == NULL) {
        LOGD("package_manager is NULL!!!");
        return NULL;
    }

    //反射獲取包名
    methodId = env->GetMethodID(context_class, "getPackageName", "()Ljava/lang/String;");
    jstring package_name = (jstring)env->CallObjectMethod(context_object, methodId);
    if (package_name == NULL) {
        LOGD("package_name is NULL!!!");
        return NULL;
    }
    env->DeleteLocalRef(context_class);

    //獲取PackageInfo物件
    jclass pack_manager_class = env->GetObjectClass(package_manager);
    methodId = env->GetMethodID(pack_manager_class, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    env->DeleteLocalRef(pack_manager_class);
    jobject package_info = env->CallObjectMethod(package_manager, methodId, package_name, 0x40);
    if (package_info == NULL) {
        LOGD("getPackageInfo() is NULL!!!");
        return NULL;
    }
    env->DeleteLocalRef(package_manager);

    //獲取簽名信息
    jclass package_info_class = env->GetObjectClass(package_info);
    jfieldID fieldId = env->GetFieldID(package_info_class, "signatures", "[Landroid/content/pm/Signature;");
    env->DeleteLocalRef(package_info_class);
    jobjectArray signature_object_array = (jobjectArray)env->GetObjectField(package_info, fieldId);
    if (signature_object_array == NULL) {
        LOGD("signature is NULL!!!");
        return NULL;
    }
    jobject signature_object = env->GetObjectArrayElement(signature_object_array, 0);
    env->DeleteLocalRef(package_info);

    //簽名信息轉換成sha1值
    jclass signature_class = env->GetObjectClass(signature_object);
    methodId = env->GetMethodID(signature_class, "toByteArray", "()[B");
    env->DeleteLocalRef(signature_class);
    jbyteArray signature_byte = (jbyteArray) env->CallObjectMethod(signature_object, methodId);
    jclass byte_array_input_class=env->FindClass("java/io/ByteArrayInputStream");
    methodId=env->GetMethodID(byte_array_input_class,"<init>","([B)V");
    jobject byte_array_input=env->NewObject(byte_array_input_class,methodId,signature_byte);
    jclass certificate_factory_class=env->FindClass("java/security/cert/CertificateFactory");
    methodId=env->GetStaticMethodID(certificate_factory_class,"getInstance","(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
    jstring x_509_jstring=env->NewStringUTF("X.509");
    jobject cert_factory=env->CallStaticObjectMethod(certificate_factory_class,methodId,x_509_jstring);
    methodId=env->GetMethodID(certificate_factory_class,"generateCertificate",("(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"));
    jobject x509_cert=env->CallObjectMethod(cert_factory,methodId,byte_array_input);
    env->DeleteLocalRef(certificate_factory_class);
    jclass x509_cert_class=env->GetObjectClass(x509_cert);
    methodId=env->GetMethodID(x509_cert_class,"getEncoded","()[B");
    jbyteArray cert_byte=(jbyteArray)env->CallObjectMethod(x509_cert,methodId);
    env->DeleteLocalRef(x509_cert_class);
    jclass message_digest_class=env->FindClass("java/security/MessageDigest");
    methodId=env->GetStaticMethodID(message_digest_class,"getInstance","(Ljava/lang/String;)Ljava/security/MessageDigest;");
    jstring sha1_jstring=env->NewStringUTF("SHA1");
    jobject sha1_digest=env->CallStaticObjectMethod(message_digest_class,methodId,sha1_jstring);
    methodId=env->GetMethodID(message_digest_class,"digest","([B)[B");
    jbyteArray sha1_byte=(jbyteArray)env->CallObjectMethod(sha1_digest,methodId,cert_byte);
    env->DeleteLocalRef(message_digest_class);

    //轉換成char
    jsize array_size=env->GetArrayLength(sha1_byte);
    jbyte* sha1 =env->GetByteArrayElements(sha1_byte,NULL);
    char *hex_sha=new char[array_size*2+1];
    for (int i = 0; i <array_size ; ++i) {
        hex_sha[2*i]=hexcode[((unsigned char)sha1[i])/16];
        hex_sha[2*i+1]=hexcode[((unsigned char)sha1[i])%16];
    }
    hex_sha[array_size*2]='\0';

    LOGD("hex_sha %s ",hex_sha);
    return hex_sha;
}

jboolean checkValidity(JNIEnv *env,char *sha1){
    //比較簽名
    LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------sha1=%s", sha1);
    LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------app_sha1=%s", app_sha1);
    LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------strcmp(sha1, app_sha1)=%d", strcmp(sha1, app_sha1));
    if (strcmp(sha1, app_sha1)==0)
    {
        LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------簽名校驗成功");
        return JNI_TRUE;
    }
    LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------簽名校驗失敗");
    return JNI_FALSE;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_ushaqi_zhuishushenqi_signture_getSignaturesSha1(
        JNIEnv *env,
        jobject,
        jobject contextObject) {

    return env->NewStringUTF(app_sha1);
}

/**
 * 簽名校驗
 */
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_ushaqi_zhuishushenqi_signture_checkSha1(
        JNIEnv *env,
        jobject,
        jobject contextObject) {

    char *sha1 = getSha1(env, contextObject);

    jboolean result = checkValidity(env, sha1);

    return result;
}

/**
 * 簽名校驗
 */
extern "C"
JNIEXPORT jstring JNICALL
Java_com_ushaqi_zhuishushenqi_signture_getToken(
        JNIEnv *env,
        jobject,
        jobject contextObject,
        jstring userId) {
    char *sha1 = getSha1(env, contextObject);
    jboolean result = checkValidity(env, sha1);

    if (result) {
        return env->NewStringUTF("獲取Token成功");
    } else {
        return env->NewStringUTF("獲取失敗,請檢查valid.cpp檔案配置的sha1值");
    }
}

/**
 * 獲取MD5校驗碼
 */
extern "C"
JNIEXPORT jstring JNICALL
Java_com_ushaqi_zhuishushenqi_signture_getMd5(
        JNIEnv *env,
        jobject,
        jstring strText
) {
    char *szText = (char *) env->GetStringUTFChars(strText, 0);

    MD5_CTX context = {0};
    MD5Init(&context);
    MD5Update(&context, (unsigned char *) szText, strlen(szText));
    unsigned char dest[16] = {0};
    MD5Final(&context, dest);
    env->ReleaseStringUTFChars(strText, szText);

    int i = 0;
    char szMd5[32] = {0};
    for (i = 0; i < 16; i++) {
        sprintf(szMd5, "%s%02x", szMd5, dest[i]);
    }

    return env->NewStringUTF(szMd5);
}

/**
 *讀取assets檔案內容
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_ushaqi_zhuishushenqi_signture_readFromAssets(JNIEnv *env, jclass type,
                                                      jobject assetManager, jstring filename_)
{
    LOGW("ReadAssets");
    AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
    if(mgr==NULL)
    {
        LOGI(" %s","AAssetManager==NULL");
        return ;
    }
    jboolean iscopy;
    const char *mfile = env->GetStringUTFChars(filename_, &iscopy);
    AAsset* asset = AAssetManager_open(mgr, mfile,AASSET_MODE_UNKNOWN);
    env->ReleaseStringUTFChars(filename_, mfile);
    if(asset==NULL)
    {
        LOGI(" %s","asset==NULL");
        return ;
    }
    off_t bufferSize = AAsset_getLength(asset);
    //LOGI("file size : %d\n",bufferSize);
    char *buffer=(char *)malloc(bufferSize+1);
    buffer[bufferSize]=0;
    int numBytesRead = AAsset_read(asset, buffer, bufferSize);
    LOGI(": %s",buffer);
    LOGW(">>>>>>>>>>>>>>>>>>>assets=%s", buffer);
    LOGW(">>>>>>>>>>>>>>>>>>>assets=%d", numBytesRead);
    free(buffer);
    AAsset_close(asset);
}

extern "C"
JNIEXPORT jbyte * JNICALL
Java_com_ushaqi_zhuishushenqi_signture_Hex2Byte(JNIEnv *env, jclass type, jstring str, jint len){
    const char *src = env->GetStringUTFChars(str, 0);
    unsigned char *des  = (unsigned char *) malloc((len/2)+1);
    HexStrToByte(src, des, len);
    des[(len/2)] = 0x00;
    env->ReleaseStringUTFChars(str, src);
    jbyteArray retArr = env->NewByteArray((len/2)+1);
    env->SetByteArrayRegion(retArr, 0, (len/2)+1, (const jbyte *) des);
    jbyte * ret = env->GetByteArrayElements(retArr, 0);
    free(des);
    return ret;
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_ushaqi_zhuishushenqi_signture_Byte2Hex(JNIEnv *env, jclass type, jbyteArray bytes, jint len){
    const char *src = (char *)env->GetByteArrayElements(bytes, 0);
     char * des = ( char *) malloc((len * 2) + 1);
    ByteToHexStr((unsigned char*)src, des, len);
    env->ReleaseByteArrayElements(bytes, (jbyte*)src, 0);
    jstring ret = env->NewStringUTF(des);
    free(des);
    return ret;
}

extern "C"
 JNIEXPORT jstring JNICALL
 Java_com_ushaqi_zhuishushenqi_signture_des_1Decrypt(JNIEnv *env, jclass type, jbyteArray bytes_,
                                                     jint len) {
     jbyte *bytes = env->GetByteArrayElements(bytes_, NULL);
     int des_len=0;
     char * des = DES_Decrypt((char *) bytes, len, "abcdefg", &des_len);
     env->ReleaseByteArrayElements(bytes_, bytes, 0);

     return env->NewStringUTF(des);
 }

extern "C"
 JNIEXPORT jbyte* JNICALL
 Java_com_ushaqi_zhuishushenqi_signture_des_1Encrypt(JNIEnv *env, jclass type, jstring str_,
                                                     jint len) {
     const char *str = env->GetStringUTFChars(str_, 0);
     char  * des = ( char *) malloc((len * 2) + 1);
     HexStrToByte(str, (unsigned char *) des, len);
     env->ReleaseStringUTFChars(str_, str);
     jbyteArray arr = env->NewByteArray((len * 2) + 1);
     env->SetByteArrayRegion(arr, 0, (len * 2) + 1, ( jbyte *) des);
     jbyte* ret = env->GetByteArrayElements((jbyteArray) des, 0);//env->NewStringUTF(des);
     free(des);
     return ret;
 }
extern "C"
 JNIEXPORT jboolean JNICALL
 Java_com_ushaqi_zhuishushenqi_signture_isMd5Check(JNIEnv *env, jclass type, jobject contextObject) {
     char * cc = "com/ushaqi/zhuishushenqi/signture";
     jclass cls = env->FindClass(cc);
     jmethodID mothod =  env->GetStaticMethodID(cls, "isVerification", "(Landroid/content/Context;)Z");
     jboolean result = (jboolean) env->CallStaticBooleanMethod(cls, mothod, contextObject);
     LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------result=%d", result);
     char *sha1 = getSha1(env, contextObject);
     jboolean bSignature = checkValidity(env, sha1);
     LOGI("jiaABC>>>>>>>>>>>>>>>>>>>--------------------bSignature=%d", bSignature);
     jmethodID dlg1 =  env->GetStaticMethodID(cls, "ErrorDialog", "(Landroid/content/Context;)V");
     jmethodID dlg2 =  env->GetStaticMethodID(cls, "RightDialog", "(Landroid/content/Context;)V");
     if (result && bSignature) {
         env->CallStaticVoidMethod(cls, dlg2, contextObject);
     }else{
         env->CallStaticVoidMethod(cls, dlg1, contextObject);
     }
     return result && bSignature;
 }
extern "C"
 JNIEXPORT void JNICALL
 Java_com_ushaqi_zhuishushenqi_signture_showDialog(JNIEnv *env, jclass type, jobject context,
                                                   jboolean isRigh) {


 }
extern "C"
JNIEXPORT void JNICALL
Java_com_ushaqi_zhuishushenqi_signture_show(JNIEnv *env, jobject thiz, jobject context,
                                            jstring cstr) {
    jclass jc_Toast = env->FindClass("android/widget/Toast");
    jmethodID jm_makeText = env->GetStaticMethodID(jc_Toast, "makeText",
                                                   "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;");
    jobject jo_Toast = env->CallStaticObjectMethod(jc_Toast, jm_makeText, context, cstr, 0);
    jmethodID jm_Show = env->GetMethodID(jc_Toast, "show", "()V");
    env->CallVoidMethod(jo_Toast, jm_Show);
}