Android apk加固(加殼)整理

一、Dex加殼由來(lái)

最近在學(xué)習(xí)apk加密,在網(wǎng)上看了一篇《Android中的Apk的加固(加殼)原理解析和實(shí)現(xiàn)》,我發(fā)現(xiàn)原文把整個(gè)apk都寫入到dex文件中,如果apk小還好,當(dāng)原APK大于200M,客戶端解殼很費(fèi)勁,打開后應(yīng)用就卡住了,如果只是把原apk的dex加殼不就很容易解開了嘛。我不是原創(chuàng),只是按照我自己的思路將大神的加固稍作調(diào)整,并且將整個(gè)項(xiàng)目整理如下。

二、Dex結(jié)構(gòu)

dex_structure.png

如圖所示,新的dex由解殼dex、dex集合、dex集合描述和描述長(zhǎng)度組成
三、核心代碼

  • 加殼
    /**
     * 給apk加殼
     * @param primaryApkPath 原apk
     * @param unShellApkPath 解殼apk
     * @param outApkPath 加殼后新APK
     * @throws Exception
     */
    public static void apkShell(String primaryApkPath,String unShellApkPath,String outApkPath) throws Exception{
        if(!FileUtils.isExit(primaryApkPath, unShellApkPath)){
            throw new RuntimeException("check params");
        }
        //解壓原apk
        String unPrimaryApkDstPath = primaryApkPath.replace(".apk", "");
        ApkToolUtils.decompile(primaryApkPath, unPrimaryApkDstPath);
        String primaryManifestPath = unPrimaryApkDstPath + File.separator + "AndroidManifest.xml";

        //解壓解殼apk
        String unShellApkDstPath = unShellApkPath.replace(".apk", "");
        ApkToolUtils.decompile(unShellApkPath, unShellApkDstPath);
        String unShellManifestPath = unShellApkDstPath + File.separator + "AndroidManifest.xml";
        String unShellDexPath = unShellApkDstPath + File.separator + "classes.dex";
        File unShellFile = new File(unShellDexPath);


        File unApkDir = new File(unPrimaryApkDstPath);
        ArrayList<File> dexArray = new ArrayList<File>();
        for(File file : unApkDir.listFiles()){//讀取解殼后的dex
            if(file.getName().endsWith(".dex")){
                dexArray.add(file);
            }
        }
        String shellDexPath = unPrimaryApkDstPath + File.separator + "classes.dex";
        shellDex(dexArray, unShellFile, shellDexPath);//生產(chǎn)新的dex(加殼)

        String mateInfPath = unPrimaryApkDstPath + File.separator +"META-INF";//刪除meta-inf,重新簽名后會(huì)生成
        FileUtils.delete(mateInfPath);

        for(File file : dexArray){//清理多余dex文件
            if(file.getName().equals("classes.dex")){
                continue;
            }
            FileUtils.delete(file.getAbsolutePath());
        }

        String unShellApplicationName = AndroidXmlUtils.readApplicationName(unShellManifestPath);//解殼ApplicationName
        String primaryApplicationName = AndroidXmlUtils.readApplicationName(primaryManifestPath);//原applicationName
        AndroidXmlUtils.changeApplicationName(primaryManifestPath, unShellApplicationName);//改變?cè)瑼pplicationname為解殼ApplicationName
        if(primaryApplicationName != null){//將原ApplicationName寫入mateData中,解殼application中會(huì)讀取并替換應(yīng)用Application
            AndroidXmlUtils.addMateData(primaryManifestPath, "APPLICATION_CLASS_NAME", primaryApplicationName);
        }
        //回編,回編系統(tǒng)最好是linux
        ApkToolUtils.compile(unPrimaryApkDstPath,outApkPath);
        //v1簽名
        SignUtils.V1(outApkPath, SignUtils.getDefaultKeystore());
        //清理目錄
        FileUtils.delete(unPrimaryApkDstPath);
        FileUtils.delete(unShellApkDstPath);
    }

