InMemoryDexClassLoader加载内存dex的流程

一、前言

在之前分析dex文件加载流程中(dex文件加载流程(一)(二)(三)),我们分析过了DexClassLoader和PathClassLoader这两个类加载器加载dex的流程。它们两个都是用来记载文件dex的,但是在Android 8新增了InMemoryDexClassLoader,专门用于加载内存dex,接下来我们就根据源码来剖析内存dex的加载流程。

二、InMemoryDexClassLoader加载内存dex的流程

2.1 InMemoryDexClassLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//路径:/libcore/dalvik/src/main/java/dalvik/system/InMemoryDexClassLoader.java
public final class InMemoryDexClassLoader extends BaseDexClassLoader {
/**
* Create an in-memory DEX class loader with the given dex buffers.
*
* @param dexBuffers array of buffers containing DEX files between
* <tt>buffer.position()</tt> and <tt>buffer.limit()</tt>.
* @param parent the parent class loader for delegation.
* @hide
*/
public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) {
super(dexBuffers, parent);
}

public InMemoryDexClassLoader(ByteBuffer dexBuffer, ClassLoader parent) {
this(new ByteBuffer[] { dexBuffer }, parent);
}
}

InMemoryDexClassLoader继承自BaseDexClassLoader,是专门用来加载内存dex的,其中第一个构造方法接受ByteBuffer数组,因此是可以支持多dex的。它的方法都在父类中实现,其中对应的构造方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//路径:/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
/**
* Constructs an instance.
*
* dexFile must be an in-memory representation of a full dexFile.
*
* @param dexFiles the array of in-memory dex files containing classes.
* @param parent the parent class loader
*
* @hide
*/
public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexFiles);
}

在该方法中,会创建一个DexPathList对象。

2.2 DexPathList实例化

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
//路径:/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) {
//校验参数
if (definingContext == null) {
throw new NullPointerException("definingContext == null");
}
if (dexFiles == null) {
throw new NullPointerException("dexFiles == null");
}
if (Arrays.stream(dexFiles).anyMatch(v -> v == null)) {
throw new NullPointerException("dexFiles contains a null Buffer!");
}

this.definingContext = definingContext;
// 加载本地库
this.nativeLibraryDirectories = Collections.emptyList();
this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
this.nativeLibraryPathElements = makePathElements(this.systemNativeLibraryDirectories);

ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// 加载dex
this.dexElements = makeInMemoryDexElements(dexFiles, suppressedExceptions);
if (suppressedExceptions.size() > 0) {
this.dexElementsSuppressedExceptions =
suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
} else {
dexElementsSuppressedExceptions = null;
}
}

值得注意的是,它在这里调用的是makeInMemoryDexElements()方法,而在DexClassLoader和PathClassLoader流程中,调用的是makeDexElements()方法。

2.3 makeInMemoryDexElements()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//路径:/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
private static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles,
List<IOException> suppressedExceptions) {
//根据传入的内存dex个数创建Element对象
Element[] elements = new Element[dexFiles.length];
int elementPos = 0;
//遍历内存dex数组
for (ByteBuffer buf : dexFiles) {
try {
//给内存dex创建DexFile对象
DexFile dex = new DexFile(buf);
//将刚创建的DexFile封装成Element对象
elements[elementPos++] = new Element(dex);
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + buf, suppressed);
suppressedExceptions.add(suppressed);
}
}
if (elementPos != elements.length) {
elements = Arrays.copyOf(elements, elementPos);
}
return elements;
}

这个方法主要是遍历内存dex数组,给每个内存dex创建一个DexFile对象,并封装成Element对象。

2.4 DexFile实例化

1
2
3
4
5
6
//路径:/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
DexFile(ByteBuffer buf) throws IOException {
mCookie = openInMemoryDexFile(buf);
mInternalCookie = mCookie;
mFileName = null;
}

调用openInMemoryDexFile()方法,返回的cookie是用于操作dex文件的唯一凭证,可以理解为”身份码”。

