-
-
[原创]菜鸟学8.1版本dex加载流程笔记--第二篇:DexFile::Open流程与简单脱壳原理
-
发表于: 2020-3-2 19:15 8166
-
菜鸟刚刚学完了
dex_file.cc这个源码,大致搞明白了大佬们hook脱整体加固的原理了,原理在帖子最后
学习了大佬
angelToms
的帖子https://bbs.pediy.com/thread-252828.htm与https://bbs.pediy.com/thread-252284.htm,总结的很清晰,dex 加载到内存之后,只要想方设法找到
DexFile的实例,就可以通过它的数据结构搞出整体加固的dex了,下面我们看下
dex_file.cc
的源码,顺便理解下初步脱壳的基本原理。还是先贴一下流程。
DexFile::Open(OpenDexFilesFromOat里,通过oatfile获得dexfile走这个) { OpenCommon } DexFile::Open (OpenDexFilesFromOat里,通过oatfile获得dexfile失败,直接打开源dex获得dexfile走这个 ) { OpenAndReadMagic if zip OpenZip { OpenAllDexFilesFromZip { OpenOneDexFileFromZip { OpenCommon } } } else dex OpenFile { OpenCommon } }
1.先看DexFile::Open,看一下头文件,它在源码中有好几个重载函数 ,
第一个是OpenDexFilesFromOat里,通过oatfile获得dexfile成功的调用,看OpenDexFile函数中DexFile::Open的参数可以判断;
第三个是OpenDexFilesFromOat里,通过oatfile获得dexfile失败后,直接打开源dex的调用,看
OpenDexFilesFromOat
函数最后的DexFile::Open的参数可以判断
static std::unique_ptr<const 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);//这是OpenDexFilesFromOat里,通过oatfile获得dexfile走这个,看OpenDexFile函数中DexFile::Open的参数可以判断,不清楚的去看下上一篇 // Opens .dex file that has been memory-mapped by the caller. static std::unique_ptr<const DexFile> Open(const std::string& location, uint32_t location_checkum, std::unique_ptr<MemMap> mem_map, bool verify, bool verify_checksum, std::string* error_msg);//这是打开已经被调用者memory-mapped过的 // Opens all .dex files found in the file, guessing the container format based on file extension. static bool Open(const char* filename, const std::string& location, bool verify_checksum, std::string* error_msg, std::vector<std::unique_ptr<const DexFile>>* dex_files);//这是OpenDexFilesFromOat里,通过oatfile获得dexfile失败,直接打开源dex走这个,看OpenDexFilesFromOat函数中DexFile::Open的参数可以判断
1.1这里还是把
OpenDexFilesFromOat
这个函数贴一下,具体代码看前一篇
std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( const char* dex_location, jobject class_loader, jobjectArray dex_elements, const OatFile** out_oat_file, std::vector<std::string>* error_msgs) { 。。。 // Get the oat file on disk. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());//这句获得了oat_file,下面LoadDexFiles使用这个oat_file获得dex_files 。。。 if (accept_oat_file) { VLOG(class_linker) << "Registering " << oat_file->GetLocation(); source_oat_file = RegisterOatFile(std::move(oat_file));//这里把oat_file注册给source_oat_file *out_oat_file = source_oat_file; } } std::vector<std::unique_ptr<const DexFile>> dex_files; // Load the dex files from the oat file. 。。。 dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);//这里通过加载source_oat_file获得dex_files,最终调用了DexFile::Open,这里的DexFile::Open是一个重载 。。。 // Fall back to running out of the original dex file if we couldn't load any // dex_files from the oat file. if (dex_files.empty()) { if (oat_file_assistant.HasOriginalDexFiles()) { if (Runtime::Current()->IsDexFileFallbackEnabled()) { static constexpr bool kVerifyChecksum = true; if (!DexFile::Open( dex_location, dex_location, kVerifyChecksum, /*out*/ &error_msg, &dex_files)) {//如果LoadDexFiles上面没有获得dex_files,直接DexFile::Open打开加载原始的dexfile,这里的DexFile::Open是另一个重载 LOG(WARNING) << error_msg; error_msgs->push_back("Failed to open dex files from " + std::string(dex_location) + " because: " + error_msg); } 。。。 return dex_files; }
1.2这个就是走oatfile获得dexfile路径的DexFile::Open,没啥花样,直接调用OpenCommon
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,//这个参数就是上一篇的this,就是oat_dex_file,是从这里面找出dex_file哦,其他2个重载函数都不是从oat_dex_file里找到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); }
2.下面这个就是不通过oat_file直接打开dex文件的DexFile::Open,稍微复杂一点,先判断打开的是zip压缩包还是dex,最终其实也是调用了
OpenCommon
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; File fd = OpenAndReadMagic(filename, &magic, error_msg);//OpenAndReadMagic也是一个常用脱壳点,如果不是直接打开dex走这个函数,不会在这里被调用 if (fd.Fd() == -1) { DCHECK(!error_msg->empty()); return false; } if (IsZipMagic(magic)) { return DexFile::OpenZip(fd.Release(), location, verify_checksum, error_msg, dex_files);//如果是Zip,调用DexFile::OpenZip } if (IsDexMagic(magic)) { std::unique_ptr<const DexFile> dex_file(DexFile::OpenFile(fd.Release(), location, /* verify */ true, verify_checksum, error_msg));//如果是Dex,调用DexFile::OpenFile 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; }
2.1先看OpenZip的逻辑,先通过fd文件描述符获得ZipArchive指针,在使用这个指针调用了OpenAllDexFilesFromZip处理
ZipArchive
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"; 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; } return DexFile::OpenAllDexFilesFromZip(*zip_archive, location, verify_checksum, error_msg, dex_files); }
2.2再看
OpenAllDexFilesFromZip
,调用了OpenOneDexFileFromZip,因为可能有多个dex,依次打开
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; 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 { // Had at least classes.dex. dex_files->push_back(std::move(dex_file)); // Now try some more. // We could try to avoid std::string allocations by working on a char array directly. As we // do not expect a lot of iterations, this seems too involved and brittle. for (size_t i = 1; ; ++i) { std::string name = GetMultiDexClassesDexName(i); std::string fake_location = GetMultiDexLocation(i, location.c_str()); 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; } }
2.3最后在OpenOneDexFileFromZip里最终也是调用了OpenCommon,殊途同归,最后都来到这个关键函数
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()); 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))) { // Do not mmap unaligned ZIP entries because // doing so would fail dex verification which requires 4 byte alignment. LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " << "please zipalign to " << alignof(Header) << " bytes. " << "Falling back to extracting file."; } else { // Map uncompressed files within zip as file-backed to avoid a dirty copy. 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_ = std::move(map); 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; }
3.最后我们认真分析OpenCommon这个函数,无论是通过oat_file获得的
oat_dex_file
获得
dex_file
也好,是直接打开zip或者dex文件获得
dex_file
也好,最终都得用到这个函数,所以它作为常用脱壳点的意义就很清楚了,它的前2个参数分别是dex的起始地址和大小,直接hook就可以dump出dex了。
其实只要有base地址,通过dex数据结构就可以定位size,加个偏移就行parseInt(base,16) + 0x20
std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base,//这里是dex的开始 size_t size,//这里是dex的大小 const std::string& location,//这里是地址 uint32_t location_checksum, const OatDexFile* oat_dex_file,//如果直接打开文件而不是通过oat文件获得dex,这个参数是kNoOatDexFile,hook打印这个参数就可以判断一些壳是否放弃了oat文件强制以dex解释运行 bool verify, bool verify_checksum, std::string* error_msg, VerifyResult* verify_result) { if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyNotAttempted; } std::unique_ptr<DexFile> dex_file(new DexFile(base, size, location, location_checksum, oat_dex_file));//这里new了一个dex_file实例,至此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; } if (!dex_file->Init(error_msg)) {//init初始化 dex_file.reset(); return nullptr; } if (verify && !DexFileVerifier::Verify(dex_file.get(), dex_file->Begin(), dex_file->Size(), location.c_str(), verify_checksum, error_msg)) {//Verify验证 if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyFailed; } return nullptr; } if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifySucceeded; } return dex_file; }
最后,仍然是画个及其丑陋的图,辅助自己理解。
4.下面贴一下frida hook脱壳脚本,很简单,每一步我都备注了,上次问我的同学仔细看一下,看看还有什么问题
DexFile::Open(OpenDexFilesFromOat里,通过oatfile获得dexfile走这个) { OpenCommon } DexFile::Open (OpenDexFilesFromOat里,通过oatfile获得dexfile失败,直接打开源dex获得dexfile走这个 ) { OpenAndReadMagic if zip OpenZip { OpenAllDexFilesFromZip { OpenOneDexFileFromZip { OpenCommon } } } else dex OpenFile { OpenCommon } }
1.先看DexFile::Open,看一下头文件,它在源码中有好几个重载函数 ,
第一个是OpenDexFilesFromOat里,通过oatfile获得dexfile成功的调用,看OpenDexFile函数中DexFile::Open的参数可以判断;
第三个是OpenDexFilesFromOat里,通过oatfile获得dexfile失败后,直接打开源dex的调用,看
OpenDexFilesFromOat
函数最后的DexFile::Open的参数可以判断
static std::unique_ptr<const 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);//这是OpenDexFilesFromOat里,通过oatfile获得dexfile走这个,看OpenDexFile函数中DexFile::Open的参数可以判断,不清楚的去看下上一篇 // Opens .dex file that has been memory-mapped by the caller. static std::unique_ptr<const DexFile> Open(const std::string& location, uint32_t location_checkum, std::unique_ptr<MemMap> mem_map, bool verify, bool verify_checksum, std::string* error_msg);//这是打开已经被调用者memory-mapped过的 // Opens all .dex files found in the file, guessing the container format based on file extension. static bool Open(const char* filename, const std::string& location, bool verify_checksum, std::string* error_msg, std::vector<std::unique_ptr<const DexFile>>* dex_files);//这是OpenDexFilesFromOat里,通过oatfile获得dexfile失败,直接打开源dex走这个,看OpenDexFilesFromOat函数中DexFile::Open的参数可以判断
1.1这里还是把
OpenDexFilesFromOat
这个函数贴一下,具体代码看前一篇
std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( const char* dex_location, jobject class_loader, jobjectArray dex_elements, const OatFile** out_oat_file, std::vector<std::string>* error_msgs) { 。。。 // Get the oat file on disk. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());//这句获得了oat_file,下面LoadDexFiles使用这个oat_file获得dex_files 。。。 if (accept_oat_file) { VLOG(class_linker) << "Registering " << oat_file->GetLocation(); source_oat_file = RegisterOatFile(std::move(oat_file));//这里把oat_file注册给source_oat_file *out_oat_file = source_oat_file; } } std::vector<std::unique_ptr<const DexFile>> dex_files; // Load the dex files from the oat file. 。。。 dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);//这里通过加载source_oat_file获得dex_files,最终调用了DexFile::Open,这里的DexFile::Open是一个重载 。。。 // Fall back to running out of the original dex file if we couldn't load any // dex_files from the oat file. if (dex_files.empty()) { if (oat_file_assistant.HasOriginalDexFiles()) { if (Runtime::Current()->IsDexFileFallbackEnabled()) { static constexpr bool kVerifyChecksum = true; if (!DexFile::Open( dex_location, dex_location, kVerifyChecksum, /*out*/ &error_msg, &dex_files)) {//如果LoadDexFiles上面没有获得dex_files,直接DexFile::Open打开加载原始的dexfile,这里的DexFile::Open是另一个重载 LOG(WARNING) << error_msg; error_msgs->push_back("Failed to open dex files from " + std::string(dex_location) + " because: " + error_msg); } 。。。 return dex_files; }
1.2这个就是走oatfile获得dexfile路径的DexFile::Open,没啥花样,直接调用OpenCommon
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,//这个参数就是上一篇的this,就是oat_dex_file,是从这里面找出dex_file哦,其他2个重载函数都不是从oat_dex_file里找到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); }
2.下面这个就是不通过oat_file直接打开dex文件的DexFile::Open,稍微复杂一点,先判断打开的是zip压缩包还是dex,最终其实也是调用了
OpenCommon
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; File fd = OpenAndReadMagic(filename, &magic, error_msg);//OpenAndReadMagic也是一个常用脱壳点,如果不是直接打开dex走这个函数,不会在这里被调用 if (fd.Fd() == -1) { DCHECK(!error_msg->empty()); return false; } if (IsZipMagic(magic)) { return DexFile::OpenZip(fd.Release(), location, verify_checksum, error_msg, dex_files);//如果是Zip,调用DexFile::OpenZip } if (IsDexMagic(magic)) { std::unique_ptr<const DexFile> dex_file(DexFile::OpenFile(fd.Release(), location, /* verify */ true, verify_checksum, error_msg));//如果是Dex,调用DexFile::OpenFile 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; }
2.1先看OpenZip的逻辑,先通过fd文件描述符获得ZipArchive指针,在使用这个指针调用了OpenAllDexFilesFromZip处理
ZipArchive
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"; 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; } return DexFile::OpenAllDexFilesFromZip(*zip_archive, location, verify_checksum, error_msg, dex_files); }
2.2再看
OpenAllDexFilesFromZip
,调用了OpenOneDexFileFromZip,因为可能有多个dex,依次打开
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; 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 { // Had at least classes.dex. dex_files->push_back(std::move(dex_file)); // Now try some more. // We could try to avoid std::string allocations by working on a char array directly. As we // do not expect a lot of iterations, this seems too involved and brittle. for (size_t i = 1; ; ++i) { std::string name = GetMultiDexClassesDexName(i); std::string fake_location = GetMultiDexLocation(i, location.c_str()); 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; } }
2.3最后在OpenOneDexFileFromZip里最终也是调用了OpenCommon,殊途同归,最后都来到这个关键函数
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()); 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))) { // Do not mmap unaligned ZIP entries because // doing so would fail dex verification which requires 4 byte alignment. LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " << "please zipalign to " << alignof(Header) << " bytes. " << "Falling back to extracting file."; } else { // Map uncompressed files within zip as file-backed to avoid a dirty copy. 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_ = std::move(map); 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; }
3.最后我们认真分析OpenCommon这个函数,无论是通过oat_file获得的
oat_dex_file
获得
dex_file
也好,是直接打开zip或者dex文件获得
dex_file
也好,最终都得用到这个函数,所以它作为常用脱壳点的意义就很清楚了,它的前2个参数分别是dex的起始地址和大小,直接hook就可以dump出dex了。
其实只要有base地址,通过dex数据结构就可以定位size,加个偏移就行parseInt(base,16) + 0x20
std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base,//这里是dex的开始 size_t size,//这里是dex的大小 const std::string& location,//这里是地址 uint32_t location_checksum, const OatDexFile* oat_dex_file,//如果直接打开文件而不是通过oat文件获得dex,这个参数是kNoOatDexFile,hook打印这个参数就可以判断一些壳是否放弃了oat文件强制以dex解释运行 bool verify, bool verify_checksum, std::string* error_msg, VerifyResult* verify_result) { if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyNotAttempted; } std::unique_ptr<DexFile> dex_file(new DexFile(base, size, location, location_checksum, oat_dex_file));//这里new了一个dex_file实例,至此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; } if (!dex_file->Init(error_msg)) {//init初始化 dex_file.reset(); return nullptr; } if (verify && !DexFileVerifier::Verify(dex_file.get(), dex_file->Begin(), dex_file->Size(), location.c_str(), verify_checksum, error_msg)) {//Verify验证 if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyFailed; } return nullptr; } if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifySucceeded; } return dex_file; }
最后,仍然是画个及其丑陋的图,辅助自己理解。
4.下面贴一下frida hook脱壳脚本,很简单,每一步我都备注了,上次问我的同学仔细看一下,看看还有什么问题
static std::unique_ptr<const 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);//这是OpenDexFilesFromOat里,通过oatfile获得dexfile走这个,看OpenDexFile函数中DexFile::Open的参数可以判断,不清楚的去看下上一篇 // Opens .dex file that has been memory-mapped by the caller. static std::unique_ptr<const DexFile> Open(const std::string& location, uint32_t location_checkum, std::unique_ptr<MemMap> mem_map, bool verify, bool verify_checksum, std::string* error_msg);//这是打开已经被调用者memory-mapped过的 // Opens all .dex files found in the file, guessing the container format based on file extension. static bool Open(const char* filename, const std::string& location, bool verify_checksum, std::string* error_msg, std::vector<std::unique_ptr<const DexFile>>* dex_files);//这是OpenDexFilesFromOat里,通过oatfile获得dexfile失败,直接打开源dex走这个,看OpenDexFilesFromOat函数中DexFile::Open的参数可以判断
1.1这里还是把
OpenDexFilesFromOat
这个函数贴一下,具体代码看前一篇
std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( const char* dex_location, jobject class_loader, jobjectArray dex_elements, const OatFile** out_oat_file, std::vector<std::string>* error_msgs) { 。。。 // Get the oat file on disk. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());//这句获得了oat_file,下面LoadDexFiles使用这个oat_file获得dex_files 。。。 if (accept_oat_file) { VLOG(class_linker) << "Registering " << oat_file->GetLocation(); source_oat_file = RegisterOatFile(std::move(oat_file));//这里把oat_file注册给source_oat_file *out_oat_file = source_oat_file; } } std::vector<std::unique_ptr<const DexFile>> dex_files; // Load the dex files from the oat file. 。。。 dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);//这里通过加载source_oat_file获得dex_files,最终调用了DexFile::Open,这里的DexFile::Open是一个重载 。。。 // Fall back to running out of the original dex file if we couldn't load any // dex_files from the oat file. if (dex_files.empty()) { if (oat_file_assistant.HasOriginalDexFiles()) { if (Runtime::Current()->IsDexFileFallbackEnabled()) { static constexpr bool kVerifyChecksum = true; if (!DexFile::Open( dex_location, dex_location, kVerifyChecksum, /*out*/ &error_msg, &dex_files)) {//如果LoadDexFiles上面没有获得dex_files,直接DexFile::Open打开加载原始的dexfile,这里的DexFile::Open是另一个重载 LOG(WARNING) << error_msg; error_msgs->push_back("Failed to open dex files from " + std::string(dex_location) + " because: " + error_msg); } 。。。 return dex_files; }
1.2这个就是走oatfile获得dexfile路径的DexFile::Open,没啥花样,直接调用OpenCommon
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,//这个参数就是上一篇的this,就是oat_dex_file,是从这里面找出dex_file哦,其他2个重载函数都不是从oat_dex_file里找到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); }
2.下面这个就是不通过oat_file直接打开dex文件的DexFile::Open,稍微复杂一点,先判断打开的是zip压缩包还是dex,最终其实也是调用了
OpenCommon
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; File fd = OpenAndReadMagic(filename, &magic, error_msg);//OpenAndReadMagic也是一个常用脱壳点,如果不是直接打开dex走这个函数,不会在这里被调用 if (fd.Fd() == -1) { DCHECK(!error_msg->empty()); return false; } if (IsZipMagic(magic)) { return DexFile::OpenZip(fd.Release(), location, verify_checksum, error_msg, dex_files);//如果是Zip,调用DexFile::OpenZip } if (IsDexMagic(magic)) { std::unique_ptr<const DexFile> dex_file(DexFile::OpenFile(fd.Release(), location, /* verify */ true, verify_checksum, error_msg));//如果是Dex,调用DexFile::OpenFile 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; }
2.1先看OpenZip的逻辑,先通过fd文件描述符获得ZipArchive指针,在使用这个指针调用了OpenAllDexFilesFromZip处理
ZipArchive
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"; 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; } return DexFile::OpenAllDexFilesFromZip(*zip_archive, location, verify_checksum, error_msg, dex_files); }
2.2再看
OpenAllDexFilesFromZip
,调用了OpenOneDexFileFromZip,因为可能有多个dex,依次打开
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; 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 { // Had at least classes.dex. dex_files->push_back(std::move(dex_file)); // Now try some more. // We could try to avoid std::string allocations by working on a char array directly. As we // do not expect a lot of iterations, this seems too involved and brittle. for (size_t i = 1; ; ++i) { std::string name = GetMultiDexClassesDexName(i); std::string fake_location = GetMultiDexLocation(i, location.c_str()); 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; } }
2.3最后在OpenOneDexFileFromZip里最终也是调用了OpenCommon,殊途同归,最后都来到这个关键函数
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()); 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))) { // Do not mmap unaligned ZIP entries because // doing so would fail dex verification which requires 4 byte alignment. LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " << "please zipalign to " << alignof(Header) << " bytes. " << "Falling back to extracting file."; } else { // Map uncompressed files within zip as file-backed to avoid a dirty copy. 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_ = std::move(map); 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; }
3.最后我们认真分析OpenCommon这个函数,无论是通过oat_file获得的
oat_dex_file
获得
dex_file
也好,是直接打开zip或者dex文件获得
dex_file
也好,最终都得用到这个函数,所以它作为常用脱壳点的意义就很清楚了,它的前2个参数分别是dex的起始地址和大小,直接hook就可以dump出dex了。
其实只要有base地址,通过dex数据结构就可以定位size,加个偏移就行parseInt(base,16) + 0x20
std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base,//这里是dex的开始 size_t size,//这里是dex的大小 const std::string& location,//这里是地址 uint32_t location_checksum, const OatDexFile* oat_dex_file,//如果直接打开文件而不是通过oat文件获得dex,这个参数是kNoOatDexFile,hook打印这个参数就可以判断一些壳是否放弃了oat文件强制以dex解释运行 bool verify, bool verify_checksum, std::string* error_msg, VerifyResult* verify_result) { if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyNotAttempted; } std::unique_ptr<DexFile> dex_file(new DexFile(base, size, location, location_checksum, oat_dex_file));//这里new了一个dex_file实例,至此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; } if (!dex_file->Init(error_msg)) {//init初始化 dex_file.reset(); return nullptr; } if (verify && !DexFileVerifier::Verify(dex_file.get(), dex_file->Begin(), dex_file->Size(), location.c_str(), verify_checksum, error_msg)) {//Verify验证 if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyFailed; } return nullptr; } if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifySucceeded; } return dex_file; }
最后,仍然是画个及其丑陋的图,辅助自己理解。
4.下面贴一下frida hook脱壳脚本,很简单,每一步我都备注了,上次问我的同学仔细看一下,看看还有什么问题
static std::unique_ptr<const 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);//这是OpenDexFilesFromOat里,通过oatfile获得dexfile走这个,看OpenDexFile函数中DexFile::Open的参数可以判断,不清楚的去看下上一篇 // Opens .dex file that has been memory-mapped by the caller. static std::unique_ptr<const DexFile> Open(const std::string& location, uint32_t location_checkum, std::unique_ptr<MemMap> mem_map, bool verify, bool verify_checksum, std::string* error_msg);//这是打开已经被调用者memory-mapped过的 // Opens all .dex files found in the file, guessing the container format based on file extension. static bool Open(const char* filename, const std::string& location, bool verify_checksum, std::string* error_msg, std::vector<std::unique_ptr<const DexFile>>* dex_files);//这是OpenDexFilesFromOat里,通过oatfile获得dexfile失败,直接打开源dex走这个,看OpenDexFilesFromOat函数中DexFile::Open的参数可以判断
1.1这里还是把
OpenDexFilesFromOat
这个函数贴一下,具体代码看前一篇
std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( const char* dex_location, jobject class_loader, jobjectArray dex_elements, const OatFile** out_oat_file, std::vector<std::string>* error_msgs) { 。。。 // Get the oat file on disk. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());//这句获得了oat_file,下面LoadDexFiles使用这个oat_file获得dex_files 。。。 if (accept_oat_file) { VLOG(class_linker) << "Registering " << oat_file->GetLocation(); source_oat_file = RegisterOatFile(std::move(oat_file));//这里把oat_file注册给source_oat_file *out_oat_file = source_oat_file; } } std::vector<std::unique_ptr<const DexFile>> dex_files; // Load the dex files from the oat file. 。。。 dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);//这里通过加载source_oat_file获得dex_files,最终调用了DexFile::Open,这里的DexFile::Open是一个重载 。。。 // Fall back to running out of the original dex file if we couldn't load any // dex_files from the oat file. if (dex_files.empty()) { if (oat_file_assistant.HasOriginalDexFiles()) { if (Runtime::Current()->IsDexFileFallbackEnabled()) { static constexpr bool kVerifyChecksum = true; if (!DexFile::Open( dex_location, dex_location, kVerifyChecksum, /*out*/ &error_msg, &dex_files)) {//如果LoadDexFiles上面没有获得dex_files,直接DexFile::Open打开加载原始的dexfile,这里的DexFile::Open是另一个重载 LOG(WARNING) << error_msg; error_msgs->push_back("Failed to open dex files from " + std::string(dex_location) + " because: " + error_msg); } 。。。 return dex_files; }
1.2这个就是走oatfile获得dexfile路径的DexFile::Open,没啥花样,直接调用OpenCommon
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,//这个参数就是上一篇的this,就是oat_dex_file,是从这里面找出dex_file哦,其他2个重载函数都不是从oat_dex_file里找到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); }
2.下面这个就是不通过oat_file直接打开dex文件的DexFile::Open,稍微复杂一点,先判断打开的是zip压缩包还是dex,最终其实也是调用了
OpenCommon
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; File fd = OpenAndReadMagic(filename, &magic, error_msg);//OpenAndReadMagic也是一个常用脱壳点,如果不是直接打开dex走这个函数,不会在这里被调用 if (fd.Fd() == -1) { DCHECK(!error_msg->empty()); return false; } if (IsZipMagic(magic)) { return DexFile::OpenZip(fd.Release(), location, verify_checksum, error_msg, dex_files);//如果是Zip,调用DexFile::OpenZip } if (IsDexMagic(magic)) { std::unique_ptr<const DexFile> dex_file(DexFile::OpenFile(fd.Release(), location, /* verify */ true, verify_checksum, error_msg));//如果是Dex,调用DexFile::OpenFile 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; }
2.1先看OpenZip的逻辑,先通过fd文件描述符获得ZipArchive指针,在使用这个指针调用了OpenAllDexFilesFromZip处理
ZipArchive
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"; 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; } return DexFile::OpenAllDexFilesFromZip(*zip_archive, location, verify_checksum, error_msg, dex_files); }
2.2再看
OpenAllDexFilesFromZip
,调用了OpenOneDexFileFromZip,因为可能有多个dex,依次打开
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; 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 { // Had at least classes.dex. dex_files->push_back(std::move(dex_file)); // Now try some more. // We could try to avoid std::string allocations by working on a char array directly. As we // do not expect a lot of iterations, this seems too involved and brittle. for (size_t i = 1; ; ++i) { std::string name = GetMultiDexClassesDexName(i); std::string fake_location = GetMultiDexLocation(i, location.c_str()); 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; } }
2.3最后在OpenOneDexFileFromZip里最终也是调用了OpenCommon,殊途同归,最后都来到这个关键函数
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()); 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))) { // Do not mmap unaligned ZIP entries because // doing so would fail dex verification which requires 4 byte alignment. LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " << "please zipalign to " << alignof(Header) << " bytes. " << "Falling back to extracting file."; } else { // Map uncompressed files within zip as file-backed to avoid a dirty copy. 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_ = std::move(map); 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; }
3.最后我们认真分析OpenCommon这个函数,无论是通过oat_file获得的
oat_dex_file
获得
dex_file
也好,是直接打开zip或者dex文件获得
dex_file
也好,最终都得用到这个函数,所以它作为常用脱壳点的意义就很清楚了,它的前2个参数分别是dex的起始地址和大小,直接hook就可以dump出dex了。
其实只要有base地址,通过dex数据结构就可以定位size,加个偏移就行parseInt(base,16) + 0x20
std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base,//这里是dex的开始 size_t size,//这里是dex的大小 const std::string& location,//这里是地址 uint32_t location_checksum, const OatDexFile* oat_dex_file,//如果直接打开文件而不是通过oat文件获得dex,这个参数是kNoOatDexFile,hook打印这个参数就可以判断一些壳是否放弃了oat文件强制以dex解释运行 bool verify, bool verify_checksum, std::string* error_msg, VerifyResult* verify_result) { if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyNotAttempted; } std::unique_ptr<DexFile> dex_file(new DexFile(base, size, location, location_checksum, oat_dex_file));//这里new了一个dex_file实例,至此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; } if (!dex_file->Init(error_msg)) {//init初始化 dex_file.reset(); return nullptr; } if (verify && !DexFileVerifier::Verify(dex_file.get(), dex_file->Begin(), dex_file->Size(), location.c_str(), verify_checksum, error_msg)) {//Verify验证 if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyFailed; } return nullptr; } if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifySucceeded; } return dex_file; }
最后,仍然是画个及其丑陋的图,辅助自己理解。
4.下面贴一下frida hook脱壳脚本,很简单,每一步我都备注了,上次问我的同学仔细看一下,看看还有什么问题
1.1这里还是把
OpenDexFilesFromOat
这个函数贴一下,具体代码看前一篇
std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( const char* dex_location, jobject class_loader, jobjectArray dex_elements, const OatFile** out_oat_file, std::vector<std::string>* error_msgs) { 。。。 // Get the oat file on disk. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());//这句获得了oat_file,下面LoadDexFiles使用这个oat_file获得dex_files 。。。 if (accept_oat_file) { VLOG(class_linker) << "Registering " << oat_file->GetLocation(); source_oat_file = RegisterOatFile(std::move(oat_file));//这里把oat_file注册给source_oat_file *out_oat_file = source_oat_file; } } std::vector<std::unique_ptr<const DexFile>> dex_files; // Load the dex files from the oat file. 。。。 dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);//这里通过加载source_oat_file获得dex_files,最终调用了DexFile::Open,这里的DexFile::Open是一个重载 。。。 // Fall back to running out of the original dex file if we couldn't load any // dex_files from the oat file. if (dex_files.empty()) { if (oat_file_assistant.HasOriginalDexFiles()) { if (Runtime::Current()->IsDexFileFallbackEnabled()) { static constexpr bool kVerifyChecksum = true; if (!DexFile::Open( dex_location, dex_location, kVerifyChecksum, /*out*/ &error_msg, &dex_files)) {//如果LoadDexFiles上面没有获得dex_files,直接DexFile::Open打开加载原始的dexfile,这里的DexFile::Open是另一个重载 LOG(WARNING) << error_msg; error_msgs->push_back("Failed to open dex files from " + std::string(dex_location) + " because: " + error_msg); } 。。。 return dex_files; }
1.2这个就是走oatfile获得dexfile路径的DexFile::Open,没啥花样,直接调用OpenCommon
std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( const char* dex_location, jobject class_loader, jobjectArray dex_elements, const OatFile** out_oat_file, std::vector<std::string>* error_msgs) { 。。。 // Get the oat file on disk. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());//这句获得了oat_file,下面LoadDexFiles使用这个oat_file获得dex_files 。。。 if (accept_oat_file) { VLOG(class_linker) << "Registering " << oat_file->GetLocation(); source_oat_file = RegisterOatFile(std::move(oat_file));//这里把oat_file注册给source_oat_file *out_oat_file = source_oat_file; } } std::vector<std::unique_ptr<const DexFile>> dex_files; // Load the dex files from the oat file. 。。。 dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);//这里通过加载source_oat_file获得dex_files,最终调用了DexFile::Open,这里的DexFile::Open是一个重载 。。。 // Fall back to running out of the original dex file if we couldn't load any // dex_files from the oat file. if (dex_files.empty()) { if (oat_file_assistant.HasOriginalDexFiles()) { if (Runtime::Current()->IsDexFileFallbackEnabled()) { static constexpr bool kVerifyChecksum = true; if (!DexFile::Open( dex_location, dex_location, kVerifyChecksum, /*out*/ &error_msg, &dex_files)) {//如果LoadDexFiles上面没有获得dex_files,直接DexFile::Open打开加载原始的dexfile,这里的DexFile::Open是另一个重载 LOG(WARNING) << error_msg; error_msgs->push_back("Failed to open dex files from " + std::string(dex_location) + " because: " + error_msg); } 。。。 return dex_files; }
1.2这个就是走oatfile获得dexfile路径的DexFile::Open,没啥花样,直接调用OpenCommon
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,//这个参数就是上一篇的this,就是oat_dex_file,是从这里面找出dex_file哦,其他2个重载函数都不是从oat_dex_file里找到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); }
2.下面这个就是不通过oat_file直接打开dex文件的DexFile::Open,稍微复杂一点,先判断打开的是zip压缩包还是dex,最终其实也是调用了
OpenCommon
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; File fd = OpenAndReadMagic(filename, &magic, error_msg);//OpenAndReadMagic也是一个常用脱壳点,如果不是直接打开dex走这个函数,不会在这里被调用 if (fd.Fd() == -1) { DCHECK(!error_msg->empty()); return false; } if (IsZipMagic(magic)) { return DexFile::OpenZip(fd.Release(), location, verify_checksum, error_msg, dex_files);//如果是Zip,调用DexFile::OpenZip } if (IsDexMagic(magic)) { std::unique_ptr<const DexFile> dex_file(DexFile::OpenFile(fd.Release(), location, /* verify */ true, verify_checksum, error_msg));//如果是Dex,调用DexFile::OpenFile 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; }
2.1先看OpenZip的逻辑,先通过fd文件描述符获得ZipArchive指针,在使用这个指针调用了OpenAllDexFilesFromZip处理
ZipArchive
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"; 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; } return DexFile::OpenAllDexFilesFromZip(*zip_archive, location, verify_checksum, error_msg, dex_files); }
2.2再看
OpenAllDexFilesFromZip
,调用了OpenOneDexFileFromZip,因为可能有多个dex,依次打开
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; 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 { // Had at least classes.dex. dex_files->push_back(std::move(dex_file)); // Now try some more. // We could try to avoid std::string allocations by working on a char array directly. As we // do not expect a lot of iterations, this seems too involved and brittle. for (size_t i = 1; ; ++i) { std::string name = GetMultiDexClassesDexName(i); std::string fake_location = GetMultiDexLocation(i, location.c_str()); 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; } }
2.3最后在OpenOneDexFileFromZip里最终也是调用了OpenCommon,殊途同归,最后都来到这个关键函数
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()); 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))) { // Do not mmap unaligned ZIP entries because // doing so would fail dex verification which requires 4 byte alignment. LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " << "please zipalign to " << alignof(Header) << " bytes. " << "Falling back to extracting file."; } else { // Map uncompressed files within zip as file-backed to avoid a dirty copy. 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_ = std::move(map); 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; }
3.最后我们认真分析OpenCommon这个函数,无论是通过oat_file获得的
oat_dex_file
获得
dex_file
也好,是直接打开zip或者dex文件获得
dex_file
也好,最终都得用到这个函数,所以它作为常用脱壳点的意义就很清楚了,它的前2个参数分别是dex的起始地址和大小,直接hook就可以dump出dex了。
其实只要有base地址,通过dex数据结构就可以定位size,加个偏移就行parseInt(base,16) + 0x20
std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base,//这里是dex的开始 size_t size,//这里是dex的大小 const std::string& location,//这里是地址 uint32_t location_checksum, const OatDexFile* oat_dex_file,//如果直接打开文件而不是通过oat文件获得dex,这个参数是kNoOatDexFile,hook打印这个参数就可以判断一些壳是否放弃了oat文件强制以dex解释运行 bool verify, bool verify_checksum, std::string* error_msg, VerifyResult* verify_result) { if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyNotAttempted; } std::unique_ptr<DexFile> dex_file(new DexFile(base, size, location, location_checksum, oat_dex_file));//这里new了一个dex_file实例,至此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; } if (!dex_file->Init(error_msg)) {//init初始化 dex_file.reset(); return nullptr; } if (verify && !DexFileVerifier::Verify(dex_file.get(), dex_file->Begin(), dex_file->Size(), location.c_str(), verify_checksum, error_msg)) {//Verify验证 if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyFailed; } return nullptr; } if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifySucceeded; } return dex_file; }
最后,仍然是画个及其丑陋的图,辅助自己理解。
4.下面贴一下frida hook脱壳脚本,很简单,每一步我都备注了,上次问我的同学仔细看一下,看看还有什么问题
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; File fd = OpenAndReadMagic(filename, &magic, error_msg);//OpenAndReadMagic也是一个常用脱壳点,如果不是直接打开dex走这个函数,不会在这里被调用 if (fd.Fd() == -1) { DCHECK(!error_msg->empty()); return false; } if (IsZipMagic(magic)) { return DexFile::OpenZip(fd.Release(), location, verify_checksum, error_msg, dex_files);//如果是Zip,调用DexFile::OpenZip } if (IsDexMagic(magic)) { std::unique_ptr<const DexFile> dex_file(DexFile::OpenFile(fd.Release(), location, /* verify */ true, verify_checksum, error_msg));//如果是Dex,调用DexFile::OpenFile 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; }
2.1先看OpenZip的逻辑,先通过fd文件描述符获得ZipArchive指针,在使用这个指针调用了OpenAllDexFilesFromZip处理
ZipArchive
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"; 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; } return DexFile::OpenAllDexFilesFromZip(*zip_archive, location, verify_checksum, error_msg, dex_files); }
2.2再看
OpenAllDexFilesFromZip
,调用了OpenOneDexFileFromZip,因为可能有多个dex,依次打开
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; 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 { // Had at least classes.dex. dex_files->push_back(std::move(dex_file)); // Now try some more. // We could try to avoid std::string allocations by working on a char array directly. As we // do not expect a lot of iterations, this seems too involved and brittle. for (size_t i = 1; ; ++i) { std::string name = GetMultiDexClassesDexName(i); std::string fake_location = GetMultiDexLocation(i, location.c_str()); 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; } }
2.3最后在OpenOneDexFileFromZip里最终也是调用了OpenCommon,殊途同归,最后都来到这个关键函数
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()); 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))) { // Do not mmap unaligned ZIP entries because // doing so would fail dex verification which requires 4 byte alignment. LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " << "please zipalign to " << alignof(Header) << " bytes. " << "Falling back to extracting file."; } else { // Map uncompressed files within zip as file-backed to avoid a dirty copy. 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_ = std::move(map); 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; }
3.最后我们认真分析OpenCommon这个函数,无论是通过oat_file获得的
oat_dex_file
获得
dex_file
也好,是直接打开zip或者dex文件获得
dex_file
也好,最终都得用到这个函数,所以它作为常用脱壳点的意义就很清楚了,它的前2个参数分别是dex的起始地址和大小,直接hook就可以dump出dex了。
其实只要有base地址,通过dex数据结构就可以定位size,加个偏移就行parseInt(base,16) + 0x20
std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base,//这里是dex的开始 size_t size,//这里是dex的大小 const std::string& location,//这里是地址 uint32_t location_checksum, const OatDexFile* oat_dex_file,//如果直接打开文件而不是通过oat文件获得dex,这个参数是kNoOatDexFile,hook打印这个参数就可以判断一些壳是否放弃了oat文件强制以dex解释运行 bool verify, bool verify_checksum, std::string* error_msg, VerifyResult* verify_result) { if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyNotAttempted; } std::unique_ptr<DexFile> dex_file(new DexFile(base, size, location, location_checksum, oat_dex_file));//这里new了一个dex_file实例,至此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; } if (!dex_file->Init(error_msg)) {//init初始化 dex_file.reset(); return nullptr; } if (verify && !DexFileVerifier::Verify(dex_file.get(), dex_file->Begin(), dex_file->Size(), location.c_str(), verify_checksum, error_msg)) {//Verify验证 if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyFailed; } return nullptr; } if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifySucceeded; } return dex_file; }
最后,仍然是画个及其丑陋的图,辅助自己理解。
4.下面贴一下frida hook脱壳脚本,很简单,每一步我都备注了,上次问我的同学仔细看一下,看看还有什么问题
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"; 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; } return DexFile::OpenAllDexFilesFromZip(*zip_archive, location, verify_checksum, error_msg, dex_files); }
2.2再看
OpenAllDexFilesFromZip
,调用了OpenOneDexFileFromZip,因为可能有多个dex,依次打开
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; 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 { // Had at least classes.dex. dex_files->push_back(std::move(dex_file)); // Now try some more. // We could try to avoid std::string allocations by working on a char array directly. As we // do not expect a lot of iterations, this seems too involved and brittle. for (size_t i = 1; ; ++i) { std::string name = GetMultiDexClassesDexName(i); std::string fake_location = GetMultiDexLocation(i, location.c_str()); 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; } }
2.3最后在OpenOneDexFileFromZip里最终也是调用了OpenCommon,殊途同归,最后都来到这个关键函数
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()); 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))) { // Do not mmap unaligned ZIP entries because // doing so would fail dex verification which requires 4 byte alignment. LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " << "please zipalign to " << alignof(Header) << " bytes. " << "Falling back to extracting file."; } else { // Map uncompressed files within zip as file-backed to avoid a dirty copy. 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_ = std::move(map); 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; }
3.最后我们认真分析OpenCommon这个函数,无论是通过oat_file获得的
oat_dex_file
获得
dex_file
也好,是直接打开zip或者dex文件获得
dex_file
也好,最终都得用到这个函数,所以它作为常用脱壳点的意义就很清楚了,它的前2个参数分别是dex的起始地址和大小,直接hook就可以dump出dex了。
其实只要有base地址,通过dex数据结构就可以定位size,加个偏移就行parseInt(base,16) + 0x20
std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base,//这里是dex的开始 size_t size,//这里是dex的大小 const std::string& location,//这里是地址 uint32_t location_checksum, const OatDexFile* oat_dex_file,//如果直接打开文件而不是通过oat文件获得dex,这个参数是kNoOatDexFile,hook打印这个参数就可以判断一些壳是否放弃了oat文件强制以dex解释运行 bool verify, bool verify_checksum, std::string* error_msg, VerifyResult* verify_result) { if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyNotAttempted; } std::unique_ptr<DexFile> dex_file(new DexFile(base, size, location, location_checksum, oat_dex_file));//这里new了一个dex_file实例,至此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; } if (!dex_file->Init(error_msg)) {//init初始化 dex_file.reset(); return nullptr; } if (verify && !DexFileVerifier::Verify(dex_file.get(), dex_file->Begin(), dex_file->Size(), location.c_str(), verify_checksum, error_msg)) {//Verify验证 if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyFailed; } return nullptr; } if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifySucceeded; } return dex_file; }
最后,仍然是画个及其丑陋的图,辅助自己理解。
4.下面贴一下frida hook脱壳脚本,很简单,每一步我都备注了,上次问我的同学仔细看一下,看看还有什么问题
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; 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 { // Had at least classes.dex. dex_files->push_back(std::move(dex_file)); // Now try some more. // We could try to avoid std::string allocations by working on a char array directly. As we // do not expect a lot of iterations, this seems too involved and brittle. for (size_t i = 1; ; ++i) { std::string name = GetMultiDexClassesDexName(i); std::string fake_location = GetMultiDexLocation(i, location.c_str()); 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; } }
2.3最后在OpenOneDexFileFromZip里最终也是调用了OpenCommon,殊途同归,最后都来到这个关键函数
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()); 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))) { // Do not mmap unaligned ZIP entries because // doing so would fail dex verification which requires 4 byte alignment. LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " << "please zipalign to " << alignof(Header) << " bytes. " << "Falling back to extracting file."; } else { // Map uncompressed files within zip as file-backed to avoid a dirty copy. 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_ = std::move(map); 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; }
3.最后我们认真分析OpenCommon这个函数,无论是通过oat_file获得的
oat_dex_file
获得
dex_file
也好,是直接打开zip或者dex文件获得
dex_file
也好,最终都得用到这个函数,所以它作为常用脱壳点的意义就很清楚了,它的前2个参数分别是dex的起始地址和大小,直接hook就可以dump出dex了。
其实只要有base地址,通过dex数据结构就可以定位size,加个偏移就行parseInt(base,16) + 0x20
std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base,//这里是dex的开始 size_t size,//这里是dex的大小 const std::string& location,//这里是地址 uint32_t location_checksum, const OatDexFile* oat_dex_file,//如果直接打开文件而不是通过oat文件获得dex,这个参数是kNoOatDexFile,hook打印这个参数就可以判断一些壳是否放弃了oat文件强制以dex解释运行 bool verify, bool verify_checksum, std::string* error_msg, VerifyResult* verify_result) { if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyNotAttempted; } std::unique_ptr<DexFile> dex_file(new DexFile(base, size, location, location_checksum, oat_dex_file));//这里new了一个dex_file实例,至此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; } if (!dex_file->Init(error_msg)) {//init初始化 dex_file.reset(); return nullptr; } if (verify && !DexFileVerifier::Verify(dex_file.get(), dex_file->Begin(), dex_file->Size(), location.c_str(), verify_checksum, error_msg)) {//Verify验证 if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifyFailed; } return nullptr; } if (verify_result != nullptr) { *verify_result = VerifyResult::kVerifySucceeded; } return dex_file; }
最后,仍然是画个及其丑陋的图,辅助自己理解。
4.下面贴一下frida hook脱壳脚本,很简单,每一步我都备注了,上次问我的同学仔细看一下,看看还有什么问题
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()); 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))) { // Do not mmap unaligned ZIP entries because // doing so would fail dex verification which requires 4 byte alignment. LOG(WARNING) << "Can't mmap dex file " << location << "!" << entry_name << " directly; " << "please zipalign to " << alignof(Header) << " bytes. " << "Falling back to extracting file."; } else { // Map uncompressed files within zip as file-backed to avoid a dirty copy. 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_ = std::move(map); 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; }
3.最后我们认真分析OpenCommon这个函数,无论是通过oat_file获得的
oat_dex_file
获得
dex_file
也好,是直接打开zip或者dex文件获得
dex_file
也好,最终都得用到这个函数,所以它作为常用脱壳点的意义就很清楚了,它的前2个参数分别是dex的起始地址和大小,直接hook就可以dump出dex了。
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
他的文章
- cocos2d逆向入门和某捕鱼游戏分析 26607
- [原创]capstone2llvmir入门---如何把汇编转换为llvmir 20913
- [原创]利用编译器优化干掉控制流平坦化flatten 40634
- [求助][原创]利用编译器优化干掉虚假控制流 14970
- [求助][原创]对类抽取加固的一点尝试与遇到的问题 7912
谁下载
谁下载
看原图
赞赏
雪币:
留言: