Android 一二三代殼加固原理分析

簡介

  • 所有的加固代碼 都需要通過Classloader加載然后才可以執(zhí)行

classloader介紹

  • 雙親委派機制

    雙親委派模式的工作原理的是;如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請求委托給父類的加載器去執(zhí)行
    如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終將到達頂層的啟動類加載器
    如果父類加載器可以完成類加載任務(wù),就成功返回,倘若父類加載器無法完成此加載任務(wù),子加載器才會嘗試自己去加載,這就是雙親委派模式
    即每個兒子都不愿意干活,每次有活就丟給父親去干,直到父親說這件事我也干不了時,兒子自己想辦法去完成,這個就是雙親委派。

  • Android中的classloader

    ClassLoader為抽象類;
    BootClassLoader預(yù)加載常用類,單例模式。
    BaseDexClassLoader是PathClassLoader、DexClassLoader、InMemoryDexClassLoader的父類,類加載的主要邏輯都是在BaseDexClassLoader完成的。
    SecureClassLoader繼承了抽象類ClassLoader,拓展了ClassLoader類加入了權(quán)限方面的功能,加強了安全性,其子類URLClassLoader是用URL路徑從jar文件中加載類和資源。
    其中重點關(guān)注的是PathClassLoader和DexClassLoader。
    PathClassLoader是Android默認使用的類加載器,一個apk中的Activity等類便是在其中加載。
    DexClassLoader可以加載任意目錄下的dex/jar/apk/zip文件,比PathClassLoader更靈活,是實現(xiàn)插件化、熱修復(fù)以及dex加殼的重點。
    Android8.0新引入InMemoryDexClassLoader,從名字便可看出是用于直接從內(nèi)存中加載dex。

596742716226444.png

APP 的啟動流程

534413116246610.png

APP類加載過程

  • BootClassLoader加載系統(tǒng)核心庫
  • PathClassLoader加載APP自身dex
  • 進入APP自身組件開始執(zhí)行,調(diào)用聲明Application的attachBaseContext,onCreate
    • AppThread中有LoadedApk對象。
    • AppThread handledBindApplication(應(yīng)該是AMS 回調(diào)的ApplicationThread)中,第一次進入到App自身的代碼中去。
    • Application attach -> oncreate最先被執(zhí)行,如果加殼的話,此時的classloader只有殼的代碼 沒有自身代碼
    • 如果不處理classloader的話,只有殼代碼,是無法啟動注冊的Activity Service等的。
      *<mark> 所以殼代碼需要在這里加載真正的代碼</mark>

組件生命周期的處理

DexClassLoader加載的類是沒有組件生命周期的,也就是說即使DexClassLoader通過對APK的動態(tài)加載完成了對組件類的加載,當系統(tǒng)啟動該組件時,依然會出現(xiàn)加載類失敗的異常。
需要替換系統(tǒng)組件的classloader才可以。加固廠商必然饒不過去
兩種解決方案:
1、替換系統(tǒng)組件類加載器為我們的DexClassLoader,同時設(shè)置DexClassLoader的parent為系統(tǒng)組件類加載器;

