Android Apk加固

一、加固原理

加固原理相对简单,首先对apk进行解压获取到原dex, 接着对原dex 进行加密,制作并生成壳dex(加载时用来解密原dex), 并从新打包成apk, 运行时利用壳dex对加密的dex进行解密并加载到内存中。 是不是很简单? 当然,这只是大概的原理,下面我们将详细叙述。

1.1 加密

加密的方式有很多种,如RSA,AES等,加固中常用的加密算法是AES,由于加密算法不是本文的重点,读者可自行去了解相关算法的区别。 这里我使用gradle插件的方式在编译的时候自动解压加密并重新打包,避免了手动加密的繁琐。 解压加密的核心代码处理如下:

 // 解压 apk 文件 , 获取所有的 dex 文件

    // 被解压的 apk 文件
    var apkFile = File(apk)
    // 解压的目标文件夹
    var apkUnZipFile = File("app/build/outputs/apk/release/unZipFile")

    // 解压文件
    var rawPathList=unZip(apkFile, apkUnZipFile)
//    println(Arrays.asList(rawPathList))

    // 从被解压的 apk 文件中找到所有的 dex 文件, 小项目只有 1 个, 大项目可能有多个
    // 使用文件过滤器获取后缀是 .dex 的文件
    var dexFiles : Array<File> = apkUnZipFile.listFiles({ file: File, s: String ->
        s.endsWith(".dex")
    })

    // 加密找到的 dex 文件
    var aes = AES(AES.DEFAULT_PWD)
    // 遍历 dex 文件
    for(dexFile: File in dexFiles){
        // 读取文件数据
        var bytes = getBytes(dexFile)
        // 加密文件数据
        var encryptedBytes = aes.encrypt(bytes)

        // 将加密后的数据写出到指定目录
        var outputFile = File(apkUnZipFile, "secret-${dexFile.name}")
        // 创建对应输出流
        var fileOutputStream = FileOutputStream(outputFile)

        // 将加密后的 dex 文件写出, 然后刷写 , 关闭该输出流
        fileOutputStream.write(encryptedBytes)
        fileOutputStream.flush()
        fileOutputStream.close()

        // 删除原来的文件
        dexFile.delete()
    }

1.2 制作壳dex

为了在点击桌面icon首先执行我们的壳Application, 首先要在打包过程中,将原Application 替换成 壳程序的Application, 同时使用Meta-Data 记录原Application的全路径名,最终的实现如下:

   <!-- 写入权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:name="com.test.multipledex.ProxyApplication"
        android:theme="@style/Theme.AppEncrypton">

        <!-- app_name 值是该应用的 Application 的真实全类名
    真实 Application : kim.hsl.dex.MyApplication
    代理 Application : kim.hsl.multipledex.ProxyApplication -->
        <meta-data android:name="app_name" android:value="com.test.appencrypton.MyApplication"/>
        <!-- DEX 解密之后的目录名称版本号 , 完整目录名称为 :
                kim.hsl.dex.MyApplication_1.0 -->
        <meta-data android:name="app_version" android:value="1.0"/>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

1.3 解密

