Dex加密(上)

App通常都會做混淆防止別人反編譯浸须,即使反編譯出來也是a、b琉闪、c這種迹炼,但是這種還是會被一些有心的人還原代碼,這樣我們需要給dex加密颠毙,這樣別人就不容易反編譯斯入。
效果:



別人是沒辦法看見主工程的代碼只能看見解密工程的java代碼,因?yàn)榻饷苁窃贑中實(shí)現(xiàn)的蛀蜜,所以密鑰和解密方法都還是安全的刻两。

APK本質(zhì)就是一個(gè)zip壓縮包,解壓之后包含AndroidManifest.xml滴某、class.dex磅摹、resources.arsc等等文件


要把Dex加密,但是系統(tǒng)是不認(rèn)識我們加密后的Dex的霎奢,所以還需要一個(gè)解密户誓,我們做一個(gè)解密工程生成主Dex(系統(tǒng)能識別的Dex,這個(gè)不加密),然后利用這個(gè)Dex解密我們加密過的原App的Dex幕侠。



整個(gè)工程分三個(gè)部分帝美,工程結(jié)構(gòu):



不是利用Android Studio的自動打包,而是我們?nèi)ド梢粋€(gè)apk
大致步驟:

1.主APP生成apk晤硕,解密工程生成aar悼潭,提供給后面使用

  1. 獲取解密工程生成的aar中的class.jar包,并把它生成為classes.dex窗骑。
    3.獲得主APK里面的所有dex女责,并給所有dex加密成系統(tǒng)不能識別的dex。
  2. 把解密dex放入加密dex的目錄创译,重新生成一個(gè)apk抵知,并簽名這個(gè)apk
  3. 替換回application(也就是AndroidManifest.xml中定義的application)。替換Application

1. 創(chuàng)建解密工程

首先在主APP工程中創(chuàng)建一個(gè)Module作為解密工程,別忘了在主工程的gradle中添加

implementation project(':proxy_guard_core')

一個(gè)app的入口都是Application刷喜,所以我們創(chuàng)建一個(gè)Application在這里實(shí)現(xiàn)解密残制,并在主APP的AndroidManifest.xml中使用這個(gè)Application。


準(zhǔn)備工作完畢掖疮,下面擼解密代碼
Application最先調(diào)用的方法是attachBaseContext(Context base)初茶。

