Android中的软件安全和逆向分析[二]—apk反破解技术与安全保护机制

  在Android应用开发中,当我们开发完软件之后,我们不希望别人能够反编译破解我们的应用程序,不能修改我们的代码逻辑。实际上,在应用程序的安全机制考虑中,我们希望自己的应用程序安全性高,通过各种加密操作等来增大竞争对手的反编译破解成本。设想,竞争对手开发一个同样的应用程序需要10天,而破解我们的软件程序需要100天,那么势必会打消黑客程序员破解我们应用程序的念头。如何增加对手的破解成本,就需要考验我们应用程序的安全性有多高,加密技术有多强。一个优秀的应用程序,不仅能为用户带来利益,同时也能保护自己的核心技术。


  本文主要从以下几个方面简单介绍下Android应用程序的保护机制。

一、不让程序安装到模拟器上

  在上一篇文章中Android中的软件安全和逆向分析[一]—apk反编译破解以及java汇编代码读写 里面介绍了的大多数操作是Android里的静态反编译破解apk文件。有些应用程序作了一些混淆,从SDK2.3开始我们可以看到在android-sdk-windows\tools\下面多了一个proguard文件夹,proguard是一个java代码混淆的工具,具体介绍参考这篇博文,使的代码逻辑比较复杂,此时我们如果还要静态反编译破解的话,就需要插入一些smali代码动态的去调试观察apk的逻辑。那么如何保护自己的应用程序免受反编译破解?我们需要阻止别的程序员反编译,动态调试我的应用。换句话说,就是需要阻止黑客程序员安装我的应用到模拟器上。
  一种方式是不让自己的应用程序运行在模拟器上,当应用程序发现自己安装到模拟器上的时候就会自杀。

package com.example.cracktest;

import android.app.Activity;
import android.os.Build;
import android.os.Bundle;

public class MainActivity1 extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (isEmulator()) {
            // 立刻自杀,发现自己运行在模拟器上,赶紧自杀
            android.os.Process.killProcess(android.os.Process.myPid());
        }
        setContentView(R.layout.activity_main);
    }

    /**
     * 判断应用程序是否运行在模拟器上
     * 
     * @return
     */
    public boolean isEmulator() {
        // model:Android SDK built for x86
        //只要是在模拟器中,不管是什么版本的模拟器,在它的MODEL信息里就会带有关键字参数sdk
        if (Build.MODEL.contains("sdk") || Build.MODEL.contains("SDK")) {
            return true;
        } else {
            return false;
        }
    }
}

  上述应用程序部署到真机上是可以运行的, 但是在模拟器上是打不开的,这样就防止了黑客程序员动态调试我们的应用程序。

二、检测应用程序的完整性

  第二种保护应用程序的安全机制是检测应用程序的完整性,我们可以用jni技术校验应用程序的完整性,也可以利用数字签名的方式来检测应用程序的完整性。我们知道当一个apk文件被反编译破解、修改完代码逻辑之后,要使用jarsigner工具来重新给apk签名,才能运行修改后的apk文件。一个应用程序的签名,是识别一个开发者的唯一标识,如果一个应用程序被别人反编译,那么这个应用程序的签名肯定会改变。当发现程序的签名改变时,我们使之自杀就可以避免程序带来损失。下面我们来介绍下数字签名的方式检测应用程序的完整性。

package com.example.cracktest;

import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;

public class MainActivity2 extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        System.out.println(getSignature());
    }

    /**
     * 获得应用程序的数字签名
     * 
     * @return
     */
    public String getSignature() {
        PackageManager pm = getPackageManager();
        try {
            // 得到当前应用程序的签名
            PackageInfo info = pm.getPackageInfo(getPackageName(),
                    PackageManager.GET_SIGNATURES);
            return info.signatures[0].toCharsString();
        } catch (NameNotFoundException e) {
            e.printStackTrace();
            return "";
        }
    }
}

  上述简单实例代码获取到的应用程序的数字签名为,
  技术分享
  任何一个对原apk的操作都会修改原apk的数字签名,为了在程序中判断方便,我们取数字签名的MD5哈希值来判断。
  

package com.example.cracktest;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5 {

    /**
     * 对字符串进行MD5加密
     * @param content
     * @return
     */
    public static String getMD5(String content) {
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            digest.update(content.getBytes());
            return getHashString(digest);

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static String getHashString(MessageDigest digest) {
        StringBuilder builder = new StringBuilder();
        for (byte b : digest.digest()) {
            builder.append(Integer.toHexString((b >> 4) & 0xf));
            builder.append(Integer.toHexString(b & 0xf));
        }
        return builder.toString();
    }
}

  技术分享
  我们在应用程序运行时,判断当前的数字签名的MD5哈希值是否等于上述图片中的哈希值,如果等于,合法运行,如果不等于,就能够证明apk文件被反编译破解过,这个时候我们1.5s后自杀程序。
  

package com.example.cracktest;

import android.app.Activity;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.widget.Toast;

public class MainActivity2 extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        System.out.println(getSignature());

        // 正确的签名信息为77e980cae813e5dacc11fdb4c67988fd
        if ("77e980cae813e5dacc11fdb4c67988fd".equals(getSignature())) {
            Toast.makeText(this, "自校验完毕,合法正常运行。", 0).show();
        } else {
            Toast.makeText(this, "你敢破解我的代码!", 0).show();
            new Thread() {
                public void run() {
                    try {
                        Thread.sleep(1500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    android.os.Process.killProcess(android.os.Process.myPid());
                };
            }.start();
        }
    }

    /**
     * 获得应用程序的数字签名
     * 
     * @return
     */
    public String getSignature() {
        PackageManager pm = getPackageManager();
        try {
            // 得到当前应用程序的签名
            PackageInfo info = pm.getPackageInfo(getPackageName(),
                    PackageManager.GET_SIGNATURES);
            return MD5.getMD5(info.signatures[0].toCharsString());
        } catch (NameNotFoundException e) {
            e.printStackTrace();
            return "";
        }
    }
}

  具体的反编译过程可以参见上一篇文章 Android中的软件安全和逆向分析[一]—apk反编译破解以及java汇编代码读写 ,当反编译apk,修改smali代码,重新给apk签名后,部署到模拟器上运行,发现1.5s后程序自杀。
  检测包名,版本名和版本号,然后做判断,与检测数字签名方法大同小异。有兴趣的可以自己研究研究。

三、动态字节码技术

  应用程序的运行字节码不在本地,而是在网络端,就是说我们的应用程序的关键性核心代码不要放在本地,应该放在网络服务器端上。而在网络端的字节码如何获取呢?我们通过URLClassLoader从服务器上把字节码下载到本地,然后编译运行。这种动态从网络获取字节码的技术可以防止黑客程序员本地静态的反编译我们的应用程序。

四、zip包加密

五、花指令加壳技术

  开发程序员先做一个so的壳,当做应用程序的运行环境,壳里面运行着我们的程序代码,相当于在java虚拟机的上层加了一个代码监视器。也可以说是在加密后的虚拟机里运行我们的应用程序,增加了安全性。

六、参考引用

未完待续。。。

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。