Android安全防護(hù)篇

目錄

前言

隨著技術(shù)的發(fā)展,APP的破解技術(shù)也越來越成熟,因此為了防止APP被破解我們可以進(jìn)行一系列安全方面的檢測(cè)和加固,讓破解人員的破解成本增加,這樣的話就可以在一定程度上防止APP被破解

代碼實(shí)現(xiàn)

1.模擬器檢測(cè)

大部分破解人員調(diào)試APP一般都是在模擬器上進(jìn)行,因此首先我們先檢測(cè)模擬器,這里為了偽造檢測(cè)的方法,我把設(shè)置Activity布局的邏輯也加在了檢測(cè)方法中,另外為了提升安全等級(jí),我將檢測(cè)的函數(shù)在NDK層實(shí)現(xiàn),同時(shí)檢測(cè)后的彈窗也在NDK層創(chuàng)建,這樣可以很大程度誤導(dǎo)破解人員,具體如下:
首先我們創(chuàng)建一個(gè)工具類,用于檢測(cè),我們需要傳入布局文件和Activity對(duì)象,目的是為了在NDK層設(shè)置布局加以混淆視線

public class CheckUtil {
    static {
        System.loadLibrary("native-lib");
    }

    public native static void init(int layoutId, Activity activity);
}

然后我們創(chuàng)建BaseActivity,其子類需要實(shí)現(xiàn)getLayoutId()方法返回布局文件

public abstract class BaseActivity extends AppCompatActivity {
    @LayoutRes
    protected abstract int getLayoutId();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        CheckUtil.init(getLayoutId(),this);
    }
}

然后我們的邏輯大部分都是用C語言實(shí)現(xiàn),這里大體邏輯為先判斷是否在模擬器上運(yùn)行,如果是的話就彈窗并且在彈窗關(guān)閉的監(jiān)聽事件中退出APP

#include <jni.h>
#include <string>
#include "android/log.h"
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "安全", __VA_ARGS__))
enum DialogType{
    EMULATOR,//模擬器
    JKSERROR,//簽名錯(cuò)誤
    WIFIPROXY,//WIFI代理
};

void showDialog(JNIEnv *env,DialogType dialogType,jobject activity){
    std::string messageText = "";
    switch (dialogType) {
        case DialogType::EMULATOR:
            messageText = "請(qǐng)不要在模擬器上運(yùn)行";
            break;
        case DialogType::JKSERROR:
            messageText = "請(qǐng)使用正版應(yīng)用";
            break;
        case DialogType::WIFIPROXY:
            messageText = "請(qǐng)不要使用網(wǎng)絡(luò)代理";
            break;
    }

    //創(chuàng)建AlertDialog.Builder
    jclass builderClazz = env->FindClass("android/app/AlertDialog$Builder");
    jmethodID initMethodID = env->GetMethodID(builderClazz,"<init>","(Landroid/content/Context;)V");
    jobject builder = env->NewObject(builderClazz,initMethodID,activity);
    jmethodID setPositiveButtonMethodID = env->GetMethodID(builderClazz,"setPositiveButton","(Ljava/lang/CharSequence;Landroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;");

    //設(shè)置確認(rèn)按鈕和點(diǎn)擊事件
    jclass onClickClazz = env->FindClass("com/itfitness/safedemo/dialog/MDialogClickListener");
    jmethodID onClickInitMethodID = env->GetMethodID(onClickClazz,"<init>", "()V");
    jobject onClickListener = env->NewObject(onClickClazz,onClickInitMethodID);
    std::string buttonText = "確定";
    env->CallObjectMethod(builder,setPositiveButtonMethodID,env->NewStringUTF(buttonText.c_str()),onClickListener);

    //設(shè)置標(biāo)題
    jmethodID setTitleMethodID = env->GetMethodID(builderClazz,"setTitle","(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;");
    std::string titleText = "提示";
    env->CallObjectMethod(builder,setTitleMethodID,env->NewStringUTF(titleText.c_str()));

    //設(shè)置提示內(nèi)容
    jmethodID setMessageMethodID = env->GetMethodID(builderClazz,"setMessage","(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;");
    env->CallObjectMethod(builder,setMessageMethodID,env->NewStringUTF(messageText.c_str()));

    //顯示彈窗
    jmethodID createMethodID = env->GetMethodID(builderClazz,"create",
                                                "()Landroid/app/AlertDialog;");
    jobject alertDialog = env->CallObjectMethod(builder,createMethodID);
    jclass alertDialogClazz = env->GetObjectClass(alertDialog);

    //設(shè)置Dialog關(guān)閉的監(jiān)聽
    jmethodID setOnDismissListenerMethodID = env->GetMethodID(alertDialogClazz,"setOnDismissListener",
                                                              "(Landroid/content/DialogInterface$OnDismissListener;)V");
    jclass onDismissListenerClazz = env->FindClass("com/itfitness/safedemo/dialog/DialogDismissListener");
    jmethodID onDismissListenerInitMethodID = env->GetMethodID(onDismissListenerClazz,"<init>", "()V");
    jobject onDismissListener = env->NewObject(onDismissListenerClazz,onDismissListenerInitMethodID);
    env->CallVoidMethod(alertDialog,setOnDismissListenerMethodID,onDismissListener);

    //顯示
    jmethodID showMethodID = env->GetMethodID(alertDialogClazz,"show", "()V");
    env->CallVoidMethod(alertDialog,showMethodID);

}