//classloader 已經(jīng)加載了 自身代碼 并且parent是原始的classloader
public void replaceClassloader(ClassLoader classloader){
        try {
            Class<?> ActivityThreadClazz=classloader.loadClass("android.app.ActivityThread");
            Method currentActivityThreadMethod= ActivityThreadClazz.getDeclaredMethod("currentActivityThread");
            currentActivityThreadMethod.setAccessible(true);
            Object activityThreadObj=currentActivityThreadMethod.invoke(null);
            //final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
            Field mPackagesField=ActivityThreadClazz.getDeclaredField("mPackages");
            mPackagesField.setAccessible(true);
            ArrayMap mPackagesObj= (ArrayMap) mPackagesField.get(activityThreadObj);
            WeakReference wr= (WeakReference) mPackagesObj.get(this.getPackageName());
            Object loadedApkObj=wr.get();

            Class LoadedApkClazz=classloader.loadClass("android.app.LoadedApk");
            //private ClassLoader mClassLoader;
            Field mClassLoaderField=LoadedApkClazz.getDeclaredField("mClassLoader");
            mClassLoaderField.setAccessible(true);
            mClassLoaderField.set(loadedApkObj,classloader);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

2、打破原有的雙親關(guān)系,在系統(tǒng)組件類加載器和BootClassLoader的中間插入我們自己的DexClassLoader即可;

public void startTestActivitySecondMethod(Context context,String dexfilepath){

        File optfile=context.getDir("opt_dex",0);
        File libfile=context.getDir("lib_path",0);
        ClassLoader pathClassloader=MainActivity.class.getClassLoader();
        ClassLoader bootClassloader=MainActivity.class.getClassLoader().getParent();
        DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),bootClassloader);
        try {
            Field parentField=ClassLoader.class.getDeclaredField("parent");
            parentField.setAccessible(true);
            parentField.set(pathClassloader,dexClassLoader);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

dex整體加密型加固

  • 將Dex整體加密,在需要的時候解密,通過Classloader加入內(nèi)存中。

關(guān)鍵代碼

public class TestActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //com.example.myapplication.TestClass.testFunc
        Context appContext=this.getApplicationContext();
        //testDexClassLoader(appContext,"/sdcard/3.dex");
        //startTestActivity(this,"/sdcard/4.dex");
        startTestActivitySecondMethod(this,"/sdcard/6.dex");
    }

    //替換LoadedApk中的mClassLoader 加載自身代碼
    public void replaceClassloader(ClassLoader classloader){
        try {
            Class<?> ActivityThreadClazz=classloader.loadClass("android.app.ActivityThread");
            Method currentActivityThreadMethod= ActivityThreadClazz.getDeclaredMethod("currentActivityThread");
            currentActivityThreadMethod.setAccessible(true);
            Object activityThreadObj=currentActivityThreadMethod.invoke(null);
            //final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
            Field mPackagesField=ActivityThreadClazz.getDeclaredField("mPackages");
            mPackagesField.setAccessible(true);
            ArrayMap mPackagesObj= (ArrayMap) mPackagesField.get(activityThreadObj);
            WeakReference wr= (WeakReference) mPackagesObj.get(this.getPackageName());
            Object loadedApkObj=wr.get();

            Class LoadedApkClazz=classloader.loadClass("android.app.LoadedApk");
            //private ClassLoader mClassLoader;
            Field mClassLoaderField=LoadedApkClazz.getDeclaredField("mClassLoader");
            mClassLoaderField.setAccessible(true);
            mClassLoaderField.set(loadedApkObj,classloader);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }


    }

    public void startTestActivityFirstMethod(Context context,String dexfilepath){

        File optfile=context.getDir("opt_dex",0);
        File libfile=context.getDir("lib_path",0);
        ClassLoader parentClassloader=MainActivity.class.getClassLoader();
        ClassLoader tmpClassLoader=context.getClassLoader();
        DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),MainActivity.class.getClassLoader());

        replaceClassloader(dexClassLoader);

        Class<?> clazz=null;
        try {
            clazz = dexClassLoader.loadClass("com.example.myapplication.TestActivity");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        context.startActivity(new Intent(context,clazz));
    }
    public void startTestActivitySecondMethod(Context context,String dexfilepath){

        File optfile=context.getDir("opt_dex",0);
        File libfile=context.getDir("lib_path",0);
        ClassLoader pathClassloader=MainActivity.class.getClassLoader();
        ClassLoader bootClassloader=MainActivity.class.getClassLoader().getParent();
        //加載dex 這里直接加載了未加密的,可以解密后 再加載,然后刪除。也可以替換為memoryclassloader加載 ,實現(xiàn)不落地加載
        DexClassLoader dexClassLoader=new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),bootClassloader);
        try {
            //插入我們自己的dexClassLoader 加入生命周期的處理
            Field parentField=ClassLoader.class.getDeclaredField("parent");
            parentField.setAccessible(true);
            parentField.set(pathClassloader,dexClassLoader);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
/*
* this:dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.xyh.loaddex-8_fCxispeBuExjw1ryrRZg==/base.apk"],nativeLibraryDirectories=[/data/app/com.xyh.loaddex-8_fCxispeBuExjw1ryrRZg==/lib/arm64, /system/lib64, /vendor/lib64]]]--parent:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/6.dex"],nativeLibraryDirectories=[/data/user/0/com.xyh.loaddex/app_lib_path, /system/lib64, /vendor/lib64]]]
this:dalvik.system.DexClassLoader[DexPathList[[dex file "/sdcard/6.dex"],nativeLibraryDirectories=[/data/user/0/com.xyh.loaddex/app_lib_path, /system/lib64, /vendor/lib64]]]--parent:java.lang.BootClassLoader@fd4323d
root:java.lang.BootClassLoader@fd4323d*/
        ClassLoader tmpClassloader=pathClassloader;
        ClassLoader parentClassloader=pathClassloader.getParent();
        while(parentClassloader!=null){
            Log.i("xyh","this:"+tmpClassloader+"--parent:"+parentClassloader);
            tmpClassloader=parentClassloader;
            parentClassloader=parentClassloader.getParent();
        }
        Log.i("xyh","root:"+tmpClassloader);
        Class<?> clazz=null;
        try {
            clazz = dexClassLoader.loadClass("com.example.myapplication.TestActivity");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        context.startActivity(new Intent(context,clazz));
    }
}

