Android apk加固實(shí)現(xiàn)原理

apk加固是每一個(gè)app發(fā)布之前必須要做的事情;如果一個(gè)apk沒(méi)有加固那么別人就很容易被別人反編譯,看到這其中的原碼中跌,雖然現(xiàn)在有代碼混淆咨堤、把業(yè)務(wù)寫(xiě)到native層,但是這都是治標(biāo)不治本漩符。反編譯的技術(shù)在更新一喘,那么保護(hù)Apk的技術(shù)就不能停止,雖然網(wǎng)上有很多加固的第三方框架陨仅,但是這加固的原理還是有必要去了解一下的津滞,其實(shí)加固有些人認(rèn)為很高深的技術(shù),其實(shí)不然灼伤,說(shuō)的簡(jiǎn)單點(diǎn)就是對(duì)源Apk的dex進(jìn)行加密触徐,然后在套上一層殼即可,當(dāng)然這里還有一些細(xì)節(jié)需要處理狐赡,這就是本文需要介紹的內(nèi)容了撞鹉。

加固原理

1.在主工程中添加一個(gè)Module這個(gè)module就是用來(lái)給apk加殼用的。
2.在主工程中添加一個(gè)java的中l(wèi)ib庫(kù)這個(gè)是用來(lái)加密主apk的dex颖侄、生成加固后的apk


工程目錄.png

一個(gè)apk需要啟動(dòng)系統(tǒng)會(huì)去解壓這個(gè)apk去得用dex文件鸟雏,去解析這些dex文件去啟動(dòng)改apk,因?yàn)橐堰@個(gè)主apk的dex加密來(lái)達(dá)到不被反編譯的效果览祖,所以這些dex文件已經(jīng)被加密了孝鹊,系統(tǒng)去加載這些dex文件肯定是會(huì)報(bào)錯(cuò)的。為了能讓apk能正常啟動(dòng)那就應(yīng)該要去啟動(dòng)這個(gè)殼的Application展蒂。所以得在app的AndroidManifest注冊(cè)殼的Application;這樣才能正常的啟動(dòng)App又活。

下面的Apk加固的流程圖:


加密.png

解密.png

加密的流程

先要生成Module中的aar庫(kù)苔咪,生成后要解壓該庫(kù)得apk的殼dex包,在proxy_tools的java庫(kù)中進(jìn)行加密