extern "C"
JNIEXPORT void JNICALL
Java_com_itfitness_safedemo_utils_CheckUtil_init(JNIEnv *env, jclass clazz, jint layout_id,
                                                 jobject activity) {
    //設(shè)置布局文件
    jclass activityClazz = env->GetObjectClass(activity);
    //獲取Java層方法的id
    char* methodName = "setContentView";
    char* methodSig = "(I)V";
    jmethodID setContentViewMethodId = env->GetMethodID(activityClazz,methodName,methodSig);
    //調(diào)用方法
    env->CallVoidMethod(activity, setContentViewMethodId,layout_id);


    //檢測(cè)是否是模擬器
    jclass deviceUtilsClazz = env->FindClass("com/blankj/utilcode/util/DeviceUtils");
    jmethodID isEmulatorMethodId = env->GetStaticMethodID(deviceUtilsClazz,"isEmulator", "()Z");
    jboolean isEmulator = env->CallStaticBooleanMethod(deviceUtilsClazz,isEmulatorMethodId);
    if(isEmulator){
        showDialog(env,DialogType::EMULATOR,activity);
        return;
    }
}

按鈕點(diǎn)擊和彈窗關(guān)閉的監(jiān)聽事件如下

public class DialogDismissListener implements DialogInterface.OnDismissListener{
    @Override
    public void onDismiss(DialogInterface dialog) {
        AppUtils.exitApp();
    }
}
public class MDialogClickListener implements DialogInterface.OnClickListener {
    @Override
    public void onClick(DialogInterface dialog, int which) {
        dialog.dismiss();
    }
}

然后我們創(chuàng)建MainActivity

public class MainActivity extends BaseActivity {

    @Override
    protected int getLayoutId() {
        return R.layout.activity_main;
    }
}

運(yùn)行APP效果如下(分別為模擬器和真機(jī))


模擬器

真機(jī)
2.簽名校驗(yàn)

假如某些破解人員發(fā)現(xiàn)模擬器不能運(yùn)行,由于APP破解價(jià)值較高,他可能會(huì)繼續(xù)使用真機(jī)進(jìn)行調(diào)試,那么他在反編譯代碼并重新打包的過程中必定會(huì)對(duì)APK進(jìn)行重新簽名,因此接下來我們就對(duì)APK的簽名進(jìn)行校驗(yàn),我們的校驗(yàn)邏輯同樣在NDK層,我們?cè)谀M器檢測(cè)的邏輯下面加入簽名校驗(yàn),在這之前我們先通過命令獲取我們APK簽名的SHA-1值,如下

keytool -list -v -keystore safedemo.jks

然后我們加入檢測(cè)邏輯

const char * SHA1 = "B5:AA:30:95:6A:AA:67:F3:74:FB:CB:91:A6:1C:A2:E2:A8:61:87:8B";
extern "C"
JNIEXPORT void JNICALL
Java_com_itfitness_safedemo_utils_CheckUtil_init(JNIEnv *env, jclass clazz, jint layout_id,
                                                 jobject activity) {
   ...省略部分代碼

    //檢測(cè)簽名是否正確
    jclass appUtilsClazz = env->FindClass("com/blankj/utilcode/util/AppUtils");
    jmethodID getAppSignaturesSHA1MethodId = env->GetStaticMethodID(appUtilsClazz,"getAppSignaturesSHA1", "()Ljava/util/List;");
    jobject sha1ArrayList = env->CallStaticObjectMethod(appUtilsClazz,getAppSignaturesSHA1MethodId);
    jclass listClazz = env->FindClass("java/util/List");
    jmethodID getMethodId = env->GetMethodID(listClazz,"get", "(I)Ljava/lang/Object;");
    jstring javaSha1 = (jstring)env->CallObjectMethod(sha1ArrayList,getMethodId,0);
    char * cSha1 = (char*)env->GetStringUTFChars(javaSha1,0);
    LOGE("%s",cSha1);
    //將獲取到的SHA-1值與正確簽名的SHA-1值比較
    if(strcmp(cSha1,SHA1) != 0){
        showDialog(env,DialogType::JKSERROR,activity);
        return;
    }
}

運(yùn)行APP效果如下(分別為正版簽名和盜版簽名)


正版簽名

盜版簽名
3.WiFi代理校驗(yàn)

