IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

Android安全–DexClassLoader动态加载分析

Coder 2015-09-21 13:45:43 累计浏览 1,205 次
本机暂存

看到原来有把原始的dex文件加密保存,然后解密后使用DexClassLoader加载文件的方法,就来分析下DexClassLoader的加载流程:

源码地址:http://androidxref.com/4.4_r1/xref/libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

该class加载器是加载包含classes.dex文件的jar文件或者apk文件,需要一个应用私有的,可写的目录去缓存优化的classes。可以用使用File dexoutputDir = context.getDir(“dex”,0);创建一个这样的目录,不要使用外部缓存,以保护你的应用被代码注入。

构造方法如下:

public class DexClassLoader extends BaseDexClassLoader {
下面这段注释详细地说明了这个构造函数中各个参数地意义,不作阐述了,希望大家能够认真阅读,思考;

public DexClassLoader(String dexPath, String optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(dexPath, new File(optimizedDirectory), libraryPath, parent);
}
}

dexpath为jar或apk文件目录。

optimizedDirectory为优化dex缓存目录。

libraryPath包含native lib的目录路径。

parent父类加载器。

然后执行的是父类的构造函数:

super(dexPath, new File(optimizedDirectory), libraryPath, parent);

BaseDexClassLoader 的构造函数如下:

public BaseDexClassLoader(String dexPath, File optimizedDirectory,
 String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}

第一句调用的还是父类的构造函数,也就是ClassLoader的构造函数:

protected ClassLoader(ClassLoader parentLoader) {
this(parentLoader, false);
}
/*
* constructor for the BootClassLoader which needs parent to be null.
*/
ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
  if (parentLoader == null && !nullAllowed) {
throw new NullPointerException(“parentLoader == null && !nullAllowed”);
 }
 parent = parentLoader;
}

该构造函数把传进来的父类加载器赋给了私有变量parent。

再来看

this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);

pathList为该类的私有成员变量,类型为DexPathList,进去DexPathList函数:

public DexPathList(ClassLoader definingContext, String dexPath,
String libraryPath, File optimizedDirectory) {
………..    
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
  suppressedExceptions);
………..
}

前面是一些对于传入参数的验证,然后调用了makeDexElements。

private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory,
ArrayList<IOException> suppressedExceptions) {
ArrayList<Element> elements = new ArrayList<Element>();
for (File file : files) {
File zip = null;
DexFile dex = null;
String name = file.getName();

if (name.endsWith(DEX_SUFFIX)) {               //dex文件处理
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException ex) {
System.logE(“Unable to load dex file: ” + file, ex);
}
} else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
|| name.endsWith(ZIP_SUFFIX)) {   //apk,jar,zip文件处理
zip = file;

try {
dex = loadDexFile(file, optimizedDirectory);
} catch (IOException suppressed) {
suppressedExceptions.add(suppressed);
}
} else if (file.isDirectory()) {
elements.add(new Element(file, true, null, null));
} else {
System.logW(“Unknown file type for: ” + file);
}

if ((zip != null) || (dex != null)) {
elements.add(new Element(file, false, zip, dex));
}
}

return elements.toArray(new Element[elements.size()]);
}
}

不管是dex文件,还是apk文件最终加载的都是loadDexFile,跟进这个函数:

private static DexFile loadDexFile(File file, File optimizedDirectory)
throws IOException {
if (optimizedDirectory == null) {
return new DexFile(file);
} else {
String optimizedPath = optimizedPathFor(file, optimizedDirectory);
return DexFile.loadDex(file.getPath(), optimizedPath, 0);
}
}

如果optimizedDirectory为null就会调用openDexFile(fileName, null, 0);加载文件。

否则调用DexFile.loadDex(file.getPath(), optimizedPath, 0);

而这个函数也只是直接调用new DexFile(sourcePathName, outputPathName, flags);

里面调用的也是openDexFile(sourceName, outputName, flags);

所以最后都是调用openDexFile,跟进这个函数:

private static int openDexFile(String sourceName, String outputName,
int flags) throws IOException {
return openDexFileNative(new File(sourceName).getCanonicalPath(),
(outputName == null) ? null : new File(outputName).getCanonicalPath(),
flags);
}

而这个函数调用的是so的openDexFileNative这个函数。打开成功则返回一个cookie。

接下来就是分析native函数的实现部分了。

———-openDexFileNative———-

代码地址:http://androidxref.com/4.4_r1/xref/dalvik/vm/native/dalvik_system_DexFile.cpp

