浅谈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.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代码保护技术_ 加固的更多相关文章
-
浅谈android代码保护技术_加固
可看原文: http://www.cnblogs.com/jiaoxiake/p/6536824.html 导语 我们知道Android中的反编译工作越来越让人操作熟练,我们辛苦的开发出一个apk,结 ...
-
浅谈Android应用保护(一):Android应用逆向的基本方法
对于未进行保护的Android应用,有很多方法和思路对其进行逆向分析和攻击.使用一些基本的方法,就可以打破对应用安全非常重要的机密性和完整性,实现获取其内部代码.数据,修改其代码逻辑和机制等操作.这篇 ...
-
浅谈Android应用保护(零):出发点和背景
近几年来,无线平台特别是Android平台的安全逐渐成为各厂商关注的重点.各种新的思路和玩法层出不穷.所以,笔者基于前一段时间的学习和整理,写了这系列关于Android应用安全和保护的文章. 这5篇文 ...
-
浅谈Android保护技术__代码混淆
浅谈Android保护技术__代码混淆 代码混淆 代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为.将代码中的各种元 ...
-
安卓开发_浅谈Android动画(四)
Property动画 概念:属性动画,即通过改变对象属性的动画. 特点:属性动画真正改变了一个UI控件,包括其事件触发焦点的位置 一.重要的动画类及属性值: 1. ValueAnimator 基本属 ...
-
浅谈Android Studio3.0更新之路(遇坑必入)
>可以参考官网设置-> 1 2 >> Fantasy_Lin_网友评论原文地址是:简书24K纯帅豆写的我也更新一下出处[删除]Fa 转自脚本之家 浅谈Android Studi ...
-
浅谈Android应用性能之内存
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 文/ jaunty [博主导读]在Android开发中,不免会遇到许多OOM现象,一方面可能是由于开 ...
-
浅谈PHP代码设计结构
浅谈PHP代码设计结构 您的评价: 还行 收藏该经验 coding多年,各种代码日夜相伴,如何跟代码友好的相处,不光成为职业生涯的一种回应,也是编写者功力的直接显露. 如何看 ...
-
浅谈Android五大布局
Android的界面是有布局和组件协同完成的,布局好比是建筑里的框架,而组件则相当于建筑里的砖瓦.组件按照布局的要求依次排列,就组成了用户所看见的界面.Android的五大布局分别是LinearLay ...
随机推荐
-
[笔记]linux用户与用户组
sudo useradd -m john 自动建立主目录 sudo passwd john sudo useradd -g groupusers mike sudo useradd -s /bin/b ...
-
nth-child和蝉原则实现的奇妙随机效果(译)
此文翻译自charlotte jackson的<Magic randomisation with nth-child and Cicada Principle> 在做伪装的随机模式时将nt ...
-
【转】DCC32的参数详解
完整的内容如下: // DCC32编译器的设置说明. // Dcc32 [options] filename [options] // DCC32 [操作选项] 文件名称 [操作选项] // -A&l ...
-
PHP的一些函数
//进制转换类 base_convert //字符转十六进制 binhex
-
XmlNode和XmlElement区别
今天在做ASP.NET操作XML文档的过程中,发现了两个类:XmlNode和XmlElement.这两个类的功能极其类似(因为我们一般都是在对Element节点进行操作).上网搜罗了半天,千篇一律的答 ...
-
第 12 章 MySQL 可扩展设计的基本原则
前言: 随着信息量的飞速增加,硬件设备的发展已经慢慢的无法跟上应用系统对处理能力的要求了.此时,我们如何来解决系统对性能的要求?只有一个办法,那就是通过改造系统的架构体系,提升系统的扩展能力,通过组合 ...
-
Struts(十七):通过CURD来学习paramsPrepareParams拦截器栈
背景: 通过上一章节<Struts(十六):通过CURD来学习Struts流程及ModelDriven的用法>学习了ModelDriven拦截器的用法,上章节中讲到了edit功能. 要修改 ...
-
c++String类的运算符重载---21
原创博文,转载请标明出处--周学伟http://www.cnblogs.com/zxouxuewei/ 一,创建测试程序包 测试代码如下: /* Date: 2017-5-4 * Descripti ...
-
新手Python第一天(接触)
Python 变量 Python的变量由字母,数字,下划线组成不包含特殊字符,不能以数字开头 可以使用的名称 例如:name,name2,my_name 不可使用的名称 例如:if...(Python ...
-
ArcGIS最权威、最专业的技术分享网站:积思园(www.iarcgis.com)
你对iArcGIS.com说点什么 为什么会有该网站的产生 在这个所谓的“大数据”的时代,每个人都深陷于海量信息无法自拔,因为过多碎片化的数据只会让自己的思维更加迷离,快餐式的阅读只会让自己变得虚胖. ...