有時(shí)破解人員也會(huì)通過設(shè)置WiFi代理來截取手機(jī)的數(shù)據(jù),因此這里我們也檢測(cè)下是否有WIFI代理,如下所示,我們創(chuàng)建了一個(gè)檢測(cè)代理的工具類,當(dāng)然最好也是通過C語言實(shí)現(xiàn),不過這里我暫時(shí)就用Java代碼實(shí)現(xiàn)

public class WifiUtil {
    /*
     * 判斷設(shè)備 是否使用代理上網(wǎng)
     * */
    public static boolean isWifiProxy(Context context) {
        // 是否大于等于4.0
        final boolean IS_ICS_OR_LATER = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
        String proxyAddress;
        int proxyPort;
        if (IS_ICS_OR_LATER) {
            proxyAddress = System.getProperty("http.proxyHost");
            String portStr = System.getProperty("http.proxyPort");
            proxyPort = Integer.parseInt((portStr != null ? portStr : "-1"));
        } else {
            proxyAddress = android.net.Proxy.getHost(context);
            proxyPort = android.net.Proxy.getPort(context);
        }
        return (!TextUtils.isEmpty(proxyAddress)) && (proxyPort != -1);
    }
}

然后我們?cè)贘NI函數(shù)中加入邏輯

extern "C"
JNIEXPORT void JNICALL
Java_com_itfitness_safedemo_utils_CheckUtil_init(JNIEnv *env, jclass clazz, jint layout_id,
                                                 jobject activity) {
    ...省略部分代碼


    //檢測(cè)是否使用了WIFI代理
    jclass wifiUtilsClazz = env->FindClass("com/itfitness/safedemo/utils/WifiUtil");
    jmethodID isWifiProxyMethodId = env->GetStaticMethodID(wifiUtilsClazz,"isWifiProxy",
                                                           "(Landroid/content/Context;)Z");
    jboolean isWifiProxy = env->CallStaticBooleanMethod(wifiUtilsClazz,isWifiProxyMethodId,activity);
    if(isWifiProxy){
        showDialog(env,DialogType::WIFIPROXY,activity);
        return;
    }
}

運(yùn)行APP效果如下(分別為有代理和無代理)


有代理

無代理

具體的代理設(shè)置方法可以參考這篇文章:charles連接手機(jī)抓包

4.代碼混淆

接下來我們?yōu)榱俗尨a更不易閱讀可以進(jìn)行代碼混淆

buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        debug {
            signingConfig signingConfigs.release
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

不過在混淆代碼的時(shí)候要注意將JNI函數(shù)用到的類取消混淆

-keep class com.itfitness.safedemo.utils.** { *; }
-keep class com.itfitness.safedemo.dialog.** { *; }
-keep class com.blankj.utilcode.util.** { *; }
5.安全加固

最后我們?yōu)榱烁颖kU(xiǎn),同時(shí)也是為了給破解人員再增加破解難度,這時(shí)我們可以進(jìn)行安全加固,這里我知道的免費(fèi)加固有:愛加密360加固,如果認(rèn)為免費(fèi)的不保險(xiǎn)可以使用付費(fèi)的,完成以上步驟后,一般情況下如果沒有特別深的仇恨或是APP價(jià)值與破解成本相比還是很高的話一般是沒人再去破解了

結(jié)語

我現(xiàn)在還在學(xué)習(xí)中,假如有哪些地方寫的不好還請(qǐng)大家?guī)臀抑刚?,謝謝大家

案例源碼

https://gitee.com/itfitness/safe-demo

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 我們?cè)诰幋a美麗微信公眾號(hào)已經(jīng)弄過了很多app了,不管是協(xié)議還是外掛,我們都是那么一路走過來了,在操作的過程中也發(fā)現(xiàn)...
    格老子閱讀 1,630評(píng)論 2 36
  • 我們?cè)诰幋a美麗微信公眾號(hào)已經(jīng)弄過了很多app了,不管是協(xié)議還是外掛,我們都是那么一路走過來了,在操作的過程中也發(fā)現(xiàn)...
    Simplelove_f033閱讀 3,241評(píng)論 1 3
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險(xiǎn)厭惡者,不喜歡去冒險(xiǎn),但是人生放棄了冒險(xiǎn),也就放棄了無數(shù)的可能。 ...
    yichen大刀閱讀 7,541評(píng)論 0 4
  • 公元:2019年11月28日19時(shí)42分農(nóng)歷:二零一九年 十一月 初三日 戌時(shí)干支:己亥乙亥己巳甲戌當(dāng)月節(jié)氣:立冬...
    石放閱讀 7,384評(píng)論 0 2
  • 今天上午陪老媽看病,下午健身房跑步,晚上想想今天還沒有斷舍離,馬上做,衣架和旁邊的的布衣架,一看亂亂,又想想自己是...
    影子3623253閱讀 3,055評(píng)論 3 8

友情鏈接更多精彩內(nèi)容