-
-
[原创]Android一代加固学习史(1)
-
发表于: 2024-9-12 21:33 627
-
Android一代加固学习史是一系列文章,将详细介绍Android第一代壳的相关原理,从源码分析,到代码实现。体现了理论结合实战的方法,帮助初学者理解学习。
在本系列中,使用了3种方法加载dex文件(落地加载),从难到易,逐步理解。
这是第一篇文章,稍微会有一些枯燥,主要介绍一些在第一代加固会用到的相关类。初学者可以对其有个大概的印象,建立相关类的体系结构,后面都会用到的哦
1、总体结构
这里借用寒冰老师的图,主要是不想再画了(
该图介绍了Android ClassLoader相关的继承关系
这里我们只需要记住下面5点
- ClassLoader是抽象基础类,Android各种ClassLoader都继承他的相关属性和方法
- BaseDexClassLoader 实现了大部分CLassLoader类加载的逻辑,是PathClassLoader、DexClassLoader、InMemoryDexClassLoader的父类
- PathClassLoader 是Android默认使用的类加载器,APP中的四大组件等类由其加载,在后面我们也会用代码来验证。
- DexClassLoader 可以加载任意目录下的dex、jar、apk、zip等文件,是实现插件化、热修复和dex加壳的重点关注对象
- InMemoryDexClassLoader(Android8+)可以从内存中加载dex
下面开始分别在Android源代码中,寻找这些类(建议初学者能够自己动手寻找,加深理解)
首先,我们寻找三兄弟类(PathClassLoader、DexClassLoader、InMemoryDexClassLoader)
DexClassLoader
1 2 3 4 5 6 7 8 9 | / / 第一个参数dex文件路径 ,必填的参数 / / 第二个参数编译优化存放路径,选个app可以访问的路径即可 / / 第三个参数so库的搜索路径 ,一般为null / / 第四个参数为父classLoader,一般需要填写 53 public DexClassLoader(String dexPath, String optimizedDirectory, 54 String librarySearchPath, ClassLoader parent) { / / 调用父类:BaseDexClassLoader的构造函数 55 super (dexPath, null, librarySearchPath, parent); 56 } |
- DexClassLoader 可以加载任意目录下的dex、jar、apk、zip等文件,是实现插件化、热修复和dex加壳的重点关注对象
哦豁,我们发现,大哥DexClassLoader啥都没实现,直接用父类BaseDexClassLoader的构造函数,并传递相关的参数(源码有解释),其他兄弟是不是也是这样的呢?
PathClassLoader
我们来看看二哥 PathClassLoader
http://aospxref.com/android-13.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
PathClassLoader 是Android默认使用的类加载器,APP中的四大组件等类由其加载,在后面我们也会用代码来验证。
跟大哥一样的性格,把重担都交父类BaseDexClassLoader,传递参数并调用父类构造函数
InMemoryDexClassLoader
http://aospxref.com/android-13.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/InMemoryDexClassLoader.java
InMemoryDexClassLoader(Android8+)可以从内存中加载dex
不再废话,重担都交父类BaseDexClassLoader,传递参数并调用父类构造函数
下面介绍重量级选手,处理大部分逻辑的类
BaseDexClassLoader
http://aospxref.com/android-13.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
我们先看一下他的构造函数,他的三个儿子都在调用
构造函数
可以看到BaseDexClassLoader
有多个构造函数,我们向下找到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 150 public BaseDexClassLoader(String dexPath, 151 String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, 152 ClassLoader[] sharedLibraryLoadersAfter, 153 boolean isTrusted) { 154 super (parent); 155 / / Setup shared libraries before creating the path list . ART relies on the class loader 156 / / hierarchy being finalized before loading dex files. 157 this.sharedLibraryLoaders = sharedLibraryLoaders = = null 158 ? null 159 : Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length); 160 this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted); 161 162 this.sharedLibraryLoadersAfter = sharedLibraryLoadersAfter = = null 163 ? null 164 : Arrays.copyOf(sharedLibraryLoadersAfter, sharedLibraryLoadersAfter.length); 165 / / Run background verification after having set 'pathList' . 166 this.pathList.maybeRunBackgroundVerification(this); 167 168 reportClassLoaderChain(); 169 } |
观察一下参数,发现跟子类传递的十分相同。
1 2 3 4 | public BaseDexClassLoader(String dexPath,String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, ClassLoader[] sharedLibraryLoadersAfter, boolean isTrusted) |
这么多代码,其实我们只用关注一行代码,其他的都可以不管。
1 | this.pathList = new DexPathList(this, dexPath, librarySearchPath, null,isTrusted); |
可以发现将相关的dexPath传给了DexPathList类。
那么,我们接下来需要跟踪 DexPathList 类,看看他在搞什么。
这里区分一下:DexPathList 和 DexClassLoader 是不一样的,不要看混了哈
DexPathList
http://aospxref.com/android-13.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
DexPathList是DexClassLoader和BaseDexClassLoader等类加载器用于处理DEX文件路径的一个内部类。当使用DexClassLoader或BaseDexClassLoader加载DEX文件时,DexPathList起到了关键的作用。
DexPathList在BaseDexClassLoader的构造函数中被创建。构造DexPathList时,需要提供DEX文件的路径、优化目录、库路径以及父类加载器等参数。
1 2 3 4 5 | public BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) { super (parent); this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory); } |
内容代码那么多,我们只需要关注他的一个成员:
DexPathList有一个私有的final成员变量dexElements
,是一个Element数组,包含了所有DEX文件的Element对象,每个Element对象对应一个DEX文件。
到这里,有没有读者恍然大悟,为什么很多文章都指向了 Element 对象。这里就是原因,一个Element对象对应一个Dex文件。
Dex文件是我们加固的对象,分析逻辑的对象,能拿到Dex文件,还害怕不能分析吗?
所以,Element对象对我们来说很重要
1 2 3 4 5 6 7 8 9 | private final Element[] dexElements; public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { ... this.definingContext = definingContext; this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,suppressedExceptions); ... } |
那么,与Element对象大大相关的方法就是makeDexElements()
在DexPathList的构造函数中,会调用makeDexElements()
方法来加载DEX文件。这个方法会遍历提供的DEX文件路径列表,并为每个DEX文件创建一个Element对象,然后将这些Element对象添加到dexElements
数组中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | private static Element[] makeDexElements(ArrayList< File > files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) { / / 1. 创建Element集合 ArrayList<Element> elements = new ArrayList<Element>(); / / 2. 遍历所有dex文件(也可能是jar、apk或 zip 文件) for ( File file : files) { ZipFile zip = null; DexFile dex = null; String name = file .getName(); ... / / 如果是dex文件 if (name.endsWith(DEX_SUFFIX)) { dex = loadDexFile( file , optimizedDirectory); / / 如果是apk、jar、 zip 文件(这部分在不同的Android版本中,处理方式有细微差别) } else { zip = file ; dex = loadDexFile( file , optimizedDirectory); } ... / / 3. 将dex文件或压缩文件包装成Element对象,并添加到Element集合中 if (( zip ! = null) || (dex ! = null)) { elements.add(new Element( file , false, zip , dex)); } } / / 4. 将Element集合转成Element数组返回 return elements.toArray(new Element[elements.size()]); } |
当类加载器需要加载一个类时,会通过DexPathList的loadClass()
方法来实现。这个方法会遍历dexElements
数组中的每个Element对象,并尝试从对应的DEX文件中加载类。一旦找到需要加载的类,就会返回该类的Class对象。
同时,这里需要大家记住: Element 类是DexPathList的内部类,在后面的学习中,我们将使用反射来获取Element对象,获取的方法会有一些不同。
Element类
http://androidxref.com/9.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java#606
在DexPathList的构造函数中,会调用makeDexElements()
方法来加载DEX文件。这个方法会遍历提供的DEX文件路径列表,并为每个DEX文件创建一个Element对象,然后将这些Element对象添加到dexElements
数组中。
Element类有一个很重要的成员
1 | 613 private final DexFile dexFile; |
DexFile类,相信读者们知道,DexFile代表了一个dex文件,在脱壳加固我们都会用到dex文件。
DexFile类
Cross Reference: /libcore/dalvik/src/main/java/dalvik/system/DexFile.java (androidxref.com)
很重要的方法:getClassNameList() 通过这个方法获得 ClassLoader加载的所有类名列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 309 private static class DFEnum implements Enumeration<String> { 310 private int mIndex; 311 private String[] mNameList; 312 313 DFEnum(DexFile df) { 314 mIndex = 0 ; 315 mNameList = getClassNameList(df.mCookie); 316 } 317 318 public boolean hasMoreElements() { 319 return (mIndex < mNameList.length); 320 } 321 322 public String nextElement() { 323 return mNameList[mIndex + + ]; 324 } 325 } |
在介绍完这些常见的类(加固会用到)
我们再来学习他们的祖爷爷:ClassLoader 抽象基类
ClassLoader
http://aospxref.com/android-13.0.0_r3/xref/libcore/ojluni/src/main/java/java/lang/ClassLoader.java
CLassLoader类包含了许多公共的方法和属性,这些方法和属性,其子类都可以使用,比如:获取父加载器的parent方法
1 2 3 | 1046 public final ClassLoader getParent() { 1047 return parent; 1048 } |
还有一些属性,当然在后面文章中,我们遇到的时候再去学习,今天这篇文章先构建一个大概的逻辑。
验证四大组件由PathClassLoader加载
1 2 3 4 5 6 7 8 9 10 11 12 | public static void testClassLoader() { ClassLoader mainclassloader = MainActivity. class .getClassLoader(); ClassLoader parent = mainclassloader.getParent(); ClassLoader temp = mainclassloader; while (parent! = null){ Log.e( "MYclassloader" , "thisclassloader : " + temp + " thisclassloader_parent : " + parent); temp = parent; parent = parent.getParent(); } } |
输出日志
1 2 3 | MYclassloader com.example.myapplication E thisclassloader : dalvik.system.PathClassLoader[DexPathList[[ zip file "/data/app/com.example.myapplication-Dav-JtjREj9RrEHH64kzBA==/base.apk" ],nativeLibraryDirectories = [ / data / app / com.example.myapplication - Dav - JtjREj9RrEHH64kzBA = = / lib / x86_64, / data / app / com.example.myapplication - Dav - JtjREj9RrEHH64kzBA = = / base.apk! / lib / x86_64, / system / lib64, / system / vendor / lib64]]] thisclassloader_parent : java.lang.BootClassLoader@d613d4a |
验证了四大组件由 PathClassLoader 类加载器加载
如果一个APP没有加壳,就会由PathClassLoader类加载
为什么APP没有加壳,就由PathClassLoader加载呢?
不用着急,这个问题在后面讲解都会理解哈哈
思考
这里提出一个小问题,我们如何获得 当前ClassLoader的类名列表?
提供一个思路,串起这篇文章讲的内容:
BaseDexClassLoader的3个子类任选一个 -> BaseDexClassLoader -> DexPathList -> Element -> DexFile -> getClassNameList()
在下篇,我们将使用代码解决这个问题
文章书写不易,换您手中一个赞不过分吧^_^