for (File file : files) {
                String name = file.getName();
                //文件名是 .dex結(jié)尾, 并且不是主dex 放入 dexDir 目錄
                if (name.endsWith(".dex") && !TextUtils.equals(name, "classes.dex")) {
                    try {
                        //從文件中讀取 byte數(shù)組 加密后的dex數(shù)據(jù)
                        byte[] bytes = Utils.getBytes(file);
                        //將dex 文件 解密 并且寫入 原文件file目錄
                        Utils.decrypt(bytes, file.getAbsolutePath());
                        dexFiles.add(file);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

系統(tǒng)在加載dex的時(shí)候首先加載的是classes.dex浊闪,后面我們需要把解密工程生成classes.dex恼布,所以這里遍歷得到所以的dex文件,classes.dex除外搁宾。Utils.decrypt()調(diào)用openssl實(shí)現(xiàn)解密

 //加解密的 上下文
    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
    int outlen;
    unsigned char outbuf[1024];
    //初始化上下文 設(shè)置解碼參數(shù)
    EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, userkey, NULL);

    //密文比明文長折汞,所以肯定能保存下所有的明文
    uint8_t *out = malloc(src_len);
    //數(shù)據(jù)置空
    memset(out, 0, src_len);
    int len;
    //解密   abcdefg  z    z
    EVP_DecryptUpdate(ctx, out, &outlen, src, src_len);
    len = outlen;
    //解密剩余的所有數(shù)據(jù) 校驗(yàn)
    EVP_DecryptFinal_ex(ctx, out + outlen, &outlen);
    len += outlen;
    EVP_CIPHER_CTX_free(ctx);

    //寫文件 以二進(jìn)制形式寫出
    FILE *f = fopen(path, "wb");
    fwrite(out, len, 1, f);
    fclose(f);
    free(out);

openSSL的編譯看最后面。
我們把加密的dex解密成普通的dex之后盖腿,這個(gè)時(shí)候系統(tǒng)已經(jīng)運(yùn)行完成了多dex自動加載過程爽待,但是我們的dex并沒有被加載,所以我們需要自己實(shí)現(xiàn)多dex的加載翩腐。多dex加載原理看后面鸟款,5.0之后開始支持多dex加載,源碼MultiDex里也是這樣實(shí)現(xiàn)的茂卦。(但是MultiDex適配19(4.4)以上的之調(diào)用下面的第一個(gè)if何什,并沒有適配6.x,就能做到19以上系統(tǒng)都能支持多dex加載等龙,具體原因是什么還不清楚富俄,或者我看錯(cuò)了)

 //1.1  獲得classloader中的pathList => DexPathList
        Field pathListField = Utils.findField(getClassLoader(), "pathList");
        Object pathList = pathListField.get(getClassLoader());
        //1.2 獲得pathList類中的 dexElements
        Field dexElementsField = Utils.findField(pathList, "dexElements");
        Object[] dexElements = (Object[]) dexElementsField.get(pathList);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && Build.VERSION.SDK_INT <
                Build.VERSION_CODES.M) {
            //5.x
             makeDexElements = Utils.findMethod(pathList, "makeDexElements", ArrayList.class,
                    File.class, ArrayList.class);
            addElements = (Object[]) makeDexElements.invoke(pathList, dexFiles,
                    optimizedDirectory,
                    suppressedExceptions);
        } else if(Build.VERSION.SDK_INT < Build.VERSION_CODES.N &&Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
            //6.x
            makeDexElements = Utils.findMethod(pathList, "makePathElements", List.class,
                    File.class, List.class);
            addElements = (Object[]) makeDexElements.invoke(pathList, dexFiles,
                    optimizedDirectory,
                    suppressedExceptions);
        }else {
            makeDexElements = Utils.findMethod(pathList, "makeDexElements",
                    List.class, File.class, List.class,ClassLoader.class);
            Field definingContextField = Utils.findField(pathList, "definingContext");
            ClassLoader definingContext = (ClassLoader) definingContextField.get(pathList);
            addElements = (Object[]) makeDexElements.invoke(pathList,dexFiles, optimizedDirectory, suppressedExceptions,definingContext);
        }

合并兩個(gè)Element[]并替換原來的Element[]

//創(chuàng)建一個(gè)數(shù)組
        Object[] newElements = (Object[]) Array.newInstance(dexElements.getClass()
                .getComponentType(), dexElements.length +
                addElements.length);
        System.arraycopy(dexElements, 0, newElements, 0, dexElements.length);
        System.arraycopy(addElements, 0, newElements, dexElements.length, addElements.length);
  /**
         * 4.替換classloader中的 element數(shù)組
         */
        dexElementsField.set(pathList, newElements);

2. 解密工程生成dex

生成dex我們需要借助androidSDK\build-tools\28.0.3下的dx.bat工具,這個(gè)工具可以把class/jar生成dex而咆。
一條命令就能生成

dx --dex --output out.dex in.jar

把之前的解密工程生成aar,拿到其中的classes.jar生成dex

        /**
         * 1幕袱、制作只包含解密代碼的dex 文件
         */
        //1.1 解壓aar 獲得classes.jar
        File aarFile = new File("proxy-guard-core/build/outputs/aar/proxy-guard-core-debug.aar");
        File aarTemp = new File("proxy-guard-tools/temp");
        Zip.unZip(aarFile, aarTemp);
        File classesJar = new File(aarTemp, "classes.jar");
        //1.2 執(zhí)行dx命令 將jar變成dex文件
        File classesDex = new File(aarTemp, "classes.dex");
        //執(zhí)行命令  windows:cmd /c  linux/mac不需要(cmd /c)
        Process process = Runtime.getRuntime().exec("cmd /c dx --dex --output " + classesDex
                .getAbsolutePath() + " " +
                classesJar.getAbsolutePath());
        process.waitFor();
        //失敗
        if (process.exitValue() != 0) {
            throw new RuntimeException("dex error");
        }

3. 拿到主工程所有的dex并加密

 /**
         * 2暴备、加密apk中所有dex文件
         */
        //2.1 解壓apk 獲得所有的dex文件
        File apkFile = new File("app/build/outputs/apk/debug/app-debug.apk");
        File apkTemp = new File("app/build/outputs/apk/debug/temp");
        Zip.unZip(apkFile, apkTemp);
        //獲得所有的dex
        File[] dexFiles = apkTemp.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File file, String s) {
                return s.endsWith(".dex");
            }
        });
        //初始化aes
        AES.init(AES.DEFAULT_PWD);
        for (File dex : dexFiles) {
            //讀取文件數(shù)據(jù)
            byte[] bytes = getBytes(dex);
            //加密
            byte[] encrypt = AES.encrypt(bytes);
            //寫到指定目錄
            FileOutputStream fos = new FileOutputStream(new File(apkTemp, "secret-"
                    + dex.getName()));
            fos.write(encrypt);
            fos.flush();
            fos.close();
            dex.delete();
        }

