安卓9.0脱壳时机点分析
安卓9.0是采用的art虚拟机加载dex文件,与dvm虚拟机不一样,但是在java层的加载dex文件的方法还是在BaseDexClassLoader类里面
我们看一下BaseDexClassLoader.java这个类
如下是这个类的构造方法
```java
3 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
64 String librarySearchPath, ClassLoader parent) {
65 this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
66 }
67
68 /**
69 * @hide
70 */
71 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
72 String librarySearchPath, ClassLoader parent, boolean isTrusted) {
73 super(parent);
74 this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
75
76 if (reporter != null) {
77 reportClassLoaderChain();
78 }
79 }
80
123 public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
124 // TODO We should support giving this a library search path maybe.
125 super(parent);
126 this.pathList = new DexPathList(this, dexFiles);
127 }
```
我们可以传入dex文件的路径把dex文件加载进内存,然后调用如下方法,获取dex里面的类,
我们可以传入想要获取的类的名字,
可以看到,这个方法里面又调用了pathList对象里面的findClass方法
如下方法判断是否有这个类,没有就抛出一个ClassNotFoundException的异常
有的话就返回获取到的类
```java
129 @Override
130 protected Class<?> findClass(String name) throws ClassNotFoundException {
131 List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
132 Class c = pathList.findClass(name, suppressedExceptions);
133 if (c == null) {
134 ClassNotFoundException cnfe = new ClassNotFoundException(
135 "Didn't find class \"" + name + "\" on path: " + pathList);
136 for (Throwable t : suppressedExceptions) {
137 cnfe.addSuppressed(t);
138 }
139 throw cnfe;
140 }
141 return c;
142 }
143
```
获取到类之后,我们就可以通过反射来间接调用里面的方法,获取里面的变量等。
调用案例
```java
package org.entity;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test2 {
public static void main(String[] args) {
try {
/*
这里省略加载dex文件,获取class对象
*/
//clzz为获取到的class对象
Object classObj=clzz.newInstance();
//newInstance创建实例将class类转换为对象
//调用getMethods方法获取该类的所有方法
Method[] methods = clzz.getMethods();
//遍历方法
for(Method m:methods){
if(m.getName().equals("xxxxxxx")){//找到xxxxxxx这个方法
try {
//invoke方法第一个参数是要调用的类
//第二个是要传入的参数
m.invoke(classObj, "xxxxxxxxxx");
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
```
我们回到如下构造方法中
```java
71 public BaseDexClassLoader(String dexPath, File optimizedDirectory,
72 String librarySearchPath, ClassLoader parent, boolean isTrusted) {
73 super(parent);
74 this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
75
76 if (reporter != null) {
77 reportClassLoaderChain();
78 }
79 }
```
可以看到这个构造方法创建了DexPathList对象,传入了我们的dexpath路径,
我们看一下这个方法的构造方法
这个方法判断dexpath也就是dex文件路径是否为空。
然后判断优化后的文件存放路径是否为空,判断这个存放文件夹是否存在
```java
130 public DexPathList(ClassLoader definingContext, String dexPath,
131 String librarySearchPath, File optimizedDirectory) {
132 this(definingContext, dexPath, librarySearchPath, optimizedDirectory, false);
133 }
134
135 DexPathList(ClassLoader definingContext, String dexPath,
136 String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
137 if (definingContext == null) {
138 throw new NullPointerException("definingContext == null");
139 }
140
141 if (dexPath == null) {
142 throw new NullPointerException("dexPath == null");
143 }
144
145 if (optimizedDirectory != null) {
146 if (!optimizedDirectory.exists()){
147 throw new IllegalArgumentException(
148 "optimizedDirectory doesn't exist: "
149 + optimizedDirectory);
150 }
151
152 if (!(optimizedDirectory.canRead()
153 && optimizedDirectory.canWrite())) {
154 throw new IllegalArgumentException(
155 "optimizedDirectory not readable/writable: "
156 + optimizedDirectory);
157 }
158 }
159
160 this.definingContext = definingContext;
161
162 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
163 // save dexPath for BaseDexClassLoader
164 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
165 suppressedExceptions, definingContext, isTrusted);
166
167 // Native libraries may exist in both the system and
168 // application library paths, and we use this search order:
169 //
170 // 1. This class loader's library path for application libraries (librarySearchPath):
171 // 1.1. Native library directories
172 // 1.2. Path to libraries in apk-files
173 // 2. The VM's library path from the system property for system libraries
174 // also known as java.library.path
175 //
176 // This order was reversed prior to Gingerbread; see http://b/2933456.
177 this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
178 this.systemNativeLibraryDirectories =
179 splitPaths(System.getProperty("java.library.path"), true);
180 List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
181 allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
182
183 this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
184
185 if (suppressedExceptions.size() > 0) {
186 this.dexElementsSuppressedExceptions =
187 suppressedExceptions.toArray(new IOException);
188 } else {
189 dexElementsSuppressedExceptions = null;
190 }
191 }
```
我们看一下makeDexElements这个方法,因为传入了dexpath路径,optimizedDirectory优化文件存放路径等,
这个方法先是遍历了file文件集合,然后判断这个file对象是否是文件夹或者文件,
然后调用endsWith方法判断文件名结尾是否为.dex
如下是DEX_SUFFIX变量的定义
private static final String DEX_SUFFIX = ".dex";
```java
315 /**
316 * Makes an array of dex/resource path elements, one per element of
317 * the given array.
318 */
319 private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
320 List<IOException> suppressedExceptions, ClassLoader loader) {
321 return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);
322 }
323
324
325 private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
326 List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
327 Element[] elements = new Element;
328 int elementsPos = 0;
329 /*
330 * Open all files and load the (direct or contained) dex files up front.
331 */
332 for (File file : files) {
333 if (file.isDirectory()) {
334 // We support directories for looking up resources. Looking up resources in
335 // directories is useful for running libcore tests.
336 elements = new Element(file);
337 } else if (file.isFile()) {
338 String name = file.getName();
339
340 DexFile dex = null;
341 if (name.endsWith(DEX_SUFFIX)) {
342 // Raw dex file (not inside a zip/jar).
343 try {
344 dex = loadDexFile(file, optimizedDirectory, loader, elements);
345 if (dex != null) {
346 elements = new Element(dex, null);
347 }
348 } catch (IOException suppressed) {
349 System.logE("Unable to load dex file: " + file, suppressed);
350 suppressedExceptions.add(suppressed);
351 }
352 } else {
353 try {
354 dex = loadDexFile(file, optimizedDirectory, loader, elements);
355 } catch (IOException suppressed) {
356 /*
357 * IOException might get thrown "legitimately" by the DexFile constructor if
358 * the zip file turns out to be resource-only (that is, no classes.dex file
359 * in it).
360 * Let dex == null and hang on to the exception to add to the tea-leaves for
361 * when findClass returns null.
362 */
363 suppressedExceptions.add(suppressed);
364 }
365
366 if (dex == null) {
367 elements = new Element(file);
368 } else {
369 elements = new Element(dex, file);
370 }
371 }
372 if (dex != null && isTrusted) {
373 dex.setTrusted();
374 }
375 } else {
376 System.logW("ClassLoader referenced unknown path: " + file);
377 }
378 }
379 if (elementsPos != elements.length) {
380 elements = Arrays.copyOf(elements, elementsPos);
381 }
382 return elements;
383 }
```
可以看到下面的判断分支都调用了loadDexFile方法,传入了BaseDexClassLoader构造方法的那几个参数
```java
if (name.endsWith(DEX_SUFFIX)) {
342 // Raw dex file (not inside a zip/jar).
343 try {
344 dex = loadDexFile(file, optimizedDirectory, loader, elements);
345 if (dex != null) {
346 elements = new Element(dex, null);
347 }
348 } catch (IOException suppressed) {
349 System.logE("Unable to load dex file: " + file, suppressed);
350 suppressedExceptions.add(suppressed);
351 }
352 } else {
353 try {
354 dex = loadDexFile(file, optimizedDirectory, loader, elements);
355 } catch (IOException suppressed) {
356 /*
357 * IOException might get thrown "legitimately" by the DexFile constructor if
358 * the zip file turns out to be resource-only (that is, no classes.dex file
359 * in it).
360 * Let dex == null and hang on to the exception to add to the tea-leaves for
361 * when findClass returns null.
362 */
363 suppressedExceptions.add(suppressed);
364 }
365
366 if (dex == null) {
367 elements = new Element(file);
368 } else {
369 elements = new Element(dex, file);
370 }
371 }
```
我们看一下loadDexFile方法
可以看到有两个判断分支,一个是创建了一个DexFile对象,
一个是调用了DexFile对象里的,loadDex方法
主要区别就是这个优化dex文件的存放路径optimizedDirectory
```java
390 private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
391 Element[] elements)
392 throws IOException {
393 if (optimizedDirectory == null) {
394 return new DexFile(file, loader, elements);
395 } else {
396 String optimizedPath = optimizedPathFor(file, optimizedDirectory);
397 return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
398 }
399 }
```
我们看一下DexFile类的构造方法,还有loadDex方法
如下是DexFile类的构造方法
```java
52 /**
53 * Opens a DEX file from a given File object.
54 *
55 * @deprecated Applications should use one of the standard classloaders such
56 * as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed
57 * in a future Android release</b>.
58 */
59 @Deprecated
60 public DexFile(File file) throws IOException {
61 this(file.getPath());
62 }
63 /*
64 * Private version with class loader argument.
65 *
66 * @param file
67 * the File object referencing the actual DEX file
68 * @param loader
69 * the class loader object creating the DEX file object
70 * @param elements
71 * the temporary dex path list elements from DexPathList.makeElements
72 */
73 DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
74 throws IOException {
75 this(file.getPath(), loader, elements);
76 }
77
78 /**
79 * Opens a DEX file from a given filename.
80 *
81 * @deprecated Applications should use one of the standard classloaders such
82 * as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed
83 * in a future Android release</b>.
84 */
85 @Deprecated
86 public DexFile(String fileName) throws IOException {
87 this(fileName, null, null);
88 }
89
90 /*
91 * Private version with class loader argument.
92 *
93 * @param fileName
94 * the filename of the DEX file
95 * @param loader
96 * the class loader creating the DEX file object
97 * @param elements
98 * the temporary dex path list elements from DexPathList.makeElements
99 */
100 DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
101 mCookie = openDexFile(fileName, null, 0, loader, elements);
102 mInternalCookie = mCookie;
103 mFileName = fileName;
104 //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
105 }
106
107 DexFile(ByteBuffer buf) throws IOException {
108 mCookie = openInMemoryDexFile(buf);
109 mInternalCookie = mCookie;
110 mFileName = null;
111 }
112
113 /**
114 * Opens a DEX file from a given filename, using a specified file
115 * to hold the optimized data.
116 *
117 * @param sourceName
118 *Jar or APK file with "classes.dex".
119 * @param outputName
120 *File that will hold the optimized form of the DEX data.
121 * @param flags
122 *Enable optional features.
123 * @param loader
124 *The class loader creating the DEX file object.
125 * @param elements
126 *The temporary dex path list elements from DexPathList.makeElements
127 */
128 private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,
129 DexPathList.Element[] elements) throws IOException {
130 if (outputName != null) {
131 try {
132 String parent = new File(outputName).getParent();
133 if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
134 throw new IllegalArgumentException("Optimized data directory " + parent
135 + " is not owned by the current user. Shared storage cannot protect"
136 + " your application from code injection attacks.");
137 }
138 } catch (ErrnoException ignored) {
139 // assume we'll fail with a more contextual error later
140 }
141 }
142
143 mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
144 mInternalCookie = mCookie;
145 mFileName = sourceName;
146 //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
147 }
```
因为传入的是三个参数的构造方法,我们只需要看如下构造方法
```java
100 DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
101 mCookie = openDexFile(fileName, null, 0, loader, elements);
102 mInternalCookie = mCookie;
103 mFileName = fileName;
104 //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
105 }
106
```
可以看到调用了openDexFile方法
```java
347 /*
348 * Open a DEX file.The value returned is a magic VM cookie.On
349 * failure, an IOException is thrown.
350 */
351 private static Object openDexFile(String sourceName, String outputName, int flags,
352 ClassLoader loader, DexPathList.Element[] elements) throws IOException {
353 // Use absolute paths to enable the use of relative paths when testing on host.
354 return openDexFileNative(new File(sourceName).getAbsolutePath(),
355 (outputName == null)
356 ? null
357 : new File(outputName).getAbsolutePath(),
358 flags,
359 loader,
360 elements);
361 }
```
我们再看一下loadDex方法
这个方法传入了五个参数,
```java
return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
```
如下是loadDex方法的实现
```java
149 /**
150 * Open a DEX file, specifying the file in which the optimized DEX
151 * data should be written.If the optimized form exists and appears
152 * to be current, it will be used; if not, the VM will attempt to
153 * regenerate it.
154 *
155 * @deprecated Applications should use one of the standard classloaders such
156 * as {@link dalvik.system.PathClassLoader} instead. <b>This API will be removed
157 * in a future Android release</b>.
158 */
159 @Deprecated
160 static public DexFile loadDex(String sourcePathName, String outputPathName,
161 int flags) throws IOException {
162
163 /*
164 * TODO: we may want to cache previously-opened DexFile objects.
165 * The cache would be synchronized with close().This would help
166 * us avoid mapping the same DEX more than once when an app
167 * decided to open it multiple times.In practice this may not
168 * be a real issue.
169 */
170 return loadDex(sourcePathName, outputPathName, flags, null, null);
171 }
172
173 /*
174 * Private version of loadDex that also takes a class loader.
175 *
176 * @param sourcePathName
177 *Jar or APK file with "classes.dex".(May expand this to include
178 *"raw DEX" in the future.)
179 * @param outputPathName
180 *File that will hold the optimized form of the DEX data.
181 * @param flags
182 *Enable optional features.(Currently none defined.)
183 * @param loader
184 *Class loader that is aloading the DEX file.
185 * @param elements
186 *The temporary dex path list elements from DexPathList.makeElements
187 * @return
188 *A new or previously-opened DexFile.
189 * @throws IOException
190 *If unable to open the source or output file.
191 */
192 static DexFile loadDex(String sourcePathName, String outputPathName,
193 int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
194
195 /*
196 * TODO: we may want to cache previously-opened DexFile objects.
197 * The cache would be synchronized with close().This would help
198 * us avoid mapping the same DEX more than once when an app
199 * decided to open it multiple times.In practice this may not
200 * be a real issue.
201 */
202 return new DexFile(sourcePathName, outputPathName, flags, loader, elements);
203 }
```
我们可以看到这个方法最后还是调用了DexFile对象里面的构造方法,而且是五个参数的。
```java
113 /**
114 * Opens a DEX file from a given filename, using a specified file
115 * to hold the optimized data.
116 *
117 * @param sourceName
118 *Jar or APK file with "classes.dex".
119 * @param outputName
120 *File that will hold the optimized form of the DEX data.
121 * @param flags
122 *Enable optional features.
123 * @param loader
124 *The class loader creating the DEX file object.
125 * @param elements
126 *The temporary dex path list elements from DexPathList.makeElements
127 */
128 private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,
129 DexPathList.Element[] elements) throws IOException {
130 if (outputName != null) {
131 try {
132 String parent = new File(outputName).getParent();
133 if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
134 throw new IllegalArgumentException("Optimized data directory " + parent
135 + " is not owned by the current user. Shared storage cannot protect"
136 + " your application from code injection attacks.");
137 }
138 } catch (ErrnoException ignored) {
139 // assume we'll fail with a more contextual error later
140 }
141 }
142
143 mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
144 mInternalCookie = mCookie;
145 mFileName = sourceName;
146 //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
147 }
```
最后面还是调用了openDexFile方法
```java
351 private static Object openDexFile(String sourceName, String outputName, int flags,
352 ClassLoader loader, DexPathList.Element[] elements) throws IOException {
353 // Use absolute paths to enable the use of relative paths when testing on host.
354 return openDexFileNative(new File(sourceName).getAbsolutePath(),
355 (outputName == null)
356 ? null
357 : new File(outputName).getAbsolutePath(),
358 flags,
359 loader,
360 elements);
361 }
```
我们跟踪一下openDexFileNative方法,这个方法是native修饰的,所以方法实现在c/c++层
我们可以看到这个方法是动态注册的
```cpp
841static JNINativeMethod gMethods[] = {
842NATIVE_METHOD(DexFile, closeDexFile, "(Ljava/lang/Object;)Z"),
843NATIVE_METHOD(DexFile,
844 defineClassNative,
845 "(Ljava/lang/String;"
846 "Ljava/lang/ClassLoader;"
847 "Ljava/lang/Object;"
848 "Ldalvik/system/DexFile;"
849 ")Ljava/lang/Class;"),
850NATIVE_METHOD(DexFile, getClassNameList, "(Ljava/lang/Object;)[Ljava/lang/String;"),
851NATIVE_METHOD(DexFile, isDexOptNeeded, "(Ljava/lang/String;)Z"),
852NATIVE_METHOD(DexFile, getDexOptNeeded,
853 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZ)I"),
854NATIVE_METHOD(DexFile, openDexFileNative,
855 "(Ljava/lang/String;"
856 "Ljava/lang/String;"
857 "I"
858 "Ljava/lang/ClassLoader;"
859 "[Ldalvik/system/DexPathList$Element;"
860 ")Ljava/lang/Object;"),
861NATIVE_METHOD(DexFile, createCookieWithDirectBuffer,
862 "(Ljava/nio/ByteBuffer;II)Ljava/lang/Object;"),
863NATIVE_METHOD(DexFile, createCookieWithArray, "([BII)Ljava/lang/Object;"),
864NATIVE_METHOD(DexFile, isValidCompilerFilter, "(Ljava/lang/String;)Z"),
865NATIVE_METHOD(DexFile, isProfileGuidedCompilerFilter, "(Ljava/lang/String;)Z"),
866NATIVE_METHOD(DexFile,
867 getNonProfileGuidedCompilerFilter,
868 "(Ljava/lang/String;)Ljava/lang/String;"),
869NATIVE_METHOD(DexFile,
870 getSafeModeCompilerFilter,
871 "(Ljava/lang/String;)Ljava/lang/String;"),
872NATIVE_METHOD(DexFile, isBackedByOatFile, "(Ljava/lang/Object;)Z"),
873NATIVE_METHOD(DexFile, getDexFileStatus,
874 "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
875NATIVE_METHOD(DexFile, getDexFileOutputPaths,
876 "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"),
877NATIVE_METHOD(DexFile, getStaticSizeOfDexFile, "(Ljava/lang/Object;)J"),
878NATIVE_METHOD(DexFile, getDexFileOptimizationStatus,
879 "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"),
880NATIVE_METHOD(DexFile, setTrusted, "(Ljava/lang/Object;)V")
881};
```
如下是这个函数的实现
```cpp
266// TODO(calin): clean up the unused parameters (here and in libcore).
267static jobject DexFile_openDexFileNative(JNIEnv* env,
268 jclass,
269 jstring javaSourceName,
270 jstring javaOutputName ATTRIBUTE_UNUSED,
271 jint flags ATTRIBUTE_UNUSED,
272 jobject class_loader,
273 jobjectArray dex_elements) {
274ScopedUtfChars sourceName(env, javaSourceName);
275if (sourceName.c_str() == nullptr) {
276 return 0;
277}
278
279Runtime* const runtime = Runtime::Current();
280ClassLinker* linker = runtime->GetClassLinker();
281std::vector<std::unique_ptr<const DexFile>> dex_files;
282std::vector<std::string> error_msgs;
283const OatFile* oat_file = nullptr;
284
285dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
286 class_loader,
287 dex_elements,
288 /*out*/ &oat_file,
289 /*out*/ &error_msgs);
290
291if (!dex_files.empty()) {
292 jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
293 if (array == nullptr) {
294 ScopedObjectAccess soa(env);
295 for (auto& dex_file : dex_files) {
296 if (linker->IsDexFileRegistered(soa.Self(), *dex_file)) {
297 dex_file.release();
298 }
299 }
300 }
301 return array;
302} else {
303 ScopedObjectAccess soa(env);
304 CHECK(!error_msgs.empty());
305 // The most important message is at the end. So set up nesting by going forward, which will
306 // wrap the existing exception as a cause for the following one.
307 auto it = error_msgs.begin();
308 auto itEnd = error_msgs.end();
309 for ( ; it != itEnd; ++it) {
310 ThrowWrappedIOException("%s", it->c_str());
311 }
312
313 return nullptr;
314}
315}
316
```
我们看到java层传入的参数传入了OpenDexFilesFromOat这个函数,
这个函数判断了dex_location dex的文件路径 是否为空,判断classloader 类加载器是否为空
```cpp
394std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(
395 const char* dex_location,
396 jobject class_loader,
397 jobjectArray dex_elements,
398 const OatFile** out_oat_file,
399 std::vector<std::string>* error_msgs) {
400ScopedTrace trace(__FUNCTION__);
401CHECK(dex_location != nullptr);
402CHECK(error_msgs != nullptr);
403
404// Verify we aren't holding the mutator lock, which could starve GC if we
405// have to generate or relocate an oat file.
406Thread* const self = Thread::Current();
407Locks::mutator_lock_->AssertNotHeld(self);
408Runtime* const runtime = Runtime::Current();
409
410std::unique_ptr<ClassLoaderContext> context;
411// If the class_loader is null there's not much we can do. This happens if a dex files is loaded
412// directly with DexFile APIs instead of using class loaders.
413if (class_loader == nullptr) {
414 LOG(WARNING) << "Opening an oat file without a class loader. "
415 << "Are you using the deprecated DexFile APIs?";
416 context = nullptr;
417} else {
418 context = ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements);
419}
420
421OatFileAssistant oat_file_assistant(dex_location,
422 kRuntimeISA,
423 !runtime->IsAotCompiler(),
424 only_use_system_oat_files_);
425
426// Lock the target oat location to avoid races generating and loading the
427// oat file.
428std::string error_msg;
429if (!oat_file_assistant.Lock(/*out*/&error_msg)) {
430 // Don't worry too much if this fails. If it does fail, it's unlikely we
431 // can generate an oat file anyway.
432 VLOG(class_linker) << "OatFileAssistant::Lock: " << error_msg;
433}
434
435const OatFile* source_oat_file = nullptr;
436
437if (!oat_file_assistant.IsUpToDate()) {
438 // Update the oat file on disk if we can, based on the --compiler-filter
439 // option derived from the current runtime options.
440 // This may fail, but that's okay. Best effort is all that matters here.
441 // TODO(calin): b/64530081 b/66984396. Pass a null context to verify and compile
442 // secondary dex files in isolation (and avoid to extract/verify the main apk
443 // if it's in the class path). Note this trades correctness for performance
444 // since the resulting slow down is unacceptable in some cases until b/64530081
445 // is fixed.
446 // We still pass the class loader context when the classpath string of the runtime
447 // is not empty, which is the situation when ART is invoked standalone.
448 ClassLoaderContext* actual_context = Runtime::Current()->GetClassPathString().empty()
449 ? nullptr
450 : context.get();
451 switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/ false,
452 actual_context,
453 /*out*/ &error_msg)) {
454 case OatFileAssistant::kUpdateFailed:
455 LOG(WARNING) << error_msg;
456 break;
457
458 case OatFileAssistant::kUpdateNotAttempted:
459 // Avoid spamming the logs if we decided not to attempt making the oat
460 // file up to date.
461 VLOG(oat) << error_msg;
462 break;
463
464 case OatFileAssistant::kUpdateSucceeded:
465 // Nothing to do.
466 break;
467 }
468}
469
```
我们看一下如下函数,可以看到创建了一个OatFileAssistant 对象,调用了oat_file_assistant构造方法。
传入了java层的相关参数,我们跟进去看一下这个函数,这个构造方法传入了四个参数
```cpp
421OatFileAssistant oat_file_assistant(dex_location,
422 kRuntimeISA,
423 !runtime->IsAotCompiler(),
424 only_use_system_oat_files_);
```
可以看到这个构造函数又继续调用了OatFileAssistant函数
```cpp
75OatFileAssistant::OatFileAssistant(const char* dex_location,
76 const InstructionSet isa,
77 bool load_executable,
78 bool only_load_system_executable)
79 : OatFileAssistant(dex_location,
80 isa,
81 load_executable,
82 only_load_system_executable,
83 -1 /* vdex_fd */,
84 -1 /* oat_fd */,
85 -1 /* zip_fd */) {}
86
```
如下是OatFileAssistant的重载函数,有七个参数,函数开头还是在判断dex_location 是否为空
```cpp
88OatFileAssistant::OatFileAssistant(const char* dex_location,
89 const InstructionSet isa,
90 bool load_executable,
91 bool only_load_system_executable,
92 int vdex_fd,
93 int oat_fd,
94 int zip_fd)
95 : isa_(isa),
96 load_executable_(load_executable),
97 only_load_system_executable_(only_load_system_executable),
98 odex_(this, /*is_oat_location*/ false),
99 oat_(this, /*is_oat_location*/ true),
100 zip_fd_(zip_fd) {
101CHECK(dex_location != nullptr) << "OatFileAssistant: null dex location";
102
103if (zip_fd < 0) {
104 CHECK_LE(oat_fd, 0) << "zip_fd must be provided with valid oat_fd. zip_fd=" << zip_fd
105 << " oat_fd=" << oat_fd;
106 CHECK_LE(vdex_fd, 0) << "zip_fd must be provided with valid vdex_fd. zip_fd=" << zip_fd
107 << " vdex_fd=" << vdex_fd;;
108}
109
110dex_location_.assign(dex_location);
111
112if (load_executable_ && isa != kRuntimeISA) {
113 LOG(WARNING) << "OatFileAssistant: Load executable specified, "
114 << "but isa is not kRuntimeISA. Will not attempt to load executable.";
115 load_executable_ = false;
116}
117
118// Get the odex filename.
119std::string error_msg;
120std::string odex_file_name;
121if (DexLocationToOdexFilename(dex_location_, isa_, &odex_file_name, &error_msg)) {
122 odex_.Reset(odex_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd);
123} else {
124 LOG(WARNING) << "Failed to determine odex file name: " << error_msg;
125}
126
127if (!UseFdToReadFiles()) {
128 // Get the oat filename.
129 std::string oat_file_name;
130 if (DexLocationToOatFilename(dex_location_, isa_, &oat_file_name, &error_msg)) {
131 oat_.Reset(oat_file_name, false /* use_fd */);
132 } else {
133 LOG(WARNING) << "Failed to determine oat file name for dex location "
134 << dex_location_ << ": " << error_msg;
135 }
136}
137
138// Check if the dex directory is writable.
139// This will be needed in most uses of OatFileAssistant and so it's OK to
140// compute it eagerly. (the only use which will not make use of it is
141// OatFileAssistant::GetStatusDump())
142size_t pos = dex_location_.rfind('/');
143if (pos == std::string::npos) {
144 LOG(WARNING) << "Failed to determine dex file parent directory: " << dex_location_;
145} else if (!UseFdToReadFiles()) {
146 // We cannot test for parent access when using file descriptors. That's ok
147 // because in this case we will always pick the odex file anyway.
148 std::string parent = dex_location_.substr(0, pos);
149 if (access(parent.c_str(), W_OK) == 0) {
150 dex_parent_writable_ = true;
151 } else {
152 VLOG(oat) << "Dex parent of " << dex_location_ << " is not writable: " << strerror(errno);
153 }
154}
155}
```
我们看一下如下函数DexLocationToOdexFilename,相关参数都传入了当前函数
```cpp
862bool OatFileAssistant::DexLocationToOatFilename(const std::string& location,
863 InstructionSet isa,
864 std::string* oat_filename,
865 std::string* error_msg) {
866CHECK(oat_filename != nullptr);
867CHECK(error_msg != nullptr);
868
869std::string cache_dir = GetDalvikCache(GetInstructionSetString(isa));
870if (cache_dir.empty()) {
871 *error_msg = "Dalvik cache directory does not exist";
872 return false;
873}
874
875// TODO: The oat file assistant should be the definitive place for
876// determining the oat file name from the dex location, not
877// GetDalvikCacheFilename.
878return GetDalvikCacheFilename(location.c_str(), cache_dir.c_str(), oat_filename, error_msg);
879}
```
我们继续跟踪GetDalvikCacheFilename函数
我们看到这个函数返回值是bool类型的,获取DalvikCacheFilename,获取Dalvik缓存文件名。
通过指针形式给内存空间赋值。
```cpp
269bool GetDalvikCacheFilename(const char* location, const char* cache_location,
270 std::string* filename, std::string* error_msg) {
271if (location != '/') {
272 *error_msg = StringPrintf("Expected path in location to be absolute: %s", location);
273 return false;
274}
275std::string cache_file(&location);// skip leading slash
276if (!android::base::EndsWith(location, ".dex") &&
277 !android::base::EndsWith(location, ".art") &&
278 !android::base::EndsWith(location, ".oat")) {
279 cache_file += "/";
280 cache_file += DexFileLoader::kClassesDex;
281}
282std::replace(cache_file.begin(), cache_file.end(), '/', '@');
283*filename = StringPrintf("%s/%s", cache_location, cache_file.c_str());
284return true;
285}
```
我们回到前面
可以看到这里定义了一个容器,
里面用来存放Dex文件对象的相关信息
```cpp
std::vector<std::unique_ptr<const DexFile>> dex_files;
```
在DexFile类里面
我们可以看到dex文件结构相关信息,比如checksum,string_ids_,header_,field_ids_,method_ids_等

