吾爱汇编

 找回密码
 立即注册

QQ登录

绑定QQ避免忘记帐号

查看: 2056|回复: 14

[安卓逆向图文] 安卓9.0脱壳时机点分析

  [复制链接]
白云点缀的蓝 发表于 2022-7-4 00:59 | 显示全部楼层 |阅读模式

安卓9.0是采用的art虚拟机加载dex文件,与dvm虚拟机不一样,但是在java层的加载dex文件的方法还是 在BaseDexClassLoader类里面 我们看一下BaseDexClassLoader.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的异常 有的话就返回获取到的类

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

获取到类之后,我们就可以通过反射来间接调用里面的方法,获取里面的变量等。 调用案例

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

    }

}

我们回到如下构造方法中

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文件路径是否为空。 然后判断优化后的文件存放路径是否为空,判断这个存放文件夹是否存在

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[suppressedExceptions.size()]);
188        } else {
189            dexElementsSuppressedExceptions = null;
190        }
191    }

我们看一下makeDexElements这个方法,因为传入了dexpath路径,optimizedDirectory优化文件存放路径等, 这个方法先是遍历了file文件集合,然后判断这个file对象是否是文件夹或者文件, 然后调用endsWith方法判断文件名结尾是否为.dex 如下是DEX_SUFFIX变量的定义 private static final String DEX_SUFFIX = ".dex";

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[files.size()];
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[elementsPos++] = 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[elementsPos++] = 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[elementsPos++] = new Element(file);
368                  } else {
369                      elements[elementsPos++] = 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构造方法的那几个参数

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[elementsPos++] = 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[elementsPos++] = new Element(file);
368                  } else {
369                      elements[elementsPos++] = new Element(dex, file);
370                  }
371              }

我们看一下loadDexFile方法 可以看到有两个判断分支,一个是创建了一个DexFile对象, 一个是调用了DexFile对象里的,loadDex方法 主要区别就是这个优化dex文件的存放路径optimizedDirectory

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类的构造方法

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    }

因为传入的是三个参数的构造方法,我们只需要看如下构造方法

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方法

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方法 这个方法传入了五个参数,

 return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);

如下是loadDex方法的实现

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对象里面的构造方法,而且是五个参数的。

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方法

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++层 我们可以看到这个方法是动态注册的

841static JNINativeMethod gMethods[] = {
842  NATIVE_METHOD(DexFile, closeDexFile, "(Ljava/lang/Object;)Z"),
843  NATIVE_METHOD(DexFile,
844                defineClassNative,
845                "(Ljava/lang/String;"
846                "Ljava/lang/ClassLoader;"
847                "Ljava/lang/Object;"
848                "Ldalvik/system/DexFile;"
849                ")Ljava/lang/Class;"),
850  NATIVE_METHOD(DexFile, getClassNameList, "(Ljava/lang/Object;)[Ljava/lang/String;"),
851  NATIVE_METHOD(DexFile, isDexOptNeeded, "(Ljava/lang/String;)Z"),
852  NATIVE_METHOD(DexFile, getDexOptNeeded,
853                "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZZ)I"),
854  NATIVE_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;"),
861  NATIVE_METHOD(DexFile, createCookieWithDirectBuffer,
862                "(Ljava/nio/ByteBuffer;II)Ljava/lang/Object;"),
863  NATIVE_METHOD(DexFile, createCookieWithArray, "([BII)Ljava/lang/Object;"),
864  NATIVE_METHOD(DexFile, isValidCompilerFilter, "(Ljava/lang/String;)Z"),
865  NATIVE_METHOD(DexFile, isProfileGuidedCompilerFilter, "(Ljava/lang/String;)Z"),
866  NATIVE_METHOD(DexFile,
867                getNonProfileGuidedCompilerFilter,
868                "(Ljava/lang/String;)Ljava/lang/String;"),
869  NATIVE_METHOD(DexFile,
870                getSafeModeCompilerFilter,
871                "(Ljava/lang/String;)Ljava/lang/String;"),
872  NATIVE_METHOD(DexFile, isBackedByOatFile, "(Ljava/lang/Object;)Z"),
873  NATIVE_METHOD(DexFile, getDexFileStatus,
874                "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"),
875  NATIVE_METHOD(DexFile, getDexFileOutputPaths,
876                "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"),
877  NATIVE_METHOD(DexFile, getStaticSizeOfDexFile, "(Ljava/lang/Object;)J"),
878  NATIVE_METHOD(DexFile, getDexFileOptimizationStatus,
879                "(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"),
880  NATIVE_METHOD(DexFile, setTrusted, "(Ljava/lang/Object;)V")
881};