4. 放入解密工程的dex,并簽名

https://developer.android.google.cn/studio/publish/app-signing

/**
         * 3们豌、把classes.dex 放入 apk解壓目錄 在壓縮成apk
         */
        classesDex.renameTo(new File(apkTemp, "classes.dex"));
        File unSignedApk = new File("app/build/outputs/apk/debug/app-unsigned.apk");
        Zip.zip(apkTemp, unSignedApk);
//4.1 對齊
//       26.0.2不認(rèn)識-p參數(shù) zipalign -v -p 4 my-app-unsigned.apk my-app-unsigned-aligned.apk
        File alignedApk = new File("app/build/outputs/apk/debug/app-unsigned-aligned.apk");
        process = Runtime.getRuntime().exec("cmd /c zipalign -f 4 " + unSignedApk
                .getAbsolutePath() + " " +
                alignedApk.getAbsolutePath());
        process.waitFor();
        //失敗
        if (process.exitValue() != 0) {
            throw new RuntimeException("zipalign error");
        }

        //4.2 簽名
//        apksigner sign  --ks jks文件地址 --ks-key-alias 別名 --ks-pass pass:jsk密碼 --key-pass
// pass:別名密碼 --out  out.apk in.apk
        //官方文檔沒有 --ks-key-alias等參數(shù) 有點(diǎn)坑爹啊
        File signedApk = new File("app/build/outputs/apk/debug/app-signed-aligned.apk");
        File jks = new File("proxy-guard-tools/proxyDex.jks");
        process = Runtime.getRuntime().exec("cmd /c apksigner sign  --ks " + jks.getAbsolutePath
                () + " --ks-key-alias hz --ks-pass pass:123456 --key-pass  pass:123456 --out" +
                " " + signedApk.getAbsolutePath() + " " + alignedApk.getAbsolutePath());
        process.waitFor();
        //失敗
        if (process.exitValue() != 0) {
            throw new RuntimeException("apksigner error");
        }

多Dex加載原理

Dex的加載是通過ClassLoader來加載涯捻,源碼目錄:
libcore\dalvik\src\main\java\dalvik\system



加載一個(gè)類我們常常會調(diào)用

getClassLoader().loadClass("類名");

通過getClassLoader()我們實(shí)際獲得是一個(gè)PathClassLoader對象,loadClass方法在抽象類ClassLoader中

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }

這里的findClass實(shí)際又是PathClassLoader的父類BaseDexClassLoader的一個(gè)方法

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }

類的加載實(shí)際是調(diào)用pathList的findClass方法通過類名來找到一個(gè)類望迎。而pathList是一個(gè)DexPathList 對象障癌。

private final DexPathList pathList;

再來看看DexPathList 的findClass方法:

 public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }
    /**
     * Finds the named class in one of the dex files pointed at by
     * this instance. This will find the one in the earliest listed
     * path element. If the class is found but has not yet been
     * defined, then this method will define it in the defining
     * context that this instance was constructed with.
     */
    public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

按照注釋的意思就是從一個(gè)dex文件中找到class。
通過遍歷dexElements辩尊,由此可見一個(gè)Element 對應(yīng)一個(gè)dex涛浙。
果然dexElements的注釋也證明了。

    /**
     * List of dex/resource (class path) elements.
     * Should be called pathElements, but the Facebook app uses reflection
     * to modify 'dexElements' (http://b/7726934).
     */
    private Element[] dexElements;

所以需要把我們生成的dex加入到dexElements這個(gè)數(shù)組中就能實(shí)現(xiàn)多dex的加載
先來看看怎么生成dexElements。

    public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {
        .......
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext);
        ......

