標籤:android jni 反編譯 簽名資訊
最近業餘時間寫了一款應用《搖啊搖》,安智、安卓、360等幾個市集已經陸續審核通過並上線。從有想法到最終將產品做出來並發布,斷斷續續花了近二個半月的業餘時間,總體來講還算順利,雖然期間也遇到幾個小技術痛點,最後解決的還算滿意。今天說下其中一個小技術痛點,現在想想這個小技術痛點也很平常,但還是分享出來,希望對有相同疑惑的同學有協助。 因為java語言自身特性的原因,導致android程式很容易被反編譯,雖然可以採用代碼混淆的方式,但是如果用了第三方庫,混淆腳步編寫不好,代碼混淆後又會出現程式運行不穩定問題。而沒有混淆的程式一旦被反編譯後,源碼中大量的敏感資訊將會暴露無遺。比如與伺服器互動的url地址資訊,如果使用了動態連結程式庫,那麼native方法也將暴露,況且混淆時是不混淆native方法的。別人看到native方法,就可以自己載入so檔案,那麼很多核心的東西,別人就可以間接的使用了,雖然他不一定能用好,但至少可以調用了。針對上面的林林總總程式被反編譯後可能出現的問題,我的解決方案是使用jni技術,在ndk環境下做包簽名資訊核查,由於是ndk環境,所以這個很難反編譯,也很難繞過該核查。理論上,簽名檔案keystore是唯一的,並且只有程式作者才擁有。具體做法為,在所有的native方法內,增加簽名資訊核查判斷,只有簽名資訊核查通過,程式才能做進一步操作,否則直接返回NULL,這樣,即使別人拿到了so檔案,摸清楚了native方法參數及用法,但由於簽名資訊不一致,native方法全部返回NULL,so檔案瞬間變成磚頭。同樣,對於url地址等敏感資訊,增加簽名資訊核查,只有核查通過,程式才會返回正確的字串,否則直接返回NULL,這樣可以很好的隱藏和保護敏感資訊。說了半天,關鍵的一步是如何在ndk環境下,擷取包簽名資訊,下面的代碼為相關實現。
jstring loadSignature(JNIEnv* env, jobject obj){// 獲得Context類 jclass cls = (*env)->GetObjectClass(env, obj); // 得到getPackageManager方法的ID jmethodID mid = (*env)->GetMethodID(env, cls, "getPackageManager", "()Landroid/content/pm/PackageManager;"); // 獲得應用程式套件的管理器 jobject pm = (*env)->CallObjectMethod(env, obj, mid); // 得到getPackageName方法的ID mid = (*env)->GetMethodID(env, cls, "getPackageName", "()Ljava/lang/String;"); // 獲得當前應用程式套件名 jstring packageName = (jstring)(*env)->CallObjectMethod(env, obj, mid); // 獲得PackageManager類 cls = (*env)->GetObjectClass(env, pm); // 得到getPackageInfo方法的ID mid = (*env)->GetMethodID(env, cls, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"); // 獲得應用程式套件的資訊 jobject packageInfo = (*env)->CallObjectMethod(env, pm, mid, packageName, 0x40); //GET_SIGNATURES = 64; // 獲得PackageInfo 類 cls = (*env)->GetObjectClass(env, packageInfo); // 獲得簽名數組屬性的ID jfieldID fid = (*env)->GetFieldID(env, cls, "signatures", "[Landroid/content/pm/Signature;"); // 得到簽名數組 jobjectArray signatures = (jobjectArray)(*env)->GetObjectField(env, packageInfo, fid); // 得到簽名 jobject sign = (*env)->GetObjectArrayElement(env, signatures, 0); // 獲得Signature類 cls = (*env)->GetObjectClass(env, sign); // 得到toCharsString方法的ID mid = (*env)->GetMethodID(env, cls, "toCharsString", "()Ljava/lang/String;"); // 返回當前應用簽名資訊 return (jstring)(*env)->CallObjectMethod(env, sign, mid);} 上述代碼獲得的包簽名資訊實際是一個很長的字串,為了更高效的進行簽名資訊比對,還可以將其進行md5加密,加密成32位字串形式。另外,我在查閱資料過程中,看到有的資料提到用包簽名的hashcode值做比對,這種方式更簡單一點,但我沒有採用這種方式,總覺得這種方式可能不精確,僅僅是個人覺得,有興趣的同學可以查閱更多相關資料。這裡還要說明一點,上面代碼獲得的包簽名字串資訊,使用md5加密後,得到的加密結果與包簽名實際的md5 fingerprint是不一致的。主要是因為將簽名資訊使用toCharsString()轉換成字串後在進行md5加密所致,如果使用toByteArray()將其轉成數組,然後加密,加密結果與包簽名實際md5 fingerprint將是一致的。
使用jni技術進行android應用簽名資訊核查及敏感資訊保護