2.5 openInMemoryDexFile()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//路径:/libcore/dalvik/src/main/java/dalvik/system/DexFile.java
private static Object openInMemoryDexFile(ByteBuffer buf) throws IOException {
//ByteBuffer.isDirect()用于判断当前缓冲区是否是直接缓冲区
if (buf.isDirect()) {
//单dex
return createCookieWithDirectBuffer(buf, buf.position(), buf.limit());
} else {
//多dex
return createCookieWithArray(buf.array(), buf.position(), buf.limit());
}
}

private static native Object createCookieWithDirectBuffer(ByteBuffer buf, int start, int end);
private static native Object createCookieWithArray(byte[] buf, int start, int end);

最终调用两个native层的方法,它们都在art/runtime/native/dalvik_system_DexFile.cc中实现。

2.5.1 createCookieWithDirectBuffer()

createCookieWithDirectBuffer()对应DexFile_createCookieWithDirectBuffer()方法,代码如下:

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
//路径:/art/runtime/native/dalvik_system_DexFile.cc
static jobject DexFile_createCookieWithDirectBuffer(JNIEnv* env,
jclass,
jobject buffer,
jint start,
jint end) {
//获取起始地址
uint8_t* base_address = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));
if (base_address == nullptr) {
ScopedObjectAccess soa(env);
ThrowWrappedIOException("dexFileBuffer not direct");
return 0;
}
//分配内存
std::unique_ptr<MemMap> dex_mem_map(AllocateDexMemoryMap(env, start, end));
if (dex_mem_map == nullptr) {
DCHECK(Thread::Current()->IsExceptionPending());
return 0;
}

size_t length = static_cast<size_t>(end - start);
//复制到dex_mem_map中
memcpy(dex_mem_map->Begin(), base_address, length);
//调用CreateSingleDexFileCookie创建Cookie
return CreateSingleDexFileCookie(env, std::move(dex_mem_map));
}

主要是将java层的数据转换成native层的数据,最后调用CreateSingleDexFileCookie()方法。

2.5.2 createCookieWithArray()

createCookieWithArray()对应DexFile_createCookieWithArray()方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//路径:/art/runtime/native/dalvik_system_DexFile.cc
static jobject DexFile_createCookieWithArray(JNIEnv* env,
jclass,
jbyteArray buffer,
jint start,
jint end) {
//分配内存
std::unique_ptr<MemMap> dex_mem_map(AllocateDexMemoryMap(env, start, end));
if (dex_mem_map == nullptr) {
DCHECK(Thread::Current()->IsExceptionPending());
return 0;
}
//获取dex_mem_map的首地址
auto destination = reinterpret_cast<jbyte*>(dex_mem_map.get()->Begin());
//复制到destination指向的内存
env->GetByteArrayRegion(buffer, start, end - start, destination);
//调用CreateSingleDexFileCookie创建Cookie
return CreateSingleDexFileCookie(env, std::move(dex_mem_map));
}

同样最终也是调用CreateSingleDexFileCookie()方法。

2.6 CreateSingleDexFileCookie()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//路径:/art/runtime/native/dalvik_system_DexFile.cc
static jobject CreateSingleDexFileCookie(JNIEnv* env, std::unique_ptr<MemMap> data) {
//调用CreateDexFile()创建DexFile对象
std::unique_ptr<const DexFile> dex_file(CreateDexFile(env, std::move(data)));
if (dex_file.get() == nullptr) {
DCHECK(env->ExceptionCheck());
return nullptr;
}
//DexFile数组
std::vector<std::unique_ptr<const DexFile>> dex_files;
//将刚刚创建的dex_file放入数组中
dex_files.push_back(std::move(dex_file));
//调用ConvertDexFilesToJavaArray()方法转成可供Java层使用的数据
return ConvertDexFilesToJavaArray(env, nullptr, dex_files);
}

主要是调用CreateDexFile()方法为内存dex创建DexFile对象,然后移入DexFile数组中,最后调用ConvertDexFilesToJavaArray()方法将DexFile数组转成Java数组。这里我们主要关注CreateDexFile()方法。