加固類型-函數(shù)抽取

殼與脫殼之二代殼函數(shù)抽取/#源碼分析

Dalvik指令抽取原理介紹

Android中實現(xiàn)「類方法指令抽取方式」加固方案原理解析
Android免Root權(quán)限通過Hook系統(tǒng)函數(shù)修改程序運行時內(nèi)存指令邏輯

  • 在函數(shù)粒度上保護代碼
  • 將函數(shù)指令全部替換為Nop或無效指令,在執(zhí)行時還原代碼。
  • 主要原理是將原指令置為nop,然后hook,dexFindClass,找到原始的方法指令指針,DexCode->insns,替換insns。
207665618235835.png

Art 抽取殼介紹

Art下實現(xiàn)難點:dex2oat編譯流程,dex2oat是可以進行脫殼,dex2oat完成了對抽取的dex進行編譯生成了oat文件,后續(xù)的函數(shù)運行中,從oat中取出函數(shù)編譯生成的二進制代碼來執(zhí)行,因此函數(shù)對dex填充后,如果時機不對,時機在dex2oat后,自然從dex2oat后那么我們動態(tài)修改的dex中的smali指令流就不會生效,因為后面app運行調(diào)用的真正的代碼就會從dex2oat編譯生成的oat文件,和以前的dex無關(guān)了。因此如果希望填充回去smali指令生效要么禁用dex2oat實現(xiàn)阻止編譯,這樣對加載到內(nèi)存中的dex文件進行填充始終會保持生效,要么保持dex2oat編譯,但是還原代碼時機要早于dex2oat就ok了,保證dex2oat再次對dex編譯的時候,dex已經(jīng)是一個完整dex,不會影響我們填充的代碼,但是肯定dex文件存在完整的時候,可以利用dex2oat編譯的流程進行脫殼,一般加殼廠商都是犧牲掉app一部分的運行效率,干掉dex2oat的過程,因為google本身提倡dex2oat就是為了提升app運行效率。
<mark>ART模式下抽取殼 要么阻止dex2oat 要么在dex2oat之前保證加載的dex是一個完整的dex</mark>
如果選擇第一種方案 , 在 dex2oat 之前進行恢復(fù) , 這沒有任何意義 , dex2oat 編譯后 , 生成的 oat 文件是完整的 , 此時 可以 完整的將 oat 文件 dump 到 SD 卡中 , 基本等于沒有加固 , 還是一個一代殼 ;
因此 , 大部分加固廠商 , 選擇 禁用 dex2oat 機制 ; 這樣處于安全考慮 , 犧牲了應(yīng)用的運行效率 ;

