可看原文:

http://www.cnblogs.com/jiaoxiake/p/6536824.html

导语

我们知道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.Bradcast,Severices 等组件的, 并且在这个类中找到一个静态函数。获取当前activity的ActivityThread 对象

总结:

* 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. 如果员APK中lib有SO文件, 需要释放到指定目录下, 代码如下:

加固版本一原理:

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

再进基类BaseDexClassLoader的构造函数

在进DexPathList 的构造函数

在进makeDexElements函数

在进makeDexElements函数

代码行为分析

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

在进loadDex函数

在进DexFile构造函数

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

进入Native层看openDexFile 的实现代码

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

行为分析结果:

2.我们在从使用代码, ClassLoader的loadClass分析函数

再进

再进

再进:

再进Native 的实现层:

原理总结:

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

2. 然后通过 DexClassLoader的loadClass 函数去加载类

版本一评价:

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

版本二:

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

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

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

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

原理: 我们在分析版本一的原理,发现版本利用DexFile.openDexFile() 实现的, 如果能在这个类找到一个相似的函数。 于是代开在 android源码的\libcore\dalvik\src\main\java\dalvik\system 下的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本身具有代码加密功能, 并且在运行前,解密后运行, 这种技术叫做:运行时自修改字节码技术(RSMC,Run Self Modify Code) ,利用这种技术就可以把我们核心的代码使用DEX的运行。

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

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

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

再进invokeNative 函数查看

再进invokeNative的native 实现层查看代码

再进dvmInvokeMethod 查看: 代码:(dalvik\vm\interp\Stack.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 强制是ART(Andorid Runtime)模式。 ART模式下,APK安装时,就会把字节码编译成汇编码,这个方法就不能使用了, 由于没有字节码

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

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

版本四:

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

原理:

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

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

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

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

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

4、支持更低的硬件。

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

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

浅谈android代码保护技术_加固的更多相关文章

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

    浅谈android代码保护技术_加固 导语 我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结果被人反编译了,那心情真心不舒服.虽然我们混淆,做到native层,但 ...

  2. 浅谈Android应用保护(一):Android应用逆向的基本方法

    对于未进行保护的Android应用,有很多方法和思路对其进行逆向分析和攻击.使用一些基本的方法,就可以打破对应用安全非常重要的机密性和完整性,实现获取其内部代码.数据,修改其代码逻辑和机制等操作.这篇 ...

  3. 浅谈Android应用保护(零):出发点和背景

    近几年来,无线平台特别是Android平台的安全逐渐成为各厂商关注的重点.各种新的思路和玩法层出不穷.所以,笔者基于前一段时间的学习和整理,写了这系列关于Android应用安全和保护的文章. 这5篇文 ...

  4. 浅谈Android保护技术__代码混淆

    浅谈Android保护技术__代码混淆   代码混淆 代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为.将代码中的各种元 ...

  5. 安卓开发_浅谈Android动画(四)

    Property动画 概念:属性动画,即通过改变对象属性的动画. 特点:属性动画真正改变了一个UI控件,包括其事件触发焦点的位置 一.重要的动画类及属性值: 1.  ValueAnimator 基本属 ...

  6. 浅谈Android Studio3.0更新之路(遇坑必入)

    >可以参考官网设置-> 1 2 >> Fantasy_Lin_网友评论原文地址是:简书24K纯帅豆写的我也更新一下出处[删除]Fa 转自脚本之家 浅谈Android Studi ...

  7. 浅谈Android应用性能之内存

    本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 文/ jaunty [博主导读]在Android开发中,不免会遇到许多OOM现象,一方面可能是由于开 ...

  8. 浅谈PHP代码设计结构

    浅谈PHP代码设计结构 您的评价:       还行  收藏该经验       coding多年,各种代码日夜相伴,如何跟代码友好的相处,不光成为职业生涯的一种回应,也是编写者功力的直接显露. 如何看 ...

  9. 浅谈Android五大布局

    Android的界面是有布局和组件协同完成的,布局好比是建筑里的框架,而组件则相当于建筑里的砖瓦.组件按照布局的要求依次排列,就组成了用户所看见的界面.Android的五大布局分别是LinearLay ...

随机推荐

  1. POJ3764,BZOJ1954 The xor-longest Path

    题意 In an edge-weighted tree, the xor-length of a path p is defined as the xor sum of the weights of ...

  2. 2.2 web工程的目录结构

    [转] 一个最简单的Web应用的目录结构如下所示: Web应用的结构定义在Servlet的规范中,目前最新版本为3.1. 下载地址:https://jcp.org/aboutJava/communit ...

  3. openfaas cli 安装

     1. 安装脚本 curl -sL https://cli.get-faas.com/ | sudo sh   备注安装完成之后如果没有 faas-cli 可以下载脚本,手工执行   2. 使用二进制 ...

  4. EL and JSTL(Jsp Standard Tag Libary)(转)

    一.什么是 EL 语言. 表达式语言(EL)是 JSP 2.0 引入的一种计算和输出 Java 对象的简单语音. 二.EL 语言的作用. 为了使JSP写起来更加简单.表达式语言的灵感来自于 ECMAS ...

  5. bzoj 3796 Mushroom追妹纸——后缀数组

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3796 长度一般都是 1e5 ,看这个是 5e4 ,一看就是把两个串接起来做. 自己本来想的是 ...

  6. List,ArrayList

    List是一个接口,而ListArray是一个类. ListArray继承并实现了List. 所以List不能被构造,但可以向上面那样为List创建一个引用,而ListArray就可以被构造. Lis ...

  7. EditText动态转换只读/编辑状态

    public class MyActivity extends Activity { private KeyListener listener; private EditText editText; ...

  8. js 格式化相关的时间

    javascript Date format(js日期格式化) 方法一: // 对Date的扩展,将 Date 转化为指定格式的String // 月(M).日(d).小时(h).分(m).秒(s). ...

  9. Java 对象和实例的区别

    本来我以为是一样的,其实是不一样的 参看:http://www.blogjava.net/dreamstone/archive/2011/06/03/101733.html

  10. python‘s tenth day for me

    动态参数    *args   **kwargs   *args  动态参数,万能参数 # args 接受的就是实参对应的  所有位置参数,并将其放在元祖中. def func(*args): pri ...