手寫apk加固

apk加固的目的其實就是對app的核心代碼做防護工作,避免被其他人反編譯;
廢話不多說了,直接開始!

加殼

首先,要想對apk加固的話,需要以下幾個步驟:

  • 解壓原apk
  • 對原apk裏面的classes.dex 進行AES加密
  • 重命名classes.dex爲classes_.dex
  • 對殼(aar)文件解壓
  • 用dx工具將jar 轉成classes.dex文件
  • 將殼dex文件複製到原apk的解壓目錄下
  • 打包壓縮原apk爲xxx.apk文件
  • 對打包後的apk進行簽名

解壓原apk並加密

public static File encryptAPKFile(File srcAPKfile, File dstApkFile) throws Exception {
        if (srcAPKfile == null) {
        	System.out.println("encryptAPKFile :srcAPKfile null");
            return null;
        }
//        File disFile = new File(srcAPKfile.getAbsolutePath() + "unzip");
//		Zip.unZip(srcAPKfile, disFile);
        Zip.unZip(srcAPKfile, dstApkFile);
        //������е�dex ����Ҫ����ְ������
        File[] dexFiles = dstApkFile.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File file, String s) {
                return s.endsWith(".dex");
            }
        });

        File mainDexFile = null;
        byte[] mainDexData = null;

        for (File dexFile: dexFiles) {
        	//������
            byte[] buffer = Utils.getBytes(dexFile);
            //����
            byte[] encryptBytes = AES.encrypt(buffer);

            if (dexFile.getName().endsWith("classes.dex")) {
                mainDexData = encryptBytes;
                mainDexFile = dexFile;
            }
           //д����  �滻ԭ��������
            FileOutputStream fos = new FileOutputStream(dexFile);
            fos.write(encryptBytes);
            fos.flush();
            fos.close();
        }
        return mainDexFile;
    }

重命名dex文件

 if(newApkFile.isDirectory()){
            for(File newApkDirFile: Objects.requireNonNull(newApkFile.listFiles())){
                if(newApkDirFile.isFile()){
                    if(newApkDirFile.getName().endsWith(".dex")){
                        String dexName = newApkDirFile.getName();
                        int cursor = dexName.indexOf(".dex");
                        String reName = newApkDirFile.getParent() + File.separator + dexName.substring(0, cursor) + "_" + ".dex";
                        System.out.println("reName value:" + reName);
                        newApkDirFile.renameTo(new File(reName));
                    }
                }
            }
        }