2.7 CreateDexFile()

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
//路径:/art/runtime/native/dalvik_system_DexFile.cc
static const DexFile* CreateDexFile(JNIEnv* env, std::unique_ptr<MemMap> dex_mem_map) {
//StringPrintf格式化字符串
std::string location = StringPrintf("Anonymous-DexFile@%p-%p",
dex_mem_map->Begin(),
dex_mem_map->End());
std::string error_message;
//调用DexFile::Open()创建DexFile对象
std::unique_ptr<const DexFile> dex_file(DexFile::Open(location,
0,
std::move(dex_mem_map),
/* verify */ true,
/* verify_location */ true,
&error_message));
if (dex_file == nullptr) {
ScopedObjectAccess soa(env);
ThrowWrappedIOException("%s", error_message.c_str());
return nullptr;
}

if (!dex_file->DisableWrite()) {
ScopedObjectAccess soa(env);
ThrowWrappedIOException("Failed to make dex file read-only");
return nullptr;
}
//调用release()释放dex_file所拥有的内存
return dex_file.release();
}

主要是调用DexFile::Open()方法,之后的流程就跟DexClassLoader和PathClassLoader一样了,可以详见dex文件加载流程(三),本文就不再详细分析了。

2.8 DexFile::Open()

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
//路径:/art/runtime/dex_file.cc
std::unique_ptr<const DexFile> DexFile::Open(const std::string& location,
uint32_t location_checksum,
std::unique_ptr<MemMap> map,
bool verify,
bool verify_checksum,
std::string* error_msg) {
ScopedTrace trace(std::string("Open dex file from mapped-memory ") + location);
CHECK(map.get() != nullptr);
//内存dex的长度不能小于它的dex文件头长度
if (map->Size() < sizeof(DexFile::Header)) {
*error_msg = StringPrintf(
"DexFile: failed to open dex file '%s' that is too short to have a header",
location.c_str());
return nullptr;
}
//调用OpenCommon()创建DexFile
std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),
map->Size(),
location,
location_checksum,
kNoOatDexFile,
verify,
verify_checksum,
error_msg);
if (dex_file != nullptr) {
dex_file->mem_map_.reset(map.release());
}
return dex_file;
}

主要是调用OpenCommon()方法。

2.9 OpenCommon()

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
//路径:/art/runtime/dex_file.cc
std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base,
size_t size,
const std::string& location,
uint32_t location_checksum,
const OatDexFile* oat_dex_file,
bool verify,
bool verify_checksum,
std::string* error_msg,
VerifyResult* verify_result) {
if (verify_result != nullptr) {
*verify_result = VerifyResult::kVerifyNotAttempted;
}
//调用DexFile的构造方法真正为内存dex创建DexFile实例,里面会解析dex
std::unique_ptr<DexFile> dex_file(new DexFile(base,
size,
location,
location_checksum,
oat_dex_file));
if (dex_file == nullptr) {
*error_msg = StringPrintf("Failed to open dex file '%s' from memory: %s", location.c_str(),
error_msg->c_str());
return nullptr;
}
//检查magic
if (!dex_file->Init(error_msg)) {
dex_file.reset();
return nullptr;
}
//检查dex文件的结构
if (verify && !DexFileVerifier::Verify(dex_file.get(),
dex_file->Begin(),
dex_file->Size(),
location.c_str(),
verify_checksum,
error_msg)) {
if (verify_result != nullptr) {
*verify_result = VerifyResult::kVerifyFailed;
}
return nullptr;
}
if (verify_result != nullptr) {
*verify_result = VerifyResult::kVerifySucceeded;
}
return dex_file;
}

从这里可以看出,不管是DexClassLoader、PathClassLoader,还是InMemoryDexClassLoader,尽管它们的流程可能会不太一样,但最终都会调用OpenCommon()方法创建dexfile,而且该方法的参数含有dex的起始地址(base)和大小(size),因此这里可以作为一个非常好的脱壳点。


参考:

http://aospxref.com/android-8.0.0_r36


InMemoryDexClassLoader加载内存dex的流程
http://example.com/2023/12/06/Android安全/InMemoryDexClassLoader加载内存dex的流程/
作者
gla2xy
发布于
2023年12月6日
许可协议