当前位置: 首页 > news >正文

安卓逆向之脱壳-认识一下动态加载 双亲委派(二)

一:动态加载与双亲委派模型

在 Java 和 Android 中,ClassLoader 是一个非常重要的组件,负责将 .class 文件或 .dex 文件的字节码加载到内存中,供程序使用。在这其中,有两种关键的概念需要深入理解:动态加载双亲委派模型。这两者结合起来,可以帮助我们理解类加载机制、插件化框架的实现、动态代码注入等技术。

1. 双亲委派模型 (Parent Delegation Model)

双亲委派模型是 Java 类加载机制的基础,指的是在类加载器中,每个加载器都有一个父类加载器。加载器在加载类时,首先会将请求委托给父类加载器。如果父类加载器无法找到该类,再由当前类加载器进行加载。

1.1 双亲委派的流程

  1. 加载类的请求:当一个 ClassLoader 收到加载类的请求时,它不会立刻去加载该类,而是将请求委托给其父 ClassLoader
  2. 父类加载器尝试加载:父类加载器(通常是上层的 ClassLoader)会尝试去加载该类。如果父类加载器找到了该类,就返回它;如果没有找到,父类加载器会继续将请求委托给它的父类加载器,直到找到系统根类加载器(Bootstrap ClassLoader)。
  3. 当前类加载器加载:如果所有的父类加载器都没有找到该类,那么当前的 ClassLoader 才会去查找并加载这个类。
  4. 防止重复加载:这种委托模式的好处是,避免了同一个类被多个加载器加载,减少了内存消耗和类冲突问题。

1.2 双亲委派的实现

Java 类加载器的默认实现遵循双亲委派机制,ClassLoader 有两个重要的字段:

  • 父类加载器parent):即当前类加载器的父类加载器。可以通过构造函数传入。
  • loadClass() 方法:用于加载类,默认的 loadClass() 会通过调用 findClass() 实现类的加载。
public class ClassLoader {protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {Class<?> c = findLoadedClass(name); // 检查类是否已经加载if (c == null) {// 委托父加载器加载类if (parent != null) {c = parent.loadClass(name);}if (c == null) {// 如果父类加载器不能加载,则调用子类加载器的 findClass 方法来加载类c = findClass(name);}}if (resolve) {resolveClass(c); // 可选的解析过程}return c;}
}

1.3 双亲委派的优点

  • 安全性:系统核心类(如 java.lang.*java.util.*)通过 Bootstrap ClassLoader 加载,这些类不会被自定义的 ClassLoader 加载,从而保证了 Java 类库的安全性。
  • 减少重复加载:由于加载过程严格按照父类加载器先行的原则,避免了不同的 ClassLoader 加载同一类的情况。
  • 统一管理:通过父子层级关系,类加载器的管理变得更加清晰,易于维护。

2. 动态加载

动态加载是指在程序运行时,根据需要动态地加载类。Java 中的类加载是懒加载的,只有在第一次使用该类时,类才会被加载到内存中。动态加载通常是在运行时根据特定条件来加载不同的类、插件或模块。

2.1 动态加载的基本方式

Java 提供了反射机制,可以通过动态地加载类来实现:

  • Class.forName():这是最常用的动态加载方式。它通过类的完全限定名(即 package.ClassName)来加载类。
Class<?> clazz = Class.forName("com.example.MyClass");
Object obj = clazz.newInstance(); // 创建对象
  • ClassLoader.loadClass():通过自定义的 ClassLoader 加载类。
ClassLoader classLoader = MyClass.class.getClassLoader();
Class<?> clazz = classLoader.loadClass("com.example.MyClass");
  • DexClassLoader:在 Android 中,通常使用 DexClassLoader 来加载外部的 dex 文件,实现动态加载。它可以加载从外部传入的 .dex 文件或 APK 中的 dex 文件。
DexClassLoader dexClassLoader = new DexClassLoader(dexFile, optimizedDirectory, null, parentClassLoader);
Class<?> clazz = dexClassLoader.loadClass("com.example.MyClass");

2.2 动态加载的应用场景

  • 插件化框架:如 RePluginDroidPlugin,允许应用在运行时加载插件,用户可以在不重新安装应用的情况下更新功能模块。
  • 热修复:如 TinkerSophix,通过动态加载修复包,允许开发者修复已经发布的应用中的 bug。
  • 动态生成与执行代码:比如使用 Java Compiler APIJVM 来动态编译和加载代码。

3. 动态加载与双亲委派的关系

动态加载和双亲委派模型密切相关。在动态加载的过程中,虽然我们动态地加载类,但是这个过程依然是由 ClassLoader 完成的。通过 双亲委派模型,父类加载器会优先尝试加载标准的系统类(如 java.lang.*),而自定义的类加载器则可以动态加载其他模块。

3.1 自定义 ClassLoader 与动态加载

自定义 ClassLoader 通常用于插件化框架中,它允许我们根据特定的需求,动态加载外部的 dex 文件或插件包。在实现自定义 ClassLoader 时,我们通常需要做两件事:

