dex文件加载流程(三)

书接上文dex文件加载流程(二) (gal2xy.github.io)

一、DexFile::Open()

根据上一篇文章的学习可知,在OpenDexFilesFromOat()方法中,如果能够从oat文件中加载dex文件,则最终在OatFile::OatDexFile::OpenDexFile()方法中调用带8个参数的DexFile::Open()方法:

1
2
3
4
5
6
7
8
DexFile::Open(dex_file_pointer_,
FileSize(),
dex_file_location_,
dex_file_location_checksum_,
this,
kVerify,
kVerifyChecksum,
error_msg);

否则直接调用带5个参数的DexFile::Open()方法:

1
DexFile::Open(dex_location, dex_location, kVerifyChecksum, /*out*/ &error_msg, &dex_files)

1.1 带8个参数的DexFile::Open()

带8个参数的DexFile::Open()方法比较简单,直接调用OpenCommon()方法,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//路径:/art/runtime/dex_file.cc
std::unique_ptr<const DexFile> DexFile::Open(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) {
ScopedTrace trace(std::string("Open dex file from RAM ") + location);
return OpenCommon(base,
size,
location,
location_checksum,
oat_dex_file,
verify,
verify_checksum,
error_msg);
}

DexFile::Open()的参数进行说明:

  • base:指向dex文件的内存地址。
  • size:dex文件的大小。
  • location:dex文件的位置。
  • location_checksum:dex文件位置的校验和。
  • oat_dex_file:指向OatDexFile对象的指针。
  • verify:表示是否要对dex文件进行验证。
  • verify_checksum:表示是否要验证dex文件的校验和。
  • error_msg:指向错误信息字符串的指针。

1.2 带5个参数的DexFile::Open()

带5个参数的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
31
32
33
34
35
36
37
38
//路径:/art/runtime/dex_file.cc
bool DexFile::Open(const char* filename,
const std::string& location,
bool verify_checksum,
std::string* error_msg,
std::vector<std::unique_ptr<const DexFile>>* dex_files) {
ScopedTrace trace(std::string("Open dex file ") + std::string(location));
DCHECK(dex_files != nullptr) << "DexFile::Open: out-param is nullptr";
uint32_t magic;
//打开文件读取magic[8],OpenAndReadMagic可作为一个常用脱壳点
File fd = OpenAndReadMagic(filename, &magic, error_msg);
if (fd.Fd() == -1) {
DCHECK(!error_msg->empty());
return false;
}
//如果是zip文件头
if (IsZipMagic(magic)) {
//通过DexFile::OpenZip()打开zip文件
return DexFile::OpenZip(fd.Release(), location, verify_checksum, error_msg, dex_files);
}
//如果是dex文件头
if (IsDexMagic(magic)) {
//通过DexFile::OpenFile()打开dex文件
std::unique_ptr<const DexFile> dex_file(DexFile::OpenFile(fd.Release(),
location,
/* verify */ true,
verify_checksum,
error_msg));
if (dex_file.get() != nullptr) {
dex_files->push_back(std::move(dex_file));
return true;
} else {
return false;
}
}
*error_msg = StringPrintf("Expected valid zip or dex file: '%s'", filename);
return false;
}

DexFile::Open()的参数进行说明:

  • filename:dex文件名。
  • location:dex文件的位置。
  • verify_checksum:表示是否要验证dex文件的校验和。
  • error_msg:指向错误信息字符串的指针。
  • dex_files:存储dex file的DexFile数组。

整个代码逻辑比较简单,首先通过OpenAndReadMagic()获取文件头,然后根据文件头判断文件类型,执行不同代码:如果是zip文件,则调用DexFile::OpenZip()打开zip文件,如果是dex文件,则调用DexFile::OpenFile()打开dex文件。

其中OpenAndReadMagic()可以作为一个脱壳点,但并不是很适合,因为此时dex文件还没有加载到内存中。

1.2.1 DexFile::OpenZip()

