移动安全测试 Android 安全专项-Apk 的加固

易寒 · 2016年03月23日 · 最后由 小小南瓜 回复于 2017年02月13日 · 7062 次阅读
本帖已被设为精华帖!

参考文章

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

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

<?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>

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

添加 meta-data 信息

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

<meta-data android:name="APPLICATION_CLASS_NAME" android:value="doctorq.com.mysourceapk.MyApplication"/>

修改主 Activity

我们要讲解壳程序本身的 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 都要在解壳程序的配置文件中配置:

 <activity android:name="doctorq.com.mysourceapk.SubActivity" >
</activity>

在 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中,这又怎么办呢?我暂时也不知道,思考一下吧

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 9 条回复 时间 点赞

偶像又有新帖了😊,前排支持下!

3楼 已删除

#3 楼 @nostop 我以前的头像?不是小罗哦,是普约尔

#4 楼 @doctorq 你那个是什么模拟器?

—— 来自 TesterHome 官方 安卓客户端

#5 楼 @taki https://github.com/DoctorQ/minicap_java 自己写的一个工具,你也可以用 google 的一个插件 Vysor (Beta),挺好用的

图片的效果是怎么弄的呢? 是不是先录制成视频文件,然后在用工具把视频文件转换成 GIF 文件

#8 楼 @DoctorQ 真的很好用,多谢

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册