注意:這里是DexPathList的四個(gè)參數(shù)的構(gòu)造方法轿亮,而不是兩個(gè)參數(shù)的構(gòu)造方法疮薇,兩個(gè)參數(shù)的構(gòu)造方法,是加載內(nèi)存中的dexFile我注。7.0一下源碼沒有兩個(gè)參數(shù)的構(gòu)造方法按咒。
所以我們可以通過反射調(diào)用makeDexElements來幫我們生成dexElements。7.0以上都是一樣但骨,但是4.4到5.x參數(shù)不一樣励七,6.x源碼這個(gè)方法名就不一樣了。

  • 6.x
        public DexPathList(ClassLoader definingContext, String dexPath,
                String libraryPath, File optimizedDirectory) {
      ......
        this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,
                                                       suppressedExceptions);
      ......
  • 4.4-5.x
    public DexPathList(ClassLoader definingContext, String dexPath,
                           String libraryPath, File optimizedDirectory) {
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                                       suppressedExceptions);

4.4-5.x雖然也是makeDexElements但是只有三個(gè)參數(shù)奔缠。

編譯openSSL

加密需要openSSL掠抬,所以首先先編譯一個(gè)openSSL。

  1. 下載openSSL
    https://www.openssl.org/source/
    linux 環(huán)境下載
wget https://www.openssl.org/source/openssl-1.1.1b.tar.gz

解壓之后編譯添坊。官網(wǎng)沒有編譯教程剿另,github上也沒有編譯教程
還好有個(gè)wiki
https://wiki.openssl.org/index.php/Android
把setenv-android.sh下載下來,并全部復(fù)制贬蛙。
openSSL源碼目錄下創(chuàng)建一個(gè)build.sh雨女,復(fù)制setenv-android.sh中的代碼。我的NDK 是r17b版本阳准,然后修改其中的配置氛堕。

_ANDROID_EABI="arm-linux-androideabi-4.9"

源碼中是4.8,根據(jù)ndk版本來設(shè)置野蝇,我的ndk中是4.9讼稚。
添加ndk根目錄

export ANDROID_NDK_HOME=/ndk/android-ndk-r17b
export ANDROID_NDK_ROOT=/ndk/android-ndk-r17b

如果不設(shè)置ANDROID_NDK_HOME會報(bào)ANDROID_NDK_HOME未定義的錯(cuò)誤。
設(shè)置了 ANDROID_NDK_ROOT之后


這個(gè)配置就不用管了绕沈。
這些都是配置锐想,還需要編譯模塊


完整的編譯腳本在demo中。Dex加密下

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乍狐,一起剝皮案震驚了整個(gè)濱河市赠摇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌浅蚪,老刑警劉巖藕帜,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異惜傲,居然都是意外死亡洽故,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進(jìn)店門盗誊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來时甚,“玉大人隘弊,你說我怎么就攤上這事∽睬铮” “怎么了长捧?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長吻贿。 經(jīng)常有香客問我串结,道長,這世上最難降的妖魔是什么舅列? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任肌割,我火速辦了婚禮,結(jié)果婚禮上帐要,老公的妹妹穿的比我還像新娘把敞。我一直安慰自己,他們只是感情好榨惠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布奋早。 她就那樣靜靜地躺著,像睡著了一般赠橙。 火紅的嫁衣襯著肌膚如雪耽装。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天期揪,我揣著相機(jī)與錄音掉奄,去河邊找鬼。 笑死凤薛,一個(gè)胖子當(dāng)著我的面吹牛姓建,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缤苫,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼速兔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了活玲?” 一聲冷哼從身側(cè)響起憨栽,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎翼虫,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屡萤,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡珍剑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了死陆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片招拙。...
    茶點(diǎn)故事閱讀 39,745評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡唧瘾,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出别凤,到底是詐尸還是另有隱情饰序,我是刑警寧澤,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布规哪,位于F島的核電站求豫,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏诉稍。R本人自食惡果不足惜蝠嘉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杯巨。 院中可真熱鬧蚤告,春花似錦、人聲如沸服爷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仍源。三九已至心褐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間镜会,已是汗流浹背檬寂。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留戳表,地道東北人桶至。 一個(gè)月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像匾旭,于是被迫代替她去往敵國和親镣屹。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評論 2 354

推薦閱讀更多精彩內(nèi)容