本篇內(nèi)容主要講解“如何理解Android敏感數(shù)據(jù)泄露”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“如何理解Android敏感數(shù)據(jù)泄露”吧!
公司主營(yíng)業(yè)務(wù):網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站建設(shè)、移動(dòng)網(wǎng)站開(kāi)發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。成都創(chuàng)新互聯(lián)公司是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開(kāi)放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來(lái)驚喜。成都創(chuàng)新互聯(lián)公司推出含山免費(fèi)做網(wǎng)站回饋大家。
1.事件始末
一個(gè)平淡的午后,我還悠哉悠哉的敲著代碼品著茶。突然服務(wù)端同事告訴我,關(guān)注接口正在被機(jī)械式調(diào)用,懷疑是有人在使用腳本刷接口(目的主要是從平臺(tái)導(dǎo)流)。
納尼?不會(huì)吧,因?yàn)閾?jù)我所知接口請(qǐng)求是做了加密處理的,除非知道加密的密鑰和加密方式,不然是不會(huì)調(diào)用成功的,一定是你感覺(jué)錯(cuò)了。然而當(dāng)服務(wù)端同事把接口調(diào)用日志發(fā)給我看時(shí),徹底否定了我的僥幸心理。
接口調(diào)用頻率固定為1s 一次
被關(guān)注者的id每次調(diào)用依次加一(目前業(yè)務(wù)上用戶id的生成是按照注冊(cè)時(shí)間依次遞增的)
加密的密鑰始終使用固定的一個(gè)(正常的是在固定的幾個(gè)密鑰中每次會(huì)隨機(jī)使用一個(gè))
綜合以上三點(diǎn)就可以斷定,肯定是存在刷接口的行為了。
2.事件分析
既然上述刷接口的行為成立,也就意味著密鑰和加密方式被對(duì)方知道了,原因無(wú)非是以下兩點(diǎn):
內(nèi)部人員泄露
apk被破解
經(jīng)過(guò)確認(rèn)基本排除了第一點(diǎn),那就只剩下apk被破解了,可是apk發(fā)布出去的包是進(jìn)行過(guò)加固和混淆處理的,難道對(duì)方脫殼了?不管三七二十一,自己先來(lái)反編譯試試。于是乎從最近發(fā)布的版本一個(gè)一個(gè)去反編譯,最后在反編譯到較早前的一個(gè)版本時(shí)發(fā)現(xiàn),保存密鑰和加密的工具類(lèi)居然源碼完全暴露了。
炸了鍋了,排查了一下這個(gè)版本居然未加固過(guò)就發(fā)布出去了,而且這個(gè)加密工具類(lèi)未被混淆。雖然還不太清楚對(duì)方是不是按照這種方式獲取的密鑰和加密算法,但無(wú)疑這是客戶端存在的一個(gè)安全漏洞。
3.事件處理
既然已經(jīng)發(fā)現(xiàn)了上述問(wèn)題,那就要想辦法解決。首先不考慮加固,如何盡最大可能保證客戶端中的敏感數(shù)據(jù)不泄露?另一方面即使對(duì)方想要破解,也要想辦法設(shè)障,增大破解難度。想到這里基本就大致確定了一個(gè)思路:使用NDK,將敏感數(shù)據(jù)和加密方式放到native層,因?yàn)镃++代碼編譯后生成的so庫(kù)是一個(gè)二進(jìn)制文件,這無(wú)疑會(huì)增加破解的難度。利用這個(gè)特性,可以將客戶端的敏感數(shù)據(jù)寫(xiě)在C++代碼中,從而增強(qiáng)應(yīng)用的安全性。 說(shuō)干就干吧!!!
1.首先創(chuàng)建了加密工具類(lèi):
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)的頭文件:
#include#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.編寫(xiě)相應(yīng)的cpp文件:
在相應(yīng)的Module中創(chuàng)建jni目錄,將com_test_util_HttpKeyUtil.h拷貝進(jìn)來(lái),然后再創(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 JNICALL Java_com_test_util_HttpKeyUtil_getSecretValue (JNIEnv *env, jclass cls, jbyteArray jbyteArray1) { //加密算法各有不同,這里我就用md5做個(gè)示范 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); // 在末尾補(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!!!雖然將密鑰和加密算法寫(xiě)在了c++中,貌似好像是比較安全了。但是但是萬(wàn)一別人反編譯后,拿到c++代碼最終生成的so庫(kù),然后直接調(diào)用so庫(kù)里的方法去獲取密鑰并調(diào)用加密方法怎么破?看來(lái)我們還是要加一步身份校驗(yàn)才行:即在native層對(duì)應(yīng)用的包名、簽名進(jìn)行鑒權(quán)校驗(yàn),校驗(yàn)通過(guò)才返回正確結(jié)果。下面就是獲取apk包名和簽名校驗(yàn)的代碼:
const char *PACKAGE_NAME = "你的ApplicationId"; //(簽名的md5值自己可以寫(xiě)方法獲取,或者用簽名工具直接獲取,一般對(duì)接微信sdk的時(shí)候也會(huì)要應(yīng)用簽名的MD5值) const char *SIGN_MD5 = "你的應(yīng)用簽名的MD5值注意是大寫(xiě)"; //獲取Application實(shí)例 jobject getApplication(JNIEnv *env) { jobject application = NULL; //這里是你的Application的類(lèi)路徑,混淆時(shí)注意不要混淆該類(lèi)和該類(lèi)獲取實(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)通過(guò)后,后邊就不在進(jìn)行校驗(yàn) jobject context = getApplication(env); // 獲得Context類(lèi) 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類(lèi) 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 類(lèi) 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類(lèi) 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)通過(guò) 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 JNICALL Java_com_test_util_HttpKeyUtil_getSecretValue (JNIEnv *env, jclass cls, jbyteArray jbyteArray1) { //加密算法各有不同,這里我就用md5做個(gè)示范 if (getSignature(env)){//校驗(yàn)通過(guò) return toMd5(env, jbyteArray1); }else { return env->NewStringUTF(UNKNOWN); } } 作者:AirSj 鏈接:https://juejin.im/post/6862732328406351879 來(lái)源:掘金 著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。
到此,相信大家對(duì)“如何理解Android敏感數(shù)據(jù)泄露”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!