首先获取到安装APK 文件的加密dex,然后将加密后的dex 文件进行解密 并加载到内存中。


            /*
                I . 解密与加载多 DEX 文件
                    先进行解密, 然后再加载解密之后的 DEX 文件

                    1. 先获取当前的 APK 文件
                    2. 然后解压该 APK 文件
             */

                // 获取当前的 APK 文件, 下面的 getApplicationInfo().sourceDir 就是本应用 APK 安装文件的全路径
                File apkFile = new File(getApplicationInfo().sourceDir);

                // 获取在 app Module 下的 AndroidManifest.xml 中配置的元数据,
                // 应用真实的 Application 全类名
                // 解密后的 dex 文件存放目录
                ApplicationInfo applicationInfo = null;
                packageName=getPackageName();
                applicationInfo = getPackageManager().getApplicationInfo(
                        packageName,
                        PackageManager.GET_META_DATA
                );

                Bundle metaData = applicationInfo.metaData;
                if (metaData != null) {
                    // 检查是否存在 app_name 元数据
                    if (metaData.containsKey("app_name")) {
                        app_name = metaData.getString("app_name").toString();
                    }
                    // 检查是否存在 app_version 元数据
                    if (metaData.containsKey("app_version")) {
                        app_version = metaData.getString("app_version").toString();
                    }
                }

                // 创建用户的私有目录 , 将 apk 文件解压到该目录中
                File privateDir = getDir(app_name + "_" + app_version, MODE_PRIVATE);

                Log.i(TAG, "attachBaseContext 创建用户的私有目录 : " + privateDir.getAbsolutePath());

                // 在上述目录下创建 app 目录
                // 创建该目录的目的是存放解压后的 apk 文件的
                File appDir = new File(privateDir, "app");

                // app 中存放的是解压后的所有的 apk 文件
                // app 下创建 dexDir 目录 , 将所有的 dex 目录移动到该 dexDir 目录中
                // dexDir 目录存放应用的所有 dex 文件
                // 这些 dex 文件都需要进行解密
                File dexDir = new File(appDir, "dexDir");

                // 遍历解压后的 apk 文件 , 将需要加载的 dex 放入如下集合中
                ArrayList<File> dexFiles = new ArrayList<File>();
                // 如果该 dexDir 不存在 , 或者该目录为空 , 并进行 MD5 文件校验
                if (!dexDir.exists() || dexDir.list().length == 0) {
                    // 将 apk 中的文件解压到了 appDir 目录
                    ZipUtils.unZipApk(apkFile, appDir);
                    if (!dexDir.exists()){
                        dexDir.mkdir();
                    }

                    // 获取 appDir 目录下的所有文件
                    File[] files = appDir.listFiles();

//                Log.i(TAG, "attachBaseContext appDir 目录路径 : " + appDir.getAbsolutePath());
//                Log.i(TAG, "attachBaseContext appDir 目录内容 : " + files);

                    // 遍历文件名称集合
                    for (int i = 0; i < files.length; i++) {
                        File file = files[i];

//                    Log.i(TAG, "attachBaseContext 遍历 " + i + " . " + file);

                        // 如果文件后缀是 .dex , 并且不是 主 dex 文件 classes.dex
                        // 符合上述两个条件的 dex 文件放入到 dexDir 中
                        if (file.getName().endsWith(".dex") &&
                                !TextUtils.equals(file.getName(), "classes.dex")) {
                            // 筛选出来的 dex 文件都是需要解密的
                            // 解密需要使用 OpenSSL 进行解密

                            // 获取该文件的二进制 Byte 数据
                            // 这些 Byte 数组就是加密后的 dex 数据
                            byte[] bytes = OpenSSL.getBytes(file);

                            // 解密该二进制数据, 并替换原来的加密 dex, 直接覆盖原来的文件即可
                            File temp=new File(dexDir,file.getName());
                            Log.i(TAG, "temp: " + temp.getAbsolutePath());
                            OpenSSL.decrypt(bytes, temp);

                            // 将解密完毕的 dex 文件放在需要加载的 dex 集合中
                            dexFiles.add(temp);

                            // 拷贝到 dexDir 中

                            Log.i(TAG, "attachBaseContext 解密完成 被解密文件是 : " + temp);

                        }// 判定是否是需要解密的 dex 文件
                    }// 遍历 apk 解压后的文件

                } else {
                    Log.i(TAG,  "再次启动");
                    // 已经解密完成, 此时不需要解密, 直接获取 dexDir 中的文件即可
                    for (File file : dexDir.listFiles()) {
                        if (file.getName().endsWith(".dex")){
                            dexFiles.add(file);
                        }
                    }
                }

                Log.i(TAG, "attachBaseContext 解密完成 dexFiles : " + dexFiles);

                for (int i = 0; i < dexFiles.size(); i++) {
                    Log.i(TAG, i + " . " + dexFiles.get(i).getAbsolutePath());
                }

                // 截止到此处 , 已经拿到了解密完毕 , 需要加载的 dex 文件
                // 加载自己解密的 dex 文件
                loadDex(dexFiles, privateDir);

                Log.i(TAG, "attachBaseContext 完成");
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容