功夫不负有心人,经过我们的一番挖掘,在系统的 dalvik 源码里面果然找到了这一隐藏入口:
/*
-
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.
*/
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;
pDexOrJar->fileName = strdup(“”); // Needs to be free()able.
addToDexFileTable(pDexOrJar);
RETURN_PTR(pDexOrJar);
}
这个方法可以做到对原始 DEX 文件做加载,而不依赖 ODEX 文件,它其实就做了这么几件事:
-
接受一个
byte[]
参数,也就是原始 DEX 文件的字节码。 -
调用
dvmRawDexFileOpenArray
函数来处理byte[]
,生成RawDexFile
对象 -
由
RawDexFile
对象生成一个DexOrJar
,通过addToDexFileTable
添加到虚拟机内部,这样后续就可以正常使用它了 -
返回这个
DexOrJar
的地址给上层,让上层用它作为 cookie 来构造一个合法的DexFile
对象
这样,上层在取得所有 Seconary DEX 的DexFile
对象后,调用 makeDexElements 插入到 ClassLoader 里面,就完成 install 操作了。如此一来,我们就能完美地避过 ODEX 优化,让 APP 正常执行下去了。
寻找入口
看起来似乎很顺利,然而在我们却遇到了一个意外状况。
我们从Dalvik_dalvik_system_DexFile_openDexFile_bytearray
这个函数的名字可以明显看出,这是一个 JNI 方法,从 4.0 到 4.3 版本都能找到它的 Java 原型:
/*
-
Open a DEX file based on a {@code byte[]}. The value returned
-
is a magic VM cookie. On failure, a RuntimeException is thrown.
*/
native private static int openDexFile(byte[] fileContents);
然而我们在 4.4 版本上,Java 层它并没有对应的 native 方法。这样我们便无法直接在上层调用了。
当然,我们很容易想到,可以用 dlsym 来直接搜寻这个函数的符号来调用。但是可惜的是,Dalvik_dalvik_system_DexFile_openDexFile_bytearray
这个方法是static
的,因此它并没有被导出。我们实际去解析libdvm.so
的时候,也确实没有找到Dalvik_dalvik_system_DexFile_openDexFile_bytearray
这个符号。
不过,由于它是 JNI 函数,也是通过正常方式注册到虚拟机里面的。因此,我们可以找到它对应的函数注册表:
const DalvikNativeMethod dvm_dalvik_system_DexFile[] = {
{ “openDexFileNative”, “(Ljava/lang/String;Ljava/lang/String;I)I”,
Dalvik_dalvik_system_DexFile_openDexFileNative },
{ “openDexFile”, “([B)I”,
Dalvik_dalvik_system_DexFile_openDexFile_bytearray },
{ “closeDexFile”, “(I)V”,
Dalvik_dalvik_system_DexFile_closeDexFile },
{ “defineClassNative”, “(Ljava/lang/String;Ljava/lang/ClassLoader;I)Ljava/lang/Class;”,
Dalvik_dalvik_system_DexFile_defineClassNative },
{ “getClassNameList”, “(I)[Ljava/lang/String;”,
Dalvik_dalvik_system_DexFile_getClassNameList },
{ “isDexOptNeeded”, “(Ljava/lang/String;)Z”,
Dalvik_dalvik_system_DexFile_isDexOptNeeded },
{ NULL, NULL, NULL },
};
dvm_dalvik_system_DexFile
这个数组需要被虚拟机在运行时动态地注册进去,因此,这个符号是一定会被导出的。
这么一来,我们也就可以通过 dlsym 取得这个数组,按照逐个元素字符串匹配的方式来搜寻openDexFile
对应的Dalvik_dalvik_system_DexFile_openDexFile_bytearray
方法了。
具体代码实现如下:
const char *name = “openDexFile”;
JNINativeMethod* func = (JNINativeMethod*) dlsym(handler, “dvm_dalvik_system_DexFile”);;
size_t len_name = strlen(name);
while (func->name != nullptr) {
if ((strncmp(name, func->name, len_name) == 0)
&& (strncmp(“([B)I”, func->signature, len_name) == 0)) {
return reinterpret_cast<func_openDexFileBytes>(func->fnPtr);
}
func++;
}
捋清步骤
小结一下,绕过 ODEX 直接加载 DEX 的方案,主要有以下步骤:
-
从 APK 中解压获取原始 Secondary DEX 文件的字节码
-
通过 dlsym 获取
dvm_dalvik_system_DexFile
数组 -
在数组中查询得到
Dalvik_dalvik_system_DexFile_openDexFile_bytearray
函数 -
调用该函数,逐个传入之前从 APK 获取的 DEX 字节码,完成 DEX 加载,得到合法的
DexFile
对象 -
把
DexFile
对象都添加到 APP 的PathClassLoader
的 pathList 里
完成了上述几步操作,我们就可以正常访问到 Secondary DEX 里面的类了
然而,正当我们顺利注入原始 DEX 往下执行的时候,却在 4.4 的机型上马上遇到了一个必现的崩溃:
JNI WARNING: JNI function NewGlobalRef called with exception pending
in Ljava/lang/Class;.getDex:()Lcom/android/dex/Dex; (NewGlobalRef)
Pending exception is:
java.lang.IndexOutOfBoundsException: index=0, limit=0
at java.nio.Buffer.checkIndex(Buffer.java:156)
at java.nio.DirectByteBuffer.get(DirectByteBuffer.java:157)
at com.android.dex.Dex.create(Dex.java:129)
at java.lang.Class.getDex(Native Method)
at libcore.reflect.AnnotationAccess.getSignature(AnnotationAccess.java:447)
at java.lang.Class.getGenericSuperclass(Class.java:824)
at com.google.gson.reflect.TypeToken.getSuperclassTypeParameter(TypeToken.java:82)
at com.google.gson.reflect.TypeToken.(TypeToken.java:62)
at com.google.gson.Gson$1.(Gson.java:112)
at com.google.gson.Gson.(Gson.java:112)
… …
可以看到,Gson 里面使用到了Class.getGenericSuperclass
方法,而它最终调用了Class.getDex
,它是一个 native 方法,对应实现如下:
JNIEXPORT jobject JNICALL Java_java_lang_Class_getDex(JNIEnv* env, jclass javaClass) {
Thread* self = dvmThreadSelf();
ClassObject* c = (ClassObject*) dvmDecodeIndirectRef(self, javaClass);
DvmDex* dvm_dex = c->pDvmDex;
if (dvm_dex == NULL) {
return NULL;
}
// Already cached?
if (dvm_dex->dex_object != NULL) {
return dvm_dex->dex_object;
}
jobject byte_buffer = env->NewDirectByteBuffer(dvm_dex->memMap.addr, dvm_dex->memMap.length);
if (byte_buffer == NULL) {
return NULL;
}
jclass com_android_dex_Dex = env->FindClass(“com/android/dex/Dex”);
if (com_android_dex_Dex == NULL) {
return NULL;
}
jmethodID com_android_dex_Dex_create =
env->GetStaticMethodID(com_android_dex_Dex,
“create”, “(Ljava/nio/ByteBuffer;)Lcom/android/dex/Dex;”);
if (com_android_dex_Dex_create == NULL) {
return NULL;
}
jvalue args[1];
args[0].l = byte_buffer;
jobject local_ref = env->CallStaticObjectMethodA(com_android_dex_Dex,
com_android_dex_Dex_create,
args);
if (local_ref == NULL) {
return NULL;
}
// Check another thread didn’t cache an object, if we’ve won install the object.
ScopedPthreadMutexLock lock(&dvm_dex->modLock);
if (dvm_dex->dex_object == NULL) {
dvm_dex->dex_object = env->NewGlobalRef(local_ref);
}
return dvm_dex->dex_object;
}
结合堆栈和代码来看,崩溃的点是在 JNI 里面执行com.android.dex.Dex.create
的时候:
jobject local_ref = env->CallStaticObjectMethodA(com_android_dex_Dex,
com_android_dex_Dex_create,
args);
由于是 JNI 方法,这个调用发生异常后如果没有 check,在后续执行到env->NewGlobalRef
调用的时候会检查到前面发生了异常,从而抛出。
而com.android.dex.Dex.create
之所以会执行失败,主要原因是入参有问题,这里的参数是dvm_dex->memMap
取到的一块 map 内存。dvm_dex 是从这个 Class 里面取得的。虚拟机代码里面,每个 Class 对应是结构是ClassObject
中,其中有这个字段:
struct ClassObject : Object {
… …
/* DexFile from which we came; needed to resolve constant pool entries */
/* (will be NULL for VM-generated, e.g. arrays and primitive classes) */
DvmDex* pDvmDex;
… …
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
学习分享
在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了
很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘
如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。
2021最新上万页的大厂面试真题
七大模块学习资料:如NDK模块开发、Android框架体系架构…
只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。如有需要获取完整的资料文档的朋友点击我的GitHub免费获取。
料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。
2021最新上万页的大厂面试真题
[外链图片转存中…(img-EyzAsiWE-1710818058723)]
七大模块学习资料:如NDK模块开发、Android框架体系架构…
[外链图片转存中…(img-wzvQRZSI-1710818058723)]
只有系统,有方向的学习,才能在段时间内迅速提高自己的技术。
这份体系学习笔记,适应人群:
**第一,**学习知识比较碎片化,没有合理的学习路线与进阶方向。
**第二,**开发几年,不知道如何进阶更进一步,比较迷茫。
第三,到了合适的年纪,后续不知道该如何发展,转型管理,还是加强技术研究。如果你有需要,我这里恰好有为什么,不来领取!说不定能改变你现在的状态呢!
由于文章内容比较多,篇幅不允许,部分未展示内容以截图方式展示 。如有需要获取完整的资料文档的朋友点击我的GitHub免费获取。