APK的安全(二)--如何防御

来源:互联网 发布:node.js是js吗 编辑:程序博客网 时间:2024/06/16 20:35

如何防御

1.代码混淆

原理:

“用不能直接猜出含义的通用变量名和函数名a,b,c等”替换编译后程序包中“具有明显语义信息的变量名和函数名”。这样,通过逆向工程得到的只是 难以理解 的代码。

从混淆的原理可以得出以下两点信息:

  1. 重命名变量名可能会导致程序异常。因为程序是需要跟平台交互的,平台只会以固定类名来调用我们的app,这就涉及到需要屏蔽不能重命名的函数及类 proguard.cfg文件就是起这个作用的,混淆后哪个地方出错,就将相关的类和函数加入到混淆屏蔽列表里面去
  2. 由于混淆只改变字符串,并不能改变程序逻辑,耐心的hacker还是能够理解程序的设计思路并尝试修改。但这类人群不会太多,加上有我们自定义框架的牵制,逆向工程也绝非轻而易举。

实现:

1、全局混淆选项,修改build/core/package.mk

ifndef LOCAL_PROGUARD_ENABLEDifneq ($(filter user userdebug, $(TARGET_BUILD_VARIANT)),)# turn on Proguard by default for user & userdebug build    LOCAL_PROGUARD_ENABLED :=fullendifendif

2、全局flag文件修改,屏蔽-dontobfuscate。修改build/core/proguard.flags

# Don't obfuscate. We only need dead code striping.# -dontobfuscate

3、在自己模块下创建proguard.cfg文件,用来配置混淆选项,初始文件如下:

-optimizationpasses 5-dontusemixedcaseclassnames-dontskipnonpubliclibraryclasses-dontpreverify-verbose-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*-keep public class * extends android.app.Activity-keep public class * extends android.app.Application-keep public class * extends android.app.Service-keep public class * extends android.content.BroadcastReceiver-keep public class * extends android.content.ContentProvider-keep public class * extends android.app.backup.BackupAgentHelper-keep public class * extends android.preference.Preference-keep public class com.android.vending.licensing.ILicensingService-keepclasseswithmembernames class * {    native <methods>;}-keepclasseswithmembers class * {    public <init>(android.content.Context, android.util.AttributeSet);}-keepclasseswithmembers class * {    public <init>(android.content.Context, android.util.AttributeSet, int);}-keepclassmembers class * extends android.app.Activity {   public void *(android.view.View);}-keepclassmembers enum * {    public static **[] values();    public static ** valueOf(java.lang.String);}-keep class * implements android.os.Parcelable {  public static final android.os.Parcelable$Creator *;}

4、在Android.mk里每个package类型的LOCAL_MODULE里LOCAL_PACKAGE_NAME下面添加两句,
​ LOCAL_PROGUARD_ENABLED := full #指定当前的应用打开混淆
​ LOCAL_PROGUARD_FLAG_FILES := proguard.cfg #指定混淆配置文件

5、编译时设置环境变量使用. ./setenv.sh -bv user

6、遇到报错需要就需要修改proguard.cfg文件,规则可以找google。

2.APK完整性

Android对每一个Apk文件都会进行签名,在Apk文件安装时,系统会对其签名信息进行比对,判断程序的完整性,从而决定该Apk文件是否可以安装,在一定程度上达到安全的目的。

  1. MANIFEST.MF:这是摘要文件。程序遍历Apk包中的所有文件(entry),对非文件夹非签名文件的文件,逐个用SHA1生成摘要信息,再用Base64进行编码。如果你改变了apk包中的文件,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同,于是程序就不能成功安装。
  2. CERT.SF:这是对摘要的签名文件。对前一步生成的MANIFEST.MF,使用SHA1-RSA算法,用开发者的私钥进行签名。在安装时只能使用公钥才能解密它。解密之后,将它与未加密的摘要信息(即,MANIFEST.MF文件)进行对比,如果相符,则表明内容没有被异常修改。
  3. CERT.RSA文件中保存了公钥、所采用的加密算法等信息。系统对签名文件进行解密,所需要的公钥就是从这个文件里取出来的。
    这三个文件在apk META-INFO文件夹

结论:从上面的总结可以看出,META-INFO里面的说那个文件环环相扣,从而保证Android程序的安全性。(只是防止开发者的程序不被攻击者修改,如果开发者的公私钥对对攻击者得到或者开发者开发出攻击程序,Android系统都无法检测出来。)

也可以通过校验classes.dex的方式进行完整性的检测

3.APK加固

原理解析:

下面就来看一下Android中加壳的原理:

r4z8u.png

我们在加固的过程中需要三个对象:

1、需要加密的Apk(源Apk)