145113619231589.png
  • classloader加載dex的時候 會進行dex2oat的優(yōu)化,將dex的解釋執(zhí)行,變成直接執(zhí)行,提升運行效率。
  • 7.0之后會根據(jù)函數(shù)使用頻率進行dex2oat的優(yōu)化,并不會直接全部dex2oat

https://source.android.google.cn/devices/tech/dalvik/configure
ART 使用預(yù)先 (AOT) 編譯,并且從 Android 7.0(代號 Nougat,簡稱 N)開始結(jié)合使用 AOT、即時 (JIT) 編譯和配置文件引導(dǎo)型編譯。所有這些編譯模式的組合均可配置,我們將在本部分中對此進行介紹。例如,Pixel 設(shè)備配置了以下編譯流程:
7.0之后最初安裝應(yīng)用時不進行任何 AOT 編譯。應(yīng)用前幾次運行時,系統(tǒng)會對其進行解譯,并對經(jīng)常執(zhí)行的方法進行 JIT 編譯。
當設(shè)備閑置和充電時,編譯守護程序會運行,以便根據(jù)在應(yīng)用前幾次運行期間生成的配置文件對常用代碼進行 AOT 編譯。
下一次重新啟動應(yīng)用時將會使用配置文件引導(dǎo)型代碼,并避免在運行時對已經(jīng)過編譯的方法進行 JIT 編譯。在應(yīng)用后續(xù)運行期間經(jīng)過 JIT 編譯的方法將會添加到配置文件中,然后編譯守護程序?qū)@些方法進行 AOT 編譯。
ART 的運作方式 在 Android O 版本中,將會生成以下文件:
.vdex:其中包含 APK 的未壓縮 DEX 代碼,以及一些旨在加快驗證速度的元數(shù)據(jù)。
使用010editer就可以找到dex
.odex:其中包含 APK 中已經(jīng)過 AOT 編譯的方法代碼。
oatdump 可以反編譯odex文件,可以dump出函數(shù)的smali指令
.art (optional):其中包含 APK 中列出的某些字符串和類的 ART 內(nèi)部表示,用于加快應(yīng)用啟動速度。
全部進行oat優(yōu)化的話,非常耗時,所以7.0之后選取了保守的優(yōu)化策略
odex 優(yōu)化后,oatdump還是可以dump出原始的smali指令,只是優(yōu)化后的函數(shù) 在CODE段,會有優(yōu)化后的二進制匯編指令,在執(zhí)行的時候會直接進入quick模式,執(zhí)行匯編指令,加快運行速度。具有CODE段后,將不再進入解釋執(zhí)行模式。

  • art抽取殼必須在dex2oat之前還原,否則dex2oat之后,就無法還原。要么阻止dex2oat 要么提前還原
  • 首先要干掉Dex2oat的過程??梢詤⒖糶ithub TurboDex(快速加載dex 阻止dex2oat) dex2oat 非常耗時;不進行oat的話 會影響dex的運行效率。
  • 阻止了dex2oat后 所有代碼都是解釋執(zhí)行了。那么必然會去內(nèi)存中尋找ArtMethod的code_item去執(zhí)行
  • 那么在art類加載的時候還原code_item就可以正常去執(zhí)行了
  • 剩下的和dalvik基本一致
    • 1、先通過010edite將一個函數(shù)全部抽取為nop
    • 2、hook art loadmethod的過程
    • 3、將原始的指令還原到artmethod中

ART的類加載執(zhí)行流程

強烈建議閱讀此文章
ART 在 Android 安全攻防中的應(yīng)用

關(guān)鍵代碼