首先来看DexFile::OpenZip()方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool DexFile::OpenZip(int fd,
const std::string& location,
bool verify_checksum,
std::string* error_msg,
std::vector<std::unique_ptr<const DexFile>>* dex_files) {
ScopedTrace trace("Dex file open Zip " + std::string(location));
DCHECK(dex_files != nullptr) << "DexFile::OpenZip: out-param is nullptr";
//通过ZipArchive::OpenFromFd()打开zip文件
std::unique_ptr<ZipArchive> zip_archive(ZipArchive::OpenFromFd(fd, location.c_str(), error_msg));
if (zip_archive.get() == nullptr) {
DCHECK(!error_msg->empty());
return false;
}
//通过DexFile::OpenAllDexFilesFromZip()打开zip中的所有dex文件
return DexFile::OpenAllDexFilesFromZip(*zip_archive,
location,
verify_checksum,
error_msg,
dex_files);
}

主要是调用DexFile::OpenAllDexFilesFromZip()方法,该方法的代码如下:

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
//路径:/art/runtime/dex_file.cc
bool DexFile::OpenAllDexFilesFromZip(const ZipArchive& zip_archive,
const std::string& location,
bool verify_checksum,
std::string* error_msg,
std::vector<std::unique_ptr<const DexFile>>* dex_files) {
ScopedTrace trace("Dex file open from Zip " + std::string(location));
DCHECK(dex_files != nullptr) << "DexFile::OpenFromZip: out-param is nullptr";
ZipOpenErrorCode error_code;
//调用OpenOneDexFileFromZip()从zip文件中打开dex文件
std::unique_ptr<const DexFile> dex_file(OpenOneDexFileFromZip(zip_archive, kClassesDex, location, verify_checksum, error_msg, &error_code));
if (dex_file.get() == nullptr) {
return false;
} else {
dex_files->push_back(std::move(dex_file));
//zip文件中存在多个dex文件
for (size_t i = 1; ; ++i) {
std::string name = GetMultiDexClassesDexName(i);
std::string fake_location = GetMultiDexLocation(i, location.c_str());
//调用OpenOneDexFileFromZip()
std::unique_ptr<const DexFile> next_dex_file(OpenOneDexFileFromZip(zip_archive, name.c_str(), fake_location, verify_checksum, error_msg, &error_code));
if (next_dex_file.get() == nullptr) {
if (error_code != ZipOpenErrorCode::kEntryNotFound) {
LOG(WARNING) << "Zip open failed: " << *error_msg;
}
break;
} else {
dex_files->push_back(std::move(next_dex_file));
}

if (i == kWarnOnManyDexFilesThreshold) {
LOG(WARNING) << location << " has in excess of " << kWarnOnManyDexFilesThreshold
<< " dex files. Please consider coalescing and shrinking the number to "
" avoid runtime overhead.";
}

if (i == std::numeric_limits<size_t>::max()) {
LOG(ERROR) << "Overflow in number of dex files!";
break;
}
}

return true;
}
}

主要是调用OpenOneDexFileFromZip()方法单独打开zip文件中的dex文件,该方法的代码如下:

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
//路径:/art/runtime/dex_file.cc
std::unique_ptr<const DexFile> DexFile::OpenOneDexFileFromZip(const ZipArchive& zip_archive,
const char* entry_name,
const std::string& location,
bool verify_checksum,
std::string* error_msg,
ZipOpenErrorCode* error_code) {
ScopedTrace trace("Dex file open from Zip Archive " + std::string(location));
CHECK(!location.empty());

//调用zip_archive.Find()找到给定的dex文件
std::unique_ptr<ZipEntry> zip_entry(zip_archive.Find(entry_name, error_msg));
if (zip_entry == nullptr) {
*error_code = ZipOpenErrorCode::kEntryNotFound;
return nullptr;
}
if (zip_entry->GetUncompressedLength() == 0) {
*error_msg = StringPrintf("Dex file '%s' has zero length", location.c_str());
*error_code = ZipOpenErrorCode::kDexFileError;
return nullptr;
}

std::unique_ptr<MemMap> map;
if (zip_entry->IsUncompressed()) {
//是否文件对齐
if (!zip_entry->IsAlignedTo(alignof(Header))) {
......
} else {
// dex文件解压缩?
map.reset(zip_entry->MapDirectlyFromFile(location.c_str(), /*out*/error_msg));
if (map == nullptr) {
LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; "
<< "is your ZIP file corrupted? Falling back to extraction.";
// Try again with Extraction which still has a chance of recovery.
}
}
}

if (map == nullptr) {
// Default path for compressed ZIP entries,
// and fallback for stored ZIP entries.
map.reset(zip_entry->ExtractToMemMap(location.c_str(), entry_name, error_msg));
}

if (map == nullptr) {
*error_msg = StringPrintf("Failed to extract '%s' from '%s': %s", entry_name, location.c_str(), error_msg->c_str());
*error_code = ZipOpenErrorCode::kExtractToMemoryError;
return nullptr;
}
VerifyResult verify_result;
std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),
map->Size(),
location,
zip_entry->GetCrc32(),
kNoOatDexFile,
/* verify */ true,
verify_checksum,
error_msg,
&verify_result);
if (dex_file == nullptr) {
if (verify_result == VerifyResult::kVerifyNotAttempted) {
*error_code = ZipOpenErrorCode::kDexFileError;
} else {
*error_code = ZipOpenErrorCode::kVerifyError;
}
return nullptr;
}
dex_file->mem_map_.reset(map.release());
if (!dex_file->DisableWrite()) {
*error_msg = StringPrintf("Failed to make dex file '%s' read only", location.c_str());
*error_code = ZipOpenErrorCode::kMakeReadOnlyError;
return nullptr;
}
CHECK(dex_file->IsReadOnly()) << location;
if (verify_result != VerifyResult::kVerifySucceeded) {
*error_code = ZipOpenErrorCode::kVerifyError;
return nullptr;
}
*error_code = ZipOpenErrorCode::kNoError;
return dex_file;
}

