Android JNI 防二次打包應(yīng)用簽名檢測(cè)

國(guó)內(nèi)android市場(chǎng)的環(huán)境比較混亂碗殷,大小市場(chǎng)有數(shù)百家,應(yīng)用被修改或加廣告病毒后二次打包發(fā)布也是常有的事情仿粹,對(duì)開(kāi)發(fā)者和用戶都造成了損失

經(jīng)各方資料研究搁吓,在C++中做簽名檢測(cè)處理,安全性更高一些吭历,結(jié)合網(wǎng)上資料堕仔,寫(xiě)下此文

簽名檢測(cè)邏輯

  • 獲取應(yīng)用簽名生成的MD5值寫(xiě)入C++中 (C++中會(huì)打印出當(dāng)前的MD5,后面會(huì)說(shuō))
  • 獲取Application
  • 獲取應(yīng)用的簽名信息
  • 將簽名信息MD5化
  • 匹配簽名MD5是否與寫(xiě)入的相同
  • 簽名不同時(shí)在JNI_OnLoad 中返回 -1
  • 在App的代碼中加載so文件,會(huì)自動(dòng)調(diào)用JNI_OnLoad,若返回-1晌区,App Crash掉

NDK編譯

NDK編譯使用的實(shí)驗(yàn)性NDK構(gòu)建插件 Experimental Plugin
只要是因?yàn)樵摬寮С諧語(yǔ)言Debug調(diào)試,但是使用該插件需要修改Module的Build.gradle文件摩骨,不太建議使用在開(kāi)發(fā)的項(xiàng)目中,可以使用之前的NDK或CMake來(lái)編譯NDK

這里不介紹實(shí)驗(yàn)性NDK插件了朗若,附上使用手冊(cè)Experimental Plugin use guide

Java代碼上的邏輯

/**
 * 獲取Application恼五,ActivityThread是一個(gè)不被公開(kāi)的類,java代碼是不可直接調(diào)用的
 * 對(duì)應(yīng)C++代碼中的static jobject getApplication(JNIEnv *env)
 */
Application application = ActivityThread.currentApplication();

/**
 * 獲取Signature數(shù)據(jù)
 * 對(duì)應(yīng)C++ 的jstring loadSignature(JNIEnv *env, jobject * *context) 方法
 */
        PackageManager manager = application.getPackageManager();
        String packageName = application.getPackageName();
        PackageInfo packageInfo = manager.getPackageInfo(packageName,PackageManager.GET_SIGNATURES);
        Signature signature = packageInfo.signatures[0];
        byte[] chars = signature.toByteArray();
        
/**
* 將數(shù)據(jù)MD5化
* 對(duì)應(yīng)C++的jstring ToMd5(JNIEnv *env, jbyteArray source)
*/
        MessageDigest digest = MessageDigest.getInstance("md5");
        digest.update(chars);
        byte[] objArraySign = digest.digest(chars);
        
//MD5化的C++ 代碼
        jsize intArrayLength = env->GetArrayLength(objArraySign);
        jbyte *byte_array_elements = env->GetByteArrayElements(objArraySign, NULL);
        size_t length = (size_t) intArrayLength * 2 + 1;
        char *char_result = (char *) malloc(length);
        memset(char_result, 0, length);
        // 將byte數(shù)組轉(zhuǎn)換成16進(jìn)制字符串哭懈,發(fā)現(xiàn)這里不用強(qiáng)轉(zhuǎn)灾馒,jbyte和unsigned char應(yīng)該字節(jié)數(shù)是一樣的
        ByteToHexStr((const char *) byte_array_elements, char_result, intArrayLength);
        // 在末尾補(bǔ)\0
    *(char_result + intArrayLength * 2) = '\0';
        jstring stringResult = env->NewStringUTF(char_result);

//檢測(cè)當(dāng)前應(yīng)用的簽名MD5與我們?cè)O(shè)置的MD5是否一致

奉上C++ 代碼


#include <jni.h>
#include <string.h>
#include <android/log.h>
#include <malloc.h>

#define  LOG_TAG    "native-dev"
#define  LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

const char *APP_SIGNATURE = "10645EA8A12BE7A2C04B1F81DF3B4D90";

void ByteToHexStr(const char *source, char *dest, int sourceLen) {
    short i;
    char highByte, lowByte;

    for (i = 0; i < sourceLen; i++) {
        highByte = source[i] >> 4;
        lowByte = source[i] & 0x0f;
        highByte += 0x30;

        if (highByte > 0x39) {
            dest[i * 2] = highByte + 0x07;
        } else {
            dest[i * 2] = highByte;
        }

        lowByte += 0x30;
        if (lowByte > 0x39) {
            dest[i * 2 + 1] = lowByte + 0x07;
        } else {
            dest[i * 2 + 1] = lowByte;
        }
    }
}


