AES加密加固apk

在apk安全上,最基本的是通過混淆來對apk進行保護,但這只是加大了對源碼的閱讀難度,並不能真正的保護你的源碼,反編譯是可以輕鬆拿到apk的源碼的,我們可以通過將非核心的dex文件暴露來達到保護核心dex文件的目的;
加固的整體思想如下圖
在這裏插入圖片描述
準備工作
處理存放apk的文件夾

/**
		 * 準備工作
		 */
       //存儲源核心apk中的解壓後的文件
		File tempFileApk = new File("app/source/apk/temp");
		if (tempFileApk.exists()) {
			File[]files = tempFileApk.listFiles();
			for(File file: files){
				if (file.isFile()) {
					file.delete();//先清空文件夾
				}
			}
		}

		//存儲殼arr中的解壓後的文件
		File tempFileAar = new File("app/source/aar/temp");
		if (tempFileAar.exists()) {
			File[]files = tempFileAar.listFiles();
			for(File file: files){
				if (file.isFile()) {
					file.delete();//先清空文件夾
				}
			}
		}

第一步 處理原始apk 加密核心dex

/**
		 * 第一步 處理原始apk 加密dex
		 *
		 */
		AES.init(AES.DEFAULT_PWD);
		//這樣一個最簡單的AES加解密就完成了,
		// 但有一個缺點,密碼的長度必須爲128位,也就是16個字節,否則會報錯; 
		//解壓apk
		File apkFile = new File("app/source/apk/app-debug.apk");
		File newApkFile = new File(apkFile.getParent() + File.separator + "temp");
		if(!newApkFile.exists()) {
			newApkFile.mkdirs();
		}
		File mainDexFile = AES.encryptAPKFile(apkFile,newApkFile);
		if (newApkFile.isDirectory()) {
			File[] listFiles = newApkFile.listFiles();
			for (File file : listFiles) {
				if (file.isFile()) {
					if (file.getName().endsWith(".dex")) {
						String name = file.getName();
						System.out.println(" name :"+name);
						int cursor = name.indexOf(".dex");
						String newName = file.getParent()+ File.separator + name.substring(0, cursor) + "_" + ".dex";
						System.out.println("newName:"+newName);
						file.renameTo(new File(newName));
					}
				}
			}
		}

第二步 處理aar 獲得殼dex

/**
		 * 第二步 處理aar 獲得殼dex
		 */
		File aarFile = new File("app/source/aar/protectapp-debug.aar");
		File aarDex  = Dx.jar2Dex(aarFile);
//        aarData = Utils.getBytes(aarDex);   //將dex文件讀到byte 數組


		File tempMainDex = new File(newApkFile.getPath() + File.separator + "classes.dex");
		if (!tempMainDex.exists()) {
			tempMainDex.createNewFile();
		}
        System.out.println("MyMain" + tempMainDex.getAbsolutePath());
		FileOutputStream fos = new FileOutputStream(tempMainDex);
		byte[] fbytes = Utils.getBytes(aarDex);
		fos.write(fbytes);
		fos.flush();
		fos.close();

第3步 打包簽名

/**
		 * 第3步 打包簽名
		 */
		File unsignedApk = new File("app/result/apk-unsigned.apk");
		unsignedApk.getParentFile().mkdirs();
//        File disFile = new File(apkFile.getAbsolutePath() + File.separator+ "temp");
		Zip.zip(newApkFile, unsignedApk);
		//不用插件就不能自動使用原apk的簽名...
		File signedApk = new File("app/result/apk-signed.apk");
		Signature.signature(unsignedApk, signedApk);