最终还是调用OpenCommon()方法。

1.2.2 DexFile::OpenFile()

然后是DexFile::OpenFile(),代码如下:

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
//路径:/art/runtime/dex_file.cc
std::unique_ptr<const DexFile> DexFile::OpenFile(int fd,
const std::string& location,
bool verify,
bool verify_checksum,
std::string* error_msg) {
ScopedTrace trace(std::string("Open dex file ") + std::string(location));
CHECK(!location.empty());
std::unique_ptr<MemMap> map;
{
File delayed_close(fd, /* check_usage */ false);
struct stat sbuf;
memset(&sbuf, 0, sizeof(sbuf));
if (fstat(fd, &sbuf) == -1) {
*error_msg = StringPrintf("DexFile: fstat '%s' failed: %s", location.c_str(),
strerror(errno));
return nullptr;
}
if (S_ISDIR(sbuf.st_mode)) {
*error_msg = StringPrintf("Attempt to mmap directory '%s'", location.c_str());
return nullptr;
}
size_t length = sbuf.st_size;
//将dex文件映射到内存中
map.reset(MemMap::MapFile(length,
PROT_READ,
MAP_PRIVATE,
fd,
0,
/*low_4gb*/false,
location.c_str(),
error_msg));
if (map == nullptr) {
DCHECK(!error_msg->empty());
return nullptr;
}
}

if (map->Size() < sizeof(DexFile::Header)) {
*error_msg = StringPrintf(
"DexFile: failed to open dex file '%s' that is too short to have a header",
location.c_str());
return nullptr;
}

const Header* dex_header = reinterpret_cast<const Header*>(map->Begin());

std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(),
map->Size(),
location,
dex_header->checksum_,
kNoOatDexFile,
verify,
verify_checksum,
error_msg);
if (dex_file != nullptr) {
dex_file->mem_map_.reset(map.release());
}

return dex_file;
}

最终也是调用OpenCommon()方法。

由此看见,无论dex文件的打开过程多么曲折和多样,最终都会调用OpenCommon()方法,因此该方法可作为脱壳点使用(Android 7对应的是OpenMemory()

二、OpenCommon()

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对象
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;
}
//Init()初始化dex file
if (!dex_file->Init(error_msg)) {
dex_file.reset();
return nullptr;
}
//Verify()对dex file进行验证
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;
}

主要是调用DexFile()创建DexFile对象,然后调用Init()方法进行初始化工作,最后调用Verify()方法对dex文件进行验证。

2.1 DexFile()

