Android加壳脱壳学习—动态加载和类加载机制详解

移动安全 2年前 (2022) admin
619 0 0

Android加壳脱壳学习—动态加载和类加载机制详解

本文为看雪论坛优秀文章
看雪论坛作者ID:随风而行aa




前言


最近一直在学习Android 加壳和脱壳,在进行Android加壳和脱壳的学习中,第一步便是深入理解类加载器和动态加载二者之间的关系。

本文详细的介绍了类加载器和动态加载之间的关系和原理,之所以详细的讲解两者之间的关系,一是学习脱壳和加壳的需要,二是为后面Android插件化漏洞挖掘的讲解做铺垫。




类加载器


Android中的类加载器机制与JVM一样遵循双亲委派模式。


1.双亲委派模式


(1)双亲委派模式定义

(1)加载.class文件时,以递归的形式逐级向上委托给父加载器ParentClassLoader加载,如果加载过了,就不用再加载一遍(2)如果父加载器没有加载过,继续委托给父加载器去加载,一直到这条链路的顶级,顶级ClassLoader如果没有加载过,则尝试加载,加载失败,则逐级向下交还调用者加载


Android加壳脱壳学习—动态加载和类加载机制详解
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{           //1.先检查是否已经加载过--findLoaded           Class<?> c = findLoadedClass(name);           if (c == null) {               try {                   //2.如果自己没加载过,存在父类,则委托父类                   if (parent != null) {                       c = parent.loadClass(name, false);                   } else {                       c = findBootstrapClassOrNull(name);                   }               } catch (ClassNotFoundException e) {               }
if (c == null) { //3.如果父类也没加载过,则尝试本级classLoader加载 c = findClass(name); } } return c; }


代码解释:

① 先检查自己是否已经加载过class文件,用findLoadedClass方法,如果已经加载了直接返。

② 如果自己没有加载过,存在父类,则委派父类去加载,用parent.loadClass(name,false)方法,此时会向上传递,然后去父加载器中循环第1步,一直到顶级ClassLoader。

③ 如果父类没有加载,则尝试本级classLoader加载,如果加载失败了就会向下传递,交给调用方式实现.class文件的加载。


(2)双亲委派模式加载流程

Android加壳脱壳学习—动态加载和类加载机制详解


(3)双亲委派的作用


① 防止同一个.class文件重复加载。

② 对于任意一个类确保在虚拟机中的唯一性。由加载它的类加载器和这个类的全类名一同确立其在Java虚拟机中的唯一性。

③ 保证.class文件不被篡改,通过委派方式可以保证系统类的加载逻辑不被篡改。



2. Android中类加载机制


(1)Android基本类预加载


我们了解Android基本类预加载,首先我们回顾上文的Dalvik虚拟机启动相关:
 
Android加壳脱壳学习—动态加载和类加载机制详解
 
我们执行app_process程序,进入main函数里面,然后进行AndroidRuntime::start:
Android加壳脱壳学习—动态加载和类加载机制详解

Zygote native 进程主要工作:

① 创建虚拟机–startVM

② 注册JNI函数–startReg

③ 通过JNI知道Java层的com.android.internal.os.ZygoteInit 类,调用main 函数,进入java 世界

Android加壳脱壳学习—动态加载和类加载机制详解
 
然后进入Java层:
Android加壳脱壳学习—动态加载和类加载机制详解

Zygote总结:

① 解析init.zygote.rc中的参数,创建AppRuntime并调用AppRuntime.start()方法。

② 调用AndroidRuntime的startVM()方法创建虚拟机,再调用startReg()注册JNI函数。

③ 通过JNI方式调用ZygoteInit.main(),第一次进入Java世界。

④ registerZygoteSocket()建立socket通道,zygote作为通信的服务端,用于响应客户端请求。

⑤ preload()预加载通用类、drawable和color资源、openGL以及共享库以及WebView,用于提高app启动效率。

⑥ 通过startSystemServer(),fork得力帮手system_server进程,也是Java Framework的运行载体(下面讲到system server再详细讲解)。

⑦ 调用runSelectLoop(),随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作。

Android的类加载机制和JVM一样遵循双亲委派模式,在dalvik/art启动时将所有Java基本类和Android系统框架的基本类加载进来,预加载的类记录在/frameworks/base/config/preloaded-classes中。
android.R$styleableandroid.accessibilityservice.AccessibilityServiceInfo$1android.accessibilityservice.AccessibilityServiceInfoandroid.accessibilityservice.IAccessibilityServiceClient$Stub$Proxyandroid.accessibilityservice.IAccessibilityServiceClient$Stubandroid.accessibilityservice.IAccessibilityServiceClientandroid.accounts.AbstractAccountAuthenticator$Transportandroid.accounts.AbstractAccountAuthenticatorandroid.accounts.Account$1android.accounts.Account...
java.lang.Shortjava.lang.StackOverflowErrorjava.lang.StackTraceElementjava.lang.StrictMathjava.lang.String$1java.lang.String$CaseInsensitiveComparatorjava.lang.Stringjava.lang.StringBufferjava.lang.StringBuilderjava.lang.StringFactoryjava.lang.StringIndexOutOfBoundsExceptionjava.lang.System$PropertiesWithNonOverrideableDefaultsjava.lang.Systemjava.lang.Thread$1...

这些类只需要在Zygote进程启动时加载一遍就可以了,后续没一个APP或Android运行时环境的进程,都是从Zygote中fork出来,天然保留了加载过的类缓存。
 
ZygoteInit.preload()
static void preload(TimingsTraceLog bootTimingsTraceLog) {    // ...省略    preloadClasses();    // ...省略}
private static void preloadClasses() { final VMRuntime runtime = VMRuntime.getRuntime();
// 读取 preloaded_classes 文件 InputStream is; try { is = new FileInputStream(PRELOADED_CLASSES); } catch (FileNotFoundException e) { Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + "."); return; }
// ...省略
try { BufferedReader br = new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);
int count = 0; String line; while ((line = br.readLine()) != null) { // Skip comments and blank lines. line = line.trim(); if (line.startsWith("#") || line.equals("")) { continue; }
Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line); try { // 逐行加载基本类 Class.forName(line, true, null); count++; // ...省略 } catch (Throwable t) { // ...省略 } }
// ...省略 } catch (IOException e) { Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e); } finally { // ...省略 }}

Android加壳脱壳学习—动态加载和类加载机制详解

(2)Android类加载器层级关系及分析

Android加壳脱壳学习—动态加载和类加载机制详解
 
Android加壳脱壳学习—动态加载和类加载机制详解

Android中的ClassLoader类型分为系统ClassLoader和自定义ClassLoader。其中系统ClassLoader包括3种是BootClassLoader、DexClassLoader、PathClassLoader。

① BootClassLoader:Android平台上所有Android系统启动时会使用BootClassLoader来预加载常用的类。

② BaseDexClassLoader:实际应用层类文件的加载,而真正的加载委托给pathList来完成。

③ DexClassLoader:可以加载dex文件以及包含dex的压缩文件(apk,dex,jar,zip),可以安装一个未安装的apk文件,一般为自定义类加载器。

④ PathClassLoader:可以加载系统类和应用程序的类,通常用来加载已安装的apk的dex文件。
 
补充:
Android 提供的原生加载器叫做基础类加载器,包括:BootClassLoader,PathClassLoader,DexClassLoader,InMemoryDexClassLoader(Android 8.0 引入),DelegateLastClassLoader(Android 8.1 引入)。

<1> BootClassLoader


启动类加载器,用于加载 Zygote 进程已经预加载的基本类,可以推测它只需从缓存中加载。这是基类 ClassLoader 的一个内部类,是包访问权限,所以应用程序无权直接访问。
public abstract class ClassLoader {    // ...省略
class BootClassLoader extends ClassLoader { private static BootClassLoader instance;
public static synchronized BootClassLoader getInstance() { if (instance == null) { instance = new BootClassLoader(); }
return instance; }
public BootClassLoader() { super(null); }
@Override protected Class<?> findClass(String name) throws ClassNotFoundException { return Class.classForName(name, false, null); }
// ...省略
@Override protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class<?> clazz = findLoadedClass(className);
if (clazz == null) { clazz = findClass(className); }
return clazz; }
// ...省略 }}


源码分析:
    我们可以看见,BootClassLoader没有父加载器,在缓存取不到类是直接调用自己的findClass()方法。
    findClass()方法调用Class.classForName()方法,而ZygoteInit.preloadClasses()中,加载基本类是Class.forName()。
ublic final class Class<T> implements java.io.Serializable,                              GenericDeclaration,                              Type,                              AnnotatedElement {    // ...省略
public static Class<?> forName(String className) throws ClassNotFoundException { Class<?> caller = Reflection.getCallerClass(); return forName(className, true, ClassLoader.getClassLoader(caller)); }
public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException { if (loader == null) { loader = BootClassLoader.getInstance(); } Class<?> result; try { result = classForName(name, initialize, loader); } catch (ClassNotFoundException e) { Throwable cause = e.getCause(); if (cause instanceof LinkageError) { throw (LinkageError) cause; } throw e; } return result; }
// 本地方法 static native Class<?> classForName(String className, boolean shouldInitialize, ClassLoader classLoader) throws ClassNotFoundException;
// ...省略}

我们可以发现,预加载时,ZygoteInit.preloadClasses()中调用Class.forName(),实际是指定BootClassLoader为类加载器,且只需要在预加载的时候进行类初始化,只需要一次。

总之,通过 Class.forName() 或者 Class.classForName() 可以且仅可以直接加载基本类,一旦基本类预加载后,对于应用程序而言,我们虽然不能直接访问BootClassLoader,但可以通过Class.forName/Class.classForName加载。

Android加壳脱壳学习—动态加载和类加载机制详解

无论是系统类加载器(PathClassLoader)还是自定义的类加载器(DexClassLoader),最顶层的祖先加载器默认是 BootClassLoader,与 JVM 一样,保证了基本类的类型安全。
 
Class文件加载:

① 通过Class.forName()方法动态加载

② 通过ClassLoader.loadClass()方法动态加载

类的加载分为3个步骤:1.装载(Load),2.链接(Link),3.初始化(Intialize)

Android加壳脱壳学习—动态加载和类加载机制详解
 
类加载时机:

1.隐式加载:
① 创建类的实例,也就是new一个对象
② 访问某个类或接口的静态变量,或者对该静态变量赋值
③ 调用类的静态方法
④ 反射Class.forName(“android.app.ActivityThread”)
⑤ 初始化一个类的子类(会首先初始化子类的父类)

2.显示加载:

① 使用LoadClass()加载

② 使用forName()加载


<2> PathClassLoader

主要用于系统和app的类加载器,其中optimizedDirectory为null, 采用默认目录/data/dalvik-cache/。

PathClassLoader 是作为应用程序的系统类加载器,也是在 Zygote 进程启动的时候初始化的(基本流程为:ZygoteInit.main() -> ZygoteInit.forkSystemServer() -> ZygoteInit.handleSystemServerProcess() -> ZygoteInit.createPathClassLoader()。在预加载基本类之后执行),所以每一个 APP 进程从 Zygote 中 fork 出来之后都自动携带了一个 PathClassLoader,它通常用于加载 apk 里面的 .dex 文件。

<3> DexClassLoader

可以从包含classes.dex的jar或者apk中,加载类的类加载器, 可用于执行动态加载, 但必须是app私有可写目录来缓存odex文件. 能够加载系统没有安装的apk或者jar文件, 因此很多热修复和插件化方案都是采用DexClassLoader。
public classDexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), librarySearchPath, parent); }}


总结:

    我们可以发现DexClassLoader与PathClassLoader都继承于BaseDexClassLoader,这两个类只是提供了自己的构造函数,没有额外的实现。


区别:

    DexClassLoader提供了optimizedDirectory,而PathClassLoader则没有,optimizedDirectory正是用来存放odex文件的地方,所以可以利用DexClassLoader实现动态加载。


<4> BaseDexClassLoader
public class BaseDexClassLoader extends ClassLoader {    private final DexPathList pathList;  //记录dex文件路径信息
public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); }}


dexPath: 包含目标类或资源的apk/jar列表;当有多个路径则采用:分割;
optimizedDirectory: 优化后dex文件存在的目录, 可以为null;
libraryPath: native库所在路径列表;当有多个路径则采用:分割;
ClassLoader:父类的类加载器。

BaseDexClassLoader会初始化dexPathList,收集dex文件和Native文件动态库。
 
初始化:
 
DexPathList:
 
该类主要用来查找Dex、SO库的路径,并这些路径整体呈一个数组。
final class DexPathList {    private Element[] dexElements;    private final List<File> nativeLibraryDirectories;    private final List<File> systemNativeLibraryDirectories;
final class DexPathList { public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { ... this.definingContext = definingContext; ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
//记录所有的dexFile文件 this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);
//app目录的native库 this.nativeLibraryDirectories = splitPaths(libraryPath, false); //系统目录的native库 this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true); List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); //记录所有的Native动态库 this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions); ... }}

DexPathList初始化过程,主要收集以下两个变量信息:
(1)dexElements: 根据多路径的分隔符“;”将dexPath转换成File列表,记录所有的dexFile
(2)nativeLibraryPathElements: 记录所有的Native动态库, 包括app目录的native库和系统目录的native库

makePathElements:
private static Element[] makePathElements(List<File> files, File optimizedDirectory,        List<IOException> suppressedExceptions) {    return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);}

makeDexElements:
 
makeDexElements方法的作用是获取一个包含dex文件的元素集合。
private static Element[] makeDexElements(List<File> files, File optimizedDirectory,        List<IOException> suppressedExceptions, ClassLoader loader) {    return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);}
private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) { Element[] elements = new Element[files.size()]; //获取文件个数 int elementsPos = 0; for (File file : files) { if (file.isDirectory()) { elements[elementsPos++] = new Element(file); } else if (file.isFile()) { String name = file.getName(); DexFile dex = null; //匹配以.dex为后缀的文件 if (name.endsWith(DEX_SUFFIX)) { dex = loadDexFile(file, optimizedDirectory, loader, elements); if (dex != null) { elements[elementsPos++] = new Element(dex, null); } } else { dex = loadDexFile(file, optimizedDirectory, loader, elements); if (dex == null) { elements[elementsPos++] = new Element(file); } else { elements[elementsPos++] = new Element(dex, file); } } if (dex != null && isTrusted) { dex.setTrusted(); } } else { System.logW("ClassLoader referenced unknown path: " + file); } } if (elementsPos != elements.length) { elements = Arrays.copyOf(elements, elementsPos); }
return elements;}

该方法的主要功能是创建Element数组。
 
loadDexFile:
 
加载DexFile文件,而且会把优化后的dex文件缓存到对应目录。
private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,                                   Element[] elements)        throws IOException {    if (optimizedDirectory == null) {        return new DexFile(file, loader, elements);  //创建DexFile对象    } else {        String optimizedPath = optimizedPathFor(file, optimizedDirectory);        return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);    }}

DexFile:
 
用来描述Dex文件,Dex的加载以及Class的查找都是由该类调用它的native方法完成的。

DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)        throws IOException {    this(file.getPath(), loader, elements);}
DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException { mCookie = openDexFile(fileName, null, 0, loader, elements); mInternalCookie = mCookie; mFileName = fileName;}

openDexFile:
private static Object openDexFile(String sourceName, String outputName, int flags,        ClassLoader loader, DexPathList.Element[] elements) throws IOException {    return openDexFileNative(new File(sourceName).getAbsolutePath(),                             (outputName == null) ? null : new File(outputName).getAbsolutePath(),                             flags,                             loader,                             elements);}
此时参数取值说明:sourceName为PathClassLoader构造函数传递的dexPath中以分隔符划分之后的文件名;outputName为null;flags = 0loader为null;elements为makeDexElements()过程生成的Element数组;


openDexFileNative:
static jobject DexFile_openDexFileNative(JNIEnv* env,                                         jclass,                                         jstring javaSourceName,                                         jstring javaOutputName ATTRIBUTE_UNUSED,                                         jint flags ATTRIBUTE_UNUSED,                                         jobject class_loader,                                         jobjectArray dex_elements) {  ScopedUtfChars sourceName(env, javaSourceName);  if (sourceName.c_str() == nullptr) {    return 0;  }  Runtime* const runtime = Runtime::Current();  ClassLinker* linker = runtime->GetClassLinker();  std::vector<std::unique_ptr<const DexFile>> dex_files;  std::vector<std::string> error_msgs;  const OatFile* oat_file = nullptr;
dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(), class_loader, dex_elements, /*out*/ &oat_file, /*out*/ &error_msgs);
if (!dex_files.empty()) { jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files); ... return array; } else { ... return nullptr; }}

这样就完成了dex的加载过程,而BaseDexClassLoader派生出两个子类加载器:PathClassLoader和DexClassLoader。

Android加壳脱壳学习—动态加载和类加载机制详解
 
Android中如果parent类加载器加载不到类,最终还是会调用ClassLoader对象自己的findClass()方法。
 
loadClass()加载:
public abstract class ClassLoader {
public Class<?> loadClass(String className) throws ClassNotFoundException { return loadClass(className, false); }
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { //判断当前类加载器是否已经加载过指定类,若已加载则直接返回 Class<?> clazz = findLoadedClass(className);
if (clazz == null) { //如果没有加载过,则调用parent的类加载递归加载该类,若已加载则直接返回 clazz = parent.loadClass(className, false);
if (clazz == null) { //还没加载,则调用当前类加载器来加载 clazz = findClass(className); } } return clazz; }}


该方法的加载流程如下:

① 判断当前类加载器是否已经加载过指定类,若已加载则直接返回,否则继续执行;

② 调用parent的类加载递归加载该类,检测是否加载,若已加载则直接返回,否则继续执行;

③ 调用当前类加载器,通过findClass加载。


findLoadedClass:
 
[-> ClassLoader.java]
protected final Class<?> findLoadedClass(String name) {    ClassLoader loader;    if (this == BootClassLoader.getInstance())        loader = null;    else        loader = this;    return VMClassLoader.findLoadedClass(loader, name);}

findClass:
 
[-> BaseDexClassLoader.java]
public class BaseDexClassLoader extends ClassLoader {    protected Class<?> findClass(String name) throws ClassNotFoundException {        Class c = pathList.findClass(name, suppressedExceptions);        ...        return c;    }}

DexPathList.findClass:
public Class findClass(String name, List<Throwable> suppressed) {    for (Element element : dexElements) {        DexFile dex = element.dexFile;        if (dex != null) {            //找到目标类,则直接返回            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);            if (clazz != null) {                return clazz;            }        }    }    return null;}

代码解释:
    一个Classloader可以包含多个dex文件,每个dex文件被封装到一个Element对象,这些Element对象排列成有序的数组 dexElements。当查找某个类时,会遍历所有的dex文件,如果找到则直接返回,不再继续遍历dexElements。也就是说当两个类不同的dex中出现,会优先处理排在前面的dex文件,这便是热修复的核心精髓,将需要修复的类所打包的dex文件插入到dexElements前面。

热修复原理:
    现在很多热修复技术就是把修复的dex文件放在DexPathList中Element[]数组的前面,这样就实现了修复后的Class抢先加载了,达到了修改bug的目的。


DexFile.loadClassBinaryName:
public final class DexFile {
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) { return defineClass(name, loader, mCookie, suppressed); }
private static Class defineClass(String name, ClassLoader loader, Object cookie, List<Throwable> suppressed) { Class result = null; try { result = defineClassNative(name, loader, cookie); } catch (NoClassDefFoundError e) { if (suppressed != null) { suppressed.add(e); } } catch (ClassNotFoundException e) { if (suppressed != null) { suppressed.add(e); } } return result; }}

defineClassNative()这是native方法。
 
defineClassNative:
 
[-> dalvik_system_DexFile.cc]
static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader,                                        jobject cookie) {  std::unique_ptr<std::vector<const DexFile*>> dex_files = ConvertJavaArrayToNative(env, cookie);  if (dex_files.get() == nullptr) {    return nullptr; //dex文件为空, 则直接返回  }
ScopedUtfChars class_name(env, javaName); if (class_name.c_str() == nullptr) { return nullptr; //类名为空, 则直接返回 }
const std::string descriptor(DotToDescriptor(class_name.c_str())); const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str())); //将类名转换为hash码 for (auto& dex_file : *dex_files) { const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str(), hash); if (dex_class_def != nullptr) { ScopedObjectAccess soa(env); ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); class_linker->RegisterDexFile(*dex_file); StackHandleScope<1> hs(soa.Self()); Handle<mirror::ClassLoader> class_loader( hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader))); //获取目标类 mirror::Class* result = class_linker->DefineClass(soa.Self(), descriptor.c_str(), hash, class_loader, *dex_file, *dex_class_def); if (result != nullptr) { // 找到目标对象 return soa.AddLocalReference<jclass>(result); } } } return nullptr; //没有找到目标类}

在native层创建目标类的对象并添加到虚拟机列表。

Android加壳脱壳学习—动态加载和类加载机制详解
 
Android加壳脱壳学习—动态加载和类加载机制详解
 
Android加壳脱壳学习—动态加载和类加载机制详解
 
我们继续分析Native层可以发现:
DexFile.defineClassNative() 的实现在 /art/runtime/native/dalvik_system_DexFile.cc,最终由 ClassLinker.DefineClass() 实现Class.classForName() 的实现在 /art/runtime/native/java_lang_Class.cc,最终由 ClassLinker.FindClass() 实现

ClassLinker核心原理:

先从已加载类的 class_table 中查询,若找到则直接返回;若找不到则说明该类是第一次加载,则执行加载流程,其中可能需要穿插加载依赖的类,加载完成后将其缓存到 class_table 中。

在 ClassLinker 中,会维护两类 class_table,一类针对基本类,一类针对其它的类。class_table 是作为缓存已经加载过的类的缓冲池。不管以什么样的方式去加载类,都需要先从 class_table 中先进行查询以提高加载性能。

ClassLinker 在加载类的时候遇到该类依赖的类,进行穿插加载依赖类:
Android加壳脱壳学习—动态加载和类加载机制详解
 
我们总结BaseDexClassLoader初始化和加载原理:
Android加壳脱壳学习—动态加载和类加载机制详解
 
Android类加载详细流程:
 
Android加壳脱壳学习—动态加载和类加载机制详解

3.案例


(1)验证类加载器


我们验证App中的MainActivity类加载器和系统类String类的类加载器:
ClassLoader thisclassloader = MainActivity.class.getClassLoader();ClassLoader StringClassloader = String.class.getClassLoader();Log.e("ClassLoader1","MainActivity is in" + thisclassloader.toString());Log.e("ClassLoader1","String is in" + StringClassloader.toString());
Android加壳脱壳学习—动态加载和类加载机制详解
 
我们可以明显发现PathClassLoader加载已安装的APK类加载器,而BootClassLoader加载系统预安装的类。

(2)遍历父类加载器

public static  void printClassLoader(ClassLoader classLoader) {       Log.e("printClassLoader","this->"+ classLoader.toString());       ClassLoader parent = classLoader.getParent();       while (parent!=null){           Log.i("printClassLoader","parent->"+parent.toString());           parent = parent.getParent();       }   }


(3)验证双亲委派机制

try {            Class StringClass = thisclassloader.loadClass("java.lang.String");            Log.e("ClassLoader1","load StringClass!"+thisclassloader.toString());        } catch (ClassNotFoundException e) {            e.printStackTrace();            Log.e("ClassLoader1","load MainActivity fail!"+thisclassloader.toString());        }

我们使用PathClassLoader去加载 String.class类,还是可以加载成功,因为双亲委派的机制。

(4)动态加载


这里我借用网上寒冰大佬动态加载的案例,来进一步讲述使用DexClassLoader类实现简单的动态加载插件dex,并验证ClassLoader的继承关系。
 
我们先编写一个测试类文件,然后生成dex文件。
Android加壳脱壳学习—动态加载和类加载机制详解
 
我们先将dex文件放到模拟器的sdcard/下。
Android加壳脱壳学习—动态加载和类加载机制详解
 
我们新建一个程序,然后编写主程序的代码,并授权sd读取权限。
Context appContext = this.getApplication();testDexClassLoader(appContext,"/sdcard/classes.dex");

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

然后我们编写类加载器代码。
private void testDexClassLoader(Context context, String dexfilepath) {        //构建文件路径:/data/data/com.emaxple.test02/app_opt_dex,存放优化后的dex,lib库        File optfile = context.getDir("opt_dex",0);        File libfile = context.getDir("lib_dex",0);
ClassLoader parentclassloader = MainActivity.class.getClassLoader(); ClassLoader tmpclassloader = context.getClassLoader(); //可以为DexClassLoader指定父类加载器 DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),parentclassloader);
Class clazz = null; try { clazz = dexClassLoader.loadClass("com.example.test.TestClass"); } catch (ClassNotFoundException e) { e.printStackTrace(); } if(clazz!=null){ try { Method testFuncMethod = clazz.getDeclaredMethod("test02"); Object obj = clazz.newInstance(); testFuncMethod.invoke(obj); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }
}


(5)获得类列表


我们通过getClassNameList来获取类列表
private static native String[] getClassNameList(Object cookie);

public static void getClassListInClassLoader(ClassLoader classLoader){        //先拿到BaseDexClassLoader        try {            //拿到pathList            Class BaseDexClassLoader = Class.forName("dalvik.system.BaseDexClassLoader");            Field pathListField = BaseDexClassLoader.getDeclaredField("pathList");            pathListField.setAccessible(true);            Object pathListObj = pathListField.get(classLoader);
//拿到dexElements Class DexElementClass = Class.forName("dalvik.system.DexPathList"); Field DexElementFiled = DexElementClass.getDeclaredField("dexElements"); DexElementFiled.setAccessible(true); Object[] dexElementObj = (Object[]) DexElementFiled.get(pathListObj); //拿到dexFile Class Element = Class.forName("dalvik.system.DexPathList$Element"); Field dexFileField = Element.getDeclaredField("dexFile"); dexFileField.setAccessible(true); Class DexFile =Class.forName("dalvik.system.DexFile"); Field mCookieField = DexFile.getDeclaredField("mCookie"); mCookieField.setAccessible(true); Field mFiledNameField = DexFile.getDeclaredField("mFileName"); mFiledNameField.setAccessible(true); //拿到getClassNameList Method getClassNameListMethod = DexFile.getDeclaredMethod("getClassNameList",Object.class); getClassNameListMethod.setAccessible(true);
for(Object dexElement:dexElementObj){ Object dexfileObj = dexFileField.get(dexElement); Object mCookiedobj = mCookieField.get(dexfileObj); String mFileNameobj = (String) mFiledNameField.get(dexfileObj); String[] classlist = (String[]) getClassNameListMethod.invoke(null,mCookiedobj); for(String classname:classlist){ Log.e("classlist",classLoader.toString()+"-----"+mFileNameobj+"-----"+classname); } }
} catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }




实验总结


花了一段时间,断断续续总算把这篇类加载器和动态加载的帖子写完了,从中学习到了很多,这里如果有什么错误,就请各位大佬指正了。



参考文献:

http://gityuan.com/2017/03/19/android-classloader/

https://www.jianshu.com/p/7193600024e7

https://www.jianshu.com/p/ff489696ada2

https://www.jianshu.com/p/363a4ad0489d

https://github.com/huanzhiyazi/articles/issues/30

https://juejin.cn/post/6844903940094427150#heading-12




Android加壳脱壳学习—动态加载和类加载机制详解


看雪ID:随风而行aa

https://bbs.pediy.com/user-home-945611.htm

*本文由看雪论坛 随风而行aa 原创,转载请注明来自看雪社区


Android加壳脱壳学习—动态加载和类加载机制详解

# 往期推荐

1.某APP sig3 48位算法逆向分析

2.CVE-2021-26411漏洞分析笔记

3.Windows本地提权漏洞CVE-2014-1767分析及EXP编写指导

4.Vmprotect3.5.1 壹之型 — 暗月·宵之宫

5.高级进程注入总结

6.通过某音Cronet模块学习Quic协议



Android加壳脱壳学习—动态加载和类加载机制详解



Android加壳脱壳学习—动态加载和类加载机制详解

球分享

Android加壳脱壳学习—动态加载和类加载机制详解

球点赞

Android加壳脱壳学习—动态加载和类加载机制详解

球在看



Android加壳脱壳学习—动态加载和类加载机制详解

点击“阅读原文”,了解更多!

原文始发于微信公众号(看雪学苑):Android加壳脱壳学习—动态加载和类加载机制详解

版权声明:admin 发表于 2022年3月18日 下午6:06。
转载请注明:Android加壳脱壳学习—动态加载和类加载机制详解 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...