2、壳程序Apk(负责解密Apk工作)

3、加密工具(将源Apk进行加密和壳Dex合并成新的Dex)

主要步骤:

我们拿到需要加密的Apk和自己的壳程序Apk,然后用加密算法对源Apk进行加密在将壳Apk进行合并得到新的Dex文件,最后替换壳程序中的dex文件即可,得到新的Apk,那么这个新的Apk我们也叫作脱壳程序Apk.他已经不是一个完整意义上的Apk程序了,他的主要工作是:负责解密源Apk.然后加载Apk,让其正常运行起来。

现在反编译的一些工具:

1、jd-gui:可以查看jar中的类,其实他就是解析class文件,只要了解class文件的格式就可以

2、dex2jar:将dex文件转化成jar,原理也是一样的,只要知道Dex文件的格式,能够解析出dex文件中的类信息就可以了

当然我们在分析这个文件的时候,最重要的还是头部信息,应该他是一个文件的开始部分,也是索引部分,内部信息很重要。

r4DXD.png

我们今天只要关注上面红色标记的三个部分:

1) checksum

文件校验码 ,使用alder32 算法校验文件除去 maigc ,checksum 外余下的所有文件区域 ,用于检查文件错误 。

2) signature

使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件区域 ,用于唯一识别本文件 。

3) file_size

Dex 文件的大小 。

为什么说我们只需要关注这三个字段呢?

因为我们需要将一个文件(加密之后的源Apk)写入到Dex中,那么我们肯定需要修改文件校验码(checksum).因为他是检查文件是否有错误。那么signature也是一样,也是唯一识别文件的算法。还有就是需要修改dex文件的大小。

不过这里还需要一个操作,就是标注一下我们加密的Apk的大小,因为我们在脱壳的时候,需要知道Apk的大小,才能正确的得到Apk。那么这个值放到哪呢?这个值直接放到文件的末尾就可以了。

所以总结一下我们需要做:修改Dex的三个文件头,将源Apk的大小追加到壳dex的末尾就可以了。

我们修改之后得到新的Dex文件样式如下:

r4K2v.png

那么我们知道原理了,下面就是代码实现了。所以这里有三个工程:

1、源程序项目(需要加密的Apk)

2、脱壳项目(解密源Apk和加载Apk)

3、对源Apk进行加密和脱壳项目的Dex的合并

博客原文:http://blog.csdn.net/jiangwei0910410003/article/details/48415225

总结

1、加壳程序

任务:对源程序Apk进行加密,合并脱壳程序的Dex文件 ,然后输入一个加壳之后的Dex文件

语言:任何语言都可以,不限于Java语言

技术点:对Dex文件格式的解析

2、脱壳程序

任务:获取源程序Apk,进行解密,然后动态加载进来,运行程序

语言:Android项目(Java)

技术点:如何从Apk中获取Dex文件,动态加载Apk,使用反射运行Application

4. 将核心算法放到native层

Android 软件的开发主要使用 Java 语言,但是 Android 也提供了对本地语言 C、C++的支
持。借助 JNI,可以在 Java 类中使用 C 语言库中的特定函数,或在 C 语言程序中使用 Java
类库。一般来说,如果代码中对处理速度有较高要求或者为了更好地控制硬件,抑或者为了
复用既有的 C/C++代码,都可以考虑通过 JNI 来实现对 Native 代码的调用。
由于逆向 Native 程序的汇编代码要比逆向 Java 汇编代码困难,因此可以考虑在关键代
码部位使用 Native 代码,如注册验证,加解密操作等。一个可能的借助 Native 代码保护 APK
的方法是:将核心业务逻辑代码放入加密的.jar 或者.apk 文件中,在需要调用时使用 Native
代码进行解密,同时完成对解密后文件的完整性校验,不过不管是.jar 还是.apk 文件,解密
后都会留在物理存储上,为了避免这种情况,可以使用反射技术直接调用
dalvik.system.DexFile.openDex()方法,该方法接受 classes.dex 文件字节流返回 DexFile 对象。
关于 Native 代码的编写,可以参考 Google 官方文档的 Android NDK。

5.防二次打包

使用 apktool 进行重打包时,对于后缀为 png 的文件,会按照 png 格式的文件进行打包
处理,因此如果在项目开发时,有意将一个非 png 格式文件的文件名改为后缀为 png 的文件,
则使用 apktool 进行重打包时会出错。可以利用这种方法来对抗重打包。

  1. 在Java代码中加入签名校验(直接修改smali文件绕过)
  2. NDK中加入签名校验(ida查看直接hex修改就可以绕过)
  3. 利用二次打包工具本身的缺陷阻止打包(manifest欺骗,图片修改后缀等)