上面的就實現了apk對核心dex的加密後重新打包簽名了,加密後的apk組成結構如下
在這裏插入圖片描述其中classes_.dex就是核心dex,無法通過反編譯工具直接反編譯查看,當然殼classes.dex是可以反編譯查看的,接下來介紹一下,代碼中使用到的Runtime 調用cmd命令進行轉dex和簽名的使用方法
(1)使用dx命令轉dex
注意使用sdk自帶的工具dx.bat 目前存在的目錄已經更改,添加path環境變量 E:\Users\Sdk\build-tools\28.0.3,使用JAVA代碼調用cmd命令如下

 Runtime runtime = Runtime.getRuntime();
        Process process = runtime.exec("cmd.exe /C dx --dex --output=" + aarDex.getAbsolutePath() + " " +
                classes_jar.getAbsolutePath());
        System.out.println("dxCommand 1 " );
        try {
            process.waitFor();
            System.out.println("dxCommand 2  " );
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("dxCommand 3  " );
            throw e;
        }
        System.out.println("process.exitValue() = " +process.exitValue());
            //檢測程序是否執行成功,爲成功
        if (process.exitValue() != 0) {
            InputStream inputStream = process.getErrorStream();
            int len;
            byte[] buffer = new byte[2048];
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            while((len=inputStream.read(buffer)) != -1){
                bos.write(buffer,0,len);
            }
            System.out.println(new String(bos.toByteArray(),"GBK"));
            throw new RuntimeException("dx run failed");
        }
        process.destroy();
        System.out.println("process: process.destroy() " );

(2)使用java代碼調用window下的cmd命令,代碼如下

String cmd[] = {"cmd.exe", "/C ","jarsigner",  "-sigalg", "MD5withRSA",
                "-digestalg", "SHA1",
                "-keystore", "D:\\Documents\\ReinforceApk\\app\\shunplus.jks",
                "-storepass", "123456",
                "-keypass", "123456",
                "-signedjar", signedApk.getAbsolutePath(),
                unsignedApk.getAbsolutePath(),
                "shun"};
        Process process = Runtime.getRuntime().exec(cmd);
        System.out.println("start sign");
//        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
//        String line;
//        while ((line = reader.readLine()) != null)
//            System.out.println("tasklist: " + line);
        try {
            int waitResult = process.waitFor();
            System.out.println("waitResult: " + waitResult);
        } catch (InterruptedException e) {
            e.printStackTrace();
            throw e;
        }
        System.out.println("process.exitValue() " + process.exitValue() );
        //檢測程序是否執行成功
        if (process.exitValue() != 0) {
            InputStream inputStream = process.getErrorStream();
            int len;
            byte[] buffer = new byte[2048];
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            while((len=inputStream.read(buffer)) != -1){
                bos.write(buffer,0,len);
            }
            System.out.println(new String(bos.toByteArray(),"GBK"));
            throw new RuntimeException("簽名執行失敗");
        }
        System.out.println("finish signed");
        process.destroy();

下面奉上Github項目地址處理項目加密代碼
項目代碼地址核心apk及解密代碼

通過上面的代碼加密後還需要一個解密的過程
在殼的ShellApplication的attachBaseContext方法中,此方法運行之前核心dex還處於加密狀態,需要解密後,通過ClassLoader將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);
            }
        }

        Log.d("FAKE", list.toString());
        try {
            V19.install(getClassLoader(), list, unZipFile);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

加載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}));
        }
    }

    /**
     * A wrapper around
     * {@code private static final dalvik.system.DexPathList#makePathElements}.
     */
    private static Object[] makePathElements(
            Object dexPathList, ArrayList<File> files, File optimizedDirectory,
            ArrayList<IOException> suppressedExceptions)
            throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        Method makePathElements;
        try {
            makePathElements = findMethod(dexPathList, "makePathElements", List.class, File.class,
                    List.class);
        } catch (NoSuchMethodException e) {
            Log.e(TAG, "NoSuchMethodException: makePathElements(List,File,List) failure");
            try {
                makePathElements = findMethod(dexPathList, "makePathElements", ArrayList.class, File.class, ArrayList.class);
            } catch (NoSuchMethodException e1) {
                Log.e(TAG, "NoSuchMethodException: makeDexElements(ArrayList,File,ArrayList) failure");
                try {
                    Log.e(TAG, "NoSuchMethodException: try use v19 instead");
                    return V19.makeDexElements(dexPathList, files, optimizedDirectory, suppressedExceptions);
                } catch (NoSuchMethodException e2) {
                    Log.e(TAG, "NoSuchMethodException: makeDexElements(List,File,List) failure");
                    throw e2;
                }
            }
        }
        return (Object[]) makePathElements.invoke(dexPathList, files, optimizedDirectory, suppressedExceptions);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章