Android加壳脱壳学习—动态加载和类加载机制详解
本文为看雪论坛优秀文章
看雪论坛作者ID:随风而行aa
一
前言
二
类加载器
1.双亲委派模式
(1)双亲委派模式定义
(1)加载.class文件时,以递归的形式逐级向上委托给父加载器ParentClassLoader加载,如果加载过了,就不用再加载一遍
(2)如果父加载器没有加载过,继续委托给父加载器去加载,一直到这条链路的顶级,顶级ClassLoader如果没有加载过,则尝试加载,加载失败,则逐级向下交还调用者加载
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)双亲委派模式加载流程
(3)双亲委派的作用
2. Android中类加载机制
(1)Android基本类预加载
android.R$styleable
android.accessibilityservice.AccessibilityServiceInfo$1
android.accessibilityservice.AccessibilityServiceInfo
android.accessibilityservice.IAccessibilityServiceClient$Stub$Proxy
android.accessibilityservice.IAccessibilityServiceClient$Stub
android.accessibilityservice.IAccessibilityServiceClient
android.accounts.AbstractAccountAuthenticator$Transport
android.accounts.AbstractAccountAuthenticator
android.accounts.Account$1
android.accounts.Account
...
java.lang.Short
java.lang.StackOverflowError
java.lang.StackTraceElement
java.lang.StrictMath
java.lang.String$1
java.lang.String$CaseInsensitiveComparator
java.lang.String
java.lang.StringBuffer
java.lang.StringBuilder
java.lang.StringFactory
java.lang.StringIndexOutOfBoundsException
java.lang.System$PropertiesWithNonOverrideableDefaults
java.lang.System
java.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);
}
@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;
}
// ...省略
}
}
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 class
DexClassLoader 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 = 0
loader为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;
else
loader = 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){
//先拿到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
看雪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/
随时掌握互联网精彩
- CSO说安全 | 沈嗣贤 :信创环境下的系统异构与持续免疫防护实践
- 在看 | 80.26亿!“滴滴”让我们明白了什么道理?
- TikTok 宣布将数据存储于 Oracle 服务器!
- 历史上的今天:Amiga 之父诞生;BASIC 语言的共同开发者出生;黑莓 BBM 停运
- 2021超级CSO年度评选颁奖盛典,再再延期:?
- 被 Linux 之父骂、遭黑客威胁,英伟达终于“想开”了:正式开源 Linux GPU 内核模块!
- 高通创投看好这5大科技赛道的投资机会
- 历史上的今天:Red Hat Enterprise Linux 问世;BASIC语言作者出生;计算机协会创始人诞生
- 除了高薪,你学习Java的理由是……
- 诸子笔会 | 蔚晨:数字化与数字化安全
- 5G助力远程医疗,解锁全新解决方案
- Airflow任务调度延时问题分析和优化