static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,
JValue* pResult)
{
……………
if (hasDexExtension(sourceName)
&& dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
ALOGV(“Opening DEX file ‘%s’ (DEX)”, sourceName);

pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = true;
pDexOrJar->pRawDexFile = pRawDexFile;
pDexOrJar->pDexMemory = NULL;
} else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
ALOGV(“Opening DEX file ‘%s’ (Jar)”, sourceName);

pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = false;
pDexOrJar->pJarFile = pJarFile;
pDexOrJar->pDexMemory = NULL;
} else {
ALOGV(“Unable to open DEX file ‘%s’”, sourceName);
dvmThrowIOException(“unable to open DEX file”);
}
……………
}

这里会根据是否为dex文件或者包含classes.dex文件的jar,分别调用函数dvmRawDexFileOpen和dvmJarFileOpen来处理,最终返回一个DexOrJar的结构。

首先来看dvmRawDexFileOpen函数的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
int dvmRawDexFileOpen(const char* fileName, const char* odexOutputName,
RawDexFile** ppRawDexFile, bool isBootstrap)
{
.................
dexFd = open(fileName, O_RDONLY);
if (dexFd < 0) goto bail;

/* If we fork/exec into dexopt, don't let it inherit the open fd. */
dvmSetCloseOnExec(dexFd);

//校验前8个字节的magic是否正确,然后把校验和保存到adler32
if (verifyMagicAndGetAdler32(dexFd, &adler32) < 0) {
ALOGE("Error with header for %s", fileName);
goto bail;
}
//得到文件修改时间以及文件大小
  if (getModTimeAndSize(dexFd, &modTime, &fileSize) < 0) {
ALOGE("Error with stat for %s", fileName);
goto bail;
}
.................
//调用函数dexOptCreateEmptyHeader,构造了一个DexOptHeader结构体,写入fd并返回
optFd = dvmOpenCachedDexFile(fileName, cachedName, modTime,
adler32, isBootstrap, &newFile, /*createIfMissing=*/true);

if (optFd < 0) {
ALOGI("Unable to open or create cache for %s (%s)",
fileName, cachedName);
goto bail;
}
locked = true;

  //如果成功生了opt头
if (newFile) {
u8 startWhen, copyWhen, endWhen;
bool result;
  off_t dexOffset;

dexOffset = lseek(optFd, 0, SEEK_CUR);
result = (dexOffset > 0);

if (result) {
startWhen = dvmGetRelativeTimeUsec();
// 将dex文件中的内容写入文件的当前位置,也就是从dexOffset的偏移处开始写
result = copyFileToFile(optFd, dexFd, fileSize) == 0;
copyWhen = dvmGetRelativeTimeUsec();
}

if (result) {
//对dex文件进行优化
result = dvmOptimizeDexFile(optFd, dexOffset, fileSize,
fileName, modTime, adler32, isBootstrap);
}

if (!result) {
ALOGE("Unable to extract+optimize DEX from '%s'", fileName);
goto bail;
}

endWhen = dvmGetRelativeTimeUsec();
ALOGD("DEX prep '%s': copy in %dms, rewrite %dms",
fileName,
(int) (copyWhen - startWhen) / 1000,
(int) (endWhen - copyWhen) / 1000);
}

//dvmDexFileOpenFromFd这个函数最主要在这里干了两件事情
// 1.将优化后得dex文件(也就是odex文件)通过mmap映射到内存中,并通过mprotect修改它的映射内存为只读权限
// 2.将映射为只读的这块dex数据中的内容全部提取到DexFile这个数据结构中去
if (dvmDexFileOpenFromFd(optFd, &pDvmDex) != 0) {
ALOGI("Unable to map cached %s", fileName);
goto bail;
}

if (locked) {
/* unlock the fd */
  if (!dvmUnlockCachedDexFile(optFd)) {
/* uh oh -- this process needs to exit or we'll wedge the system */
ALOGE("Unable to unlock DEX file");
goto bail;
}
locked = false;
}

ALOGV("Successfully opened '%s'", fileName);
//填充结构体 RawDexFile
*ppRawDexFile = (RawDexFile*) calloc(1, sizeof(RawDexFile));
(*ppRawDexFile)->cacheFileName = cachedName;
  (*ppRawDexFile)->pDvmDex = pDvmDex;
cachedName = NULL;      // don't free it below
result = 0;

bail:
free(cachedName);
if (dexFd >= 0) {
close(dexFd);
}
if (optFd >= 0) {
if (locked)
(void) dvmUnlockCachedDexFile(optFd);
close(optFd);
}
return result;
}

最后成功的话,填充RawDexFile。