如下是这个函数的实现

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) {
274  ScopedUtfChars sourceName(env, javaSourceName);
275  if (sourceName.c_str() == nullptr) {
276    return 0;
277  }
278
279  Runtime* const runtime = Runtime::Current();
280  ClassLinker* linker = runtime->GetClassLinker();
281  std::vector<std::unique_ptr<const DexFile>> dex_files;
282  std::vector<std::string> error_msgs;
283  const OatFile* oat_file = nullptr;
284
285  dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
286                                                               class_loader,
287                                                               dex_elements,
288                                                               /*out*/ &oat_file,
289                                                               /*out*/ &error_msgs);
290
291  if (!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 类加载器是否为空

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) {
400  ScopedTrace trace(__FUNCTION__);
401  CHECK(dex_location != nullptr);
402  CHECK(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.
406  Thread* const self = Thread::Current();
407  Locks::mutator_lock_->AssertNotHeld(self);
408  Runtime* const runtime = Runtime::Current();
409
410  std::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.
413  if (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
421  OatFileAssistant 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.
428  std::string error_msg;
429  if (!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
435  const OatFile* source_oat_file = nullptr;
436
437  if (!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层的相关参数,我们跟进去看一下这个函数,这个构造方法传入了四个参数

421  OatFileAssistant oat_file_assistant(dex_location,
422                                      kRuntimeISA,
423                                      !runtime->IsAotCompiler(),
424                                      only_use_system_oat_files_);

可以看到这个构造函数又继续调用了OatFileAssistant函数

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 是否为空

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) {
101  CHECK(dex_location != nullptr) << "OatFileAssistant: null dex location";
102
103  if (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
110  dex_location_.assign(dex_location);
111
112  if (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.
119  std::string error_msg;
120  std::string odex_file_name;
121  if (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
127  if (!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())
142  size_t pos = dex_location_.rfind('/');
143  if (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,相关参数都传入了当前函数

862bool OatFileAssistant::DexLocationToOatFilename(const std::string& location,
863                                                InstructionSet isa,
864                                                std::string* oat_filename,
865                                                std::string* error_msg) {
866  CHECK(oat_filename != nullptr);
867  CHECK(error_msg != nullptr);
868
869  std::string cache_dir = GetDalvikCache(GetInstructionSetString(isa));
870  if (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.
878  return GetDalvikCacheFilename(location.c_str(), cache_dir.c_str(), oat_filename, error_msg);
879}

我们继续跟踪GetDalvikCacheFilename函数 我们看到这个函数返回值是bool类型的,获取DalvikCacheFilename,获取Dalvik缓存文件名。 通过指针形式给内存空间赋值。

269bool GetDalvikCacheFilename(const char* location, const char* cache_location,
270                            std::string* filename, std::string* error_msg) {
271  if (location[0] != '/') {
272    *error_msg = StringPrintf("Expected path in location to be absolute: %s", location);
273    return false;
274  }
275  std::string cache_file(&location[1]);  // skip leading slash
276  if (!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  }
282  std::replace(cache_file.begin(), cache_file.end(), '/', '@');
283  *filename = StringPrintf("%s/%s", cache_location, cache_file.c_str());
284  return true;
285}

我们回到前面 可以看到这里定义了一个容器, 里面用来存放Dex文件对象的相关信息

std::vector<std::unique_ptr<const DexFile>> dex_files;

在DexFile类里面 我们可以看到dex文件结构相关信息,比如checksum,stringids,header_,fieldids,method_ids_等 在这里插入图片描述

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) {
126  CHECK(begin_ != nullptr) << GetLocation();
127  CHECK_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.
131  CHECK_ALIGNED(begin_, alignof(Header));
132
133  InitializeSectionsFromMapList();
134}
135
84  struct Header {
85    uint8_t magic_[8] = {};
86    uint32_t checksum_ = 0;  // See also location_checksum_
87    uint8_t signature_[kSha1DigestSize] = {};
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的函数等。

150bool DexFile::CheckMagicAndVersion(std::string* error_msg) const {
151  if (!IsMagicValid()) {
152    std::ostringstream oss;
153    oss << "Unrecognized magic number in "  << GetLocation() << ":"
154            << " " << header_->magic_[0]
155            << " " << header_->magic_[1]
156            << " " << header_->magic_[2]
157            << " " << header_->magic_[3];
158    *error_msg = oss.str();
159    return false;
160  }
161  if (!IsVersionValid()) {
162    std::ostringstream oss;
163    oss << "Unrecognized version number in "  << GetLocation() << ":"
164            << " " << header_->magic_[4]
165            << " " << header_->magic_[5]
166            << " " << header_->magic_[6]
167            << " " << header_->magic_[7];
168    *error_msg = oss.str();
169    return false;
170  }
171  return true;
172}
63uint32_t DexFile::CalculateChecksum() const {
64  return CalculateChecksum(Begin(), Size());
65}
66
67uint32_t DexFile::CalculateChecksum(const uint8_t* begin, size_t size) {
68  const uint32_t non_sum_bytes = OFFSETOF_MEMBER(DexFile::Header, signature_);
69  return ChecksumMemoryRange(begin + non_sum_bytes, size - non_sum_bytes);
70}
71
72uint32_t DexFile::ChecksumMemoryRange(const uint8_t* begin, size_t size) {
73  return adler32(adler32(0L, Z_NULL, 0), begin, size);
74}
75
76int DexFile::GetPermissions() const {
77  CHECK(container_.get() != nullptr);
78  return container_->GetPermissions();
79}
80
81bool DexFile::IsReadOnly() const {
82  CHECK(container_.get() != nullptr);
83  return container_->IsReadOnly();
84}
85
86bool DexFile::EnableWrite() const {
87  CHECK(container_.get() != nullptr);
88  return container_->EnableWrite();
89}
90
91bool DexFile::DisableWrite() const {
92  CHECK(container_.get() != nullptr);
93  return container_->DisableWrite();
94}

我们回到前面,可以看到如下语句块 可以看到调用了LoadDexFiles函数,返回值就是dex_files

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这个函数

341std::vector<std::unique_ptr<const DexFile>> OatFileAssistant::LoadDexFiles(
342    const OatFile &oat_file, const char *dex_location) {
343  std::vector<std::unique_ptr<const DexFile>> dex_files;
344  if (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.
356  std::string error_msg;
357  const OatFile::OatDexFile* oat_dex_file = oat_file.GetOatDexFile(
358      dex_location.c_str(), nullptr, &error_msg);
359  if (oat_dex_file == nullptr) {
360    LOG(WARNING) << error_msg;
361    return false;
362  }
363
364  std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);
365  if (dex_file.get() == nullptr) {
366    LOG(WARNING) << "Failed to open dex file from oat dex file: " << error_msg;
367    return false;
368  }
369  out_dex_files->push_back(std::move(dex_file));
370
371  // Load the rest of the multidex entries
372  for (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  }
387  return true;
388}

我们可以看到关键函数,这个函数返回值就是dex_file

364  std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);

LoadDexFiles函数返回值为dex_file,LoadDexFiles函数又调用了OpenDexFile函数, 如下是OpenDexFile函数的定义 可以看到直接就返回了DexFile对象的指针, 这个DexFile对象里面有我们dex文件的所有信息。 我们可以选择在这个时机点脱壳,hook OpenDexFile函数的返回值即可

113const DexFile* OpenDexFile(const OatFile::OatDexFile* oat_dex_file, std::string* error_msg) {
114  DCHECK(oat_dex_file != nullptr);
115  auto it = opened_dex_files.find(oat_dex_file);
116  if (it != opened_dex_files.end()) {
117    return it->second.get();
118  }
119  const DexFile* ret = oat_dex_file->OpenDexFile(error_msg).release();
120  opened_dex_files.emplace(oat_dex_file, std::unique_ptr<const DexFile>(ret));
121  return ret;
122}

总结: java层通过调用BaseDexClassLoader加载器,传入了要加载的dex文件相关路径,然后调用了loadDexFile方法, 随后调用了native层的方法openDexFileNative,进行相关的参数判断,最后调用了OpenDexFile函数,把dex的文件相关信息加载进入了内存,返回值为DexFile对象指针,该对象里面含有dex文件的相关结构信息等。 我们可以通过hook这个OpenDexFile函数,获取返回值,得到dex文件。

评分

参与人数 23威望 +1 HB +34 THX +14 收起 理由
消逝的过去 + 1
花盗睡鼠 + 2 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
24567 + 2
Jawon + 1
徐三岁 + 1
虚心学习 + 1 [吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩!
一路走来不容易 + 1
后学真 + 1
459121520 + 1
爱汇编爱汇编 + 1 [吾爱汇编论坛52HB.COM]-感谢楼主热心分享,小小评分不成敬意!
yexing + 1
bnjzzheng + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
Cerolluo + 1 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
别来无恙 + 1
l278785481 + 1
pengyuwa1122 + 1
飞刀梦想 + 1
三月十六 + 1
zyyujq + 1
1130687409 + 1
zxjzzh + 2 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
xgbnapsua + 2
Shark恒 + 1 + 20 + 1 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!

查看全部评分

吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
zg2600 发表于 2022-7-8 13:12 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
zg2600 发表于 2022-7-21 10:33 | 显示全部楼层

[吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
智慧的猪猪 发表于 2022-8-19 13:16 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
bnjzzheng 发表于 2022-8-28 18:44 | 显示全部楼层

多谢分享
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
吃个大西瓜 发表于 2022-9-3 17:58 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
yexing 发表于 2022-9-8 10:41 | 显示全部楼层

荣耀9也是给予9.0的系统,还在研究怎么bl!
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
风里去 发表于 2022-9-8 14:12 | 显示全部楼层

谢谢楼主分享
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
头像被屏蔽
别管我了行 发表于 2022-9-14 16:53 | 显示全部楼层

提示: 作者被禁止或删除 内容自动屏蔽
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
曾经沧海 发表于 2022-9-18 14:50 | 显示全部楼层

有帮助,多多支持!
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

警告:本站严惩灌水回复,尊重自己从尊重他人开始!

1层
2层
3层
4层
5层
6层
7层
8层
9层
10层

免责声明

吾爱汇编(www.52hb.com)所讨论的技术及相关工具仅限用于研究学习,皆在提高软件产品的安全性,严禁用于不良动机。任何个人、团体、组织不得将其用于非法目的,否则,一切后果自行承担。吾爱汇编不承担任何因为技术滥用所产生的连带责任。吾爱汇编内容源于网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除。如有侵权请邮件或微信与我们联系处理。

站长邮箱:SharkHeng@sina.com
站长QQ:1140549900


QQ|RSS|手机版|小黑屋|帮助|吾爱汇编 ( 京公网安备11011502005403号 , 京ICP备20003498号-6 )|网站地图

Powered by Discuz!

吾爱汇编 www.52hb.com

快速回复 返回顶部 返回列表