DexHunter在Dalvik虚拟机模式下的脱壳原理分析
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78494671
在前面的博客《DexHunter的原理分析和使用说明(一)》、《DexHunter的原理分析和使用说明(二) 》中已经将DexHunter工具的原理和使用需要注意的地方已经学习了一下,前面的博客中只讨论了DexHunter脱壳工具在Dalvik虚拟机模式下的脱壳原理和使用,一直想分析和研究一下DexHunter脱壳工具在ART虚拟机模式下的脱壳原理,终于有时间进行知识的消化了,特此整理一下基于Android运行时的脱壳工具DexHunter的脱壳原理,又将DexHunter工具的代码再看了几遍,对DexHunter脱壳工具的脱壳原理又有啦更进一步的理解和认识,并且也将DexHunter脱壳工具在ART虚拟机模式下的脱壳原理理解清楚啦。在DexHunter脱壳工具公布之后,类似变型的脱壳工具 Xdex 也出来了-详细的信息可以参考看雪文章《Xdex(百度版)脱壳工具基本原理》,基于类方法抽取的Android加固同样可以通过修改DexHunter脱壳工具进行脱壳,只是需要自己选择Android Dex文件的类方法加载点和类方法实现数据的dump点,最后进行dex文件的重组和还原。
1. Dalvik虚拟机模式下,DexHunter脱壳工具dump出来的文件是odex文件
DexHunter脱壳工具是完全基于Android运行时进行dex文件的分步dump脱壳的,此时原始的Android Dex文件已经被Android系统优化为了odex文件进行执行。因此,DexHunter脱壳工具dump出来的dex文件是Android系统优化后的odex文件,并且DexHunter工具是分步进行odex文件的dump操作的,然后将dump出来odex文件的各部分进行重组得到一个能进行反编译分析的odex文件。
2. Dalvik虚拟机模式下Android Odex文件格式
在进行Dexhunter脱壳工具的原理分析之前,先了解一下Dalvik虚拟机模式下odex文件的格式。有关odex文件格式的详细信息,可以参考作者roland_sun的博文《Android系统ODEX文件格式解析》,写的比较详细也很不错,借用一下作者画的odex文件格式的示意图。
注意:flags域说明是用的大端字节序还是小端字节序,一般是小端,所以是0;最后是校验和的值,注意这个校验和不是算整个ODEX文件的,而是只算依赖库列表段和优化数据段的。
3. Dalvik虚拟机模式下Android Dex文件格式
Dalvik虚拟机模式下dex文件格式示意图来自于大牛Jonathan Levin的paper《Andevcon-DEX.pdf》。
下面Android dex文件格式示的意图来自paper《A_deep_dive_into_dex_file_format》。
4. DexHunter脱壳工具的odex文件dump图解
DexHunter脱壳工具对被脱壳的Android应用的odex文件进行dump操作的原理图解(需要看懂DexHunter脱壳工具的代码,才能深刻的理解下面这幅图)
Dalvik虚拟机模式下,在目标被脱壳的Android进程内存中找到了需要dump的dex文件(实际是优化后的odex文件)所在的内存区域之后,将内存中dex文件分成3部分进行内存dump出来,然后进行dex文件的重组,最终得到能够进行逆向分析的dex文件。将目标被脱壳的Android进程内存中 odex文件开头到class_defs开始之前这段内存区域的数据 内存dump保存到 part1文件中;将从 class_defs的结尾开始到整个odex文件结尾之间的这段内存数据 内存dump操作保存到 data 文件中;dex文件中最关键的类信息描述结构体数据,以 Android运行时 的类填充数据为准,从内存中进行收集和整理,类定义的描述结构体DexClassDef的数据收集之后内存dump保存到classdef文件中,类实际的填充数据DexClassData和类方法的实际数据DexCode则按照偏移的格式保存到extra文件中,最后进行odex文件的重组。
5. DexHunter脱壳工具odex文件dump点的选择
Dalvik虚拟机模式下Android dex文件的dump点主要有4个地方:1. 打开dex文件的时候,判断dex文件是否优化为odex文件,如果已经优化为odex文件,则打开odex文件加载到内存中;2. 类Class加载的时候,dex文件加载到内存后的最终表现形式为ClassObject,在类方法被调用之前需要先加载该类Class并解析;3. 创建类Class对象初始化的时候,调用实例对象相关的操作时,需要先对类Class进行数据的初始化; 4. 类方法Method被调用的时候,类方法被调用的时需要获取Method方法的实际执行代码指令,这些代码指令从哪儿来,需要从加载到内存的dex文件中来获取。
Davik虚拟机模式下,为什么DexHunter脱壳工具选择类Class加载的时候进行dex文件的dump操作呢?
Dalvik虚拟机模式下,Android 类Class加载操作的步骤。
Dalvik虚拟机模式下,Android加载类Class的两种方法,调用类反射函数Class.forName进行类加载以及调用函数ClassLoader.loadClass进行类的主动加载。
Dalvik虚拟机模式下,java层类加载函数Class.forName和函数ClassLoader.loadClass对应的Native函数调用如下所示,在类对象创建时调用的dvmResolveClass函数获取ClassObject时也会涉及到Android 类的加载操作。
Dalvik虚拟机模式下,Android类的加载最终是通过调用Native层的函数 Dalvik_dalvik_system_DexFile_defineClassNative 实现类的加载,因此我们在进行dex文件的内存dump时紧紧的卡在这个点的位置。
Dalvik虚拟机模式下,dex文件描述的类加载到内存后的ClassObject描述结构体示意图:
6.Dalvik虚拟机模式下,DexHunter脱壳工具对Android源码的修改位置(以Android 4.4.4 r1的源码为例):对 Android 4.4.4 r1 源码的文件路径 /dalvik/vm/native/dalvik_system_DexFile.cpp 中 函数Dalvik_dalvik_system_DexFile_defineClassNative 的实现代码进行修改。
http://androidxref.com/4.4.4_r1/xref/dalvik/vm/native/dalvik_system_DexFile.cpp#349
Dalvik虚拟机模式下,DexHunter脱壳工具内存dump被脱壳Android应用的odex文件的步骤详细分析。
7. Android系统进程的uid一般为0,普通应用的uid是大于0的,因此通过getuid获取当前Android进程的uid,使用uid进行简单的Android系统进程的过滤。创建原生线程,读取DexHunter脱壳工具需要的配置文件 /data/dexname 中的数据信息,顺便提示下:DexHunter脱壳工具出来以后,一些Android加固增加了对DexHunter脱壳工具的检测对抗,检测原理也比较简单--通过检测是否存在 /data/dexname 文件来检测DexHunter脱壳工具的存在, 因此在进行DexHunter脱壳工具的使用过程,最好修改一下DexHunter脱壳工具的配置文件路径。
DexHunter脱壳工具的配置文件 /data/dexname 中,有2行数据内容;第1行数据是Android加固对应的特征字符串 dexname--Android加固对应的被保护dex文件的加载路径字符串,不同Android加固的被保护dex文件的加载路径是不同的,同一种加固的被保护dex文件的加载路径也不是一直固定,脱壳操作之前需要自己先确定;第2行数据是DexHunter脱壳工具存放内存dump的odex文件数据的工作目录dumppath(要保证DexHunter脱壳工具在这个工作目录下有文件的读写权限),一般情况下这个工作目录设置为被脱壳Android应用的数据目录 /data/data/pakegname(被脱壳的Android应用的包名) 。创建并初始化定时器,主要是为了设置内存dump被脱壳Android应用的odex文件的等待时间,建议将定时器的等待时间设置的稍微长一点,因为现在的Android应用的dex文件都比较大,从内存中收集和dump被脱壳Android应用的odex文件还是比较耗费时间的;如果定时器的等待时间设置过短,会导致DexHunter脱壳失败的。
DexHunter脱壳工具开源公布时,一些常见Android加固的特征字符串即被保护dex文件的加载路径特征字符串的格式,如下表所示:
8. 使用Android加固的特征字符串 dexname 即被保护dex文件的加载路径字符串,进行内存odex文件dump操作的准确过滤。
Android加固对应的特征字符串即被保护dex文件的加载路径字符串,Dalvik虚拟机模式下是根据 cookie值 的描述结构体 DexOrJar 中的成员变量 fileName 来确定的,最终都是由加载dex文件到内存的 函数 Dalvik_dalvik_system_DexFile_openDexFileNative 和 函数Dalvik_dalvik_system_DexFile_openDexFile_bytearray 来决定的。
Dalvik虚拟机模式下,DexOrJar 结构体中的成员变量 filename 描述了被加载的dex文件的路径。
函数 Dalvik_dalvik_system_DexFile_openDexFileNative 加载dex文件到Android进程内存的实现(正常情况下,Android应用加载dex文件调用该函数)。
/*
* private static int openDexFileNative(String sourceName, String outputName,
* int flags) throws IOException
*
* Open a DEX file, returning a pointer to our internal data structure.
*
* "sourceName" should point to the "source" jar or DEX file.
*
* If "outputName" is NULL, the DEX code will automatically find the
* "optimized" version in the cache directory, creating it if necessary.
* If it's non-NULL, the specified file will be used instead.
*
* TODO: at present we will happily open the same file more than once.
* To optimize this away we could search for existing entries in the hash
* table and refCount them. Requires atomic ops or adding "synchronized"
* to the non-native code that calls here.
*
* TODO: should be using "long" for a pointer.
*/
static void Dalvik_dalvik_system_DexFile_openDexFileNative(const u4* args,
JValue* pResult)
{
StringObject* sourceNameObj = (StringObject*) args[0];
StringObject* outputNameObj = (StringObject*) args[1];
DexOrJar* pDexOrJar = NULL;
JarFile* pJarFile;
RawDexFile* pRawDexFile;
char* sourceName;
char* outputName;
if (sourceNameObj == NULL) {
dvmThrowNullPointerException("sourceName == null");
RETURN_VOID();
}
sourceName = dvmCreateCstrFromString(sourceNameObj);
if (outputNameObj != NULL)
outputName = dvmCreateCstrFromString(outputNameObj);
else
outputName = NULL;
/*
* We have to deal with the possibility that somebody might try to
* open one of our bootstrap class DEX files. The set of dependencies
* will be different, and hence the results of optimization might be
* different, which means we'd actually need to have two versions of
* the optimized DEX: one that only knows about part of the boot class
* path, and one that knows about everything in it. The latter might
* optimize field/method accesses based on a class that appeared later
* in the class path.
*
* We can't let the user-defined class loader open it and start using
* the classes, since the optimized form of the code skips some of
* the method and field resolution that we would ordinarily do, and
* we'd have the wrong semantics.
*
* We have to reject attempts to manually open a DEX file from the boot
* class path. The easiest way to do this is by filename, which works
* out because variations in name (e.g. "/system/framework/./ext.jar")
* result in us hitting a different dalvik-cache entry. It's also fine
* if the caller specifies their own output file.
*/
if (dvmClassPathContains(gDvm.bootClassPath, sourceName)) {
ALOGW("Refusing to reopen boot DEX '%s'", sourceName);
dvmThrowIOException(
"Re-opening BOOTCLASSPATH DEX files is not allowed");
free(sourceName);
free(outputName);
RETURN_VOID();
}
/*
* Try to open it directly as a DEX if the name ends with ".dex".
* If that fails (or isn't tried in the first place), try it as a
* Zip with a "classes.dex" inside.
*/
if (hasDexExtension(sourceName)
&& dvmRawDexFileOpen(sourceName, outputName, &pRawDexFile, false) == 0) {
ALOGV("Opening DEX file '%s' (DEX)", sourceName);
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = true;
pDexOrJar->pRawDexFile = pRawDexFile;
pDexOrJar->pDexMemory = NULL;
} else if (dvmJarFileOpen(sourceName, outputName, &pJarFile, false) == 0) {
ALOGV("Opening DEX file '%s' (Jar)", sourceName);
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = false;
pDexOrJar->pJarFile = pJarFile;
pDexOrJar->pDexMemory = NULL;
} else {
ALOGV("Unable to open DEX file '%s'", sourceName);
dvmThrowIOException("unable to open DEX file");
}
if (pDexOrJar != NULL) {
// 被加载的dex文件的路径描述
pDexOrJar->fileName = sourceName;
addToDexFileTable(pDexOrJar);
} else {
free(sourceName);
}
free(outputName);
RETURN_PTR(pDexOrJar);
}
函数Dalvik_dalvik_system_DexFile_openDexFile_bytearray 加载dex文件到Android进程内存的实现(Android加固加载被保护dex文件时调用该函数)。
/*
* private static int openDexFile(byte[] fileContents) throws IOException
*
* Open a DEX file represented in a byte[], returning a pointer to our
* internal data structure.
*
* The system will only perform "essential" optimizations on the given file.
*
* TODO: should be using "long" for a pointer.
*/
static void Dalvik_dalvik_system_DexFile_openDexFile_bytearray(const u4* args,
JValue* pResult)
{
ArrayObject* fileContentsObj = (ArrayObject*) args[0];
u4 length;
u1* pBytes;
RawDexFile* pRawDexFile;
DexOrJar* pDexOrJar = NULL;
if (fileContentsObj == NULL) {
dvmThrowNullPointerException("fileContents == null");
RETURN_VOID();
}
/* TODO: Avoid making a copy of the array. (note array *is* modified) */
length = fileContentsObj->length;
pBytes = (u1*) malloc(length);
if (pBytes == NULL) {
dvmThrowRuntimeException("unable to allocate DEX memory");
RETURN_VOID();
}
memcpy(pBytes, fileContentsObj->contents, length);
if (dvmRawDexFileOpenArray(pBytes, length, &pRawDexFile) != 0) {
ALOGV("Unable to open in-memory DEX file");
free(pBytes);
dvmThrowRuntimeException("unable to open in-memory DEX file");
RETURN_VOID();
}
ALOGV("Opening in-memory DEX");
pDexOrJar = (DexOrJar*) malloc(sizeof(DexOrJar));
pDexOrJar->isDex = true;
pDexOrJar->pRawDexFile = pRawDexFile;
pDexOrJar->pDexMemory = pBytes;
// 被加载的dex文件的路径描述
pDexOrJar->fileName = strdup("<memory>"); // Needs to be free()able.
addToDexFileTable(pDexOrJar);
RETURN_PTR(pDexOrJar);
}
9.通过被脱壳Android应用进程dex文件的内存描述结构体 DvmDex 的成员变量 memMap 获取到该进程的odex文件所在的内存区域,然后将 odex文件开头到class_defs数据起始偏移之间的数据 从内存中dump出来保存到文件 /data/data/pakegname/part1 中,其中pakegname为被脱壳Android应用的包名。
示意图:
10.将被脱壳Android应用的内存odex文件的 class_defs数据结束偏移到odex文件结束的这段内存数据 内存dump出来保存到文件 /data/data/pakegname/data 中,其中pakegname为被脱壳Android应用的包名。
示意图:
11. 创建原生线程,遍历被脱壳Android应用odex文件的 DexClassDef 数据数组,基于Android运行时进行被脱壳Android应用类的描述数据 DexClassDef 、DexClassData 和类方法的实现数据 DexCode 的收集和内存dump,将类的定义描述数据 DexClassDef内存dump保存到文件/data/data/pakegname/classdef 中,将类的实际描述结构体数据DexClassData 和类方法的实现数据DexCode 内存dump保存到文件/data/data/pakegname/extra 中,然后将内存dump的odex文件的 4部分数据 进行重组,得到能够反编译odex文件,其中pakegname为被脱壳Android应用的包名。
12. 创建保存内存dump的类定义描述结构体 DexClassDef 数据的文件 /data/data/pakegname/classdef 以及创建保存内存dump的类数据 DexClassData 和 类方法的实际实现数据 DexCode 的文件 /data/data/pakegname/extra其中pakegname为被脱壳Android应用的包名 。
被脱壳Android应用的odex文件中的类数据以Android运行时的 DexClassData 的实际填充数据 为基准进行内存dump,意思就是内存dump的类描述结构体数据以dex文件加载到内存后转换为ClassObject *中的类实际填充数据为基准进行内存dump,为什么要这样做呢? 因为,Android加固会对被保护的dex文件进行加固处理,对被保护的dex文件中的类数据偏移 classDataOff 进行修改并将类数据 DexClassData 进行加密处理,在类被执行时先对被加密的类数据 DexClassData 进行解密处理,然后修正类数据的偏移classDataOff 使其指向解密后正确的DexClassData数据(一般情况,被解密的DexClassData数据会存放在odex文件文件头之前的内存区域 或者 在odex文件文件尾之后的内存区域);还有一些Android加固采取的加固粒度更细,对被保护dex文件的 codeOff 进行修改并对类方法实现 DexCode 的数据进行加密处理,在类方法执行时,先解密被加密的类方法实现 DexCode的数据,然后修正指向DexCode数据的偏移 codeOff (一般情况,解密后的DexCode数据会被存放在在odex文件文件头之前的内存区域 或者 在odex文件文件尾之后的内存区域)。由于当dex文件中类被执行时,类相关的描述数据都会被解密后正确填充,因此基于Android运行时进行dex文件类数据的dump。
13. 鉴于Android进程中内存数据 4 字节对齐的要求(目的是为了提高内存数据访问的效率),因此需要对被脱壳Android进程的odex文件的内存数据进行 4 字节对齐处理,不足4字节进行 0 的数据填充,也是为了后面odex文件重组时正确的设置odex文件类数据DexClassData和DexCode的偏移。
14.为了判断被脱壳Android应用dex文件的DexClassData数据是否被Android加固所加固处理,因此需要对DexClassData数据的偏移 classDataOff 进行边界的判断,DexClassData数据保存的起始文件偏移是dex文件存放DexClassDef段数据结束的位置,DexClassData数据保存的结束文件偏移是整个odex文件结束的位置。遍历dex文件的每个类定义描述结构体DexClassDef,基于Android运行时的类数据DexClassData和DexCode的收集;在进行dex文件类数据收集时,排除过滤掉 "Landroid" 开头的Android系统类和空类的类数据DexClassData和DexCode的收集。
对于是Android系统(Landroid开头的)的类和空类,need_extra为 false 即不对 Landroid开头 的Android系统类和空类进行类实现数据DexClassData的收集,并设置这两种情况的类DexClassDef的成员变量 classDataOff 和 annotationsOff 的文件偏移值为 0 ,还有对于这两种情况的类,只保存 类定义的数据DexClassDef 到文件 /data/data/pakegname/classdef 中。这里还需要对标志 need_extra 和 pass 的意思进行说明一下,标志need_extra 的意思是是否保存类的实现数据 DexClassData 到文件/data/data/pakegname/extra 中;标志 pass的意思是 是否设置类定义DexClassDef 的成员变量 classDataOff 和 annotationsOff 的文件偏移值为 0,标志 need_extra 和 pass 的bool值总是相反的,其中pakegname为被脱壳Android应用的包名。
15.重点描述(突出DexHunter脱壳工具的基于运行时的脱壳)
未完待续~
DexHunter在Dalvik虚拟机模式下的脱壳原理分析的更多相关文章
- DexHunter在ART虚拟机模式下的脱壳原理分析
本文博客地址: http://blog.csdn.net/qq1084283172/article/details/78494620 DexHunter脱壳工具在Dalvik虚拟机模式下的脱壳原理分析 ...
- android 脱壳 之 dvmDexFileOpenPartial断点脱壳原理分析
android 脱壳 之 dvmDexFileOpenPartial断点脱壳原理分析 导语: 笔者主要研究方向是网络通信协议的加密解密, 对应用程序加固脱壳技术很少研究, 脱壳壳经历更是经历少之甚少. ...
- 基于Frida框架打造Art模式下的脱壳工具(OpenMemory)的原理分析
本文博客地址:https://blog.csdn.net/QQ1084283172/article/details/80956614 作者dstmath在看雪论坛公布一个Android的art模式下基 ...
- PHP CLI模式下的多进程应用分析
PHP在非常多时候不适合做常驻的SHELL进程, 他没有专门的gc例程, 也没有有效的内存管理途径. 所以假设用PHP做常驻SHELL, 你会常常被内存耗尽导致abort而unhappy 并且, 假设 ...
- 【odoo】【知识杂谈】单一实例多库模式下定时任务的问题分析
欢迎转载,但需标注出处,谢谢! 背景: 有客户反应有个别模块下的定时任务没有正常执行,是否是新装的模块哪些有问题?排查后发现,客户是在一台服务器上跑着一个odoo容器,对应多个数据库.个别库的定时任务 ...
- Dalvik模式下基于Android运行时类加载的函数dexFindClass脱壳
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78003184 前段时间在看雪论坛发现了<发现一个安卓万能脱壳方法>这篇 ...
- ART模式下基于dex2oat脱壳的原理分析
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78513483 一般情况下,Android Dex文件在加载到内存之前需要先对dex ...
- Dalvik模式下System.loadLibrary函数的执行流程分析
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78212010 Android逆向分析的过程中免不了碰到Android so被加固的 ...
- ART模式下基于Xposed Hook开发脱壳工具
本文博客地址:http://blog.csdn.net/qq1084283172/article/details/78092365 Dalvik模式下的Android加固技术已经很成熟了,Dalvik ...
随机推荐
- 元数据管理—动态表单设计器在crudapi系统中完整实现
表单设计 在前面文章中,我们通过一系列案例介绍了表单设计的一些基本功能,表单设计起到非常重要作用,也是crudapi核心,所以本文会详细介绍表单设计中一些其它功能. 概要 表单字段column属性 列 ...
- SVHN数据集 Format1 剪裁版
SVHN数据集官网:http://ufldl.stanford.edu/housenumbers/ SVHN数据集官方提供的有两种格式 Format1是那种在街上拍的照片,每张照片的尺寸都不同,然后l ...
- C++树——遍历二叉树
在讲遍历之前,我们要先创建一个树: #include <iostream> using namespace std; typedef struct node; typedef node * ...
- 【数据结构与算法】——链表(Linked List)
链表(Linked List)介绍 链表是有序的列表,但是它在内存中是存储如下: 链表是以节点的方式来存储的,是链式存储. 每个节点包含data域,next域:指向下一个节点. 如图:链表的各个节点不 ...
- 输出质数(Java)
输出质数 一.什么是质数 质数又称素数.一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数,否则称为合数(规定1既不是质数也不是合数). 二.代码实现 1.输出100以内的质数 i ...
- Fork/Join 框架
本文部分摘自<Java 并发编程的艺术> Fork/Join 框架概述 Fork/Join 框架是 Java7 提供的一个用于并行执行任务的框架,是把一个大任务分割成若干个小任务,最终汇总 ...
- 手把手教你如何使用Charles抓包
一.为什么使用charles 前几天因为需要通过抓包定位问题,打开了尘封已久的fiddler,结果打开软件后什么也干不了,别说手机抓包了,打开软件什么请求也抓不到. 很多时候都是如此,如果一个方案不行 ...
- TypeError: myMethod() takes no arguments (1 given) Python常见错误
忘记为方法的第一个参数添加self参数 ---------------------------------------------------------------
- Azure DevOps 跨账号连接 Azure 服务
一,引言 由于新申请的 Azure DevOps 账号中的私有项目不在享受托管代理提供的1800分钟的免费时间,又不想花钱付费,那我们只能另想版本解决没有并行作业的问题. -------------- ...
- Dynamics CRM安装教程五:ADFS安装配置
ADFS即联合身份认证是一种更加安全的身份验证方式下面开始进行ADFS打开服务器管理器,到添加角色和功能向导下一步到下面界面勾选Active Directory 联合身份认证服务,下一步 默认,下一步 ...