dvmJarFileOpen的代码处理也是差不多的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
int dvmJarFileOpen(const char* fileName, const char* odexOutputName,
JarFile** ppJarFile, bool isBootstrap)
{
...
...
...
//调用函数dexZipOpenArchive来打开zip文件,并缓存到系统内存里
if (dexZipOpenArchive(fileName, &archive) != 0)
goto bail;
archiveOpen = true;
...
//这行代码设置当执行完成后,关闭这个文件句柄
dvmSetCloseOnExec(dexZipGetArchiveFd(&archive));
...
//优先处理已经优化了的Dex文件
fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);
...
//从压缩包里找到Dex文件,然后打开这个文件
entry = dexZipFindEntry(&archive, kDexInJarName);
...
//把未经过优化的Dex文件进行优化处理,并输出到指定的文件
if (odexOutputName == NULL) {
cachedName = dexOptGenerateCacheFileName(fileName,
kDexInJarName);
}
...
//创建缓存的优化文件
fd = dvmOpenCachedDexFile(fileName, cachedName,
dexGetZipEntryModTime(&archive, entry),
dexGetZipEntryCrc32(&archive, entry),
isBootstrap, &newFile, /*createIfMissing=*/true);
...
//调用函数dexZipExtractEntryToFile从压缩包里解压文件出来
if (result) {
startWhen = dvmGetRelativeTimeUsec();
result = dexZipExtractEntryToFile(&archive, entry, fd) == 0;
extractWhen = dvmGetRelativeTimeUsec();
}
...
//调用函数dvmOptimizeDexFile对Dex文件进行优化处理
if (result) {
result = dvmOptimizeDexFile(fd, dexOffset,
dexGetZipEntryUncompLen(&archive, entry),
fileName,
dexGetZipEntryModTime(&archive, entry),
dexGetZipEntryCrc32(&archive, entry),
isBootstrap);
}
...
//调用函数dvmDexFileOpenFromFd来缓存dex文件
//并分析文件的内容。比如标记是否优化的文件,通过签名检查Dex文件是否合法
if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {
ALOGI("Unable to map %s in %s", kDexInJarName, fileName);
goto bail;
}
...
//保存文件到缓存里,标记这个文件句柄已经保存到缓存
if (locked) {
/* unlock the fd */
if (!dvmUnlockCachedDexFile(fd)) {
/* uh oh -- this process needs to exit or we'll wedge the system */
ALOGE("Unable to unlock DEX file");
goto bail;
}
locked = false;
}
...
//设置一些相关信息返回前面的函数处理。
*ppJarFile = (JarFile*) calloc(1, sizeof(JarFile));
(*ppJarFile)->archive = archive;
(*ppJarFile)->cacheFileName = cachedName;
(*ppJarFile)->pDvmDex = pDvmDex;
cachedName = NULL;      // don't free it below
result = 0;
...

}

最后成功的话,填充JarFile。

参考文章:

http://bbs.pediy.com/showthread.php?t=199230

http://0nly3nd.sinaapp.com/?p=688

http://blog.csdn.net/roland_sun/article/details/47183119

本文链接:http://www.blogfshare.com/dexclassloader.html

转载提示:除非注明,AloneMonkey的文章均为原创,转载请以链接形式注明作者和出处。谢谢合作!

同分类推荐文章

  1. 绿盟科技《APT组织研究年鉴》(2026 版)正式发布 (2026-06-16 20:21:10)
  2. 【已复现】Linux内核Fragnesia权限提升漏洞(CVE-2026-46300) (2026-06-15 10:53:58)
  3. 企业文档安全最佳实践(二):给文档上“身份证”——手动标密与智能自动标密 (2026-06-12 17:18:33)

查看更多 安全 文章 →

建议继续学习

  1. 情绪版(Mood board)操作流程的新思考 (累计阅读 41,752)
  2. android 开发入门 (累计阅读 19,526)
  3. 微信扫码登录网页实现原理 (累计阅读 17,459)
  4. Android 连接SSID隐藏网络以及 LEAP 认证的方法 (累计阅读 9,538)
  5. 让安卓手机通过代理翻墙的方法 (累计阅读 9,114)
  6. 手机产品设计方向 (累计阅读 7,952)
  7. 实时监控Android设备网络封包 (累计阅读 6,554)
  8. Eclipse开发Android应用程序入门:重装上阵 (累计阅读 6,458)
  9. 基于 PhoneGap 与 Java 开发的 Android 应用的性能对比 (累计阅读 6,406)
  10. Android用户界面设计:表格布局 (累计阅读 6,186)