http://androidxref.com/6.0.0_r1
首先,掠过一些步骤分析,这些代码很简单,一步一步跟就能进来了,这里只列出跟踪流程
dexClassLoader->BaseDexClassLoader->DexPathList->makePathElements->loadDexFile->DexFile
开始脱发!
DexFile
xref: /libcore/dalvik/src/main/java/dalvik/system/DexFile.java
97 private DexFile(String sourceName, String outputName, int flags) throws IOException { 98 if (outputName != null) { 99 try { 100 String parent = new File(outputName).getParent(); 101 if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) { 102 throw new IllegalArgumentException("Optimized data directory " + parent 103 + " is not owned by the current user. Shared storage cannot protect" 104 + " your application from code injection attacks."); 105 } 106 } catch (ErrnoException ignored) { 107 // assume we'll fail with a more contextual error later 108 } 109 } 110 111 mCookie = openDexFile(sourceName, outputName, flags); 112 mFileName = sourceName; 113 guard.open("close"); 114 //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName); 115 }
其中有这样一句代码 mCookie = openDexFile(sourceName, outputName, flags)
是否意味我们只要将mCookie值正确获取返回即可成功完成dexClassLoader的初始化呢,我们接着往下面跟
openDexFile
xref: /libcore/dalvik/src/main/java/dalvik/system/DexFile.java
private static Object openDexFile(String sourceName, String outputName, int flags) throws IOException { // Use absolute paths to enable the use of relative paths when testing on host. return openDexFileNative(new File(sourceName).getAbsolutePath(), (outputName == null) ? null : new File(outputName).getAbsolutePath(), flags); }
里面调用了一个openDexFileNative函数获取cookie的返回值
openDexFileNative
函数原型:
private static native Object openDexFileNative(String sourceName, String outputName, int flags);
gMethods
xref: /art/runtime/native/dalvik_system_DexFile.cc
内容:
static JNINativeMethod gMethods[] = { NATIVE_METHOD(DexFile, closeDexFile, "(Ljava/lang/Object;)V"), NATIVE_METHOD(DexFile, defineClassNative, "(Ljava/lang/String;Ljava/lang/ClassLoader;Ljava/lang/Object;)Ljava/lang/Class;"), NATIVE_METHOD(DexFile, getClassNameList, "(Ljava/lang/Object;)[Ljava/lang/String;"), NATIVE_METHOD(DexFile, isDexOptNeeded, "(Ljava/lang/String;)Z"), NATIVE_METHOD(DexFile, getDexOptNeeded, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)I"), NATIVE_METHOD(DexFile, openDexFileNative, "(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/Object;"), };
可以看到,追踪到了c++代码中,此处有一个数组gMethods[] 存放着接口方法的地址,这里和4.4的源码不同,没有直接传递内存数组的方法,所以只能继续往下面走。
注册宏的定义
xref: /art/runtime/jni_internal.h
#ifndef NATIVE_METHOD #define NATIVE_METHOD(className, functionName, signature) \ { #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName) } #endif #define REGISTER_NATIVE_METHODS(jni_class_name) \ RegisterNativeMethods(env, jni_class_name, gMethods, arraysize(gMethods))
翻译一下:
函数地址:className_funtionName
即我们要找的函数名为:DexFile_openDexFileNative
下面的宏定义REGISTER_NATIVE_METHODS,注册函数
DexFile_openDexFileNative
xref: /art/runtime/native/dalvik_system_DexFile.cc
static jobject DexFile_openDexFileNative( JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) { ScopedUtfChars sourceName(env, javaSourceName); if (sourceName.c_str() == nullptr) { return 0; } NullableScopedUtfChars outputName(env, javaOutputName); if (env->ExceptionCheck()) { return 0; } ClassLinker* linker = Runtime::Current()->GetClassLinker(); std::vector<std::unique_ptr<const DexFile>> dex_files; std::vector<std::string> error_msgs; dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs); if (!dex_files.empty()) { jlongArray array = ConvertNativeToJavaArray(env, dex_files); if (array == nullptr) { ScopedObjectAccess soa(env); for (auto& dex_file : dex_files) { if (Runtime::Current()->GetClassLinker()->IsDexFileRegistered(*dex_file)) { dex_file.release(); } } } return array; } else { ScopedObjectAccess soa(env); CHECK(!error_msgs.empty()); // The most important message is at the end. So set up nesting by going forward, which will // wrap the existing exception as a cause for the following one. auto it = error_msgs.begin(); auto itEnd = error_msgs.end(); for ( ; it != itEnd; ++it) { ThrowWrappedIOException("%s", it->c_str()); } return nullptr; } }
其中,有一句很关键
dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs);
OpenDexFilesFromOat:从oat中获取Dex文件数组,然后把他转成java的数组,返回数组指针,也就是我们得分析如何将自己的dex文件放到这个数组中去,继续跟。
OpenDexFilesFromOat
xref: /art/runtime/class_linker.cc
std::vector<std::unique_ptr<const DexFile>> ClassLinker::OpenDexFilesFromOat( const char* dex_location, const char* oat_location, std::vector<std::string>* error_msgs) { CHECK(error_msgs != nullptr); // Verify we aren't holding the mutator lock, which could starve GC if we // have to generate or relocate an oat file. Locks::mutator_lock_->AssertNotHeld(Thread::Current()); OatFileAssistant oat_file_assistant(dex_location, oat_location, kRuntimeISA, !Runtime::Current()->IsAotCompiler()); // Lock the target oat location to avoid races generating and loading the // oat file. std::string error_msg; if (!oat_file_assistant.Lock(&error_msg)) { // Don't worry too much if this fails. If it does fail, it's unlikely we // can generate an oat file anyway. VLOG(class_linker) << "OatFileAssistant::Lock: " << error_msg; } // Check if we already have an up-to-date oat file open. const OatFile* source_oat_file = nullptr; { ReaderMutexLock mu(Thread::Current(), dex_lock_); for (const OatFile* oat_file : oat_files_) { CHECK(oat_file != nullptr); if (oat_file_assistant.GivenOatFileIsUpToDate(*oat_file)) { source_oat_file = oat_file; break; } } } // If we didn't have an up-to-date oat file open, try to load one from disk. if (source_oat_file == nullptr) { // Update the oat file on disk if we can. This may fail, but that's okay. // Best effort is all that matters here. if (!oat_file_assistant.MakeUpToDate(&error_msg)) { LOG(WARNING) << error_msg; } // Get the oat file on disk. std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile(); if (oat_file.get() != nullptr) { // Take the file only if it has no collisions, or we must take it because of preopting. bool accept_oat_file = !HasCollisions(oat_file.get(), &error_msg); if (!accept_oat_file) { // Failed the collision check. Print warning. if (Runtime::Current()->IsDexFileFallbackEnabled()) { LOG(WARNING) << "Found duplicate classes, falling back to interpreter mode for " << dex_location; } else { LOG(WARNING) << "Found duplicate classes, dex-file-fallback disabled, will be failing to " " load classes for " << dex_location; } LOG(WARNING) << error_msg; // However, if the app was part of /system and preopted, there is no original dex file // available. In that case grudgingly accept the oat file. if (!DexFile::MaybeDex(dex_location)) { accept_oat_file = true; LOG(WARNING) << "Dex location " << dex_location << " does not seem to include dex file. " << "Allow oat file use. This is potentially dangerous."; } } if (accept_oat_file) { source_oat_file = oat_file.release(); RegisterOatFile(source_oat_file); } } } std::vector<std::unique_ptr<const DexFile>> dex_files; // Load the dex files from the oat file. if (source_oat_file != nullptr) { dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location); if (dex_files.empty()) { error_msgs->push_back("Failed to open dex files from " + source_oat_file->GetLocation()); } } // Fall back to running out of the original dex file if we couldn't load any // dex_files from the oat file. if (dex_files.empty()) { if (oat_file_assistant.HasOriginalDexFiles()) { if (Runtime::Current()->IsDexFileFallbackEnabled()) { if (!DexFile::Open(dex_location, dex_location, &error_msg, &dex_files)) { LOG(WARNING) << error_msg; error_msgs->push_back("Failed to open dex files from " + std::string(dex_location)); } } else { error_msgs->push_back("Fallback mode disabled, skipping dex files."); } } else { error_msgs->push_back("No original dex files found for dex location " + std::string(dex_location)); } } return dex_files; }
还好是从后往前看的,最后一行注释,如果没有从oat文件中找到dex文件,我们就执行下面的代码!!ohohohoh!!
下面做了什么呢?
翻译翻译?
如果dex文件数组为空,调用DexFile::Open(dex_location, dex_location, &error_msg, &dex_files),
如果返回值是true,则啥也不干,返回dex_files数组,程序正常结束,如果返回值是false,输出错误日志,往error_msgs中存入错误信息。
DexFile::Open
xref: /art/runtime/dex_file.cc
bool DexFile::Open(const char* filename, const char* location, std::string* error_msg, std::vector<std::unique_ptr<const DexFile>>* dex_files) { DCHECK(dex_files != nullptr) << "DexFile::Open: out-param is nullptr"; uint32_t magic; ScopedFd fd(OpenAndReadMagic(filename, &magic, error_msg)); if (fd.get() == -1) { DCHECK(!error_msg->empty()); return false; } if (IsZipMagic(magic)) { return DexFile::OpenZip(fd.release(), location, error_msg, dex_files); } if (IsDexMagic(magic)) { std::unique_ptr<const DexFile> dex_file(DexFile::OpenFile(fd.release(), location, true,error_msg)); if (dex_file.get() != nullptr) { dex_files->push_back(std::move(dex_file)); return true; } else { return false; } } *error_msg = StringPrintf("Expected valid zip or dex file: '%s'", filename); return false; }
ok,这段代码就是判断一下当前是什么文件,若是dex文件,则调用DexFile::OpenFile(fd.release(), location, true,error_msg),构造dex文件,并将其放入文件数组中,返回值为文件数组。
DexFile::OpenFile
xref: /art/runtime/dex_file.cc
std::unique_ptr<const DexFile> DexFile::OpenFile(int fd, const char* location, bool verify, std::string* error_msg) { CHECK(location != nullptr); std::unique_ptr<MemMap> map; { ScopedFd delayed_close(fd); struct stat sbuf; memset(&sbuf, 0, sizeof(sbuf)); if (fstat(fd, &sbuf) == -1) { *error_msg = StringPrintf("DexFile: fstat '%s' failed: %s", location, strerror(errno)); return nullptr; } if (S_ISDIR(sbuf.st_mode)) { *error_msg = StringPrintf("Attempt to mmap directory '%s'", location); return nullptr; } size_t length = sbuf.st_size; map.reset(MemMap::MapFile(length, PROT_READ, MAP_PRIVATE, fd, 0, location, error_msg)); if (map.get() == nullptr) { DCHECK(!error_msg->empty()); return nullptr; } } if (map->Size() < sizeof(DexFile::Header)) { *error_msg = StringPrintf( "DexFile: failed to open dex file '%s' that is too short to have a header", location); return nullptr; } const Header* dex_header = reinterpret_cast<const Header*>(map->Begin()); std::unique_ptr<const DexFile> dex_file(OpenMemory(location, dex_header->checksum_, map.release(),error_msg)); if (dex_file.get() == nullptr) { *error_msg = StringPrintf("Failed to open dex file '%s' from memory: %s", location,error_msg->c_str()); return nullptr; } if (verify && !DexFileVerifier::Verify(dex_file.get(), dex_file->Begin(), dex_file->Size(), location, error_msg)) { return nullptr; } return dex_file; }
关键点来了:
std::unique_ptr<const DexFile> dex_file(OpenMemory(location, dexheader->checksum, map.release(),error_msg));
调用OpenMemory函数完成了dex文件的加载
OpenMemory
这里有2个OpenMemory重载函数,我们终于找到了我们想要的一个,也就是能够直接传递数组指针的那个,代码如下
xref: /art/runtime/dex_file.cc
std::unique_ptr<const DexFile> DexFile::OpenMemory(const std::string& location, uint32_t location_checksum, MemMap* mem_map, std::string* error_msg) { return OpenMemory(mem_map->Begin(), mem_map->Size(), location, location_checksum, mem_map, nullptr, error_msg); } std::unique_ptr<const DexFile> DexFile::OpenMemory(const uint8_t* base, size_t size, const std::string& location, uint32_t location_checksum, MemMap* mem_map, const OatDexFile* oat_dex_file, std::string* error_msg) { CHECK_ALIGNED(base, 4); // various dex file structures must be word aligned std::unique_ptr<DexFile> dex_file( new DexFile(base, size, location, location_checksum, mem_map, oat_dex_file)); if (!dex_file->Init(error_msg)) { dex_file.reset(); } return std::unique_ptr<const DexFile>(dex_file.release()); }
第一个重载函数是传递的文件名,然后直接将其映射到内存,调用第二个函数加载。第二个重载函数,则是传入的内存中的缓冲区指针,第二个参数为缓冲区大小。
在6.0系统下,该方法和5.0,4.4系统都差不多,返回一个mCookie值,存放至classLoader中
具体流程:
findClass
@Override protected Class<?> findClass(String name) throws ClassNotFoundException { List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); Class c = pathList.findClass(name, suppressedExceptions); if (c == null) { ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); for (Throwable t : suppressedExceptions) { cnfe.addSuppressed(t); } throw cnfe; } return c; }
DexPathList.findClass
xref: /libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
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; } } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; }
loadClassBinaryName
xref: /libcore/dalvik/src/main/java/dalvik/system/DexFile.java
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方法,该方法为java中的JNI函数,这里我们不需要对其进行深究,调用即可
直接利用java反射机制,调用DexFile.defineClassNative即可。
Android 6.0中dex加载流程和 Android 5.0无太大区别,和Android4.4相比,略微复杂,需要进入.so层分析c++源码。
代码实现,请看下回分解。路过的大佬们轻喷,如有不足之处,还请提出。
在此郑重感谢15pb的薛老师,正是因为他的分析思路,才有了我的这篇分析文章。
2020安全开发者峰会(2020 SDC)议题征集 中国.北京 7月!
最后于 3天前 被夜航星编辑 ,原因: 排版错误