首页
社区
课程
招聘
[原创]Android一代加固学习史(1)
发表于: 2024-9-12 21:33 627

[原创]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

http://aospxref.com/android-13.0.0_r3/xref/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java
图片描述

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()
在下篇,我们将使用代码解决这个问题
文章书写不易,换您手中一个赞不过分吧^_^


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 4
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//