DexHunter在Dalvik虚拟机模式下的脱壳原理分析
Posted Fly20141201
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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虚拟机模式下的脱壳原理分析的主要内容,如果未能解决你的问题,请参考以下文章
Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段
Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段