<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Android敏感數(shù)據(jù)泄露引發(fā)的思考

          共 9118字,需瀏覽 19分鐘

           ·

          2020-09-12 00:11

          熱文推薦:

          作者:Airsj
          鏈接:https://juejin.im/post/6862732328406351879

          事件始末


          一個(gè)平淡的午后,我還悠哉悠哉的敲著代碼品著茶。突然服務(wù)端同事告訴我,關(guān)注接口正在被機(jī)械式調(diào)用,懷疑是有人在使用腳本刷接口(目的主要是從平臺(tái)導(dǎo)流)。
          納尼?不會(huì)吧,因?yàn)閾?jù)我所知接口請(qǐng)求是做了加密處理的,除非知道加密的密鑰和加密方式,不然是不會(huì)調(diào)用成功的,一定是你感覺錯(cuò)了。然而當(dāng)服務(wù)端同事把接口調(diào)用日志發(fā)給我看時(shí),徹底否定了我的僥幸心理。
          1. 接口調(diào)用頻率固定為1s?一次

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

          3. 加密的密鑰始終使用固定的一個(gè)(正常的是在固定的幾個(gè)密鑰中每次會(huì)隨機(jī)使用一個(gè))??

          綜合以上三點(diǎn)就可以斷定,肯定是存在刷接口的行為了。?


          事件分析


          既然上述刷接口的行為成立,也就意味著密鑰和加密方式被對(duì)方知道了,原因無非是以下兩點(diǎn):?
          1. 內(nèi)部人員泄露 ?

          2. apk被破解??

          經(jīng)過確認(rèn)基本排除了第一點(diǎn),那就只剩下apk被破解了,可是apk發(fā)布出去的包是進(jìn)行過加固和混淆處理的,難道對(duì)方脫殼了?不管三七二十一,自己先來反編譯試試。于是乎從最近發(fā)布的版本一個(gè)一個(gè)去反編譯,最后在反編譯到較早前的一個(gè)版本時(shí)發(fā)現(xiàn),保存密鑰和加密的工具類居然源碼完全暴露了。
          炸了鍋了,排查了一下這個(gè)版本居然未加固過就發(fā)布出去了,而且這個(gè)加密工具類未被混淆。雖然還不太清楚對(duì)方是不是按照這種方式獲取的密鑰和加密算法,但無疑這是客戶端存在的一個(gè)安全漏洞。

          事件處理



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


          1.首先創(chuàng)建了加密工具類:
          public class HttpKeyUtil {    static {        System.loadLibrary("jniSecret");    }    //根據(jù)隨機(jī)值去獲取密鑰    public static native String getHttpSecretKey(int index);    //將待加密的數(shù)據(jù)傳入,返回加密后的結(jié)果    public static native String getSecretValue(byte[] bytes);}
          2.生成相應(yīng)的頭文件:
          com_test_util_HttpKeyUtil.h
          #include #ifndef _Included_com_test_util_HttpKeyUtil#define _Included_com_test_util_HttpKeyUtil#ifdef __cplusplusextern "C" {#endifJNIEXPORT 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拷貝進(jìn)來,然后再創(chuàng)建com_test_util_HttpKeyUtil.cpp文件
          #include #include #include #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 (隨機(jī)數(shù)條件1) { return env->NewStringUTF(KEY1); } else if (隨機(jī)數(shù)條件2) { return env->NewStringUTF(KEY2); } else if (隨機(jī)數(shù)條件3) { return env->NewStringUTF(KEY3); } else { return env->NewStringUTF(UNKNOWN); }}
          extern "C" JNIEXPORT jstring JNICALLJava_com_test_util_HttpKeyUtil_getSecretValue (JNIEnv *env, jclass cls, jbyteArray jbyteArray1) { //加密算法各有不同,這里我就用md5做個(gè)示范 return toMd5(env, jbyteArray1);}
          //md5jstring 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); // 在末尾補(bǔ)\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進(jìn)制字符串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)用加密方法怎么破?看來我們還是要加一步身份校驗(yàn)才行:即在native層對(duì)應(yīng)用的包名、簽名進(jìn)行鑒權(quán)校驗(yàn),校驗(yàn)通過才返回正確結(jié)果。下面就是獲取apk包名和簽名校驗(yàn)的代碼:

          const char *PACKAGE_NAME = "你的ApplicationId";//(簽名的md5值自己可以寫方法獲取,或者用簽名工具直接獲取,一般對(duì)接微信sdk的時(shí)候也會(huì)要應(yīng)用簽名的MD5值)const char *SIGN_MD5 = "你的應(yīng)用簽名的MD5值注意是大寫";
          //獲取Application實(shí)例jobject getApplication(JNIEnv *env) { jobject application = NULL; //這里是你的Application的類路徑,混淆時(shí)注意不要混淆該類和該類獲取實(shí)例的方法比如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) {//避免每次都進(jìn)行校驗(yàn)浪費(fèi)資源,只要第一次校驗(yàn)通過后,后邊就不在進(jìn)行校驗(yàn) 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;}

          //有了校驗(yàn)的方法,所以我們要對(duì)第3步中,獲取密鑰和加密方法的進(jìn)行修改,添加校驗(yàn)的邏輯extern "C" JNIEXPORT jstring JNICALL Java_com_test_util_HttpKeyUtil_getHttpSecretKey (JNIEnv *env, jclass cls, jint index) { if (getSignature(env)){//校驗(yàn)通過 if (隨機(jī)數(shù)條件1) { return env->NewStringUTF(KEY1); } else if (隨機(jī)數(shù)條件2) { return env->NewStringUTF(KEY2); } else if (隨機(jī)數(shù)條件3) { return env->NewStringUTF(KEY3); } else { return env->NewStringUTF(UNKNOWN); } }else { return env->NewStringUTF(UNKNOWN); }}
          extern "C" JNIEXPORT jstring JNICALLJava_com_test_util_HttpKeyUtil_getSecretValue (JNIEnv *env, jclass cls, jbyteArray jbyteArray1) { //加密算法各有不同,這里我就用md5做個(gè)示范 if (getSignature(env)){//校驗(yàn)通過 return toMd5(env, jbyteArray1); }else { return env->NewStringUTF(UNKNOWN); }}

          5.總結(jié)

          以上就是此次事件native的相關(guān)代碼,至于如何生成so庫可以自行百度。從此次事件中需要反思的幾點(diǎn)是:

          • 安全性的認(rèn)識(shí),安全無小事

          • 發(fā)布出去的包必須走加固流程,為了防止疏漏,禁止人工打包加固,全部通過腳本實(shí)現(xiàn)

          • 服務(wù)端增加相關(guān)風(fēng)險(xiǎn)的報(bào)警機(jī)制



          如有收獲,歡迎分享?

          「點(diǎn)贊「評(píng)論?

          看完本文有收獲?請(qǐng)轉(zhuǎn)發(fā)分享給更多人

          ? 開發(fā)者全社區(qū)?

          5T技術(shù)資源大放送!包括但不限于:Android,Python,Java,大數(shù)據(jù),人工智能,AI等等。關(guān)注公眾號(hào)后回復(fù)「2T」,即可免費(fèi)獲??!!
          瀏覽 50
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美乱妇日本无乱码特黄大片 | 欧美操B视频 | 一道本高清一二三区视频 | 青青草污视频 | 97综合视频 |