美文网首页
App加固(dex加密)

App加固(dex加密)

作者: leap_ | 来源:发表于2020-06-15 22:56 被阅读0次

    什么是Dex文件?

    classes.dex是apk组成的一部分,包含了能被Dalvik/Art理解的可执行文件,类似Windows的exe文件;

    APK组成:

    • 1. assets目录:存放assets目录下的文件,可以通过AssetManager对象获取

    • 2. lib目录:存放所支持的CPU架构对应的二进制文件(so文件),这些文件用来各自支持自己CPU架构的二进制接口(ABI)

    • 3. res目录:存放res目录下没有被编译到arsc文件的资源,layout,drawable,mipmap等

    • 4. META-INF目录:存放签名的目录,

    • 5. classes.dex文件:dvm的可执行文件,将R.java,java source Coed,java interface打包成dex文件

    • 6. resources.arsc文件(资源映射表):res/values目录下的所有配置内容,以及在APK res目录下文件文件的映射方式

    • 7. Manifest文件:配置文件,同项目中的manifest文件

    Dex文件内容:

    • 文件头:记录了dex文件的一些基本信息, dex文件大小,dex文件头大小,sha1签名,checksum校验和,以及大致的数据分布

    • 索引区:存放数据的偏移量

    • 数据区:真实的数据存放在data数据区

    APK打包流程:

    1. aapt将资源文件打包成R.Java, resource.arsc ,res目录
    2. aidl 将aidl 接口解析成对应的java接口
    3. Javac 将源代码编译成.class字节码
    4. dx.bat将class字节码转化成dvm字节码(dex文件)
    5. 打包生成APK
    6. JarSignerapk进行debug或release签名
    7. zipalign对其操作,APK包中所有的资源文件距离文件起始偏移为4字节整数倍,这样通过内存映射访问apk文件时的速度会更快

    上述的操作都是通过Android SDK自带的工具来完成;

    Dex加密
    下面通过一个简单的demo描述APK加固的整体流程

    加密流程:
    1. 首先将未加密的APK进行解压,获取到Dex文件,然后对dex文件的每一个字节进行加密(AES),加密完成生成新的dex文件(classes2.dex)
    2. 下面创建一个dex壳,通过对arr文件(android module 的打包文件)的解压可以获取到一个classes.jar文件,再通过cmd的命令,将jar转成壳dex
    3. 将壳dex 和 加密的dex(源dex)一起打包成新的APK,然后再对APK进行签名;签名后可以正常安装,

    1. 解压APK,对Dex文件加密

    • 解压apk
            File apkFile = new File("source/apk/app-debug.apk");
    
            // 解压apk文件到unzip目录
            File apkUnZipFile = new File("source/apk/unzip");
            Zip.unZip(apkFile,apkUnZipFile);
    
    • 将dex文件转为内存中的字节数组
            // 创建dexFile,将dexFile写入内存—dexBytes
            File dexFile = new File("source/apk/unzip/classes.dex");
            RandomAccessFile inputStream = new RandomAccessFile(dexFile,"r");
            byte[] dexBytes = new byte[(int) inputStream.length()];
            inputStream.readFully(dexBytes);
            inputStream.close();
    
    • AES加密初始化
            //  AES加密操作初始化
            Cipher encoder = Cipher.getInstance("AES/ECB/PKCS5Padding");
            Cipher decoder = Cipher.getInstance("AES/ECB/PKCS5Padding");
            String key = "abcdefghijklmnop";
            byte[] keyBytes = key.getBytes();
            SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes,"AES");
            encoder.init(Cipher.ENCRYPT_MODE,secretKeySpec);
            decoder.init(Cipher.DECRYPT_MODE,secretKeySpec);
    
    • 字节数组加密
            //  对dex字节数组加密
            byte[] dexBytesEncrypted = encoder.doFinal(dexBytes);
    
    • 加密后的字节数组转为dex文件
            //  将加密后的dex字节数组写入原来的文件,
            FileOutputStream fos = new FileOutputStream(dexFile);
            fos.write(dexBytesEncrypted);
            fos.close();
    
            //  将加密后的dex文件改名为classes1.dex(源)
            dexFile.renameTo(new File("source/apk/unzip/classes1.dex"));
    

    2. 解压aar,获取壳

    • 解压aar文件,获取classes.jar
            // 将arr文件解压
            File arrFile = new File("source/aar/mylibrary-debug.aar");
            Zip.unZip(arrFile,new File("source/aar/unzip"));
    
    arr解压
    • 通过cmd调用dx将jar转为dex
            // 通过cmd 调用 dx 将jar转为dex(壳)
            File jarFile = new File("source/aar/unzip/classes.jar");
            File desDexFile = new File("source/apk/unzip/classes.dex");
            Runtime runtime = Runtime.getRuntime();
            Process process = runtime.exec("cmd.exe /C dx --dex --output="
                    + desDexFile.getAbsolutePath()
                    + " "
                    + jarFile.getAbsolutePath());
            process.waitFor(); // 等待子进程完成
            process.destroy();
    

    通过RunTime启动cmd命令,jvm会创建一个子进程Process,waitFor()表示当前进程阻塞直到子进程完成;

    3. 打包新APK,通过cmd调用jarsigner重新签名

    • 打包成新的apk文件
            // 壳 和 源 打包成新apk
            File unsignedApk = new File("source/result/unsigned.apk");
            Zip.zip(apkUnZipFile,unsignedApk);
    
    • 签名
            // 签名
            File signedApk = new File("source/result/signed.apk");
            Signature.signature(unsignedApk,signedApk);
    
    public class Signature {
        public static void signature(File unsignedApk, File signedApk) throws InterruptedException, IOException {
            String cmd[] = {"cmd.exe", "/C ","jarsigner",  "-sigalg", "MD5withRSA",
                    "-digestalg", "SHA1",
                    "-keystore", "C:/Users/allen/.android/debug.keystore",
                    "-storepass", "android",
                    "-keypass", "android",
                    "-signedjar", signedApk.getAbsolutePath(),
                    unsignedApk.getAbsolutePath(),
                    "androiddebugkey"};
            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();
        }
    }
    
    APK加密过程

    壳中是未加密的module代码,可以直接运行,并且负责源dex的解密工作

    Dex解密(脱壳)

    脱壳实现:

    脱壳解密过程一般是在壳Module的Application中进行,参考Tinker的脱壳实现:首先将apk进行解压获取到加密的classes1.dex文件,然后通过流转成Byte数组,再进行AES解密,解密后重新写回到原来的classes1.dex;至此,解密过程完成,下面需要将解密后的dex文件运行起来,

    在Application中重写attachBaseContext()
        protected void attachBaseContext(Context base) {
            if (mBase != null) {
                throw new IllegalStateException("Base context already set");
            }
            mBase = base;
        }
    

    所有的脱壳,寻找dex中的class,classloader进行类加载,都是在attachBaseContext()中完成的(即App运行启动时)

    根据指定目录获取apk,解压,寻找源dex,解密
    
            File apkFile = new File(getApplicationInfo().sourceDir);
            //data/data/包名/files/fake_apk/
            File unZipFile = getDir("fake_apk", MODE_PRIVATE);
            File app = new File(unZipFile, "app");  // 根据指定的目录找到apk文件
            if (!app.exists()) {
                Zip.unZip(apkFile, app);    // 解压apk
                File[] files = app.listFiles();
                for (File file : files) {
                    String name = file.getName();
                    if (name.equals("classes.dex")) {   //  过滤壳dex
    
                    } else if (name.endsWith(".dex")) {   //  选择源dex 解密
                        try {
                                byte[] bytes = getBytes(file);
                                FileOutputStream fos = new FileOutputStream(file);
                                byte[] decrypt = AES.decrypt(bytes);
    //                        fos.write(bytes);
                                fos.write(decrypt);  //  将解密后的字节数组写回源文件(源dex文件)
                                fos.flush();
                                fos.close();
                            } catch (Exception e) {
                                e.printStackTrace();
                        }
                    }
                }
            }
    
    将所有的dex文件从apk取出,进行类加载
            List list = new ArrayList<>();  // 解密后的dex文件
            Log.d("FAKE", Arrays.toString(app.listFiles()));
            for (File file : app.listFiles()) {
                if (file.getName().endsWith(".dex")) {
                    list.add(file);
                    System.gc();
                }
            }
    

    对源dex中加密的类进行类加载

    ClassLoader原理
    先看一下上文类加载的原理,在介绍加固脱壳的类加载思路:

    1. 通过反射获取classLoaderpathList(DexPathList类)
    2. 再获取pathListDexElements(element[])
    3. 传入源dex,通过反射调用DexPathList类makeDexElements创建新的Element[],
    4. 合并两个数组
    5. 反射将新的element[] setclassLoader

    相关文章

      网友评论

          本文标题:App加固(dex加密)

          本文链接:https://www.haomeiwen.com/subject/xmuxtktx.html