// byte數(shù)組轉(zhuǎn)MD5字符串
jstring ToMd5(JNIEnv *env, jbyteArray source) {
    // MessageDigest類
    jclass classMessageDigest = env->FindClass("java/security/MessageDigest");
    // MessageDigest.getInstance()靜態(tài)方法
    jmethodID midGetInstance = env->GetStaticMethodID(classMessageDigest, "getInstance",
                                                      "(Ljava/lang/String;)Ljava/security/MessageDigest;");
    // MessageDigest object
    jobject objMessageDigest = env->CallStaticObjectMethod(classMessageDigest, midGetInstance,
                                                           env->NewStringUTF("md5"));

    // update方法,這個(gè)函數(shù)的返回值是void遣总,寫(xiě)V
    jmethodID midUpdate = env->GetMethodID(classMessageDigest, "update", "([B)V");
    env->CallVoidMethod(objMessageDigest, midUpdate, source);

    // digest方法
    jmethodID midDigest = env->GetMethodID(classMessageDigest, "digest", "()[B");
    jbyteArray objArraySign = (jbyteArray) env->CallObjectMethod(objMessageDigest, midDigest);

    jsize intArrayLength = env->GetArrayLength(objArraySign);
    jbyte *byte_array_elements = env->GetByteArrayElements(objArraySign, NULL);
    size_t length = (size_t) intArrayLength * 2 + 1;
    char *char_result = (char *) malloc(length);
    memset(char_result, 0, length);

    // 將byte數(shù)組轉(zhuǎn)換成16進(jìn)制字符串睬罗,發(fā)現(xiàn)這里不用強(qiáng)轉(zhuǎn),jbyte和unsigned char應(yīng)該字節(jié)數(shù)是一樣的
    ByteToHexStr((const char *) byte_array_elements, char_result, intArrayLength);
    // 在末尾補(bǔ)\0
    *(char_result + intArrayLength * 2) = '\0';

    jstring stringResult = env->NewStringUTF(char_result);
    // release
    env->ReleaseByteArrayElements(objArraySign, byte_array_elements, JNI_ABORT);
    // 釋放指針使用free
    free(char_result);
    env->DeleteLocalRef(classMessageDigest);
    env->DeleteLocalRef(objMessageDigest);

    return stringResult;
}