加殼工程是一個(gè)java工程,解壓apk使用了apktool,apktool這個(gè)工具最好是在linux下使用,xml操作使用了W3C java自帶的,不咋個(gè)好用,為了項(xiàng)目簡(jiǎn)單沒用其他的jar包。加殼項(xiàng)目中對(duì)byte數(shù)組的加密使用了aes,也可以用其他方法去實(shí)現(xiàn)。

  • 解殼
 /**
     * 從殼的dex文件中分離出原來(lái)的dex文件
     * @param data
     * @param primaryDexDir
     * @throws IOException
     */
    public void splitPrimaryDexFromShellDex(byte[] data, String primaryDexDir) throws IOException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, NoSuchPaddingException {
        int shellDexLen = data.length;
        byte[] dexFileCommentLenByte = new byte[4];//dex信息長(zhǎng)度
        System.arraycopy(data, shellDexLen-4, dexFileCommentLenByte, 0, 4);
        ByteArrayInputStream bais = new ByteArrayInputStream(dexFileCommentLenByte);
        DataInputStream in = new DataInputStream(bais);
        int dexFileCommentLen = in.readInt();

        byte[] dexFileCommentByte = new byte[dexFileCommentLen];//dex信息正文
        System.arraycopy(data,shellDexLen-4-dexFileCommentLen,dexFileCommentByte,0,dexFileCommentLen);
        String dexFileComment = new String(dexFileCommentByte);
        LogUtils.d("dex comment:"+dexFileComment);
        ArrayList&lt;DexFile&gt dexFileArrayList = (ArrayList&lt;DexFile&gt) JSON.parseArray(dexFileComment,DexFile.class);
        int currentReadEndIndex = shellDexLen - 4 - dexFileCommentLen;//當(dāng)前已經(jīng)讀取到的內(nèi)容的下標(biāo)
        for(int i = dexFileArrayList.size()-1; i&gt=0; i--){//取出所有的dex,并寫入到payload_dex目錄下
            DexFile dexFile = dexFileArrayList.get(i);
            byte[] primaryDexData = new byte[dexFile.getDexLength()];
            System.arraycopy(data,currentReadEndIndex-dexFile.getDexLength(),primaryDexData,0,dexFile.getDexLength());
            primaryDexData = decryAES(primaryDexData);//界面
            File primaryDexFile = new File(primaryDexDir,dexFile.getDexName());
            if(!primaryDexFile.exists()) primaryDexFile.createNewFile();
            FileOutputStream localFileOutputStream = new FileOutputStream(primaryDexFile);
            localFileOutputStream.write(primaryDexData);
            localFileOutputStream.close();

            currentReadEndIndex -= dexFile.getDexLength();
        }
    }

//代碼片段,DexClassLoder加載多個(gè)dex
  //找到dex并通過DexClassLoader去加載
    StringBuffer dexPaths = new StringBuffer();
    for(File file:dex.listFiles()){
        dexPaths.append(file.getAbsolutePath());
        dexPaths.append(File.pathSeparator);
    }
    dexPaths.delete(dexPaths.length()-1,dexPaths.length());
    LogUtils.d(dexPaths.toString());
    DexClassLoader classLoader = new DexClassLoader(dexPaths.toString(), odex.getAbsolutePath(),getApplicationInfo().nativeLibraryDir,(ClassLoader) RefInvoke.getFieldOjbect(
            "android.app.LoadedApk", wr.get(), "mClassLoader"));//android4.4后ART會(huì)對(duì)dex做優(yōu)化,第一次加載時(shí)間較長(zhǎng),后面就很快了

將原項(xiàng)目dex從殼dex中獲取出來(lái),然后在onCreate中將dex拼接后使用DexClassLoder加載,nativeLibrary咋們只對(duì)dex做了加殼所以可以直接使用Application的nativeLibraryDir。
其它核心代碼,application替換這類的,可以在原文中查看。

四、效果

effect.jpg

從左往右分別為原demo工程的apk,為了實(shí)現(xiàn)多dex加了很多費(fèi)代碼,加殼后的apk,解殼apk??梢钥闯黾託ず箜?xiàng)目demo工程的dex被隱藏,顯示的是解殼工程的代碼

五、待優(yōu)化

  1. 將客戶端的解密放入native層;
  2. 將解密后的dex文件隱藏;

解密后的文件依舊存于應(yīng)用的私有存儲(chǔ)空間中,ROOT了的手機(jī)和模擬器很容易就可以拿到解密后的dex,所以這種加殼方法只是將代碼從apk中隱藏。
如果有好的解決方法,或者好的加殼方法望告知!

附送整個(gè)項(xiàng)目代碼代碼傳送門

?著作權(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)容

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