#include <jni.h>
#include <string>
#include <unistd.h>
#include <android/log.h>
#include <fcntl.h>
#include <asm/fcntl.h>
#include <sys/mman.h>
#include <dlfcn.h>
//import c header
extern "C" {
#include "hook/dlfcn/dlfcn_compat.h"
#include "hook/include/inlineHook.h"
}
typedef unsigned char byte;
#define TAG "SecondShell"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
struct DexFile {
    // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
    // The class we are a part of.
    uint32_t declaring_class_;
    // Access flags; low 16 bits are defined by spec.
    void *begin;
    /* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */
    // Offset to the CodeItem.
    uint32_t size;
};
struct ArtMethod {
    // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
    // The class we are a part of.
    uint32_t declaring_class_;
    // Access flags; low 16 bits are defined by spec.
    uint32_t access_flags_;
    /* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */
    // Offset to the CodeItem.
    uint32_t dex_code_item_offset_;
    // Index into method_ids of the dex file associated with this method.
    uint32_t dex_method_index_;
};

void* *(*oriexecve)(const char *__file, char *const *__argv, char *const *__envp);

void* *myexecve(const char *__file, char *const *__argv, char *const *__envp) {
    LOGD("process:%d,enter execve:%s", getpid(), __file);
    if (strstr(__file, "dex2oat")) {
        return NULL;
    } else {
        return oriexecve(__file, __argv, __envp);
    }


}

//void ClassLinker::LoadMethod(Thread* self, const DexFile& dex_file, const ClassDataItemIterator& it,Handle<mirror::Class> klass, ArtMethod* dst)
void *(*oriloadmethod)(void *, void *, void *, void *, void *);

void *myloadmethod(void *a, void *b, void *c, void *d, void *e) {
    LOGD("process:%d,before run loadmethod:", getpid());
    struct ArtMethod *artmethod = (struct ArtMethod *) e;
    struct DexFile *dexfile = (struct DexFile *) b;
    LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d", getpid(), dexfile->begin,
         dexfile->size);//0,57344
    char dexfilepath[100] = {0};
    sprintf(dexfilepath, "/sdcard/%d_%d.dex", dexfile->size, getpid());
    int fd = open(dexfilepath, O_CREAT | O_RDWR, 0666);
    if (fd > 0) {
        write(fd, dexfile->begin, dexfile->size);
        close(fd);
    }

    void *result = oriloadmethod(a, b, c, d, e);
    LOGD("process:%d,enter loadmethod:code_offset:%d,idx:%d", getpid(),
         artmethod->dex_code_item_offset_, artmethod->dex_method_index_);

    byte *code_item_addr = static_cast<byte *>(dexfile->begin) + artmethod->dex_code_item_offset_;
    LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d,beforedumpcodeitem:%p", getpid(),
         dexfile->begin, dexfile->size, code_item_addr);


    if (artmethod->dex_method_index_ == 15203) {//TestClass.testFunc->methodidx
        LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d,start repire method", getpid(),
             dexfile->begin, dexfile->size);
        byte *code_item_addr = (byte *) dexfile->begin + artmethod->dex_code_item_offset_;
        LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d,beforedumpcodeitem:%p", getpid(),
             dexfile->begin, dexfile->size, code_item_addr);

        int result = mprotect(dexfile->begin, dexfile->size, PROT_WRITE);
        byte *code_item_start = static_cast<byte *>(code_item_addr) + 16;
        LOGD("process:%d,enter loadmethod:dexfilebegin:%p,size:%d,code_item_start:%p", getpid(),
             dexfile->begin, dexfile->size, code_item_start);
        byte inst[16] = {0x1a, 0x00, 0xed, 0x34, 0x1a, 0x01, 0x43, 0x32, 0x71, 0x20, 0x91, 0x05,
                         0x10, 0x00, 0x0e, 0x00};
        for (int i = 0; i < sizeof(inst); i++) {
            code_item_start[i] = inst[i];
        }
        //2343->i am from com.kanxue.test02.TestClass.testFunc
        code_item_start[2] = 0x43;//34ed->kanxue
        code_item_start[3] = 0x23;
        memset(dexfilepath, 0, 100);
        sprintf(dexfilepath, "/sdcard/%d_%d.dex_15203_2", dexfile->size, getpid());
        fd = open(dexfilepath, O_CREAT | O_RDWR, 0666);
        if (fd > 0) {
            write(fd, dexfile->begin, dexfile->size);
            close(fd);
        }
    }
    LOGD("process:%d,after loadmethod:code_offset:%d,idx:%d", getpid(),
         artmethod->dex_code_item_offset_, artmethod->dex_method_index_);//0,57344
    return result;

}