對殼文件操作

 File aarFile = new File(AAR_FILE_DIR + File.separator + "mylibrary-debug.aar");
            if(aarFile.exists()){
                File dexFile = Dx.jar2Dex(aarFile);
                if(!dexFile.exists()){
                    System.out.println("dex file no exit xxxxxxxxxxxxxx");
                    return;
                }

                File newDexFile = new File(apkTempFileDir.getPath() + File.separator + "classes.dex");
                if(!newDexFile.exists()){
                    newDexFile.createNewFile();
                }

                FileOutputStream dexStream = new FileOutputStream(newDexFile);

                byte[] aarBytes = Utils.getBytes(dexFile);

                dexStream.write(aarBytes);
                dexStream.flush();
                dexStream.close();

   

打包壓縮成apk文件

             File unsignApkFile = new File(PROPATH + File.separator + "apk-unsign.apk");
                if(!unsignApkFile.exists()){
                    unsignApkFile.createNewFile();
                }
                 //將apk/temp目錄下的文件進行打包壓縮
                Zip.zip(apkTempFileDir, unsignApkFile);

簽名

 //簽名
                Signature.signature(unsignApkFile, new File(PROPATH + File.separator + "apk-signed.apk"));

脫殼運行

所謂的脫殼其實就是將在apk安裝運行的時候先運行殼文件中的dex, 然後在殼文件中的Application裏面做解密處理,解密完了之後將解密後的原dex文件用BaseDexClassLoader加載到內存中;加載原dex文件的原理是仿照的tinker 框架來做的,這裏只適配的android 19版本的加載方法,其他的可參照tinker 的方法或者源碼進行適配, 主要原理其實就是通過反射BaseDexClassLoader中的DexPathList變量實現的, 具體流程如下:
在這裏插入圖片描述

從圖中可以看到它最終是將要加載dex文件設置到了pathList對象裏面的dexElements數組變量裏面,這個dexElements就是虛擬機加載dex文件的

解壓原apk, 解密原dex文件

  AES.init(getPassword());
        File apkFile = new File(getApplicationInfo().sourceDir);
        //data/data/包名/files/fake_apk/
        File unZipFile = getDir("fake_apk", MODE_PRIVATE);
        File app = new File(unZipFile, "app");
        if (!app.exists()) {
            Zip.unZip(apkFile, app);
            File[] files = app.listFiles();
            for (File file : files) {
                String name = file.getName();
                if (name.equals("classes.dex")) {

                } else if (name.endsWith(".dex")) {
                    try {
                            byte[] bytes = getBytes(file);
                            FileOutputStream fos = new FileOutputStream(file);
                            byte[] decrypt = AES.decrypt(bytes);
//                        fos.write(bytes);
                            fos.write(decrypt);
                            fos.flush();
                            fos.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                    }
                }
            }
        }
        List list = new ArrayList<>();
        Log.d("FAKE", Arrays.toString(app.listFiles()));
        for (File file : app.listFiles()) {
            if (file.getName().endsWith(".dex")) {
                list.add(file);
            }
        }

加載原dex文件

 private static final class V19 {
        private V19() {
        }

        private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
                                    File optimizedDirectory) throws IllegalArgumentException,
                IllegalAccessException, NoSuchFieldException, InvocationTargetException,
                NoSuchMethodException {

            Field pathListField = findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ArrayList suppressedExceptions = new ArrayList();
            Log.d(TAG, "Build.VERSION.SDK_INT " + Build.VERSION.SDK_INT);
            if (Build.VERSION.SDK_INT >= 23) {
                expandFieldArray(dexPathList, "dexElements", makePathElements(dexPathList, new
                                ArrayList(additionalClassPathEntries), optimizedDirectory,
                        suppressedExceptions));
            } else {
                expandFieldArray(dexPathList, "dexElements", makeDexElements(dexPathList, new
                                ArrayList(additionalClassPathEntries), optimizedDirectory,
                        suppressedExceptions));
            }

            if (suppressedExceptions.size() > 0) {
                Iterator suppressedExceptionsField = suppressedExceptions.iterator();

                while (suppressedExceptionsField.hasNext()) {
                    IOException dexElementsSuppressedExceptions = (IOException)
                            suppressedExceptionsField.next();
                    Log.w("MultiDex", "Exception in makeDexElement",
                            dexElementsSuppressedExceptions);
                }

                Field suppressedExceptionsField1 = findField(loader,
                        "dexElementsSuppressedExceptions");
                IOException[] dexElementsSuppressedExceptions1 = (IOException[]) ((IOException[])
                        suppressedExceptionsField1.get(loader));
                if (dexElementsSuppressedExceptions1 == null) {
                    dexElementsSuppressedExceptions1 = (IOException[]) suppressedExceptions
                            .toArray(new IOException[suppressedExceptions.size()]);
                } else {
                    IOException[] combined = new IOException[suppressedExceptions.size() +
                            dexElementsSuppressedExceptions1.length];
                    suppressedExceptions.toArray(combined);
                    System.arraycopy(dexElementsSuppressedExceptions1, 0, combined,
                            suppressedExceptions.size(), dexElementsSuppressedExceptions1.length);
                    dexElementsSuppressedExceptions1 = combined;
                }

                suppressedExceptionsField1.set(loader, dexElementsSuppressedExceptions1);
            }

        }

        private static Object[] makeDexElements(Object dexPathList,
                                                ArrayList<File> files, File
                                                        optimizedDirectory,
                                                ArrayList<IOException> suppressedExceptions) throws
                IllegalAccessException, InvocationTargetException, NoSuchMethodException {

                Method makeDexElements = findMethod(dexPathList, "makeDexElements", new
                        Class[]{ArrayList.class, File.class, ArrayList.class});
                return ((Object[]) makeDexElements.invoke(dexPathList, new Object[]{files,
                        optimizedDirectory, suppressedExceptions}));
           }
    }

demo下載

demo下載地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章