参考文章
Android APK 加壳技术方案【1】
Android APK 加壳技术方案【2】
Android 中的 Apk 的加固 (加壳) 原理解析和实现
原理部分我不献丑了,上面 3 篇文章说的很清楚,我直接实战,讲述从 0 开始如何最终实现加固的整个过程,踩了不少坑。
第一步创建被加固 Apk,就是你的源码 Apk。你做的工作就是防止这个 Apk 被破解。这个 APK 要注意以下几点:
从途中可知我们的主 Activity 为doctorq.com.mysourceapk.MainActivity
还有一个 Activity 名为doctorq.com.mysourceapk..SubActivity
从图中可知我们的 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());
继承于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 文件,这个文件我们第三步需要用。
第二步解壳程序,也就是源程序的宿主,他也是一个 APK,但是这个 APK 需要注意的地方更多,我踩了很多坑:
这个文件的修改位置很多,修改后如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="xposed.doctorq.com.decode2" >
<application
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">
<meta-data android:name="APPLICATION_CLASS_NAME" android:value="doctorq.com.mysourceapk.MyApplication"/>
<activity
android:name="doctorq.com.mysourceapk.MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="doctorq.com.mysourceapk.SubActivity" >
</activity>
</application>
</manifest>
主要修改位置有以下几点:
这个APPLICATION_CLASS_NAME
指向的是我们源码中的Application
的子类,这也是之前特别提醒的原因
<meta-data 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" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
我们在源码程序的中 activity 都要在解壳程序的配置文件中配置:
<activity android:name="doctorq.com.mysourceapk.SubActivity" >
</activity>
主要修改就是主 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
名啊,是识别不了的,这个时候可以通过gradle
的assemble
来打包。
这个时候得到我们解壳程序的 APK 和 dex 文件,这两个文件我们一会都要用。
第三步加固工具,这个工具是一个 java 项目,我们在 eclipse 中创建:具体的原理是将源码 APK 加到解壳程序的 dex 文件后面。这个没什么坑,没啥可讲的,加固成功后,会得到一个产物,我们将这个产物命名为 classes.dex,因为一会要替换到解壳 APK 中的 classes.dex 文件。
第四步是替换解壳程序中的 classes.dex,这个时候用到 WinRAR 工具,首先找到解壳程序,然后删除借壳程序中的 classes.dex,添加第三部产生的 classes.dex 文件
第五步是重签名,因为APK
被修改了,这个时候直接安装,会报无签名的错误,所以这个时候我们用Auto-sign
这个工具去签名,具体做法我在Android 安全专项测试之反编译中讲过。
安装完毕后,可以打开我们的解壳程序了,这个时候一定要看清楚我们进的是解壳程序的 app,而不是我们之前的源码 app:
这个demo
中我们的源码中没有布局文件,这在实际项目中是不可能,那么这些布局文件怎么添加,有人提出将所有布局文件添加到解壳程序中,这样就能找到了。
我们做了这么多,真的加固了么?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
中,这又怎么办呢?我暂时也不知道,思考一下吧