6.代码乱序

实现方式

代码乱序是将一系列的代码序列分散打乱分布在PE映像中,中间穿插跳转指令以及不改变环境的垃圾代码,从而扰乱正常分析流程。一般的来讲,连接指令以无条件的跳转jmp、变形的短跳转call、对称的条件跳转指令([jz、jnz];[jc、jnc]…)等实现,前提是不改变其他环境以免破坏代码正常功能。当然,也可以先保存环境,再使用破坏环境的条件跳转或通过SEH跳转等方式,然后再恢复先前保存的环境,这种先保存再恢复的实现方式必然有成对的指令模版。归根结底,连接指令是不会改变其他环境的。

还原方法:

我们知道连接指令是不改变其他环境的,因此我们可以逐行分析代码,记录不改变环境的跳转,将分散乱序代码合并实现乱序的还原。代码乱序的还原方法可分为动态和静态两种,动态还原以OllyDbg为例,可利用调试器自身的反汇编引擎和脚本来实现,由于脚本和跟踪执行的效率问题,这种方式存在一定的局限性,比如动态分析时不方便比对。而静态还原首先需要一个反汇编引擎来分析指令,然后根据设定的过滤条件,将不会改变其他环境的连接跳转过滤,根据指令执行顺序,线性重排,这种方式实际上是半自动的条件反汇编。

7.代码注入

android中注入需要调用ptrace,然后执行注入so中的函数。因此,防止android注入可以通过以下方式:1.ptrace附加失败。2.修改linker中的dlopen函数,防止第三方so加载。3.定时检测应用加载的第三方so库,如果发现是被注入的so,卸载加载的so。这里主要分析第三种情况。java关键函数如下:

    public static HashSet<String> getSoList(int pid, String pkg) {        HashSet<String> temp = new HashSet<String>();        File file = new File("/proc/" + pid + "/maps");        if (!file.exists()) {            return temp;        }        try {            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(file)));            String lineString = null;            while ((lineString = bufferedReader.readLine()) != null) {                String tempString = lineString.trim();                if (tempString.contains("/data/data") && !tempString.contains("/data/data/" + pkg)) {                    int index = tempString.indexOf("/data/data");                    temp.add(tempString.substring(index));                }            }            bufferedReader.close();        } catch (FileNotFoundException e) {                  //TODO Auto-generated catch block                  e.printStackTrace();            }     catch (IOException e)     {                  //TODO Auto-generated catch block                  e.printStackTrace();            }    return temp;    } //卸载加载的so库    public static native void uninstall(String soPath);

jni实现关键代码如下:1234567891011121314151617

//该方法主要根据so路径,调用dlopen卸载加载的so,需要调用多次,分析linker的dlclose可知,只有so的引用计数为0时,才会完全卸载。

staticvoidcom_hook_util_UninstallSo_uninstall(JNIEnv*env,jclass,        jstringpath){    constchar*chars=env->GetStringUTFChars(env,path);    void*handle=dlopen(chars,RTLD_NOW);    intcount=4;    inti=0;    for(i=0;i<count;i++){        if(NULL!=handle)        {            dlclose(handle);        }    }}

代码注入实例:http://www.cnblogs.com/ijiami/archive/2013/08/27/3284209.html

Shared storage cannot protect your application from code injection attacks

从Android4.1.2开始,App不能直接从sdcard中加载字节码,因为sdcard对于所有应用程序来说是共享的,为了防止恶意的代码注入,当采用DexClassLoader从sdcard中动态加载字节码的时候,就会抛出异常,
Caused by: java.lang.IllegalArgumentException: Optimized data directory /storage/emulated/0 is not owned by the current user. Shared storage cannot protect your application from code injection attacks.

源码分析

下面是DexFile的源代码,通过Libcore.os.getuid() != Libcore.os.stat(parent).st_uid判断文件的user id和进程的user id是否一致。如果不一致则抛出异常。

    private DexFile(String sourceName, String outputName, int flags) throws IOException {        if (outputName != null) {            try {                String parent = new File(outputName).getParent();                if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {                    throw new IllegalArgumentException("Optimized data directory " + parent                            + " is not owned by the current user. Shared storage cannot protect"                            + " your application from code injection attacks.");                }            } catch (ErrnoException ignored) {                // assume we'll fail with a more contextual error later            }        }        mCookie = openDexFile(sourceName, outputName, flags);        mFileName = sourceName;        guard.open("close");        //System.out.println("DEX FILE cookie is " + mCookie);    }

解决方案

使用app的私有目录,也就是data/data/应用程序包名,通过context.getDir(“libs”,0)。作为dex的加载目录。

原创粉丝点击