JNI调用Class.getDeclaredFields()报错及Android代码保护实现问询
JNI调用Class.getDeclaredFields()报错问题解决
问题描述
- 要实现Android代码保护,Java反射版本的CREATOR回滚功能正常,但JNI/C++版本运行报错
- 核心需求:在JNI中正确调用
java.lang.Class.getDeclaredFields() - 错误信息:
JNI DETECTED ERROR IN APPLICATION: can't call java.lang.reflect.Field[] java.lang.Class.getDeclaredFields() on instance of com.metadata.MetaData$1 - 疑问:该问题是代码错误还是Android平台限制?
相关代码示例
1. 目标类MetaData
package com.metadata; import android.os.Parcel; import android.os.Parcelable; import androidx.annotation.NonNull; class MetaData implements Parcelable { public String data; private MetaData(Parcel in) { data = in.readString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(data); } public static final Creator<MetaData> CREATOR = new Creator<MetaData>() { @Override public MetaData createFromParcel(Parcel in) { return new MetaData(in); } @Override public MetaData[] newArray(int size) { return new MetaData[size]; } }; }
2. 篡改CREATOR的攻击代码
public static void modify(String newData) { MetaData.Creator<MetaData> original = MetaData.CREATOR; MetaData.Creator<MetaData> fake = new Parcelable.Creator<MetaData>() { @Override public MetaData createFromParcel(Parcel source) { MetaData metaData = original.createFromParcel(source); metaData.data = newData; return metaData; } @Override public MetaData[] newArray(int size) { return original.newArray(size); } }; try { Field field = MetaData.class.getDeclaredField("CREATOR"); field.setAccessible(true); field.set(null, fake); } catch (Throwable e) { // ignore } }
3. Java版本的CREATOR回滚代码
public static void rollbackModification() { try { MetaData.Creator<MetaData> metaDataCreator = MetaData.CREATOR; Class<?> metaDataCreatorClass = metaDataCreator.getClass(); Field[] fields = metaDataCreatorClass.getDeclaredFields(); if (fields == null || fields.length == 0) return; Object originalCreator = null; for (Field field : fields) { // 查找原始CREATOR Class clazz = field.getType(); if (clazz == Parcelable.Creator.class) { field.setAccessible(true); originalCreator = field.get(metaDataCreator); break; } } if (originalCreator != null) { Field field = MetaData.class.getDeclaredField("CREATOR"); field.setAccessible(true);; field.set(null, originalCreator); } } catch (Throwable e) { // ignore } }
4. 报错的JNI/C++代码
#include <string.h> #include <jni.h> #include <android/log.h> // Android日志封装 static const char* kTAG = "native_log-jniCallback"; #define LOGI(...) \ ((void)__android_log_print(ANDROID_LOG_INFO, kTAG, __VA_ARGS__)) #define LOGW(...) \ ((void)__android_log_print(ANDROID_LOG_WARN, kTAG, __VA_ARGS__)) #define LOGE(...) \ ((void)__android_log_print(ANDROID_LOG_ERROR, kTAG, __VA_ARGS__)) extern "C" { Java_com_metadata_Test_test(JNIEnv *env, jobject obj, jobject cnt) { rollbackModification(env, obj, cnt); } rollbackModification(JNIEnv *env, jobject obj, jobject cnt) { try { jclass metaDataClass = env->FindClass("com/metadata/MetaData"); jclass parcelableCreatorClass = env->FindClass("android/os/Parcelable$Creator"); // 获取MetaData.CREATOR jfieldID fakeCreatorField = env->GetStaticFieldID(metaDataClass, "CREATOR", "Landroid/os/Parcelable$Creator;"); jobject metaDataCreator = env->GetStaticObjectField(metaDataClass, fakeCreatorField); // 检查MetaData.CREATOR的字段 if (metaDataCreator != NULL) { jclass fakeMetaDataCreatorClass = env->GetObjectClass(metaDataCreator); jmethodID fakeMetaDataCreatorClassMethod = env->GetMethodID(fakeMetaDataCreatorClass, "getClass", "()Ljava/lang/Class;"); jobject fakeMetaDataCreatorClassObject = env->CallObjectMethod(fakeMetaDataCreatorClass, fakeMetaDataCreatorClassMethod); jclass fakeMetaDataCreatorClassObjectClass = env->GetObjectClass(fakeMetaDataCreatorClassObject); // 尝试获取字段,此处报错 jmethodID declaredFieldsMethod = env->GetMethodID(fakeMetaDataCreatorClassObjectClass, "getDeclaredFields", "()[Ljava/lang/reflect/Field;"); jobjectArray fields = (jobjectArray)env->CallObjectMethod(metaDataCreator, declaredFieldsMethod); jsize numFields = env->GetArrayLength(fields); jobject originalCreator = NULL; // 查找原始字段 for (jsize i = 0; i < numFields; i++) { jobject field = env->GetObjectArrayElement(fields, i); jclass fieldClass = env->GetObjectClass(field); jmethodID getTypeMethod = env->GetMethodID(fieldClass, "getType", "()Ljava/lang/Class;"); jobject fieldType = env->CallObjectMethod(field, getTypeMethod); // 查找原始CREATOR对象 if (env->IsSameObject(fieldType, parcelableCreatorClass)) { jmethodID setAccessibleMethod = env->GetMethodID(fieldClass, "setAccessible", "(Z)V"); env->CallVoidMethod(field, setAccessibleMethod, JNI_TRUE); jmethodID getMethod = env->GetMethodID(fieldClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); originalCreator = env->CallObjectMethod(field, getMethod, metaDataCreator); break; } } // 将MetaData.CREATOR从伪造对象换回原始对象 if (originalCreator != NULL) { jmethodID setMethod = env->GetMethodID(fakeMetaDataCreatorClass, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V"); env->CallVoidMethod(metaDataCreator, setMethod, NULL, originalCreator); } } } catch (...){ } } }
问题原因分析
报错的核心是调用getDeclaredFields时用错了对象:
getDeclaredFields()是java.lang.Class类的方法,必须调用在Class实例上- 原JNI代码中,错误地调用在了
metaDataCreator(MetaData$1对象实例)上,而非fakeMetaDataCreatorClassObject(Class实例) - 另外最后替换CREATOR时,错误地调用了
metaDataCreator的set方法,实际应该调用Field对象的set方法
修正后的JNI代码
#include <string.h> #include <jni.h> #include <android/log.h> // Android日志封装 static const char* kTAG = "native_log-jniCallback"; #define LOGI(...) \ ((void)__android_log_print(ANDROID_LOG_INFO, kTAG, __VA_ARGS__)) #define LOGW(...) \ ((void)__android_log_print(ANDROID_LOG_WARN, kTAG, __VA_ARGS__)) #define LOGE(...) \ ((void)__android_log_print(ANDROID_LOG_ERROR, kTAG, __VA_ARGS__)) extern "C" { void Java_com_metadata_Test_test(JNIEnv *env, jobject obj, jobject cnt) { rollbackModification(env, obj, cnt); } void rollbackModification(JNIEnv *env, jobject obj, jobject cnt) { jclass metaDataClass = env->FindClass("com/metadata/MetaData"); if (metaDataClass == nullptr) { LOGE("Failed to find MetaData class"); return; } jclass parcelableCreatorClass = env->FindClass("android/os/Parcelable$Creator"); if (parcelableCreatorClass == nullptr) { LOGE("Failed to find Parcelable.Creator class"); return; } // 获取MetaData.CREATOR静态字段 jfieldID creatorFieldId = env->GetStaticFieldID(metaDataClass, "CREATOR", "Landroid/os/Parcelable$Creator;"); if (creatorFieldId == nullptr) { LOGE("Failed to get CREATOR field ID"); return; } jobject currentCreator = env->GetStaticObjectField(metaDataClass, creatorFieldId); if (currentCreator == nullptr) { LOGE("Current CREATOR is null"); return; } // 获取currentCreator的Class实例 jclass objectClass = env->FindClass("java/lang/Object"); jmethodID getClassMethodId = env->GetMethodID(objectClass, "getClass", "()Ljava/lang/Class;"); jobject creatorClazz = env->CallObjectMethod(currentCreator, getClassMethodId); if (creatorClazz == nullptr) { LOGE("Failed to get creator class"); return; } // 获取Class.getDeclaredFields()方法 jclass classClass = env->FindClass("java/lang/Class"); jmethodID getDeclaredFieldsMethodId = env->GetMethodID(classClass, "getDeclaredFields", "()[Ljava/lang/reflect/Field;"); jobjectArray fields = (jobjectArray)env->CallObjectMethod(creatorClazz, getDeclaredFieldsMethodId); if (fields == nullptr) { LOGE("Failed to get declared fields"); return; } jsize fieldCount = env->GetArrayLength(fields); jobject originalCreator = nullptr; // 遍历字段查找原始Creator for (jsize i = 0; i < fieldCount; i++) { jobject field = env->GetObjectArrayElement(fields, i); jclass fieldClass = env->FindClass("java/lang/reflect/Field"); // 获取字段类型 jmethodID getTypeMethodId = env->GetMethodID(fieldClass, "getType", "()Ljava/lang/Class;"); jobject fieldType = env->CallObjectMethod(field, getTypeMethodId); if (env->IsSameObject(fieldType, parcelableCreatorClass)) { // 设置字段可访问 jmethodID setAccessibleMethodId = env->GetMethodID(fieldClass, "setAccessible", "(Z)V"); env->CallVoidMethod(field, setAccessibleMethodId, JNI_TRUE); // 获取字段值(原始Creator) jmethodID getMethodId = env->GetMethodID(fieldClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;"); originalCreator = env->CallObjectMethod(field, getMethodId, currentCreator); break; } env->DeleteLocalRef(field); } // 替换回原始Creator if (originalCreator != nullptr) { jclass fieldClass = env->FindClass("java/lang/reflect/Field"); jmethodID setMethodId = env->GetMethodID(fieldClass, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V"); jmethodID setAccessibleMethodId = env->GetMethodID(fieldClass, "setAccessible", "(Z)V"); // 获取MetaData.CREATOR字段对象 jmethodID getDeclaredFieldMethodId = env->GetMethodID(classClass, "getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;"); jobject creatorField = env->CallObjectMethod(metaDataClass, getDeclaredFieldMethodId, env->NewStringUTF("CREATOR")); env->CallVoidMethod(creatorField, setAccessibleMethodId, JNI_TRUE); env->CallVoidMethod(creatorField, setMethodId, nullptr, originalCreator); env->DeleteLocalRef(creatorField); } // 释放局部引用 env->DeleteLocalRef(metaDataClass); env->DeleteLocalRef(parcelableCreatorClass); env->DeleteLocalRef(objectClass); env->DeleteLocalRef(creatorClazz); env->DeleteLocalRef(classClass); env->DeleteLocalRef(fields); env->DeleteLocalRef(currentCreator); if (originalCreator != nullptr) { env->DeleteLocalRef(originalCreator); } } }
补充说明
- 该问题属于代码逻辑错误,并非Android非SDK接口限制。只要正确调用
Class类的方法,就能正常获取字段 - 注意在JNI中及时释放局部引用,避免内存泄漏
- 针对Android高版本的非SDK接口限制,若后续遇到相关问题,需确认调用的方法是否在允许列表内
内容的提问来源于stack exchange,提问作者Prilaga
相关产品推荐
相关产品推荐