  • 处理父类委托问题:如果插件中有类与系统类或主应用类冲突,需要通过委托机制避免重复加载。
  • 动态加载插件:使用自定义的 ClassLoader 加载插件中的 .dex 文件或 .jar 文件。
public class PluginClassLoader extends ClassLoader {private String pluginDexPath;public PluginClassLoader(String pluginDexPath, ClassLoader parent) {super(parent); // 使用系统默认的类加载器作为父加载器this.pluginDexPath = pluginDexPath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {byte[] classData = loadClassData(name);if (classData == null) {throw new ClassNotFoundException("Class not found: " + name);}return defineClass(name, classData, 0, classData.length);}private byte[] loadClassData(String className) {// 逻辑:从插件的 dex 文件中读取字节码String classFilePath = pluginDexPath + "/" + className.replace('.', '/') + ".dex";// 读取文件内容并返回字节数据}
}

4. 自定义 ClassLoader 和双亲委派的结合

在自定义 ClassLoader 时,通常需要考虑如何处理父类加载器与插件类加载器的关系。一个常见的做法是通过 双亲委派 机制来确保父类加载器加载系统核心类,而自定义的类加载器只负责加载外部插件。

4.1 双亲委派的自定义实现

我们可以通过重写 loadClass() 方法来确保自定义类加载器按照双亲委派模式加载类。这样,系统类总是由父类加载器加载,而插件类由自定义的类加载器加载。

public class MyClassLoader extends ClassLoader {public MyClassLoader(ClassLoader parent) {super(parent); // 设置父加载器}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {// 先委托给父类加载器Class<?> clazz = findLoadedClass(name);if (clazz == null) {try {clazz = getParent().loadClass(name); // 委托给父加载器} catch (ClassNotFoundException e) {// 如果父加载器不能加载,则自己加载类clazz = findClass(name);}}return clazz;}
}

5. 总结

双亲委派模型是 Java 类加载机制的核心。它通过父子 ClassLoader 的委托机制,保证了类加载的安全性和高效性。父类加载器负责加载系统核心类,子类加载器负责加载用户定义的类或插件。

二:验证类加载器与双亲委派机制

在 Java 和 Android 中,类加载器遵循双亲委派机制。这种机制确保了系统类(如 java.lang.*android.*)通过核心加载器(Bootstrap ClassLoader)加载,而应用程序自定义的类通过 PathClassLoaderDexClassLoader 等加载器加载。为了理解并验证这个机制,通常需要检查类加载器的工作方式和委托行为。

以下是如何通过代码验证类加载器与双亲委派机制的基本流程。

1. 了解双亲委派模型

双亲委派模型的基本原则是:

  • 父类优先:当一个类加载器加载一个类时,它会先委托给父类加载器。只有当父类加载器找不到该类时,当前类加载器才会尝试加载该类。
  • 系统类加载器(Bootstrap ClassLoader):负责加载核心 Java 类(如 java.lang.*)。
  • 应用类加载器(PathClassLoader):加载应用的 .dex 文件或 .jar 包。
  • 插件类加载器(DexClassLoader):用于加载外部插件模块,尤其是 .dex 文件。

2. 验证类加载器类型

可以通过 ClassLoadergetClassLoader() 方法验证加载当前类的加载器类型。同时,可以通过 getParent() 方法检查当前类加载器的父类加载器。

2.1 验证类加载器类型

你可以编写如下代码来验证当前类是由哪个加载器加载的:

public class ClassLoaderTest {public static void main(String[] args) {// 获取当前类的加载器ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();// 输出类加载器的类型System.out.println("Current ClassLoader: " + classLoader);// 获取当前类加载器的父类加载器ClassLoader parentLoader = classLoader.getParent();System.out.println("Parent ClassLoader: " + parentLoader);// 获取父类加载器的父类加载器ClassLoader grandParentLoader = parentLoader.getParent();System.out.println("Grandparent ClassLoader: " + grandParentLoader);// 验证 BootClassLoader(父类加载器的父类加载器)System.out.println("Bootstrap ClassLoader: " + grandParentLoader);}
}

2.2 输出结果

根据代码的输出,我们可以看到:

Current ClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
Parent ClassLoader: sun.misc.Launcher$ExtClassLoader@74a14482
Grandparent ClassLoader: null
Bootstrap ClassLoader: null
  • Current ClassLoader 是应用的类加载器,通常是 AppClassLoader(对于普通的 Java 应用)。
  • Parent ClassLoader 是扩展类加载器(ExtClassLoader),它加载 JRE 扩展库(例如 rt.jar)。
  • Grandparent ClassLoadernull,说明它是 Bootstrap ClassLoader(Java 的根类加载器),它负责加载 JDK 核心类库。
  • 在 Java 中,Bootstrap ClassLoader 是由 JVM 本身实现的,不能通过普通的 Java 代码直接访问。

3. 验证双亲委派机制

为了验证双亲委派机制,我们可以通过自定义 ClassLoader 来修改或跟踪委派行为。我们可以重写 loadClass() 方法,并打印加载过程中的信息。

3.1 自定义 ClassLoader 来验证委派机制

以下代码演示了如何通过自定义 ClassLoader 来验证双亲委派的行为:

public class MyClassLoader extends ClassLoader {public MyClassLoader(ClassLoader parent) {super(parent);  // 设置父加载器}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {// 打印加载过程信息System.out.println("Attempting to load class: " + name);// 先检查类是否已经加载Class<?> c = findLoadedClass(name);if (c == null) {// 委托父加载器加载try {System.out.println("Delegating to parent class loader: " + name);c = getParent().loadClass(name);  // 委托给父类加载器} catch (ClassNotFoundException e) {System.out.println("Parent class loader couldn't load " + name);}// 如果父加载器不能加载,则尝试自己加载if (c == null) {System.out.println("Loading class by current class loader: " + name);c = findClass(name);  // 使用当前类加载器加载}}return c;}
}

3.2 使用自定义 ClassLoader

在测试过程中,我们可以使用自定义的 MyClassLoader 来加载一个类(如 java.lang.String),以验证委派机制:

public class ClassLoaderTest {public static void main(String[] args) {try {MyClassLoader customLoader = new MyClassLoader(ClassLoaderTest.class.getClassLoader());// 使用自定义 ClassLoader 加载 java.lang.String 类Class<?> clazz = customLoader.loadClass("java.lang.String");System.out.println("Class loaded: " + clazz.getName());} catch (ClassNotFoundException e) {e.printStackTrace();}}
}

3.3 输出结果

运行上述代码时,输出可能如下所示:

Attempting to load class: java.lang.String
Delegating to parent class loader: java.lang.String
Class loaded: java.lang.String

在这个例子中:

  • Attempting to load class:自定义 ClassLoader 首先尝试加载类。
  • Delegating to parent class loader:在未找到该类后,它将加载请求委托给父类加载器。这里的父加载器是 AppClassLoader
  • 最终,类 java.lang.String 是由 Bootstrap ClassLoader(Java 核心类加载器)加载的,因为 String 是 Java 核心类之一,属于 Bootstrap ClassLoader 的范围。

4. 验证双亲委派机制的关键点

  • 系统核心类:类如 java.lang.Stringjava.util.* 等核心类会始终通过 Bootstrap ClassLoader 加载,不会被应用类加载器(如 PathClassLoaderDexClassLoader)加载。
  • 应用类和自定义类:任何在应用程序中自定义或加载的类,都会通过 PathClassLoader(或其他自定义的 ClassLoader)加载。
  • 委派机制的验证:当我们自定义 ClassLoader 时,通过重写 loadClass() 方法,可以显式看到父类加载器如何处理类加载请求。它首先会委托给父加载器,父加载器未找到时,当前加载器再尝试加载。

5. 双亲委派与动态加载结合

双亲委派模型和动态加载经常结合在一起。自定义的 ClassLoader 可以通过双亲委派机制来动态加载外部插件或模块。这种机制特别适用于插件化框架或热修复技术(如 Android 热修复)。

  • 在插件化框架中,插件的类通常会通过自定义的 ClassLoader 加载,但系统核心类和应用核心类始终会由父类加载器加载。
  • 在热修复中,我们通常使用 DexClassLoader 来动态加载外部的 .dex 文件,而这些文件不会影响到系统核心类的加载。

6. 总结