//獲取應(yīng)用簽名
 jstring loadSignature(JNIEnv *env, jobject context) {
    // 獲得Context類
    jclass cls = env->GetObjectClass(context);
    // 得到getPackageManager方法的ID
    jmethodID mid = env->GetMethodID(cls, "getPackageManager",
                                     "()Landroid/content/pm/PackageManager;");

    // 獲得應(yīng)用包的管理器
    jobject pm = env->CallObjectMethod(context, mid);

    // 得到getPackageName方法的ID
    mid = env->GetMethodID(cls, "getPackageName", "()Ljava/lang/String;");
    // 獲得當(dāng)前應(yīng)用包名
    jstring packageName = (jstring) env->CallObjectMethod(context, mid);

    // 獲得PackageManager類
    cls = env->GetObjectClass(pm);
    // 得到getPackageInfo方法的ID
    mid = env->GetMethodID(cls, "getPackageInfo",
                           "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    // 獲得應(yīng)用包的信息
    jobject packageInfo = env->CallObjectMethod(pm, mid, packageName, 0x40); //GET_SIGNATURES = 64;
    // 獲得PackageInfo 類
    cls = env->GetObjectClass(packageInfo);
    // 獲得簽名數(shù)組屬性的ID
    jfieldID fid = env->GetFieldID(cls, "signatures", "[Landroid/content/pm/Signature;");
    // 得到簽名數(shù)組
    jobjectArray signatures = (jobjectArray) env->GetObjectField(packageInfo, fid);
    // 得到簽名
    jobject signature = env->GetObjectArrayElement(signatures, 0);

    // 獲得Signature類
    cls = env->GetObjectClass(signature);
    // 得到toCharsString方法的ID
    mid = env->GetMethodID(cls, "toByteArray", "()[B");
    // 返回當(dāng)前應(yīng)用簽名信息
    jbyteArray signatureByteArray = (jbyteArray) env->CallObjectMethod(signature, mid);

    return ToMd5(env, signatureByteArray);
}
//檢測(cè)簽名是否匹配
jboolean checkSignature(
        JNIEnv *env, jobject context) {

    jstring appSignature = loadSignature(env,
                                                                           context); // 當(dāng)前 App 的簽名
    jstring releaseSignature = env->NewStringUTF(APP_SIGNATURE); // 發(fā)布時(shí)候的簽名
    const char *charAppSignature = env->GetStringUTFChars(appSignature, NULL);
    const char *charReleaseSignature = env->GetStringUTFChars(releaseSignature, NULL);

//    LOGI("  start cmp  getSignature");
       __android_log_print(ANDROID_LOG_INFO, LOG_TAG, charAppSignature);
//    LOGI("  start cmp  getReleaseSignature");
    //  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, charAppSignature);

    jboolean result = JNI_FALSE;
    // 比較是否相等
    if (charAppSignature != NULL && charReleaseSignature != NULL) {
        if (strcmp(charAppSignature, charReleaseSignature) == 0) {
            result = JNI_TRUE;
        }
    }

    env->ReleaseStringUTFChars(appSignature, charAppSignature);
    env->ReleaseStringUTFChars(releaseSignature, charReleaseSignature);

    return result;
}

static jobject getApplication(JNIEnv *env) {
    jobject application = NULL;
    jclass activity_thread_clz = env->FindClass("android/app/ActivityThread");
    if (activity_thread_clz != NULL) {
        jmethodID currentApplication = env->GetStaticMethodID(
                activity_thread_clz, "currentApplication", "()Landroid/app/Application;");
        if (currentApplication != NULL) {
            application = env->CallStaticObjectMethod(activity_thread_clz, currentApplication);
        } else {
            //           LOGE("Cannot find method: currentApplication() in ActivityThread.");
        }
        env->DeleteLocalRef(activity_thread_clz);
    } else {
//        LOGE("Cannot find class: android.app.ActivityThread");
    }

    return application;
}

/**
 * 檢查加載該so的應(yīng)用的簽名旭斥,與預(yù)置的簽名是否一致
 */
static jboolean checkSignature(JNIEnv *env) {

    // 調(diào)用 getContext 方法得到 Context 對(duì)象
    jobject appContext = getApplication(env);

    if (appContext != NULL) {
        jboolean signatureValid = checkSignature(
                env, appContext);
        return signatureValid;
    }

    return JNI_FALSE;
}

/**
 * 加載 so 文件的時(shí)候容达,會(huì)觸發(fā) OnLoad
 * 檢測(cè)失敗,返回 -1琉预,App 就會(huì) Crash
 */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
//    LOGI("  JNI_OnLoad  ");
    if (vm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
//    LOGI("  start checkSignature  ");
    if (checkSignature(env) != JNI_TRUE) {
//        LOGI("  checkSignature = false ");
        // 檢測(cè)不通過(guò)董饰,返回 -1 就會(huì)使 App crash
        return -1;
    }

    return JNI_VERSION_1_6;
}

在C++的 checkSignature方法中,
__android_log_print(ANDROID_LOG_INFO, LOG_TAG, charAppSignature);
此代碼會(huì)打印出當(dāng)前應(yīng)用的簽名MD5碼圆米,也是我們需要寫(xiě)在C++中用于檢測(cè)的MD5碼卒暂,獲取到之后寫(xiě)C++中并注釋掉這行代碼

加載jni

static{
    System.loadLibrary("SignatureLib");
}

完整代碼的GitHub地址
https://github.com/junzLiu/JniTest

參考資料

如有問(wèn)題 歡迎指正 與君共勉

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市娄帖,隨后出現(xiàn)的幾起案子也祠,更是在濱河造成了極大的恐慌,老刑警劉巖近速,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件诈嘿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡削葱,警方通過(guò)查閱死者的電腦和手機(jī)奖亚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)析砸,“玉大人昔字,你說(shuō)我怎么就攤上這事∈追保” “怎么了作郭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)弦疮。 經(jīng)常有香客問(wèn)我夹攒,道長(zhǎng),這世上最難降的妖魔是什么胁塞? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任咏尝,我火速辦了婚禮,結(jié)果婚禮上啸罢,老公的妹妹穿的比我還像新娘编检。我一直安慰自己,他們只是感情好伺糠,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布蒙谓。 她就那樣靜靜地躺著,像睡著了一般训桶。 火紅的嫁衣襯著肌膚如雪累驮。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,268評(píng)論 1 309
  • 那天舵揭,我揣著相機(jī)與錄音谤专,去河邊找鬼。 笑死午绳,一個(gè)胖子當(dāng)著我的面吹牛置侍,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蜡坊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼杠输!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起秕衙,我...
    開(kāi)封第一講書(shū)人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蠢甲,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后据忘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體鹦牛,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年勇吊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了曼追。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汉规,死狀恐怖礼殊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鲫忍,我是刑警寧澤膏燕,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站悟民,受9級(jí)特大地震影響坝辫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜射亏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一近忙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧智润,春花似錦及舍、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至兼蜈,卻和暖如春攘残,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背为狸。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工歼郭, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辐棒。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓病曾,卻偏偏與公主長(zhǎng)得像牍蜂,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泰涂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容