```cpp
96DexFile::DexFile(const uint8_t* base,
97 size_t size,
98 const uint8_t* data_begin,
99 size_t data_size,
100 const std::string& location,
101 uint32_t location_checksum,
102 const OatDexFile* oat_dex_file,
103 std::unique_ptr<DexFileContainer> container,
104 bool is_compact_dex)
105 : begin_(base),
106 size_(size),
107 data_begin_(data_begin),
108 data_size_(data_size),
109 location_(location),
110 location_checksum_(location_checksum),
111 header_(reinterpret_cast<const Header*>(base)),
112 string_ids_(reinterpret_cast<const StringId*>(base + header_->string_ids_off_)),
113 type_ids_(reinterpret_cast<const TypeId*>(base + header_->type_ids_off_)),
114 field_ids_(reinterpret_cast<const FieldId*>(base + header_->field_ids_off_)),
115 method_ids_(reinterpret_cast<const MethodId*>(base + header_->method_ids_off_)),
116 proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)),
117 class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)),
118 method_handles_(nullptr),
119 num_method_handles_(0),
120 call_site_ids_(nullptr),
121 num_call_site_ids_(0),
122 oat_dex_file_(oat_dex_file),
123 container_(std::move(container)),
124 is_compact_dex_(is_compact_dex),
125 is_platform_dex_(false) {
126CHECK(begin_ != nullptr) << GetLocation();
127CHECK_GT(size_, 0U) << GetLocation();
128// Check base (=header) alignment.
129// Must be 4-byte aligned to avoid undefined behavior when accessing
130// any of the sections via a pointer.
131CHECK_ALIGNED(begin_, alignof(Header));
132
133InitializeSectionsFromMapList();
134}
135
```
```cpp
84struct Header {
85 uint8_t magic_ = {};
86 uint32_t checksum_ = 0;// See also location_checksum_
87 uint8_t signature_ = {};
88 uint32_t file_size_ = 0;// size of entire file
89 uint32_t header_size_ = 0;// offset to start of next section
90 uint32_t endian_tag_ = 0;
91 uint32_t link_size_ = 0;// unused
92 uint32_t link_off_ = 0;// unused
93 uint32_t map_off_ = 0;// unused
94 uint32_t string_ids_size_ = 0;// number of StringIds
95 uint32_t string_ids_off_ = 0;// file offset of StringIds array
96 uint32_t type_ids_size_ = 0;// number of TypeIds, we don't support more than 65535
97 uint32_t type_ids_off_ = 0;// file offset of TypeIds array
98 uint32_t proto_ids_size_ = 0;// number of ProtoIds, we don't support more than 65535
99 uint32_t proto_ids_off_ = 0;// file offset of ProtoIds array
100 uint32_t field_ids_size_ = 0;// number of FieldIds
101 uint32_t field_ids_off_ = 0;// file offset of FieldIds array
102 uint32_t method_ids_size_ = 0;// number of MethodIds
103 uint32_t method_ids_off_ = 0;// file offset of MethodIds array
104 uint32_t class_defs_size_ = 0;// number of ClassDefs
105 uint32_t class_defs_off_ = 0;// file offset of ClassDef array
106 uint32_t data_size_ = 0;// size of data section
107 uint32_t data_off_ = 0;// file offset of data section
108
109 // Decode=1 the dex magic version
110 uint32_t GetVersion() const;
111};
```
在DexFile类里面还要检查魔数和版本的函数
计算Checksum的函数等。
```cpp
150bool DexFile::CheckMagicAndVersion(std::string* error_msg) const {
151if (!IsMagicValid()) {
152 std::ostringstream oss;
153 oss << "Unrecognized magic number in "<< GetLocation() << ":"
154 << " " << header_->magic_
155 << " " << header_->magic_
156 << " " << header_->magic_
157 << " " << header_->magic_;
158 *error_msg = oss.str();
159 return false;
160}
161if (!IsVersionValid()) {
162 std::ostringstream oss;
163 oss << "Unrecognized version number in "<< GetLocation() << ":"
164 << " " << header_->magic_
165 << " " << header_->magic_
166 << " " << header_->magic_
167 << " " << header_->magic_;
168 *error_msg = oss.str();
169 return false;
170}
171return true;
172}
63uint32_t DexFile::CalculateChecksum() const {
64return CalculateChecksum(Begin(), Size());
65}
66
67uint32_t DexFile::CalculateChecksum(const uint8_t* begin, size_t size) {
68const uint32_t non_sum_bytes = OFFSETOF_MEMBER(DexFile::Header, signature_);
69return ChecksumMemoryRange(begin + non_sum_bytes, size - non_sum_bytes);
70}
71
72uint32_t DexFile::ChecksumMemoryRange(const uint8_t* begin, size_t size) {
73return adler32(adler32(0L, Z_NULL, 0), begin, size);
74}
75
76int DexFile::GetPermissions() const {
77CHECK(container_.get() != nullptr);
78return container_->GetPermissions();
79}
80
81bool DexFile::IsReadOnly() const {
82CHECK(container_.get() != nullptr);
83return container_->IsReadOnly();
84}
85
86bool DexFile::EnableWrite() const {
87CHECK(container_.get() != nullptr);
88return container_->EnableWrite();
89}
90
91bool DexFile::DisableWrite() const {
92CHECK(container_.get() != nullptr);
93return container_->DisableWrite();
94}
```
我们回到前面,可以看到如下语句块
可以看到调用了LoadDexFiles函数,返回值就是dex_files
```cpp
589 if (!added_image_space) {
590 DCHECK(dex_files.empty());
591 dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);
592
593 // Register for tracking.
594 for (const auto& dex_file : dex_files) {
595 dex::tracking::RegisterDexFile(dex_file.get());
596 }
597 }
```
我们看一下LoadDexFiles这个函数
```cpp
341std::vector<std::unique_ptr<const DexFile>> OatFileAssistant::LoadDexFiles(
342 const OatFile &oat_file, const char *dex_location) {
343std::vector<std::unique_ptr<const DexFile>> dex_files;
344if (LoadDexFiles(oat_file, dex_location, &dex_files)) {
345 return dex_files;
346} else {
347 return std::vector<std::unique_ptr<const DexFile>>();
348}
349}
350
351bool OatFileAssistant::LoadDexFiles(
352 const OatFile &oat_file,
353 const std::string& dex_location,
354 std::vector<std::unique_ptr<const DexFile>>* out_dex_files) {
355// Load the main dex file.
356std::string error_msg;
357const OatFile::OatDexFile* oat_dex_file = oat_file.GetOatDexFile(
358 dex_location.c_str(), nullptr, &error_msg);
359if (oat_dex_file == nullptr) {
360 LOG(WARNING) << error_msg;
361 return false;
362}
363
364std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);
365if (dex_file.get() == nullptr) {
366 LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg;
367 return false;
368}
369out_dex_files->push_back(std::move(dex_file));
370
371// Load the rest of the multidex entries
372for (size_t i = 1;; i++) {
373 std::string multidex_dex_location = DexFileLoader::GetMultiDexLocation(i, dex_location.c_str());
374 oat_dex_file = oat_file.GetOatDexFile(multidex_dex_location.c_str(), nullptr);
375 if (oat_dex_file == nullptr) {
376 // There are no more multidex entries to load.
377 break;
378 }
379
380 dex_file = oat_dex_file->OpenDexFile(&error_msg);
381 if (dex_file.get() == nullptr) {
382 LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg;
383 return false;
384 }
385 out_dex_files->push_back(std::move(dex_file));
386}
387return true;
388}
```
我们可以看到关键函数,这个函数返回值就是dex_file
```cpp
364std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);
```
LoadDexFiles函数返回值为dex_file,LoadDexFiles函数又调用了OpenDexFile函数,
如下是OpenDexFile函数的定义
可以看到直接就返回了DexFile对象的指针,
这个DexFile对象里面有我们dex文件的所有信息。
我们可以选择在这个时机点脱壳,hookOpenDexFile函数的返回值即可
```cpp
113const DexFile* OpenDexFile(const OatFile::OatDexFile* oat_dex_file, std::string* error_msg) {
114DCHECK(oat_dex_file != nullptr);
115auto it = opened_dex_files.find(oat_dex_file);
116if (it != opened_dex_files.end()) {
117 return it->second.get();
118}
119const DexFile* ret = oat_dex_file->OpenDexFile(error_msg).release();
120opened_dex_files.emplace(oat_dex_file, std::unique_ptr<const DexFile>(ret));
121return ret;
122}
```
总结:
java层通过调用BaseDexClassLoader加载器,传入了要加载的dex文件相关路径,然后调用了loadDexFile方法,
随后调用了native层的方法openDexFileNative,进行相关的参数判断,最后调用了OpenDexFile函数,把dex的文件相关信息加载进入了内存,返回值为DexFile对象指针,该对象里面含有dex文件的相关结构信息等。
我们可以通过hook这个OpenDexFile函数,获取返回值,得到dex文件。
[吾爱汇编论坛52HB.COM]-感谢楼主分享,支持一下! [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守! 过来学习学习赚点币,感谢分享 多谢分享 感谢分享 荣耀9也是给予9.0的系统,还在研究怎么bl! 谢谢楼主分享 有帮助,多多支持!
页:
[1]
2