• 浅谈android代码保护技术_ 加固


    浅谈android代码保护技术_加固

    导语

    我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结果被人反编译了,那心情真心不舒服。虽然我们混淆,做到native层,但是这都是治标不治本。反编译的技术在更新,那么保护Apk的技术就不能停止。现在网上有很多Apk加固的第三方平台,最有名的应当属于:爱加密和梆梆加固了。其实加固有些人认为很高深的技术,其实不然,说的简单点就是对源Apk进行加密,然后在套上一层壳即可,当然这里还有一些细节需要处理,这就是本文需要介绍的内容了。

     

     

    类装载器ClassLoader

    Java.class 文件类似于我们的windows DLL或者Linux下的.SO 文件,在Winodows 下我们使用DLL的服务,我们必须使用使用LoadLibaray函数加载它。同理在java中我们要使用.class

    中的的类,我们必须使用类加载器加载后,才能使用。在android中我们如果提供一个DexClassLoader(继承 ClassLoader), 这个DexClassLoader 可以用来加载.jar, .apk, .class 文件。可

    以在android源码中找到这个类的实现:

     

    关于的DexClassLoader使用,DexClassLoader 只有一个构造函数,如下。 这里我们特别关注的是ClassLoader parent. 关于ClassLoader 有一个特性:子ClassLoader加载过的类

    可以访问/使用 父ClassLoader加载过的类, 而父ClassLoader加载过的类不可以访问父 ClassLoader。对于这个值我们一般获取当前的加载器器,当前的加载器。

     

    类装载器的替换

    如何替换原来的ClassLoader, 关于为什么要替换原始ClassLoader,笔者在一个简单加固案例会阐述的。

     

    1.Android源码 搜索关于ClassLoader 的引用, 由于Android源码中ClassLoade应用特别多,搜索应用仅限Java 源码

     

    2.在众多引用有一个类LoadedApk, 于是查看觉得 这就是我们想要的类

     

    从类作用描述可知这个类,是用来维护已加载的APK的信息, 并且里面有两个类加载器,mBaseClassLoader, mClassLoader. 显然我们我想要的是mClassLoader , 要想获得mClassLoader, 我们必须先获取LoadedApk 对象

     

    3.Android源码 搜索关于LoadedApk的引用, 由于Android源码中LoadedApk应用特别多,搜索应用仅限Java 源码; 在众多引用有一个类ActivityThread, 从类作用描述看,是我们要找的类

     

     

    从类的描述可知,这个类是用于来调度Activity.BradcastSeverices 等组件的, 并且在这个类中找到一个静态函数。获取当前activityActivityThread 对象

     

     

    总结:

    * 1. 反射 调用这个函数 ActivityThread.currentActivityThread 拿到一个当前ActivityThread对象

    * 2. 反射获取 ActivityThread对象的 final HashMap<String, WeakReference<LoadedApk>> mPackages 成员

    * 3. mPackages获取已在加载的 LoadedApk 对象 

    * 4. 反射获取LoadedApk 对象   LoadedApk.mClassLoader成员

    * 5. 用新的ClassLoaded 替换原来的mClassLoader

    代码如下:

     

     

     

    Android加固简史

    版本一:

    加固方案

      1.APK文件存放壳APK的资源目录asset

      2.使用DexClassLoader 动态加载APK,并运行

     

     

     

     

    1.重写Application, 默认情况一个android 应用程序启动一个默认的Application 对象, 由于我们加固工具需要替换加固的的程序,我们必须在加固程序的Activity前动态加载我们APK

      我们可以选择重写Application 的虚函数 attachBaseContext 或者onCreate.  笔者选择在attachBaseContext 加载我们的原APK,并启动它

      

     

    2.释放资源中源APK 到目标目录下(init函数)

     

    3. 我们启动一个动态一个android应用程序程序,通常我们需要在某个组件使用意图Intent 启动, 我们学习开发的时候,我们知道Intent 是两个组件通信的的关键,如果我们要启动android程序,我们必须有一个组件,所以启动代码在加固的工具的Activity。 代码如下:

     

    分析以上代码 Class clsDestActivity = dexClsLoader.loadClass("cr.dest.DestActivity"); 这句代码返回值为null的,为什么会这样, 这是因为我们的当前ClassLoader 并不能加载源APK中类。

    所以我们必须替换ClassLoader.

     

    4. 新建一个DexClassLoader , 并且替换员ClassLoader. 关于替换ClassLoader 请参考上.

     

     

    5. 资源如何替换

    方案一:直接替换(手工替换)

    方案二:代码替换

     

    6. 如果员APKlibSO文件, 需要释放到指定目录下, 代码如下:

     

     

    加固版本一原理:

    1.由第一个版本,我们知道DexClassLoader可以实现, 于是笔者根据去看DexClassLoader的实现,

     

    再进基类BaseDexClassLoader的构造函数

     

    在进DexPathList 的构造函数

     

     

    在进makeDexElements函数

     

    在进makeDexElements函数

     

    代码行为分析

     a) 具体加载DexFile 还是使用loadDex 文件进行的, 并且loadDex是个静态函数,猜想DexFile 可能除

    在进loadDex函数

     

    在进DexFile构造函数

     

    发现我们使用了openDexFile , 这是我们最

     

    进入Native层看openDexFile 的实现代码

     

     

    发现调用核心函数addToDexFileTable, 再进

     

     

    行为分析结果:

     

     

    2.我们在从使用代码, ClassLoaderloadClass分析函数

     

    再进

     

    再进

     

    再进:

     

    再进Native 的实现层:

     

     

    原理总结:

    1. DexClassLoader 构造函数就通过遍历.APK, .JAR包所有的dex,class 文件,依次通过DexFileopenDexFile, 把DexFiledex文件中添加到一张表中(Hash表)

    2. 然后通过 DexClassLoaderloadClass 函数去加载类

     

     

    版本一评价:

    致命缺陷: 直接暴露文件路径,在新建DexClassLoader类的时候,我们发现需要指定解压好的APK地址。

    版本二:

    我们知道加固版本一的缺陷在于需要指APK文件路径。 为了更隐蔽写我们有两种改进方法:

    1.重写一个ClassLoader, 这个ClassLoader 不须指定APK的路径, 这样我们就不需要释放APK文件了。(代码量比较大)

    2.不是ClassLoader替换的方法,而是在原来的ClassLoader 上直接添加一个类。 定义一个类不需要指定.dex 文件路径

    版本2的方法就是是使用第二种改进方案. 这个版本仅限在android4.0 - android5.0之间

     

    原理: 我们在分析版本一的原理,发现版本利用DexFile.openDexFile() 实现的, 如果能在这个类找到一个相似的函数。 于是代开在 android源码的libcoredalviksrcmainjavadalviksystem 下的DexFile.java 文件, 类描述:的DexFile类就是负责把一个把文件中类加载到ClassLoader

     

    于是笔者发现函数 native private static int openDexFile(byte[] fileContents);  加固版本二的核心在openDexFile 的参数, 这个参数不需要指定具体文件,

    而是直接文件的字节数组。这就我们隐藏文件的操作。顺藤摸瓜,笔者还发发现这几个函数。

    里面有4个重要的静态方法:   

    native private static int openDexFile(byte[] fileContents);  

        native private static String[] getClassNameList(int cookie);

    native private static Class defineClass(String name, ClassLoader loader, int cookie);

    native private static void closeDexFile(int cookie);

    于是笔者就可以利用四个函数可以实现加固版的原理:

     a) 将源android应用程序的 lasses.dex 存放在加固工具工程asset 目录下, 并把源android应用程序的的资源替换加固工具的资源

     b) classes.dex 的信息读取到ByeArrayOutputStream 字节数组流中。

     d) 调用native private static int openDexFile(byte[] fileContents);得到DexFile cookie

     e) 调用 native private static String[] getClassNameList(int cookie); 获取DexFile文件中所有的类名

     f) 遍历e步骤的获取的类名信息, 调用native private static Class defineClass(String name, ClassLoader loader, int cookie); 想当前的加载器注册类

     g) 调用native private static void closeDexFile(int cookie); 关闭DexFile文件

     

    实现代码案例:

     

     然后就可以通过Class.forname 或者ClassLoader.loadClss  得到DexFile 的主Activity类,最后通过意图Inetent启动,调用startActivity

     

     

    原理探索:

    关于DexFile 是具体怎么实现,这就需要分析native 函数的实现, 笔者在此就不再探索,笔者会另辟一篇文章来探究的

    native private static int openDexFile(byte[] fileContents);  

        native private static String[] getClassNameList(int cookie);

    native private static Class defineClass(String name, ClassLoader loader, int cookie);

    native private static void closeDexFile(int cookie);

     

     

    版本二评价:

    运行版本要求高(android4.0 -android5.0之间版本,不包含android5.0) 由于版本而依赖DexFile类中3个私有静态函数,由于这个四个私有函数并没有公开,

    所以并不是所有版本都兼容。具体是否支持请查看对应android源码,笔者发现在android是支持的,但是在android5.0 就不支持了。 如果不支持,笔者建议深入

    native private static int openDexFile(byte[] fileContents);  

        native private static String[] getClassNameList(int cookie);

    native private static Class defineClass(String name, ClassLoader loader, int cookie);

    native private static void closeDexFile(int cookie);

    的实现,自己重写一个。 所以android源码的重要性不言而喻。

     

    版本三:

    版本一,版本二,都有个致命缺点,那就是在内存中有Dex 文件,利用这点,我们可以在内存中找到Dex 文件头,然后把文件dump下来,这样这两种加固都失效了。为了防止这一点我们,我们希望能在我们的Dex本身具有代码加密功能, 并且在运行前,解密后运行, 这种技术叫做:运行时自修改字节码技术(RSMCRun Self Modify Code) ,利用这种技术就可以把我们核心的代码使用DEX的运行。

     

    问题: 运行时自修改字节码技术(RSMCRun Self Modify Code),一个重要的技术难点就是如何在DEX 字节码运行的时候,找到函数的实现地址。

    现在 笔者通过分析java.lang.reflect.Method类的invoke 函数字节码存储地址。这是invoke必定会找到函数的自己吗,并且解释执行这个字节码。

    1.android源码找到java.lang.reflect.Method类的invoke的代码

     

    再进invokeNative 函数查看

     

     

    再进invokeNativenative 实现层查看代码

     

    再进dvmInvokeMethod 查看: 代码:(dalvikvminterpStack.cpp)

     

    //nativeFunc 指的函数地址为字节码解释后的结构代码(JTI)

    再进函数 void dvmInterpret(Thread* self, const Method* method, JValue* pResult)

     

    发现当前线程的pc = method->insns 可知insns  是自己地址 , 结果存储在方法的字节码存储Method 结构的insns

     

     

    2.找到JNI编程jmethodID 和结构Method的关系, 在Java层我们是无法拿到Method 结构体的,所以我们必须使用JNI编程。在JNI编程我们只能拿到一个函数jmethodID, 所以我们要找到这两个关系。

    通过分析函数是如何GetMethodID是如何获取jmethod的。

     

     

     

    所以加固版本三的原理:

     a) 通过GetMethodID 获取获取jmethodid,  并强转Method*

     b) 修改a步返回Method insns 所在页开启可写权限

     d) 解密insns内容为源DEX 的功能

     e) 解密后,又将代码加密回去,防止被Dump

     

    版本三案例:代码如下:

    Java 层代码:

     

    函数Sub:解密前是两数加法, 经过decode 运行时解密为两数加法, encode 后字节码在此称为减法, 修改字节码代码使用JNI本地代码实现。

     

    本地解密代码的实现

     

    代码行为分析:

    a)调用GetMethodID获取结构体Method

    b)调用mprotected 修改Method 结构中insns所在页属性

    c)解密操作-》 加法:0x90  减法:0x91

     

    同理解密代码: 不贴图了

     

    版本三加固评价:

    1.android5.0 以上版本不再适用了, 因为android5.0 强制是ARTAndorid Runtime)模式。 ART模式下,APK安装时,就会把字节码编译成汇编码,这个方法就不能使用了, 由于没有字节码

    运行时就找不到对象的字节码,这样加固后的android APP 是运行不了了。  所以加固版本三模式不适合单独使用

     

    2.特征代码,调用mprotected修改页属性, 利用这点做对抗,

    版本四:

    版本四就是解决版本三在android5.0 不能使用问。我们可以使用版本3 和版本1或版本2 结合使用

     

     

    原理:

    1.版本3的运行时解密, 静态反编译找不到代码/或者代码是错误的。

    2.版本3的的DEX文件/JAR 文件 使用版本1 或者版本2的方式动态运行。

     

    关于ART Dalivk 的简介
    DalvikGoogle公司自己设计用于Android平台的Java虚拟机。Dalvik虚拟机是Google等厂商合作开发的Android移动设备平台的核心组成部分之一。它可以支持已转换为 .dex(即Dalvik Executable)格式的Java应用程序的运行,.dex格式是专为Dalvik设计的一种压缩格式,适合内存和处理器速度有限的系统。Dalvik 经过优化,允许在有限的内存中同时运行多个虚拟机的实例,并且每一个Dalvik 应用作为一个独立的Linux 进程执行。独立的进程可以防止在虚拟机崩溃的时候所有程序都被关闭。


    ART Android操作系统已经成熟,GoogleAndroid团队开始将注意力转向一些底层组件,其中之一是负责应用程序运行的Dalvik运行时。Google开发者已经花了两年时间开发更快执行效率更高更省电的替代ART运行时。 ART代表Android Runtime,其处理应用程序执行的方式完全不同于DalvikDalvik是依靠一个Just-In-Time (JIT)编译器去解释字节码。开发者编译后的应用代码需要通过一个解释器在用户的设备上运行,这一机制并不高效,但让应用能更容易在不同硬件和架构上运 行。ART则完全改变了这套做法,在应用安装时就预编译字节码到机器语言,这一机制叫Ahead-Of-Time (AOT)编译。在移除解释代码这一过程后,应用程序执行将更有效率,启动更快。

     

    ART优点:
    1、系统性能的显著提升。
    2、应用启动更快、运行更快、体验更流畅、触感反馈更及时。
    3、更长的电池续航能力。

    4、支持更低的硬件。

    ART缺点:
    1、更大的存储空间占用,可能会增加10%-20%
    2、更长的应用安装时间。

    总的来说ART的功效就是空间换时间

     

  • 相关阅读:
    2/4 关于 Vue.js 中 this.$nextTick 的个人简单解释
    2/3 初次搭建 Vue 项目遇到的问题汇总
    前端中常见的布局
    如何判断一个变量是否为数组(isArray)
    ubuntu下安装截图工具
    正向代理、反向代理
    javascript中的基本数据类型
    css3 中的渐变
    javascript中的toString()
    ubuntu下面安装nodejs
  • 原文地址:https://www.cnblogs.com/jiaoxiake/p/6536824.html
Copyright © 2020-2023  润新知