void hooklibc() {
    LOGD("go into hooklibc");
    //7.0 命名空間限制
    void *libc_addr = dlopen_compat("libc.so", RTLD_NOW);
    void *execve_addr = dlsym_compat(libc_addr, "execve");
    if (execve_addr != NULL) {
        if (ELE7EN_OK == registerInlineHook((uint32_t) execve_addr, (uint32_t) myexecve,
                                            (uint32_t **) &oriexecve)) {
            if (ELE7EN_OK == inlineHook((uint32_t) execve_addr)) {
                LOGD("inlineHook execve success");
            } else {
                LOGD("inlineHook execve failure");
            }
        }
    }
}

void hookART() {
    LOGD("go into hookART");
    void *libart_addr = dlopen_compat("/system/lib/libart.so", RTLD_NOW);
    if (libart_addr != NULL) {
        void *loadmethod_addr = dlsym_compat(libart_addr,
                                             "_ZN3art11ClassLinker10LoadMethodERKNS_7DexFileERKNS_21ClassDataItemIteratorENS_6HandleINS_6mirror5ClassEEEPNS_9ArtMethodE");
        if (loadmethod_addr != NULL) {
            if (ELE7EN_OK == registerInlineHook((uint32_t) loadmethod_addr, (uint32_t) myloadmethod,
                                                (uint32_t **) &oriloadmethod)) {
                if (ELE7EN_OK == inlineHook((uint32_t) loadmethod_addr)) {
                    LOGD("inlineHook loadmethod success");
                } else {
                    LOGD("inlineHook loadmethod failure");
                }
            }
        }
    }


}

extern "C" JNIEXPORT jstring JNICALL
Java_com_kanxue_secondshell_180_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "ART SecondShell";
    return env->NewStringUTF(hello.c_str());
}
extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_secondshell_180_MainActivity_SecondShell(
        JNIEnv *env,
        jobject /* this */) {
    hooklibc();//hook execve 禁止執(zhí)行dex2oat
    hookART();//hook LoadMethod 修復(fù)被抽取的artmethod,并在修復(fù)前修復(fù)后dumpdex
    return;
}

加固類型-VMP,Dex2C

  • VMP 自定義解釋器,解釋執(zhí)行自定義的指令。
  • Dex2c 在編譯時,將java代碼編譯成c代碼 增加破解難度
  • 未來加固的主要方向->VMP,Dex2C
    • VMP 會把java函數(shù)native化,原理:VMP是做虛擬機解釋器
      • VMP的多個native函數(shù) 很可能是一個地址。多個native函數(shù) 都是調(diào)用的一個地方
      • 破解需要找到解釋器,找到smaili的映射關(guān)系即可恢復(fù)
      • github 搜索 ADVMP 可以參考 沒具體研究
    • Dex2c 開源項目 dcc,原理:對java語義分析,重新生成native函數(shù)。
      • python dcc.py dcc.apk -o dcc_out.apk 使用DCC增加保護,java函數(shù)會變成native函數(shù)。 只建議部分函數(shù)增加保護,否則會嚴重影響性能
      • dcc可以將APK的java代碼 轉(zhuǎn)化為native代碼,并且可以增加ollvm的混淆
      • dcc的native注冊函數(shù) 基本是一一對應(yīng)
      • 基本不太好破解。。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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