溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何理解Android敏感數(shù)據(jù)泄露

發(fā)布時間:2021-10-28 16:08:54 來源:億速云 閱讀:133 作者:iii 欄目:移動開發(fā)

本篇內(nèi)容主要講解“如何理解Android敏感數(shù)據(jù)泄露”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“如何理解Android敏感數(shù)據(jù)泄露”吧!

 1.事件始末

一個平淡的午后,我還悠哉悠哉的敲著代碼品著茶。突然服務(wù)端同事告訴我,關(guān)注接口正在被機械式調(diào)用,懷疑是有人在使用腳本刷接口(目的主要是從平臺導(dǎo)流)。

納尼?不會吧,因為據(jù)我所知接口請求是做了加密處理的,除非知道加密的密鑰和加密方式,不然是不會調(diào)用成功的,一定是你感覺錯了。然而當(dāng)服務(wù)端同事把接口調(diào)用日志發(fā)給我看時,徹底否定了我的僥幸心理。

接口調(diào)用頻率固定為1s 一次

被關(guān)注者的id每次調(diào)用依次加一(目前業(yè)務(wù)上用戶id的生成是按照注冊時間依次遞增的)

加密的密鑰始終使用固定的一個(正常的是在固定的幾個密鑰中每次會隨機使用一個)

綜合以上三點就可以斷定,肯定是存在刷接口的行為了。

2.事件分析

既然上述刷接口的行為成立,也就意味著密鑰和加密方式被對方知道了,原因無非是以下兩點:

  1. 內(nèi)部人員泄露

  2. apk被破解

經(jīng)過確認(rèn)基本排除了第一點,那就只剩下apk被破解了,可是apk發(fā)布出去的包是進行過加固和混淆處理的,難道對方脫殼了?不管三七二十一,自己先來反編譯試試。于是乎從最近發(fā)布的版本一個一個去反編譯,最后在反編譯到較早前的一個版本時發(fā)現(xiàn),保存密鑰和加密的工具類居然源碼完全暴露了。

如何理解Android敏感數(shù)據(jù)泄露

炸了鍋了,排查了一下這個版本居然未加固過就發(fā)布出去了,而且這個加密工具類未被混淆。雖然還不太清楚對方是不是按照這種方式獲取的密鑰和加密算法,但無疑這是客戶端存在的一個安全漏洞。

3.事件處理

既然已經(jīng)發(fā)現(xiàn)了上述問題,那就要想辦法解決。首先不考慮加固,如何盡最大可能保證客戶端中的敏感數(shù)據(jù)不泄露?另一方面即使對方想要破解,也要想辦法設(shè)障,增大破解難度。想到這里基本就大致確定了一個思路:使用NDK,將敏感數(shù)據(jù)和加密方式放到native層,因為C++代碼編譯后生成的so庫是一個二進制文件,這無疑會增加破解的難度。利用這個特性,可以將客戶端的敏感數(shù)據(jù)寫在C++代碼中,從而增強應(yīng)用的安全性。  說干就干吧!!!

1.首先創(chuàng)建了加密工具類:

public class HttpKeyUtil {     static {         System.loadLibrary("jniSecret");     }     //根據(jù)隨機值去獲取密鑰     public static native String getHttpSecretKey(int index);     //將待加密的數(shù)據(jù)傳入,返回加密后的結(jié)果     public static native String getSecretValue(byte[] bytes); }

2.生成相應(yīng)的頭文件:

#include <jni.h> #ifndef _Included_com_test_util_HttpKeyUtil #define _Included_com_test_util_HttpKeyUtil #ifdef __cplusplus extern "C" { #endif JNIEXPORT jstring JNICALL Java_com_esky_common_component_util_HttpKeyUtil_getHttpSecretKey         (JNIEnv *, jclass, jint);          JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getSecretValue         (JNIEnv *, jclass, jbyteArray);  #ifdef __cplusplus } #endif #endif

3.編寫相應(yīng)的cpp文件:

在相應(yīng)的Module中創(chuàng)建jni目錄,將com_test_util_HttpKeyUtil.h拷貝進來,然后再創(chuàng)建com_test_util_HttpKeyUtil.cpp文件

如何理解Android敏感數(shù)據(jù)泄露

#include <jni.h> #include <cstring> #include <malloc.h> #include "com_test_util_HttpKeyUtil.h"  extern "C" const char *KEY1 = "密鑰1"; const char *KEY2 = "密鑰2"; const char *KEY3 = "密鑰3"; const char *UNKNOWN = "unknown";  jstring toMd5(JNIEnv *pEnv, jbyteArray pArray);  extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getHttpSecretKey         (JNIEnv *env, jclass cls, jint index) {     if (隨機數(shù)條件1) {         return env->NewStringUTF(KEY1);     } else if (隨機數(shù)條件2) {         return env->NewStringUTF(KEY2);     } else if (隨機數(shù)條件3) {         return env->NewStringUTF(KEY3);     } else {         return env->NewStringUTF(UNKNOWN);     } }  extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getSecretValue         (JNIEnv *env, jclass cls, jbyteArray jbyteArray1) {         //加密算法各有不同,這里我就用md5做個示范         return toMd5(env, jbyteArray1); }  //md5 jstring toMd5(JNIEnv *env, jbyteArray source) {     // MessageDigest     jclass classMessageDigest = env->FindClass("java/security/MessageDigest");     // MessageDigest.getInstance()     jmethodID midGetInstance = env->GetStaticMethodID(classMessageDigest, "getInstance",                                                       "(Ljava/lang/String;)Ljava/security/MessageDigest;");     // MessageDigest object     jobject objMessageDigest = env->CallStaticObjectMethod(classMessageDigest, midGetInstance,                                                            env->NewStringUTF("md5"));      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);     toHexStr((const char *) byte_array_elements, char_result, intArrayLength);     // 在末尾補\0     *(char_result + intArrayLength * 2) = '\0';     jstring stringResult = env->NewStringUTF(char_result);     // release     env->ReleaseByteArrayElements(objArraySign, byte_array_elements, JNI_ABORT);     // 指針     free(char_result);     return stringResult; }  //轉(zhuǎn)換為16進制字符串 void toHexStr(const char *source, char *dest, int sourceLen) {     short i;     char highByte, lowByte;     for (i = 0; i < sourceLen; i++) {         highByte = source[i] >> 4;         lowByte = (char) (source[i] & 0x0f);         highByte += 0x30;         if (highByte > 0x39) {             dest[i * 2] = (char) (highByte + 0x07);         } else {             dest[i * 2] = highByte;         }         lowByte += 0x30;         if (lowByte > 0x39) {             dest[i * 2 + 1] = (char) (lowByte + 0x07);         } else {             dest[i * 2 + 1] = lowByte;         }     } }

4.事件就此結(jié)束?

到這里就此結(jié)束了?too yuang too  simple!!!雖然將密鑰和加密算法寫在了c++中,貌似好像是比較安全了。但是但是萬一別人反編譯后,拿到c++代碼最終生成的so庫,然后直接調(diào)用so庫里的方法去獲取密鑰并調(diào)用加密方法怎么破?看來我們還是要加一步身份校驗才行:即在native層對應(yīng)用的包名、簽名進行鑒權(quán)校驗,校驗通過才返回正確結(jié)果。下面就是獲取apk包名和簽名校驗的代碼:

const char *PACKAGE_NAME = "你的ApplicationId"; //(簽名的md5值自己可以寫方法獲取,或者用簽名工具直接獲取,一般對接微信sdk的時候也會要應(yīng)用簽名的MD5值) const char *SIGN_MD5 = "你的應(yīng)用簽名的MD5值注意是大寫";  //獲取Application實例 jobject getApplication(JNIEnv *env) {     jobject application = NULL;     //這里是你的Application的類路徑,混淆時注意不要混淆該類和該類獲取實例的方法比如getInstance     jclass baseapplication_clz = env->FindClass("com/test/component/BaseApplication");     if (baseapplication_clz != NULL) {         jmethodID currentApplication = env->GetStaticMethodID(                 baseapplication_clz, "getInstance",                 "()Lcom/test/component/BaseApplication;");         if (currentApplication != NULL) {             application = env->CallStaticObjectMethod(baseapplication_clz, currentApplication);         }         env->DeleteLocalRef(baseapplication_clz);     }     return application; }   bool isRight = false; //獲取應(yīng)用簽名的MD5值并判斷是否與本應(yīng)用的一致 jboolean getSignature(JNIEnv *env) {     LOGD("getSignature isRight: %d", isRight ? 1 : 0);     if (!isRight) {//避免每次都進行校驗浪費資源,只要第一次校驗通過后,后邊就不在進行校驗         jobject context = getApplication(env);         // 獲得Context類         jclass cls = env->FindClass("android/content/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);         const char *c_pack_name = env->GetStringUTFChars(packageName, NULL);          // 比較包名,若不一致,直接return包名         if (strcmp(c_pack_name, PACKAGE_NAME) != 0) {             return false;         }         // 獲得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);         mid = env->GetMethodID(cls, "toByteArray", "()[B");         // 當(dāng)前應(yīng)用簽名信息         jbyteArray signatureByteArray = (jbyteArray) env->CallObjectMethod(signature, mid);         //轉(zhuǎn)成jstring         jstring str = toMd5(env, signatureByteArray);         char *c_msg = (char *) env->GetStringUTFChars(str, 0);         LOGD("getSignature release sign md5: %s", c_msg);         isRight = strcmp(c_msg, SIGN_MD5) == 0;         return isRight;     }     return isRight; }   //有了校驗的方法,所以我們要對第3步中,獲取密鑰和加密方法的進行修改,添加校驗的邏輯 extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getHttpSecretKey         (JNIEnv *env, jclass cls, jint index) {     if (getSignature(env)){//校驗通過       if (隨機數(shù)條件1) {         return env->NewStringUTF(KEY1);       } else if (隨機數(shù)條件2) {         return env->NewStringUTF(KEY2);       } else if (隨機數(shù)條件3) {         return env->NewStringUTF(KEY3);       } else {         return env->NewStringUTF(UNKNOWN);       }     }else {         return env->NewStringUTF(UNKNOWN);     } }  extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getSecretValue         (JNIEnv *env, jclass cls, jbyteArray jbyteArray1) {         //加密算法各有不同,這里我就用md5做個示范     if (getSignature(env)){//校驗通過        return toMd5(env, jbyteArray1);     }else {         return env->NewStringUTF(UNKNOWN);     } }  作者:AirSj 鏈接:https://juejin.im/post/6862732328406351879 來源:掘金 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

到此,相信大家對“如何理解Android敏感數(shù)據(jù)泄露”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進入相關(guān)頻道進行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI