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

本文为看雪论坛优秀文章
看雪论坛作者ID:随风而行aa
一
前言
二
类加载器
1.双亲委派模式
(1)双亲委派模式定义
(1)加载.class文件时,以递归的形式逐级向上委托给父加载器ParentClassLoader加载,如果加载过了,就不用再加载一遍(2)如果父加载器没有加载过,继续委托给父加载器去加载,一直到这条链路的顶级,顶级ClassLoader如果没有加载过,则尝试加载,加载失败,则逐级向下交还调用者加载

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{//1.先检查是否已经加载过--findLoadedClass<?> 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)双亲委派模式加载流程

(3)双亲委派的作用
2. Android中类加载机制
(1)Android基本类预加载




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...
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 {// ...省略}}

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


<1> BootClassLoader
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);}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {return Class.classForName(name, false, null);}// ...省略@Overrideprotected Class<?> loadClass(String className, boolean resolve)throws ClassNotFoundException {Class<?> clazz = findLoadedClass(className);if (clazz == null) {clazz = findClass(className);}return clazz;}// ...省略}}
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;// ...省略}


① 使用LoadClass()加载
② 使用forName()加载
<2> PathClassLoader
<3> 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);}}
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);...}}
private static Element[] makePathElements(List<File> files, File optimizedDirectory,List<IOException> suppressedExceptions) {return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);}
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;}
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(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;}
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数组;
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;}}

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加载。
protected final Class<?> findLoadedClass(String name) {ClassLoader loader;if (this == BootClassLoader.getInstance())loader = null;elseloader = this;return VMClassLoader.findLoadedClass(loader, name);}
public class BaseDexClassLoader extends ClassLoader {protected Class<?> findClass(String name) throws ClassNotFoundException {Class c = pathList.findClass(name, suppressedExceptions);...return c;}}
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;}
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;}}
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; //没有找到目标类}



DexFile.defineClassNative() 的实现在 /art/runtime/native/dalvik_system_DexFile.cc,最终由 ClassLinker.DefineClass() 实现Class.classForName() 的实现在 /art/runtime/native/java_lang_Class.cc,最终由 ClassLinker.FindClass() 实现



3.案例
(1)验证类加载器
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());

(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());}
(4)动态加载


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)获得类列表
private static native String[] getClassNameList(Object cookie);public static void getClassListInClassLoader(ClassLoader classLoader){//先拿到BaseDexClassLoadertry {//拿到pathListClass BaseDexClassLoader = Class.forName("dalvik.system.BaseDexClassLoader");Field pathListField = BaseDexClassLoader.getDeclaredField("pathList");pathListField.setAccessible(true);Object pathListObj = pathListField.get(classLoader);//拿到dexElementsClass DexElementClass = Class.forName("dalvik.system.DexPathList");Field DexElementFiled = DexElementClass.getDeclaredField("dexElements");DexElementFiled.setAccessible(true);Object[] dexElementObj = (Object[]) DexElementFiled.get(pathListObj);//拿到dexFileClass 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);//拿到getClassNameListMethod 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

看雪ID:随风而行aa
https://bbs.pediy.com/user-home-945611.htm

# 往期推荐
3.Windows本地提权漏洞CVE-2014-1767分析及EXP编写指导
5.高级进程注入总结


球分享

球点赞

球在看

点击“阅读原文”,了解更多!
[广告]赞助链接:
关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

关注KnowSafe微信公众号


