8.1版本dex加载流程笔记--第二篇:DexFile::Open流程与简单脱壳原理
在看雪发了,52再发一下,共同学习菜鸟刚刚学完了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,最终其实也是调用了OpenCommonbool 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,依次打开
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脱壳脚本,很简单,每一步我都备注了,上次问我的同学仔细看一下,看看还有什么问题,一般加载dex最终都绕不开opencommon的,如果你hook不到,建议在走过的所有函数入口出口下断点或者直接hook,跟踪下流程是不是正常
/*static std::unique_ptr<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 = nullptr); */
//这里我是安卓8.1的,不同版本不一样,自己pull出libart.so,打开ida查
var OpenCommon = Module.findExportByName("libart.so", "_ZN3art7DexFile10OpenCommonEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_PNS0_12VerifyResultE");
console.log("[*] Opencommon method addr: " + OpenCommon);
Interceptor.attach(OpenCommon, {
onEnter: function (args) {
console.log("[*] begin = " + args);//dex文件begin的地址
console.log("[*] size = " + args);//其实有了base就可以算出size了,这个参数不用也行,这里没有用
var begin = args;
console.log("magic : " + Memory.readUtf8String(begin)); //打印magic看下是不是dex
var address = parseInt(begin,16) + 0x20;//通过begin计算size地址
var dex_size = Memory.readInt(ptr(address));//读出size大小
console.log("sizee : " + dex_size);//比较发现跟args是一样的,证明有begin足够脱壳
var dex_file = new File("/data/data/com.xxx.xxx/" + dex_size.toString() + ".dex", "wb");//这里自己修改下路径,最好放在apk自己的data目录下,不然以后找不着了
dex_file.write(Memory.readByteArray(begin, dex_size));//这里按字节写入dex
dex_file.flush();
dex_file.close();
console.log("dump dex success");
},
onLeave: function (retval) {
//这里也可以通过retval获得dex_file,通过dex数据结构找到begin和size,dump出来
}
});
ps:菜鸟粗粗的阅读了下源码,又自己实践了一下,参考了Android万能脱壳机-- angelToms大佬的帖子https://bbs.pediy.com/thread-252284.htm,反思一下,如何做到对整体加固的脱壳,需要以下几个条件,以下纯属本人yy,大佬轻喷:
1.时机要对,一定要在dex完全加载到内存中才能完全脱下来(因为oat_file包含dex_file,所以oat完全加载也可以,就是麻烦一点),如果在加载之前脱,脱出来的可能是壳,也可能是不完整的dex
2.脱壳最重要的就是dex文件的begin地址,拿到了这个地址,根据文件结构就可以找到size长度,就可以顺利的脱出来
3.只要脱壳时机对了,一切可以拿到dex文件的begin地址的地方都可以脱整体壳;
而begin存储在dex_file里,所以一切可以拿到dex_file数据结构的地方都可以脱整体壳;
而dex_file数据结构存储在oat_dex_file 数据结构里,所以一切可以拿到oat_dex_file数据结构的地方都可以脱整体壳;
而oat_dex_file数据结构存储在oat_file 数据结构里,所以一切可以拿到oat_file数据结构的地方都可以脱整体壳
4.这个思路只能脱dex整体加固,抽取加固等菜鸟干完活,抽空阅读dex加载后类和方法的执行源码之后再学习,
5.下一篇菜鸟再学习下oat文件加载的源码
附件源码还是建议大家原贴下载,这里备用https://bbs.pediy.com/thread-257917.htm
本帖最后由 L剑仙 于 2020-3-3 09:53 编辑
你上当了 发表于 2020-3-3 09:42
我是小米的手机max3 ,miui10 8.9.20 , 8.1
小米可能魔改了你把libart.so导出来看看有没有opencommon这个函数
不行就hook DexFile::Open第一个参数也是begin,偏移0x20就是size,一样的,把我的脚本改个函数名就行了,
_ZN3art7DexFile4OpenEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_
我大致看了一下 so 没啥问题,你断hook DexFile::Open一样可以dump出来 ,第一个参数也是begin,偏移0x20就是size,一样的,把我的脚本改个函数名就行了,
_ZN3art7DexFile4OpenEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_
你没断下来原因太多了,我这边不可能还原你的环境,还得靠你自己调试 向楼主学习! 似乎有同学不知道怎么触发hook,这里以3xx加密为例:
1.查壳软件判断是3xx的壳,或者jadx打开壳dex有com.qihoo.util类似的类和libjiagu.so
2.开一个cmd执行frida -U -f com.xxx.xxx 预启动
3.开另一个cmd执行脚本frida -U -l tkk.js com.xxx.xxx
4.回到第一个cmd执行resume恢复app
5.可以在命令行看到脱壳提示了
终于来了 大佬 牛逼 能不能来个8.1dump的教程,随便写个apk 然后dump dex 在搞回去 本帖最后由 L剑仙 于 2020-3-3 09:21 编辑
你上当了 发表于 2020-3-3 08:52
能不能来个8.1dump的教程,随便写个apk 然后dump dex 在搞回去
脚本我已经附在下面了 兄嘚 直接用这个脚本就可以dump了 你仔细看看啊
你那个图 除了openCommon 所有函数都可以是正常 我是小米的手机max3 ,miui10 8.9.20 , 8.1