Android 安全专项 - Apk 的加固

5,072 阅读5分钟
原文链接: testerhome.com

参考文章

Android APK加壳技术方案【1】
Android APK加壳技术方案【2】
Android中的Apk的加固(加壳)原理解析和实现

0x00

原理部分我不献丑了,上面3篇文章说的很清楚,我直接实战,讲述从0开始如何最终实现加固的整个过程,踩了不少坑。

0x01

第一步创建被加固Apk,就是你的源码Apk。你做的工作就是防止这个Apk被破解。这个APK要注意以下几点:

记住你的主Actvitiy名和其他Activity名

这里写图片描述

从途中可知我们的主Activity为doctorq.com.mysourceapk.MainActivity

还有一个Activity名为doctorq.com.mysourceapk..SubActivity

记住你创建的Application名

从图中可知我们的Application为doctorq.com.mysourceapk.MyApplication

尽量不要添加布局文件

采用的方式和参考文章的做法是一样的,显式添加控件,如下:

        TextView content = new TextView(this);
        content.setText("I am Source Apk");
        content.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                Intent intent = new Intent(MainActivity.this, SubActivity.class);
                startActivity(intent);
            }
        });
        setContentView(content);

        Log.i("demo", "app:" + getApplicationContext());

activity的父类都为Activity

这里写图片描述

继承于AppCompatActivity在解壳程序中运行的时候报如下的错误,自身运行没啥问题:


03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime: FATAL EXCEPTION: main
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime: Process: xposed.doctorq.com.decode2, PID: 16721
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime: java.lang.NoClassDefFoundError: doctorq/com/mysourceapk/SubActivity
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at doctorq.com.mysourceapk.MainActivity$1.onClick(MainActivity.java:21)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at android.view.View.performClick(View.java:4444)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at android.view.View$PerformClick.run(View.java:18440)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at android.os.Handler.handleCallback(Handler.java:733)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:95)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:136)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:5001)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at java.lang.reflect.Method.invokeNative(Native Method)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:515)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:806)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:622)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at dalvik.system.NativeStart.main(Native Method)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:  Caused by: java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at dalvik.system.DexFile.defineClassNative(Native Method)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at dalvik.system.DexFile.defineClass(DexFile.java:222)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at dalvik.system.DexFile.loadClassBinaryName(DexFile.java:215)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at dalvik.system.DexPathList.findClass(DexPathList.java:322)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:54)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at java.lang.ClassLoader.loadClass(ClassLoader.java:497)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at java.lang.ClassLoader.loadClass(ClassLoader.java:457)
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at doctorq.com.mysourceapk.MainActivity$1.onClick(MainActivity.java:21) 
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at android.view.View.performClick(View.java:4444) 
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at android.view.View$PerformClick.run(View.java:18440) 
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at android.os.Handler.handleCallback(Handler.java:733) 
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at android.os.Handler.dispatchMessage(Handler.java:95) 
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at android.os.Looper.loop(Looper.java:136) 
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at android.app.ActivityThread.main(ActivityThread.java:5001) 
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at java.lang.reflect.Method.invokeNative(Native Method) 
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at java.lang.reflect.Method.invoke(Method.java:515) 
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:806) 
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:622) 
03-23 13:50:12.258 16721-16721/xposed.doctorq.com.decode2 E/AndroidRuntime:     at dalvik.system.NativeStart.main(Native Method) 

效果

这里写图片描述

这个demo就是主界面上有一个TextView,可以点击跳转到下一个Activity。

这个时候我们能APK文件,这个文件我们第三步需要用。

0x02

第二步解壳程序,也就是源程序的宿主,他也是一个APK,但是这个APK需要注意的地方更多,我踩了很多坑:

修改AndroidManifest.xml

这个文件的修改位置很多,修改后如下所示:


 xmlns:android="http://schemas.android.com/apk/res/android"
    package="xposed.doctorq.com.decode2" >

    
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:name="xposed.doctorq.com.decode2.ProxyApplication">
         android:name="APPLICATION_CLASS_NAME" android:value="doctorq.com.mysourceapk.MyApplication"/>
        
            android:name="doctorq.com.mysourceapk.MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar" >
            
                 android:name="android.intent.action.MAIN" />

                 android:name="android.intent.category.LAUNCHER" />
            
        
         android:name="doctorq.com.mysourceapk.SubActivity" >
        
    

主要修改位置有以下几点:

这个APPLICATION_CLASS_NAME指向的是我们源码中的Application的子类,这也是之前特别提醒的原因

 android:name="APPLICATION_CLASS_NAME" android:value="doctorq.com.mysourceapk.MyApplication"/>

修改主Activity

我们要讲解壳程序本身的Activity替换成源码的主Activity,这个时候解壳程序里的activity其实是没有用的,删掉也不影响。

        
            android:name="doctorq.com.mysourceapk.MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar" >
            
                 android:name="android.intent.action.MAIN" />

                 android:name="android.intent.category.LAUNCHER" />
            
        

添加其他Activity

我们在源码程序的中activity都要在解壳程序的配置文件中配置:

  android:name="doctorq.com.mysourceapk.SubActivity" >

在ProxyApplication的修改

主要修改就是主Activity的修改:

           try {
                Object actObj = dLoader.loadClass("doctorq.com.mysourceapk.MainActivity");
                Log.i("demo", "actObj:" + actObj);
            } catch (Exception e) {
                Log.i("demo", "activity:" + Log.getStackTraceString(e));
            }

打包

因为你修改的包名啊,activity名啊,是识别不了的,这个时候可以通过gradleassemble来打包。

这个时候得到我们解壳程序的APK和dex文件,这两个文件我们一会都要用。

这里写图片描述

0x03

第三步加固工具,这个工具是一个java项目,我们在eclipse中创建:具体的原理是将源码APK加到解壳程序的dex文件后面。这个没什么坑,没啥可讲的,加固成功后,会得到一个产物,我们将这个产物命名为classes.dex,因为一会要替换到解壳APK中的classes.dex文件。

这里写图片描述

0x04

第四步是替换解壳程序中的classes.dex,这个时候用到WinRAR工具,首先找到解壳程序,然后删除借壳程序中的classes.dex,添加第三部产生的classes.dex文件

这里写图片描述

0x05

第五步是重签名,因为APK被修改了,这个时候直接安装,会报无签名的错误,所以这个时候我们用Auto-sign这个工具去签名,具体做法我在Android安全专项测试之反编译中讲过。

这里写图片描述

安装完毕后,可以打开我们的解壳程序了,这个时候一定要看清楚我们进的是解壳程序的app,而不是我们之前的源码app:

这里写图片描述

0x06

源码下载地址

0x07

这个demo中我们的源码中没有布局文件,这在实际项目中是不可能,那么这些布局文件怎么添加,有人提出将所有布局文件添加到解壳程序中,这样就能找到了。

0x08

我们做了这么多,真的加固了么?ok,我们来实验下,我们用apktool来反编译下:


58deMacBook-Pro-7:Auto-sign wuxian$ apktool d testerhome.apk
I: Using Apktool 2.0.3 on testerhome.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /Users/wuxian/Library/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...

实际上是成功的,有的人就会疯了,你不是说加固了,怎么还能被反编译了,我们来看看反编译后的产物:

这里写图片描述

你会发现,我们看不到源码demo项目中的代码,连包名都没发现,你能看到的只是解壳程序的东西,我们加固的目的是起到了,但是解壳程序能被反编译也是有风险的,毕竟我们的一些核心代码写在了ProxyApplication中,这又怎么办呢?我暂时也不知道,思考一下吧