生成apk的殼文件(jar轉(zhuǎn)dex)

        File aarFile = new File("proxy_core/build/outputs/aar/proxy_core-debug.aar");
        File aarTemp = new File("proxy_tools/temp");
        Zip.unZip(aarFile, aarTemp);
        File classesJar = new File(aarTemp, "classes.jar");
        File classesDex = new File(aarTemp, "classes.dex");
        Process process = null;
        try {
            process = Runtime.getRuntime().exec("cmd /c dx --dex --output " + classesDex.getAbsolutePath()
                    + " " + classesJar.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            process.waitFor();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (process.exitValue() != 0) {
            throw new RuntimeException("dex error");
        }

通過(guò)這樣的操作就可以把a(bǔ)ar中的jar轉(zhuǎn)換成dex包了柳骄,這樣apk的殼dex就可以得到了团赏。

加密apk中的dex文件

 //下面加密碼APK中所有的dex文件
        File apkFile = new File("app/build/outputs/apk/debug/app-debug.apk");
        File apkTemp = new File("app/build/outputs/apk/debug/temp");
        //把a(bǔ)pk解壓出來(lái)
        Zip.unZip(apkFile, apkTemp);

        //只要dex文件拿出來(lái)加密
        File dexFiles[] = apkTemp.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File file, String s) {
                return s.endsWith(".dex");
            }
        });

        //進(jìn)行AES加密
        AES.init(AES.DEFAULT_PWD);
        for (File dexFile : dexFiles) {
            try {
                byte[] bytes = Utils.getBytes(dexFile);
                byte[] encrypt = AES.encrypt(bytes);
                FileOutputStream fileOutputStream = new FileOutputStream(new File(apkTemp,
                        "secret-" + dexFile.getName()));
                fileOutputStream.write(encrypt);
                fileOutputStream.flush();
                fileOutputStream.close();
                dexFile.delete();//刪除沒(méi)有加密的dex
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

這樣就進(jìn)行了apk中的dex文件加密,加密完成后需要把殼dex文件放到apk的目錄下生成新apk文件耐薯。

重新生成apk文件

        //把dex放入apk解壓路徑中,重新生成apk文件
        classesDex.renameTo(new File(apkTemp, "classes.dex"));
        File unSignedApk = new File("app/build/outputs/apk/debug/app-unsigned.apk");
        try {
            //生未簽名的apk
            Zip.zip(apkTemp, unSignedApk);
        } catch (Exception e) {
            e.printStackTrace();
        }

新的apk已經(jīng)生成舔清,下面就要簽名對(duì)齊

新apk的簽名對(duì)齊

        //4.簽名對(duì)齊
        File alignedApk = new File("app/build/outputs/apk/debug/app-unsigned-aligned.apk");
        try {
            process = Runtime.getRuntime().exec("cmd /c zipalign -v -p 4 " + unSignedApk.getAbsolutePath()
                    + " " + alignedApk.getAbsolutePath());
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            process.waitFor();
        } catch (InterruptedException e) {
            System.out.println("exception " + e.toString());
            e.printStackTrace();
        }

簽名對(duì)齊后這樣就可以簽名生成可以運(yùn)行的apk了

簽名生成可以運(yùn)行的apk文件

        File jskFile = new File("proxy_tools/proxy.jks");
        System.out.println("開(kāi)始簽名");
        try {
            process = Runtime.getRuntime().exec("cmd /c apksigner sign --ks " + jskFile.getAbsolutePath()
                    + " --ks-key-alias qwert --ks-pass pass:123456 --key-pass pass:123456 --out "
                    + signedApk.getAbsolutePath() + " " + alignedApk.getAbsolutePath());
            System.out.println("簽名完成");
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            process.waitFor();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (process.exitValue() != 0) {
            throw new RuntimeException("signed apk error");
        }
        System.out.println("生成apk成功");
    }

到這里加密部分的已經(jīng)完成,加密完成后就可以進(jìn)行解密操作了曲初。

解密流程

在解密之前先說(shuō)一下apk安裝后都會(huì)在/data/app/包名-xxx/生成一個(gè)叫base.apk的文件体谒,這個(gè)是文件是apk中的所有dex文件一個(gè)包,所以需要拿到這個(gè)base.apk中的dex進(jìn)行解密。

        File apkFile = new File(getApplicationInfo().sourceDir);
        Log.d(TAG,"apk的路徑:"+apkFile.getAbsolutePath());

        File versionDir = getDir(app_name+"_"+ app_version,MODE_PRIVATE);
        File appDir = new File(versionDir,"app");
        File dexFile = new File(appDir,"dexDir");

        //得用我們的需要加載的dex文件
        List<File> dexFiles = new ArrayList<>();

        if(MD5.verification(dexFile)){
            //把a(bǔ)pk解壓出來(lái)
            Zip.unZip(apkFile,appDir);
            //獲取目錄下所有文件
            File [] files = appDir.listFiles();
             for(File file : files){
                 String name = file.getName();
                 Log.d(ProxyApplication.class.getName(),"dexName:"+name);
                 if(name.endsWith(".dex") &&!TextUtils.equals(name,"classes.dex")){
                     AES.init(AES.DEFAULT_PWD);
                     //讀取文件內(nèi)容
                     try {
                         byte[] bytes = Utils.getBytes(file);
                         //解密
                         byte[] decrypt = AES.decrypt(bytes);

                         FileOutputStream fileOutputStream = new FileOutputStream(file);
                         fileOutputStream.write(decrypt);
                         fileOutputStream.flush();
                         fileOutputStream.close();
                         dexFiles.add(file);
                     } catch (Exception e) {
                         e.printStackTrace();
                     }
                 }
             }
        }else {
            for(File file : dexFile.listFiles()){
                dexFiles.add(file);
            }
        }
        //加載解密后的dex文件到系統(tǒng)
        try {
            loadDex(dexFiles,versionDir);
        } catch (Exception e) {
            e.printStackTrace();
        }

這里就已經(jīng)完成了dex文件的解密复斥,接下來(lái)的就應(yīng)該把解密后的dex文件交給系統(tǒng)處理讓它去運(yùn)行apk营密。

把dex交給系統(tǒng)處理

要把已經(jīng)解密好的dex文件交給系統(tǒng),那么首先要知道系統(tǒng)是如何的加載dex文件的目锭,只有搞懂系統(tǒng)是如何的加載dex文件的才能把已經(jīng)解密好的dex文件交給系統(tǒng);getClassLoader這個(gè)方法相信我們都不陌生了,通過(guò)查看系統(tǒng)原碼發(fā)現(xiàn)這個(gè)getClassLoader返回的ClassLoader是具體實(shí)現(xiàn)是一個(gè)叫PathClassLoader的類(lèi)纷捞,這個(gè)類(lèi)在系統(tǒng)原碼中看到只有構(gòu)造方法就沒(méi)有其它的方法了痢虹,那再往它的父類(lèi)中看,它的父類(lèi)是一個(gè)叫BaseDexClassLoader通過(guò)查看里面的方法可以看到有一個(gè)findClass的方法去加載這些類(lèi)主儡。
findClass.png

findClass這個(gè)類(lèi)又調(diào)用了pathList的里面的findClass方法奖唯,那個(gè)這個(gè)pathList又是什么呢?這個(gè)pathList是一個(gè)叫DexPathList的類(lèi)糜值,那再到DexPathList中查看這個(gè)findClass方法干了什么事情丰捷?下面這個(gè)就是DexPathList里面的findClass方法


DexPathList中的findClass.png

通過(guò)上面的代碼可以看出這個(gè)DexPathList中的findClass方法是在遍歷一個(gè)叫dexElements的一個(gè)數(shù)組來(lái)生成Class對(duì)象;接下就要找這個(gè)dexElements數(shù)組是在哪里被初始化的呢?通過(guò)查看DexPathList這個(gè)類(lèi)的構(gòu)造方法發(fā)現(xiàn)這個(gè)dexElements是通過(guò)一個(gè)叫makePathElements的方法來(lái)完成初始化的寂汇。
dexElements的初始化.png

到這里就已經(jīng)知道系統(tǒng)是如何加載dex文件的病往,那么已經(jīng)的解密好的dex文件要放到系統(tǒng)的dexElements中才能被系統(tǒng)加載到,系統(tǒng)是調(diào)用makePathElements方法去生成的dexElements數(shù)組骄瓣,那么我們也可以通過(guò)反射的去調(diào)用這個(gè)方法生成一個(gè)自已的dexElements數(shù)組停巷,再將自已的dexElement數(shù)組和系統(tǒng)的dexElements數(shù)組合并一個(gè)新數(shù)組設(shè)置到這個(gè)DexPathList中。

private void loadDex(List<File> dexFiles,File versionDir) throws Exception{
        //通過(guò)查看源碼我們可以知道系統(tǒng)存放dex是用一個(gè)數(shù)組存的:dexElements,而dexElements是DexPathList的成員
        Field pathListField = Utils.findField(getClassLoader(),"pathList");
        Object pathList = pathListField.get(getClassLoader());
        Field dexElementsFieId = Utils.findField(pathList,"dexElements");

        //這個(gè)是DexPathList里面的原始數(shù)據(jù),也就是系統(tǒng)中的dexElements數(shù)組
        Object dexElements[] = (Object[]) dexElementsFieId.get(pathList);

        //構(gòu)建自已的dexElement
        Method makeDexElements = Utils.findMethod(pathList,"makePathElements",List.class,File.class,List.class);
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        Object[] mCustomDexElements = (Object[]) makeDexElements.invoke(pathList,dexFiles,versionDir,suppressedExceptions);

        //合并數(shù)組
        Object newElements = Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length+mCustomDexElements.length);
        System.arraycopy(dexElements,0,newElements,0,dexElements.length);
        System.arraycopy(mCustomDexElements,0,newElements,dexElements.length,mCustomDexElements.length);

        //替換掉classloader中的elements數(shù)組
        dexElementsFieId.set(pathList,newElements);


    }

通過(guò)反射的就把解密的dex文件生成dexElements數(shù)組榕栏,再和系統(tǒng)的dexElements合并生成一個(gè)新數(shù)組替換掉DexPathList中的dexElements就達(dá)到交給系統(tǒng)處理效果畔勤。


效果圖.png

總結(jié)

apk加固后主dex文件已經(jīng)是看不到原碼,只能看殼的dex文件中的原碼扒磁,這樣就達(dá)到了加固的效果庆揪,但是這樣做還是不夠完善,因?yàn)橹鰽pp的Application已經(jīng)是沒(méi)有用了妨托,這個(gè)問(wèn)題的解決方法請(qǐng)到apk加固二中查看缸榛。
apk加固二http://www.reibang.com/p/67feffa9bff3
如果大家在看的時(shí)候如果有錯(cuò)誤的地方歡迎指出检访,我們共同進(jìn)步。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仔掸,一起剝皮案震驚了整個(gè)濱河市脆贵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌起暮,老刑警劉巖卖氨,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異负懦,居然都是意外死亡筒捺,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)纸厉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)系吭,“玉大人,你說(shuō)我怎么就攤上這事颗品】铣撸” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵躯枢,是天一觀的道長(zhǎng)则吟。 經(jīng)常有香客問(wèn)我,道長(zhǎng)锄蹂,這世上最難降的妖魔是什么氓仲? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮得糜,結(jié)果婚禮上敬扛,老公的妹妹穿的比我還像新娘。我一直安慰自己朝抖,他們只是感情好啥箭,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著槽棍,像睡著了一般捉蚤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上炼七,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天缆巧,我揣著相機(jī)與錄音,去河邊找鬼豌拙。 笑死陕悬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的按傅。 我是一名探鬼主播捉超,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼胧卤,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了拼岳?” 一聲冷哼從身側(cè)響起枝誊,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惜纸,沒(méi)想到半個(gè)月后叶撒,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡耐版,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年祠够,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粪牲。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡古瓤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腺阳,到底是詐尸還是另有隱情落君,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布舌狗,位于F島的核電站叽奥,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏痛侍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一魔市、第九天 我趴在偏房一處隱蔽的房頂上張望主届。 院中可真熱鬧,春花似錦待德、人聲如沸君丁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)绘闷。三九已至,卻和暖如春较坛,著一層夾襖步出監(jiān)牢的瞬間印蔗,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工丑勤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留华嘹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓法竞,卻偏偏與公主長(zhǎng)得像耙厚,于是被迫代替她去往敵國(guó)和親强挫。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

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