DexFile()方法的代码如下:

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
DexFile::DexFile(const uint8_t* base,
size_t size,
const std::string& location,
uint32_t location_checksum,
const OatDexFile* oat_dex_file)
: begin_(base),
size_(size),
location_(location),
location_checksum_(location_checksum),
header_(reinterpret_cast<const Header*>(base)),
string_ids_(reinterpret_cast<const StringId*>(base + header_->string_ids_off_)),
type_ids_(reinterpret_cast<const TypeId*>(base + header_->type_ids_off_)),
field_ids_(reinterpret_cast<const FieldId*>(base + header_->field_ids_off_)),
method_ids_(reinterpret_cast<const MethodId*>(base + header_->method_ids_off_)),
proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)),
class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)),
method_handles_(nullptr),
num_method_handles_(0),
call_site_ids_(nullptr),
num_call_site_ids_(0),
oat_dex_file_(oat_dex_file) {

CHECK(begin_ != nullptr) << GetLocation();
CHECK_GT(size_, 0U) << GetLocation();
// Check base (=header) alignment.
// Must be 4-byte aligned to avoid undefined behavior when accessing
// any of the sections via a pointer.
CHECK_ALIGNED(begin_, alignof(Header));

InitializeSectionsFromMapList();
}

DexFile()函数后面加:是赋值的意思,即把()中的变量赋值给()前的变量。因为该方法带有dex的base和size参数,所以也可以作为一个脱壳点。

整个函数主要是调用了InitializeSectionsFromMapList()方法。该方法如下:

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
void DexFile::InitializeSectionsFromMapList() {
const MapList* map_list = reinterpret_cast<const MapList*>(begin_ + header_->map_off_);
if (header_->map_off_ == 0 || header_->map_off_ > size_) {
// Bad offset. The dex file verifier runs after this method and will reject the file.
return;
}
const size_t count = map_list->size_;

size_t map_limit = header_->map_off_ + count * sizeof(MapItem);
if (header_->map_off_ >= map_limit || map_limit > size_) {
// Overflow or out out of bounds. The dex file verifier runs after
// this method and will reject the file as it is malformed.
return;
}

for (size_t i = 0; i < count; ++i) {
const MapItem& map_item = map_list->list_[i];
if (map_item.type_ == kDexTypeMethodHandleItem) {
method_handles_ = reinterpret_cast<const MethodHandleItem*>(begin_ + map_item.offset_);
num_method_handles_ = map_item.size_;
} else if (map_item.type_ == kDexTypeCallSiteIdItem) {
call_site_ids_ = reinterpret_cast<const CallSiteIdItem*>(begin_ + map_item.offset_);
num_call_site_ids_ = map_item.size_;
}
}
}

上述代码逻辑为:通过dex文件中的map_list结构来解析dex文件。

2.2 Init()

1
2
3
4
5
6
bool DexFile::Init(std::string* error_msg) {
if (!CheckMagicAndVersion(error_msg)) {
return false;
}
return true;
}

调用CheckMagicAndVersion()方法检查magic,即文件头和版本号。

2.3 DexFileVerifier::Verify()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//路径:/art/runtime/dex_file_verifier.cc
bool DexFileVerifier::Verify(const DexFile* dex_file,
const uint8_t* begin,
size_t size,
const char* location,
bool verify_checksum,
std::string* error_msg) {
std::unique_ptr<DexFileVerifier> verifier(
new DexFileVerifier(dex_file, begin, size, location, verify_checksum));
if (!verifier->Verify()) {
*error_msg = verifier->FailureReason();
return false;
}
return true;
}

上述代码功能主要是创建DexFileVerifier对象,并调用Verify()方法验证dex文件结构。Verify()方法的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//路径:/art/runtime/dex_file_verifier.cc
bool DexFileVerifier::Verify() {
// 检查dexheaer
if (!CheckHeader()) {
return false;
}

// 检查map_list
if (!CheckMap()) {
return false;
}

// 检查剩余部分,如string_ids、type_ids等
if (!CheckIntraSection()) {
return false;
}

// Check references from one section to another.
if (!CheckInterSection()) {
return false;
}

return true;
}

三、总览图


参考:

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

[原创]菜鸟学8.1版本dex加载流程笔记–第二篇:DexFile::Open流程与简单脱壳原理-Android安全-看雪-安全社区|安全招聘|kanxue.com

[原创]ART环境下dex加载流程分析及frida dump dex方案-Android安全-看雪-安全社区|安全招聘|kanxue.com


dex文件加载流程(三)
http://example.com/2023/11/20/Android安全/dex文件加载流程(三)/
作者
gla2xy
发布于
2023年11月20日
许可协议