  • 双亲委派模型:这是 Java 类加载器的核心设计模式,确保系统核心类总是由 Bootstrap ClassLoader 加载,而自定义类通过应用类加载器加载。
  • 验证类加载器类型:可以通过 getClassLoader()getParent() 方法来验证当前类加载器及其父加载器的类型。
  • 自定义类加载器的委派:通过自定义 ClassLoader 并重写 loadClass() 方法,可以验证类加载器如何委派类加载请求。
  • 动态加载与双亲委派:动态加载可以与双亲委派机制结合使用,特别是在插件化和热修复等场景中,保证了系统核心类的安全性和灵活的插件管理。

通过上述的验证过程,我们能够深入理解 Java 类加载器的工作原理,特别是双亲委派机制如何影响类的加载流程。

三:获取类加载器中的类列表

在 Java 中,ClassLoader 负责加载类,但 ClassLoader 本身并没有提供直接的 API 来列出它加载的所有类。这是因为 Java 的类加载机制并不像文件系统那样直接暴露所有加载的类的列表。不过,你可以通过一些方法来间接实现这个需求。

1. 方法 1: 利用 ClassLoader 的自定义实现来追踪加载的类

一种常见的方法是通过自定义 ClassLoader 来追踪它加载的类。我们可以重写 findClassloadClass 方法,在每次加载类时记录下加载的类名。

示例:通过自定义 ClassLoader 记录加载的类

import java.util.HashSet;
import java.util.Set;public class MyClassLoader extends ClassLoader {private Set<String> loadedClasses;public MyClassLoader(ClassLoader parent) {super(parent);loadedClasses = new HashSet<>();}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {// 在加载类之前记录loadedClasses.add(name);// 实际加载类的逻辑byte[] classData = loadClassData(name);return defineClass(name, classData, 0, classData.length);}public Set<String> getLoadedClasses() {return loadedClasses;}private byte[] loadClassData(String className) {// 这里是简化的,实际加载字节码的代码可以通过文件、网络等方式获取return new byte[0];  // 实际应用中需要实现字节码加载的过程}
}

使用自定义 ClassLoader

public class ClassLoaderTest {public static void main(String[] args) throws ClassNotFoundException {MyClassLoader myClassLoader = new MyClassLoader(ClassLoaderTest.class.getClassLoader());// 动态加载一些类myClassLoader.loadClass("java.lang.String");myClassLoader.loadClass("java.util.ArrayList");// 获取加载的类System.out.println("Loaded classes: " + myClassLoader.getLoadedClasses());}
}

输出结果:

Loaded classes: [java.lang.String, java.util.ArrayList]

2. 方法 2: 利用反射来间接获取加载的类

尽管 ClassLoader 没有提供直接列出所有加载类的方法,但你可以通过反射和一些工具库来遍历类路径下的所有类,然后根据当前的类加载器验证是否已加载。

  • 使用 ClassLoader.getResources():列出某个包路径下的所有类资源,并通过类加载器判断哪些类已经被加载。
  • 使用工具类扫描类路径:通过使用像 ReflectionsClassGraph 这样的库,可以扫描类路径下的所有类,然后过滤出当前类加载器已经加载的类。

使用 Reflections 库来扫描类

Reflections 是一个 Java 库,用于扫描类路径,查找注解、继承关系等。通过 Reflections,可以扫描类并判断这些类是否由特定的 ClassLoader 加载。

import org.reflections.Reflections;import java.util.Set;public class ClassLoaderTest {public static void main(String[] args) {// 使用 Reflections 库来扫描指定包下的所有类Reflections reflections = new Reflections("java.lang");Set<Class<?>> allClasses = reflections.getSubTypesOf(Object.class);// 输出所有找到的类for (Class<?> clazz : allClasses) {System.out.println(clazz.getName());}}
}

输出结果:

java.lang.String
java.lang.Integer
java.lang.Double
...

3. 方法 3: 遍历 ClassLoader 加载的类

ClassLoader 本身并不保存已加载的类列表,因此没有直接方法可以遍历已经加载的类。但是你可以通过 JNI 或 JDK 内部的特定 API 来获取 ClassLoader 加载的所有类。

使用 Instrumentation 获取已加载的类

Java 提供了 Instrumentation 接口,它允许你在运行时获取已加载的所有类。通常,它用于 Java Agent 编程。

步骤:

  1. 创建一个 Java Agent。
  2. 使用 Instrumentation 获取已加载的类。
import java.lang.instrument.Instrumentation;public class Agent {public static void premain(String agentArgs, Instrumentation inst) {// 获取所有已加载的类Class[] loadedClasses = inst.getAllLoadedClasses();// 打印已加载的类for (Class clazz : loadedClasses) {System.out.println(clazz.getName());}}
}

pom.xml 文件中配置 agent:

<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.1.0</version><executions><execution><goals><goal>jar</goal></goals></execution></executions>
</plugin>

运行时,Java Agent 将会列出所有已加载的类。

获取 Instrumentation 对象

Instrumentation 的获取方式通常需要在启动应用时通过 -javaagent 参数来指定 Agent:

java -javaagent:/path/to/agent.jar -jar yourapp.jar

4. 总结

  • 自定义 ClassLoader:通过自定义 ClassLoader,我们可以记录类加载过程,并追踪加载的类名。通过重写 findClass()loadClass() 方法,记录加载的类信息。
  • Reflections:通过 Reflections 库,可以扫描类路径,找到所有符合条件的类,然后根据需要筛选出已经被加载的类。
  • Instrumentation:通过创建 Java Agent,可以获取到所有已加载的类。此方法通常用于应用程序级的监控和诊断。

这些方法提供了不同的手段来获取类加载器加载的类列表。选择哪种方式取决于你的实际需求,比如是否希望通过自定义加载器进行记录,还是通过类路径扫描或 Java Agent 进行检测。

四:类加载 API 与脱壳

在逆向工程和安卓安全领域,脱壳(Unpacking)是指从被加壳或混淆的 APK 或 DEX 文件中恢复出原始的、未经保护的代码。这通常是为了进行调试、分析或研究应用程序的行为。在这个过程中,类加载 API类加载器机制 扮演着重要角色,因为它们直接影响到类的加载过程,特别是在加壳和混淆的情况下。

1. 类加载 API 和类加载器机制

Java 和 Android 使用 类加载器(ClassLoader 来动态加载 .class.dex 文件中的类。类加载器通过网络、文件系统、或者内存等方式加载字节码,在运行时将字节码转换为内存中的类对象。

1.1 Java 类加载器(ClassLoader) 的主要 API

  • ClassLoader.loadClass(String name):这是加载一个类的标准方法。它会委托父类加载器加载类,如果父类加载器不能加载,则由当前类加载器加载。
  • ClassLoader.getResource(String name):返回资源的 URL。资源可以是类文件、配置文件等。
  • ClassLoader.findClass(String name):用于实际查找和加载类的字节码(通常是由 loadClass() 调用)。
  • defineClass(String name, byte[] b, int off, int len):用于定义一个类,将字节数组转换为类对象。
  • getParent():获取当前类加载器的父类加载器。

1.2 Android 中的 ClassLoader

Android 中的 ClassLoader 与 Java 的 ClassLoader 类似,但针对 Android 特有的 .dex 文件做了适配。常见的 ClassLoader 类型包括:

  • PathClassLoader:主要用于加载 APK 中的 .dex 文件。
  • DexClassLoader:允许加载外部的 .dex 文件,比如插件化框架会使用这个加载器加载插件。
  • BaseDexClassLoaderPathClassLoaderDexClassLoader 都继承自它,是 Android 5.0(Lollipop)之后引入的类。

1.3 如何利用 ClassLoader 进行类加载

通常,类加载的流程如下:

  1. 当需要加载一个类时,ClassLoader.loadClass() 被调用。
  2. 先尝试在当前类加载器中查找类,如果未找到,则会委托给父类加载器。
  3. findClass() 方法用于从指定的路径或字节码数据中找到并定义类。
  4. defineClass() 方法将字节数据转换为类对象,最终加载到内存。

2. 脱壳过程中的类加载器机制

在应用进行加壳保护时,保护代码通常会采取混淆、加密、压缩等手段对原始的 APK 或 DEX 进行“包装”。这样,恶意软件分析师就无法直接查看 APK 内的原始代码,防止静态分析。脱壳过程中,关键的挑战之一是正确加载和解密这些被加壳的类,并绕过类加载器的保护措施。

2.1 加壳的常见手段

  • 加密或压缩 DEX 文件:将原始的 .dex 文件加密或压缩,防止直接读取类字节码。加密或压缩后的 .dex 文件通常通过自定义的 ClassLoader 进行动态解密或解压。
  • 混淆类名和方法名:混淆工具(如 ProGuard 或 R8)通常会将类名和方法名替换为随机的字符串,从而使静态分析变得困难。
  • 动态加载 DEX 文件:加壳保护可能会将 DEX 文件存放在加密的资源中,并通过自定义 ClassLoader 动态加载、解密这些 DEX 文件。

2.2 通过自定义 ClassLoader 进行脱壳

脱壳时,可以使用 自定义 ClassLoader 来动态加载 DEX 文件并绕过加壳保护。一般来说,脱壳的过程是动态加载未加密或未加壳的 DEX 文件,逐步解密或解压后加载类。

2.2.1 自定义 ClassLoader 示例

public class MyDexClassLoader extends ClassLoader {private String dexFilePath;private File dexFile;public MyDexClassLoader(String dexFilePath, ClassLoader parent) {super(parent);this.dexFilePath = dexFilePath;this.dexFile = new File(dexFilePath);}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {// 通过自定义类加载器加载已解密的 DEX 文件try {byte[] classBytes = loadClassBytes(name);return defineClass(name, classBytes, 0, classBytes.length);} catch (IOException e) {return super.loadClass(name);}}private byte[] loadClassBytes(String className) throws IOException {// 假设类已经被解密或解压到 dexFile 中,可以直接读取并加载String classPath = dexFilePath + "/" + className.replace(".", "/") + ".dex";FileInputStream fis = new FileInputStream(classPath);byte[] classData = new byte[fis.available()];fis.read(classData);fis.close();return classData;}
}

2.2.2 加载 DEX 文件

在脱壳过程中,可能需要通过解密、解压等方式将被加壳的 DEX 文件恢复为原始的 .dex 格式。然后使用 DexClassLoader自定义 ClassLoader 来加载这些 DEX 文件中的类。

DexClassLoader dexClassLoader = new DexClassLoader(dexFile.getAbsolutePath(),getDir("dex", MODE_PRIVATE).getAbsolutePath(),null,getClassLoader()
);
Class<?> clazz = dexClassLoader.loadClass("com.example.MyClass");
Object instance = clazz.newInstance();

在此过程中,DexClassLoader 会通过加载解密后的 DEX 文件,将其中的类加载到 JVM 或 Android 的虚拟机中。

2.3 绕过类加载保护

一些加壳技术可能会在加载类时进行特殊保护,导致传统的类加载方法无法加载被加密或加壳的类。为了绕过这些保护,可以:

  • 分析并修改类加载器:例如,通过 hook 或修改加壳应用的 ClassLoader,替换默认的类加载器为自定义类加载器,从而绕过加密或压缩过程。
  • 动态注入代码:使用 Xposed 或类似框架动态注入代码,修改应用中的类加载行为。
  • 静态分析和修改 APK:使用反编译工具(如 JADXapktool)静态分析 APK 内容,找到加壳的入口点,然后修改代码或资源,恢复原始文件。

3. 脱壳过程中可能遇到的难点

  • 反混淆:在脱壳过程中,混淆的类名和方法名可能会增加分析难度。需要通过符号表、调试、动态分析等手段逆向还原原始名称。
  • 加密或压缩的 DEX 文件:有些应用可能会对 DEX 文件进行加密或压缩,解密和解压过程可能会依赖于运行时的动态加载器,分析时需要模拟和解密这些步骤。
  • 自定义 ClassLoader 的处理:有些加壳应用使用自定义的 ClassLoader 进行类加载,动态解密或解压 DEX 文件。这时需要通过 hook 或修改代码来绕过这个自定义的加载过程。

4. 总结

  • 类加载 API(如 ClassLoader.loadClass()DexClassLoader 等)是 Java 和 Android 应用加载类的核心机制。通过这些 API,可以加载加密、压缩或动态生成的类。
  • 脱壳过程中,需要关注如何绕过加壳保护(如解密 DEX 文件、恢复混淆等)。自定义 ClassLoader 是常用的手段,能够帮助动态加载解密或解压后的类。
  • 动态分析静态分析 都是逆向过程中脱壳的有效方法。逆向工具(如 JADXXposed)可以帮助理解加壳保护的实现,进而进行脱壳。

通过上述方法,你可以有效地使用 类加载器脱壳技术,绕过加壳保护,恢复原始的应用程序代码,以进行进一步的分析。

相关文章:

安卓逆向之脱壳-认识一下动态加载 双亲委派(二)

一&#xff1a;动态加载与双亲委派模型 在 Java 和 Android 中&#xff0c;ClassLoader 是一个非常重要的组件&#xff0c;负责将 .class 文件或 .dex 文件的字节码加载到内存中&#xff0c;供程序使用。在这其中&#xff0c;有两种关键的概念需要深入理解&#xff1a;动态加载…...

全程Kali linux---CTFshow misc入门(14-24)

第十四题&#xff1a; dd命令&#xff1a;dd是一个用于复制和转换数据的命令&#xff0c;它可以对文件、设备等进行操作&#xff0c;在数据备份、转换格式等场景经常使用。 ifmisc14.jpg&#xff1a;if表示 “input file”&#xff08;输入文件&#xff09;&#xff0c;这里指…...

学习数据结构(3)顺序表

1.动态顺序表的实现 &#xff08;1&#xff09;初始化 &#xff08;2&#xff09;扩容 &#xff08;3&#xff09;头部插入 &#xff08;4&#xff09;尾部插入 &#xff08;5&#xff09;头部删除 &#xff08;这里注意要保证有效数据个数不为0&#xff09; &#xff08;6&a…...

知识体系、知识管理角度的赚钱思考

从知识管理和知识体系的角度出发&#xff0c;赚钱的问题思考清单可以帮助你系统地梳理和优化自己在财富创造方面的策略。 以下是一个详细的清单&#xff0c;涵盖从知识获取、技能提升到实际应用的各个环节&#xff0c;帮助你在赚钱的道路上更加高效和有条理。 一、赚钱的目标与…...

(done) ABI 相关知识补充:内核线程切换、用户线程切换、用户内核切换需要保存哪些寄存器?

由于操作系统和编译器约定了 ABI&#xff0c;如下&#xff1a; 编译器在对 C 语言编译时&#xff0c;会自动 caller 标注的寄存器进行保存恢复。保存的步骤通常发生在进入函数的时候&#xff0c;恢复的步骤通常发生在从函数返回的时候。 内核线程切换需要保存的寄存器&#…...

QT6 + CMAKE编译OPENCV3.9

参考文档 [1] https://blog.csdn.net/rjkf_css/article/details/135676077 前提条件 配置好相关运行环境&#xff1a;QT6、OPENCV3.9的sources文件 OPENCV下载网页&#xff1a;https://opencv.org/releases/ QT6下载教程&#xff1a;https://blog.csdn.net/caoshangpa/article…...

Linux 常用命令——系统设置篇(保姆级说明)

系统设置类 显示当前运行的进程&#xff08;ps&#xff09; ps [options] [--help]# 查找指定进程格式&#xff1a; ps -ef | grep 进程关键字# 显示进程信息 ps -A 参数&#xff1a; -A 列出所有的进程 -w 显示加宽可以显示较多的资讯 -au 显示较详细的资讯 -aux 显示所有包…...

完美世界前端面试题及参考答案

如何设置事件捕获和事件冒泡? 在 JavaScript 中,可以通过addEventListener方法来设置事件捕获和事件冒泡。该方法接收三个参数,第一个参数是事件类型,如click、mousedown等;第二个参数是事件处理函数;第三个参数是一个布尔值,用于指定是否使用事件捕获机制。当这个布尔值…...

Vue3笔记——(三)hooks、路由

015 hooks 作用&#xff1a;使得代码更加模块化和可维护 Person.vue <template><div><h2>当前求和{{ sum }}</h2><button click"addFn">点我sum1</button></div> </template> <script setup lang"ts"…...

网络安全大模型和人工智能场景及应用理解

本文通过通俗易懂的方式的进行阐述&#xff0c;大家读完觉得有帮助记得及时关注和点赞&#xff01;&#xff01;&#xff01; 一、网络安全大模型的概述 网络安全大模型是一种用于识别和应对各种网络安全威胁的模型。它通过分析网络数据包、网络行为等信息&#xff0c;识别潜在…...

python-leetcode-从中序与后序遍历序列构造二叉树

106. 从中序与后序遍历序列构造二叉树 - 力扣&#xff08;LeetCode&#xff09; # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right r…...

NLP模型大对比:Transformer > RNN > n-gram

结论 Transformer 大于 RNN 大于 传统的n-gram n-gram VS Transformer 我们可以用一个 图书馆查询 的类比来解释它们的差异&#xff1a; 一、核心差异对比 维度n-gram 模型Transformer工作方式固定窗口的"近视观察员"全局关联的"侦探"依赖距离只能看前…...

MySQL查询优化(三):深度解读 MySQL客户端和服务端协议

如果需要从 MySQL 服务端获得很高的性能&#xff0c;最佳的方式就是花时间研究 MySQL 优化和执行查询的机制。一旦理解了这些&#xff0c;大部分的查询优化是有据可循的&#xff0c;从而使得整个查询优化的过程更有逻辑性。下图展示了 MySQL 执行查询的过程&#xff1a; 客户端…...

[STM32 - 野火] - - - 固件库学习笔记 - - -十三.高级定时器

一、高级定时器简介 高级定时器的简介在前面一章已经介绍过&#xff0c;可以点击下面链接了解&#xff0c;在这里进行一些补充。 [STM32 - 野火] - - - 固件库学习笔记 - - -十二.基本定时器 1.1 功能简介 1、高级定时器可以向上/向下/两边计数&#xff0c;还独有一个重复计…...

Antd React Form使用Radio嵌套多个Select和Input的处理

使用Antd React Form使用Radio会遇到嵌套多个Select和Input的处理&#xff0c;需要多层嵌套和处理默认事件和冒泡&#xff0c;具体实现过程直接上代码。 实现效果布局如下图 代码 <Formname"basic"form{form}labelWrap{...formItemLayoutSpan(5, 19)}onFinish{on…...

固有频率与模态分析

目录 引言 1. 固有频率&#xff1a;物体的“天生节奏” 1.1 定义 1.2 关键特点 1.3 实际意义 2. 有限元中的模态分析&#xff1a;给结构“体检振动” 2.1 模态分析的意义 2.2 实际案例 2.2.1 桥梁模态分析 2.2.2 飞机机翼模态分析 2.2.3 具体事例 3. 模态分析的工具…...

视频多模态模型——视频版ViT

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细解读多模态论文《ViViT: A Video Vision Transformer》&#xff0c;2021由google 提出用于视频处理的视觉 Transformer 模型&#xff0c;在视频多模态领域有…...

2859.计算K置位下标对应元素的和

示例 1&#xff1a;输入&#xff1a;nums [5,10,1,5,2], k 1 输出&#xff1a;13 解释&#xff1a;下标的二进制表示是&#xff1a; 0 0002 1 0012 2 0102 3 0112 4 1002 下标 1、2 和 4 在其二进制表示中都存在 k 1 个置位。 因此&#xff0c;答案为 nums[1] nums[…...

Redis 教程

REmote DIctionary Server(Redis) 是一个由Salvatore Sanfilippo写的key-value存储系统。 Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 它通常被称为数据结构服务器&…...

2024 CVPR Highlight Learning-Feedback

图像增强 Towards Robust Event-guided Low-Light Image Enhancement: A Large-Scale Real-World Event-Image Dataset and Novel Approach 解决的主要问题是低光照条件下的图像增强 通过多尺度整体融合分支提取事件和图像的结构和纹理信息&#xff0c;并引入信噪比&#xff0…...

maven、npm、pip、yum官方镜像修改文档

文章目录 Maven阿里云网易华为腾讯云 Npm淘宝腾讯云 pip清华源阿里中科大华科 Yum 由于各博客繁杂&#xff0c;本文旨在记录各常见镜像官网&#xff0c;及其配置文档。常用镜像及配置可评论后加入 Maven 阿里云 官方文档 setting.xml <mirror><id>aliyunmaven&l…...

UE求职Demo开发日志#15 思路与任务梳理、找需要的资源

1 思路梳理 因为有点无从下手&#xff0c;就梳理下最终形态. 基地的建设我是想单独一个场景&#xff0c;同一个关卡中小怪会每次来都会刷&#xff0c;小解密一次性的&#xff0c;关键的Boss和精英怪不会重复刷&#xff0c;同时场景里放一些资源可收集&#xff0c;基地建设锁定区…...

设置jmeter外观颜色

设置jmeter外观颜色 方法&#xff1a; 步骤一、点击顶部选项 ->外观&#xff0c;这里提供了不同的主题&#xff0c;可选自己喜欢的风格。 步骤二、选择后&#xff0c;弹框提示点击Yes。...

《一文读懂!Q-learning状态-动作值函数的直观理解》

在人工智能的强化学习领域&#xff0c;Q-learning算法是一颗耀眼的明星&#xff0c;被广泛应用于机器人控制、游戏AI开发、自动驾驶等诸多前沿领域。而想要真正掌握Q-learning算法&#xff0c;理解其核心概念——状态 - 动作值函数&#xff0c;是绕不开的关键一步。这篇文章就带…...

Angular 2 表单深度解析

Angular 2 表单深度解析 引言 Angular 2作为现代前端开发的框架之一,以其灵活性和强大的功能赢得了众多开发者的青睐。在Angular 2中,表单处理是其中一个重要且复杂的部分。本文将深入解析Angular 2的表单,从基础知识到高级应用,旨在帮助开发者更好地理解和运用Angular 2…...

使用kitty terminal遇到的‘xterm-kitty‘: unknown terminal type.

解决办法 方式一 export TERMxterm-256color使永久生效 echo export TERMxterm-256color >> ~/.zshrc # 如果用 zsh&#xff0c;如果使用的是bash就修改为bashrc source ~/.zshrc #同理如果是ssh下遇到该问题&#xff0c;参考 https://sw.kovidgoyal.net/kitty/faq/…...

Dest1ny漏洞库:用友 U8 Cloud ReleaseRepMngAction SQL 注入漏洞(CNVD-2024-33023)

大家好&#xff0c;今天是Dest1ny漏洞库的专题&#xff01;&#xff01; 会时不时发送新的漏洞资讯&#xff01;&#xff01; 大家多多关注&#xff0c;多多点赞&#xff01;&#xff01;&#xff01; 0x01 产品简介 用友U8 Cloud是用友推出的新一代云ERP&#xff0c;主要聚…...

代码随想录|动态规划 300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组

300.最长递增子序列 题目 参考文章 思路&#xff1a;dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度 因为没有连续&#xff0c;所以每一个元素都要比较&#xff0c;从而得到结果 两个递增子序列一定分别以nums[j]为结尾 和 nums[i]为结尾&#xff0c; 要不然这个比…...

Flutter_学习记录_导航和其他

Flutter 的导航页面跳转&#xff0c;是通过组件Navigator 和 组件MaterialPageRoute来实现的&#xff0c;Navigator提供了很多个方法&#xff0c;但是目前&#xff0c;我只记录我学习过程中接触到的方法&#xff1a; Navigator.push(), 跳转下一个页面Navigator.pop(), 返回上一…...

【信息系统项目管理师-选择真题】2006下半年综合知识答案和详解

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 【第1题】【第2题】【第3题】【第4题】【第5题】【第6题】【第7题】【第8题】【第9题】【第10题】【第11题】【第12题】【第13题】【第14题】【第15题】【第16题】【第17题】【第18题】【第19题】【第20题】【第…...

面向对象编程简史

注&#xff1a;本文为 “面向对象编程简史” 相关文章合辑。 英文引文&#xff0c;机翻未校。 Brief history of Object-Oriented Programming 面向对象编程简史 Tue, May 14, 2024 Throughout its history, object-oriented programming (OOP) has undergone significant …...

快速提升网站收录:内容创作的艺术

本文来自&#xff1a;百万收录网 原文链接&#xff1a;https://www.baiwanshoulu.com/15.html 快速提升网站收录&#xff0c;内容创作是关键。以下是一些关于内容创作以提升网站收录的艺术性建议&#xff1a; 一、关键词研究与优化 选择长尾关键词&#xff1a;进行深入的关键…...

高速PCB设计指南3——PCB 传输线和受控阻抗

高速PCB设计指南3——PCB 传输线和受控阻抗 1. 传输线1.1 传输线的定义1.2 传输线的分类1.3 互为传输线的情况 2. 均匀传输线的特性阻抗3. PCB中受控阻抗结构3.1 微带线&#xff08;Microstrip&#xff09;3.2 带状线&#xff08;Stripline&#xff09; 4 阻抗控制方法5. 阻抗控…...

python实现一个完整的智能教室能耗监测与管理系统的实现方案

以下是一个完整的智能教室能耗监测与管理系统的实现方案&#xff0c;主要使用Python语言&#xff0c;涵盖深度学习模型研发、教室场景适应性分析、系统架构设计、前端展示、后端服务以及测试评估等方面。 1. 数据准备 首先&#xff0c;需要收集教室的照片数据集&#xff0c;并…...

【新春特辑】2025年春节技术展望:蛇年里的科技创新与趋势预测

&#x1f525;【新春特辑】2025年春节技术展望&#xff1a;蛇年里的科技创新与趋势预测 &#x1f4c5; 发布日期&#xff1a;2025年01月29日&#xff08;大年初一&#xff09; 在这个辞旧迎新的美好时刻&#xff0c;我们迎来了充满希望的2025年&#xff0c;也是十二生肖中的蛇…...

woocommerce独立站与wordpress独立站的最大区别是什么

WooCommerce独立站与WordPress独立站的最大区别在于它们的功能定位和使用场景。 WordPress是一个开源的内容管理系统(CMS)&#xff0c;最初是作为博客平台发展起来的&#xff0c;但现在已经演变为一个功能丰富的网站构建工具。它主要用于创建动态网站&#xff0c;提供广泛的定…...

docker安装MySQL8:docker离线安装MySQL、docker在线安装MySQL、MySQL镜像下载、MySQL配置、MySQL命令

一、镜像下载 1、在线下载 在一台能连外网的linux上执行docker镜像拉取命令 docker pull mysql:8.0.41 2、离线包下载 两种方式&#xff1a; 方式一&#xff1a; -&#xff09;在一台能连外网的linux上安装docker执行第一步的命令下载镜像 -&#xff09;导出 # 导出镜…...

PTA乙级1006~1010【c++】

1006 换个格式输出整数 #include <iostream> using namespace std;int main(){int n;cin >> n;int b n / 100;int s n / 10 % 10;int g n % 10;for (int i 0; i < b; i ) cout << B;for (int i 0; i < s; i ) cout << S;for (int i 0; …...

一文掌握ADB的安装及使用

文章目录 一、什么是ADB&#xff1f;二、 安装ADB2.1 下载ADB2.2 配置环境变量 三、连接Android设备四、 常用ADB命令五、ADB高级功能5.1 屏幕截图和录制5.2 模拟按键输入5.3 文件管理5.4 系统设置管理5.5 系统操作指令5.6 日志操作指令5.7 APK操作指令5.8 设备重启和恢复 六、…...

ThinkPad E480安装Ubuntu 18.04无线网卡驱动

个人博客地址&#xff1a;ThinkPad E480安装Ubuntu 18.04无线网卡驱动 | 一张假钞的真实世界 遗憾的是虽然下面的方法可以解决&#xff0c;但是内核升级后需要重新安装。 基本信息 Ubuntu 18.04ThinkPad E480使用下面的命令查看 Linux 内核&#xff1a; $ uname -r 5.0.0-3…...

QT+mysql+python 效果:

# This Python file uses the following encoding: utf-8 import sysfrom PySide6.QtWidgets import QApplication, QWidget,QMessageBox from PySide6.QtGui import QStandardItemModel, QStandardItem # 导入需要的类# Important: # 你需要通过以下指令把 form.ui转为ui…...

关于 SR-IOV 架构论文的总结文章

关于 SR-IOV 架构论文的总结文章 在计算机虚拟化技术不断发展的进程中,SR - IOV 架构凭借其在提升 I/O 性能、优化资源利用等方面的优势,成为众多研究关注的焦点。通过对 4 篇相关论文的研读,我们可以从多个维度深入了解 SR - IOV 架构的核心要点。 一、SR - IOV 架构的原…...

MyBatis最佳实践:提升数据库交互效率的秘密武器

第一章&#xff1a;框架的概述&#xff1a; MyBatis 框架的概述&#xff1a; MyBatis 是一个优秀的基于 Java 的持久框架&#xff0c;内部对 JDBC 做了封装&#xff0c;使开发者只需要关注 SQL 语句&#xff0c;而不关注 JDBC 的代码&#xff0c;使开发变得更加的简单MyBatis 通…...

cursor ide配置远程ssh qt c++开发环境过程记录

cursor是啥就不介绍了&#xff0c;好像是目前最好用的ai ide&#xff0c;下面主要是配置远程ssh连接linux机器进行qt5 c程序运行的配置过程记录。 一、c_cpp_properties.json 在项目根目录的.vscode目录里面新建c_cpp_properties.json文件&#xff0c;根据你的实际情况配置该文…...

萌新学 Python 之数值处理函数 round 四舍五入、abs 绝对值、pow 幂次方、divmod 元组商和余数

关于数值处理的常用函数&#xff1a;round、abs、pow、divmod 1. round(数值[,小数位]) 四舍五入 说明&#xff1a;对数值进行四舍五入&#xff0c;小数位可以没有&#xff0c;也可以是负数&#xff08;从小数点左边开始进行&#xff09; round(数值[,小数位]) 四舍五入 #…...

vim交换文件的作用

1.数据恢复&#xff1a;因为vim异常的退出&#xff0c;使用交换文件可以恢复之前的修改内容。 2.防止多人同时编辑&#xff1a;vim检测到交换文件的存在,会给出提示&#xff0c;以避免一个文件同时被多人编辑。 &#xff08;vim交换文件的工作原理&#xff1a;vim交换文件的工作…...

NoSQL与SQL比较

1.认识NoSQL NoSql可以翻译做Not Only Sql&#xff08;不仅仅是SQL&#xff09;&#xff0c;或者是No Sql&#xff08;非Sql的&#xff09;数据库。是相对于传统关系型数据库而言&#xff0c;有很大差异的一种特殊的数据库&#xff0c;因此也称之为非关系型数据库。 1.1.结构…...

ThinkPHP 8模型与数据的插入、更新、删除

【图书介绍】《ThinkPHP 8高效构建Web应用》-CSDN博客 《2025新书 ThinkPHP 8高效构建Web应用 编程与应用开发丛书 夏磊 清华大学出版社教材书籍 9787302678236 ThinkPHP 8高效构建Web应用》【摘要 书评 试读】- 京东图书 使用VS Code开发ThinkPHP项目-CSDN博客 编程与应用开…...

Ollama windows安装

Ollama 是一个开源项目&#xff0c;专注于帮助用户本地化运行大型语言模型&#xff08;LLMs&#xff09;。它提供了一个简单易用的框架&#xff0c;让开发者和个人用户能够在自己的设备上部署和运行 LLMs&#xff0c;而无需依赖云服务或外部 API。这对于需要数据隐私、离线使用…...

【PyTorch】4.张量拼接操作

个人主页&#xff1a;Icomi 在深度学习蓬勃发展的当下&#xff0c;PyTorch 是不可或缺的工具。它作为强大的深度学习框架&#xff0c;为构建和训练神经网络提供了高效且灵活的平台。神经网络作为人工智能的核心技术&#xff0c;能够处理复杂的数据模式。通过 PyTorch&#xff0…...