Android6.0 dex加载之dexClassLoader分析
2020-03-30 16:07:04 Author: bbs.pediy.com(查看原文) 阅读量:285 收藏

[原创]Android6.0 dex加载之dexClassLoader分析

3天前 702

[原创]Android6.0 dex加载之dexClassLoader分析

1.源码版本和地址

http://androidxref.com/6.0.0_r1

2.分析目的

  1. 分析dexClassLoader的加载流程
  2. 分析dexClassLoader的构造方法
  3. 分析dexClassLoader的findClass方法
  4. diy MyClassLoader类,从内存加载字节流,替换系统ClassLoader实例,用findClass能够返回我们自己加载的class

3.分析流程

3.1 分析dexClassLoader的构造方法

​ 首先,掠过一些步骤分析,这些代码很简单,一步一步跟就能进来了,这里只列出跟踪流程

​ dexClassLoader->BaseDexClassLoader->DexPathList->makePathElements->loadDexFile->DexFile

开始脱发!

  1. 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的初始化呢,我们接着往下面跟

  2. 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的返回值

  3. openDexFileNative

    函数原型:

        private static native Object openDexFileNative(String sourceName, String outputName, int flags);
    
  4. 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的源码不同,没有直接传递内存数组的方法,所以只能继续往下面走。

  5. 注册宏的定义

    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,注册函数

  6. 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文件放到这个数组中去,继续跟。

  7. 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中存入错误信息。

  8. 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文件,并将其放入文件数组中,返回值为文件数组。

  9. 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文件的加载

  10. 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());
    }
    

    第一个重载函数是传递的文件名,然后直接将其映射到内存,调用第二个函数加载。第二个重载函数,则是传入的内存中的缓冲区指针,第二个参数为缓冲区大小。

3.2 dexClassLoader构造方法流程总结

在6.0系统下,该方法和5.0,4.4系统都差不多,返回一个mCookie值,存放至classLoader中

具体流程:

  1. 调用OpenDexFilesFromOat函数,返回一个Dex文件数组,转换为java数组指针,返回指针地址作为cookie值
  2. 在OpenDexFilesFromOat内部,新建一个dex文件vector数组,用OpenMemory函数通过读内存的方法打开一个dex文件,存放到vector数组中,返回该数组

3.3 Diy构造方法

  1. 找到OpenMemory函数地址,调用该函数,返回一个DexFile的遍历
  2. 新建一个DexFile数组指针,放入dex文件指针
  3. 返回该指针,作为cookie值,存放到myClassLoader中,方便findClass调用

3.4 分析dexClassLoader的findClass方法

  1. 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;
        }
    
  2. 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;
        }
    
  3. 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函数,这里我们不需要对其进行深究,调用即可

3.5 Diy findClass

直接利用java反射机制,调用DexFile.defineClassNative即可。

4.总结

​ Android 6.0中dex加载流程和 Android 5.0无太大区别,和Android4.4相比,略微复杂,需要进入.so层分析c++源码。

​ 代码实现,请看下回分解。路过的大佬们轻喷,如有不足之处,还请提出。

​ 在此郑重感谢15pb的薛老师,正是因为他的分析思路,才有了我的这篇分析文章。

2020安全开发者峰会(2020 SDC)议题征集 中国.北京 7月!

最后于 3天前 被夜航星编辑 ,原因: 排版错误


文章来源: https://bbs.pediy.com/thread-258369.htm
如有侵权请联系:admin#unsafe.sh