java函数_java函数转Native化的一些实践

本文为看雪论坛优秀文章

看雪论坛作者ID:一颗金柚子

链接:java函数转Native化的一些实践

[萌新发帖]由于权限问题,我只能在“茶余饭后”发表帖子,希望这一次过后可以多多在Android安全中发。

目的:完成MainActivity中的onCreate和replaceClassloader两个java函数的jni等价实现

首先声明一下AS工程写的是dex动态加载,然后是为了去加载另一个1.dex里的TestActivity类

16ad0f7610b59dae653343401daa432b.png

fb9dea710d3ffcd632c73372ef9cb80b.png

接着使用GDA去看要加载的1.dex中查看

ed085597454d78fb9657be0ac7d2b1b1.png

16ad0f7610b59dae653343401daa432b.png

如果Native转化成功,则会在logcat窗口中看到i am from TestActivity.onCreate

开始JNI转化:

16ad0f7610b59dae653343401daa432b.png

fc68aabdea1997f85076c2eaf9be9a0d.png

0x01 使用JNI实现onCreate函数的等价转化

①首先是注释掉onCreate,并添加声明

protected native void onCreate(Bundle SavedInstanceState);

②其中,setContentView(R.layout.activity_main);这个语句中会有R类,这是自动生成类,我们需要在GDA中观看它的反编译结果。

发现其中都是些静态Static int 属性,一会儿用Field的时候选择StaticInt 和Static

16ad0f7610b59dae653343401daa432b.png

9159637e1f73ebb3e3420dd257e0504a.png

③转Jni层实现

onCreate的前两行都没什么大问题,一般的反射思路。

//第一行:super.onCreate(savedInstanceState);因此首先需要获取superclass
jclass AppCompatActivity_cls1 = env->FindClass("androidx/appcompat/app/AppCompatActivity");//已经知道类名
jclass MainActivity_cls1 = env->FindClass("com/kanxue/loaddex/MainActivity");
//调用这个函数protected void onCreate(@Nullable Bundle savedInstanceState)
jmethodID superclassOnCreate_mid = env->GetMethodID(AppCompatActivity_cls1,"onCreate","(Landroid/os/Bundle;)V");
env->CallNonvirtualVoidMethod(thiz,AppCompatActivity_cls1,superclassOnCreate_mid,Bundle);

//第二行:setContentView(R.layout.activity_main);
jmethodID setContentView_mid = env->GetMethodID(MainActivity_cls1,"setContentView","(I)V");
jclass R_layout_cls = env->FindClass("com/kanxue/loaddex/R$layout");
jfieldID activity_main_fid=env->GetStaticFieldID(R_layout_cls,"activity_main","I");//这里对应R$layout那个图看出来的
jint activity_main_value = env->GetStaticIntField(R_layout_cls,activity_main_fid);
env->CallVoidMethod(thiz,setContentView_mid,activity_main_value);

第三行,我们跟进并观察getApplication的函数实现

16ad0f7610b59dae653343401daa432b.png

db52a2692f60acf97e90f9c99fd8b8d2.png

16ad0f7610b59dae653343401daa432b.png

16ad0f7610b59dae653343401daa432b.png

a11c74c77b6456598a6aa56edfe6e988.png
//第三行:mApplication = this.getApplication();
//其中,public final Application getApplication()
jmethodID getApplication_mid=env->GetMethodID(MainActivity_cls1,"getApplication","()Landroid/app/Application;");
jobject mApplication = env->CallObjectMethod(thiz,getApplication_mid);

第四行,原理和第三行一致:

16ad0f7610b59dae653343401daa432b.png

0118d0e3a4d1447d9c55b74ec5cc9b10.png
//第四行:appContext = this.getApplicationContext();
jmethodID getApplicationContext_mid=env->GetMethodID(MainActivity_cls1,"getApplicationContext","()Landroid/content/Context;");
jobject appContext = env->CallObjectMethod(thiz,getApplicationContext_mid);

第五行,调用了copAssetAndWrite,因此需要注意私有属性和Static类型

//copyAssetAndWrite函数
private static boolean copyAssetAndWrite(String fileName) {
try {
File cacheDir = appContext.getCacheDir();
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
File outFile = new File(cacheDir, fileName);
if (!outFile.exists()) {
boolean res = outFile.createNewFile();
if (!res) {
return false;
}
} else {
if (outFile.length() > 10) {
return true;
}
}
InputStream is = appContext.getAssets().open(fileName);
FileOutputStream fos = new FileOutputStream(outFile);
byte[] buffer = new byte[1024];
int byteCount;
while ((byteCount = is.read(buffer)) != -1) {
fos.write(buffer, 0, byteCount);
}
fos.flush();
is.close();
fos.close();
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}

16ad0f7610b59dae653343401daa432b.png

269720a56d1c0359a99a4283295f761f.png
//第五行:copyAssetAndWrite("1.dex");
//其中,private static boolean copyAssetAndWrite(String fileName)
jmethodID copyAssetAndWrite_mid=env->GetStaticMethodID(MainActivity_cls1,"copyAssetAndWrite","(Ljava/lang/String;)Z");
jstring arg1 = env->NewStringUTF("1.dex");
env->CallStaticBooleanMethod(MainActivity_cls1,copyAssetAndWrite_mid,arg1);

java.io.File android.content.Context.getCacheDir()' on a null object reference

这里遇到了一个问题,也就是当我在JNI里实现appContext的赋值后怎么让JAVA层的copyAssetAndWrite函数知道我改了值呢?

解决方法:给copyAssetAndWrite新增一个参数,就是Context类型的appContext,即我们自己来传给它

//第五行:copyAssetAndWrite("1.dex");
//其中,private static boolean copyAssetAndWrite(String fileName)
jmethodID copyAssetAndWrite_mid=env->GetStaticMethodID(MainActivity_cls1,"copyAssetAndWrite","(Landroid/content/Context;Ljava/lang/String;)Z");
jstring arg1 = env->NewStringUTF("1.dex");
env->CallStaticBooleanMethod(MainActivity_cls1,copyAssetAndWrite_mid,appContext,arg1);

相对应地,MainActivity里把copyAssetAndWrite改成

private static boolean copyAssetAndWrite(Context appContext, String fileName)

第六行,可以看到File是个构造函数,因此在最后调用的时候要用NewObject,而且寻找methodId的时候要用<init>

16ad0f7610b59dae653343401daa432b.png

a23066a3988b0c14978ba8bb78a4c860.png

因此File类,签名为:()Ljava/io/File;

16ad0f7610b59dae653343401daa432b.png

b2f78d583d3586dbf17002239567835c.png

16ad0f7610b59dae653343401daa432b.png

189933f4c1f413f8268dc2c165753388.png
//第六行:File dataFile = new File(this.getCacheDir(), "1.dex");
//其中,public File getCacheDir()      public File(File parent, String child)
jclass File_cls = env->FindClass("java/io/File");
jmethodID getCacheDir_mid=env->GetMethodID(AppActivity_cls1,"getCacheDir","()Ljava/io/File;");
jobject Cachedir = env->CallObjectMethod(thiz,getCacheDir_mid);
//对于构造函数File,函数名不是File而是<init>
jmethodID File_mid=env->GetMethodID(File_cls,"<init>","(Ljava/io/File;Ljava/lang/String;)V");
jobject dataFile = env->NewObject(File_cls,File_mid,Cachedir,arg1);

第七行,

//第七行:startTestActivityFirstMethod(this, dataFile.getAbsolutePath());
//其中,public void startTestActivityFirstMethod(Context context, String dexfilepath)
jmethodID startTestActivityFirstMethod_mid=env->GetMethodID(MainActivity_cls1,"startTestActivityFirstMethod",
                                                    "(Landroid/content/Context;Ljava/lang/String;)V");
//其中,public String getAbsolutePath()
jmethodID getAbsolutePath_mid=env->GetMethodID(MainActivity_cls1,"getAbsolutePath","()Ljava/lang/String;");
jstring arg2 = static_cast<jstring >( env->CallObjectMethod(thiz,getAbsolutePath_mid));
env->CallVoidMethod(MainActivity_cls1,startTestActivityFirstMethod_mid,thiz,arg2);

报错:can't call void com.kanxue.loaddex.MainActivity.startTestActivityFirstMethod(android.content.Context, java.lang.String) on instance of java.lang.Class<com.kanxue.loaddex.MainActivity>

经研究发现,是CallNonvirtualVoid和Callvoid之间的区别问题

CallNonVirtualVoidMethod是调用含子类重写父类函数的函数 MainActivity中的onCreate是重写,不是继承

CallVoidMethod 调用的是当前对象自己的函数

因此正确应该是写成:

//第七行:startTestActivityFirstMethod(this, dataFile.getAbsolutePath());
//其中,内部有:public String getAbsolutePath()
jmethodID getAbsolutePath_mid=env->GetMethodID(File_cls,"getAbsolutePath","()Ljava/lang/String;");
jstring arg2 = static_cast<jstring>( env->CallObjectMethod(dataFile,getAbsolutePath_mid));
//其中,外部有:public void startTestActivityFirstMethod(Context context, String dexfilepath)
jmethodID startTestActivityFirstMethod_mid=env->GetMethodID(MainActivity_cls1,"startTestActivityFirstMethod",
                                                            "(Landroid/content/Context;Ljava/lang/String;)V");
const char* content_ptr=env->GetStringUTFChars(arg2, nullptr);
__android_log_print(4,"kanxue->homework", "arg2->%s",content_ptr );//打印动态加载1.dex的路径
env->CallNonvirtualVoidMethod(thiz,MainActivity_cls1,startTestActivityFirstMethod_mid,thiz,arg2);

关于onCreate的Native化

成功打印出log

16ad0f7610b59dae653343401daa432b.png

76012759c0a8ee52e900fb5d4427aa2a.png

0x02 接下来使用JNI实现replaceClassloader

//replaceClassLoader的代码
public void replaceClassloader(ClassLoader classloader){
Class<?> ActivityThreadClazz = classloader.loadClass("android.app.ActivityThread");
Method currentActivityThreadMethod = ActivityThreadClazz.getDeclaredMethod("currentActivityThread");
currentActivityThreadMethod.setAccessible(true);
Object activityThreadObj = currentActivityThreadMethod.invoke(null);
//final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
Field mPackagesField = ActivityThreadClazz.getDeclaredField("mPackages");
mPackagesField.setAccessible(true);
ArrayMap mPackagesObj = (ArrayMap) mPackagesField.get(activityThreadObj);
WeakReference wr = (WeakReference) mPackagesObj.get(this.getPackageName());
Object loadedApkObj = wr.get();
Class LoadedApkClazz = classloader.loadClass("android.app.LoadedApk");
Field mClassLoaderField = LoadedApkClazz.getDeclaredField("mClassLoader");
mClassLoaderField.setAccessible(true);
mClassLoaderField.set(loadedApkObj, classloader);
}

这个replace函数可以有两种等价实现:

①一种是严格的翻译,使用java中反射相关的api;

根据前面的反射思路,我直接给出这部分代码,步骤非常的固定,“jclass->jmethodID->CallxxxxMethod”以这种方式循环

   extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_loaddex_MainActivity_JNI_1replaceClassloader(JNIEnv *env,
        jobject thiz,jobject classloader){
    jclass Classloader_jclass = env->FindClass("java/lang/ClassLoader");
    jmethodID loadClass_mid = env->GetMethodID(Classloader_jclass, "loadClass",
                                               "(Ljava/lang/String;)Ljava/lang/Class;");
    jstring ActivityThreadName = env->NewStringUTF("android.app.ActivityThread");
    jobject ActivityThread_class = env->CallObjectMethod(classloader, loadClass_mid,
                                                         ActivityThreadName);
    jclass Class_jclass = env->FindClass("java/lang/Class");
    jclass Method_jclass = env->FindClass("java/lang/reflect/Method");
    jmethodID setAccessible_mid = env->GetMethodID(Method_jclass, "setAccessible", "(Z)V");
    jmethodID getDeclaredMethod_mid = env->GetMethodID(Class_jclass, "getDeclaredMethod",
                                                       "(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;");
    jmethodID getDeclaredField_mid = env->GetMethodID(Class_jclass, "getDeclaredField",
                                                      "(Ljava/lang/String;)Ljava/lang/reflect/Field;");
    jstring currentActivityThread_jstring = env->NewStringUTF("currentActivityThread");
    //下面的第二个参数不能省略
    jobject currentActivityThreadMethod = env->CallObjectMethod(ActivityThread_class,
                                                                getDeclaredMethod_mid,
                                                                currentActivityThread_jstring,
                                                                nullptr);
    env->CallVoidMethod(currentActivityThreadMethod, setAccessible_mid, true);
    //Object invoke(Object obj, Object... args)
    jmethodID invoke_mid = env->GetMethodID(Method_jclass, "invoke",
                                            "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
    //下面的参数不能省略
    jobject currentActivityThreadObj = env->CallObjectMethod(currentActivityThreadMethod,
                                                             invoke_mid, nullptr, nullptr);
    /*      Field mPackagesField = ActivityThreadClazz.getDeclaredField("mPackages");
            mPackagesField.setAccessible(true);
            ArrayMap mPackagesObj = (ArrayMap) mPackagesField.get(activityThreadObj);
            */
    jstring mPackagesField_jstring = env->NewStringUTF("mPackages");
    jobject mPackagesField = env->CallObjectMethod(ActivityThread_class, getDeclaredField_mid,
                                                   mPackagesField_jstring);
    jclass Field_jclass = env->FindClass("java/lang/reflect/Field");
    env->CallVoidMethod(mPackagesField, setAccessible_mid, true);
    jmethodID get_Method = env->GetMethodID(Field_jclass, "get",
                                            "(Ljava/lang/Object;)Ljava/lang/Object;");
    jobject mPackagesObj = env->CallObjectMethod(mPackagesField, get_Method,
                                                 currentActivityThreadObj);
    jclass ArrayMap_jclass = env->FindClass("android/util/ArrayMap");
    jmethodID ArrayMap_get_mid = env->GetMethodID(ArrayMap_jclass, "get",
                                                  "(Ljava/lang/Object;)Ljava/lang/Object;");
    jclass Context_jclass = env->FindClass("android/content/Context");
    jmethodID getPackageName_mid = env->GetMethodID(Context_jclass, "getPackageName",
                                                    "()Ljava/lang/String;");
    jobject packagename = env->CallObjectMethod(thiz, getPackageName_mid);
   
 /*WeakReference wr = (WeakReference) mPackagesObj.get(this.getPackageName());
            Object loadedApkObj = wr.get();
            Class LoadedApkClazz = classloader.loadClass("android.app.LoadedApk");
            Field mClassLoaderField = LoadedApkClazz.getDeclaredField("mClassLoader");
            mClassLoaderField.setAccessible(true);
            mClassLoaderField.set(loadedApkObj, classloader);
            */
    jobject wr = env->CallObjectMethod(mPackagesObj, ArrayMap_get_mid, packagename);
    jclass WeakReference_jclass = env->FindClass("java/lang/ref/WeakReference");
    jmethodID WeakReference_get_mid = env->GetMethodID(WeakReference_jclass, "get",
                                                       "()Ljava/lang/Object;");
    jobject loadedApkObj = env->CallObjectMethod(wr, WeakReference_get_mid);
    jobject LoadedApkClazz = env->CallObjectMethod(classloader, loadClass_mid,
                                                   env->NewStringUTF("android.app.LoadedApk"));
    jobject mClassLoaderField = env->CallObjectMethod(LoadedApkClazz, getDeclaredField_mid,
                                                      env->NewStringUTF("mClassLoader"));
    env->CallVoidMethod(mClassLoaderField, setAccessible_mid, true);
    jmethodID set_mid = env->GetMethodID(Field_jclass, "set",
                                         "(Ljava/lang/Object;Ljava/lang/Object;)V");
    env->CallVoidMethod(mClassLoaderField, set_mid, loadedApkObj, classloader);
}

②另一种是不用,我们其实要明白一点:在ndk开发中是没有反射的概念的!

这部分的思路是几行几行地去理解代码究竟在干了什么,明确最终要调用的函数及其参数,不用一句句地利用反射去写,而是一种“前瞻式”翻译

extern "C" JNIEXPORT void JNICALL
Java_com_kanxue_loaddex_MainActivity_JNI_1replaceClassloader(
        JNIEnv *env,
        jobject thiz,jobject classloader){
    //第1行,Class<?> ActivityThreadClazz = classloader.loadClass("android.app.ActivityThread");
    jclass Activi_cls = env->FindClass("android/app/ActivityThread");
    if (Activi_cls != nullptr){ __android_log_print(4,"kanxue->homework", "Activi_cls is not null" );}
    //第2行,Method currentActivityThreadMethod = ActivityThreadClazz.getDeclaredMethod("currentActivityThread");
    //第3行,currentActivityThreadMethod.setAccessible(true);
    //第4行,Object activityThreadObj = currentActivityThreadMethod.invoke(null); 这句说明null后面无参数,且有NuLL是静态的
    //其实是调用一个private static函数
    jmethodID currentActivityThread_mid=env->GetStaticMethodID(Activi_cls,"currentActivityThread",
                                                     "()Landroid/app/ActivityThread;");
    jobject activityThreadObj = env->CallStaticObjectMethod(Activi_cls,currentActivityThread_mid);
    if (activityThreadObj != nullptr){ __android_log_print(4,"kanxue->homework", "activityThreadObj is not null" );}
    
    //第5行,Field mPackagesField = ActivityThreadClazz.getDeclaredField("mPackages");
    //第6行,mPackagesField.setAccessible(true);
    //第7行,ArrayMap mPackagesObj = (ArrayMap) mPackagesField.get(activityThreadObj);
    //其实,这是在取private 非静态域的mPackages属性值,有实例化对象activityThreadObj
    jfieldID mPackages_fid = env->GetFieldID(Activi_cls,"mPackages","Landroid/util/ArrayMap;");
    jobject mPackages_obj= env->GetObjectField(activityThreadObj,mPackages_fid);
    if (mPackages_obj != nullptr){ __android_log_print(4,"kanxue->homework", "mPackages_obj is not null" );}
    //第8行,WeakReference wr = (WeakReference) mPackagesObj.get(this.getPackageName());
    //其中, public String getPackageName() 位于 android.content.ContextWrapper中
    jclass ContextWrapper_cls = env->FindClass("android/content/ContextWrapper");
    jmethodID getPackageName_mid = env->GetMethodID(ContextWrapper_cls,"getPackageName",
                                                    "()Ljava/lang/String;");
    jobject PackageName = (env->CallObjectMethod(thiz, getPackageName_mid));
    const char* content_ptr = env->GetStringUTFChars(static_cast<jstring>(PackageName),nullptr) ;
    if (PackageName != nullptr){ __android_log_print(4,"kanxue->homework", "PackageName is %s, ok!",content_ptr );}
    //觉得这里的get是个函数   public V get(Object key)   尝试过(Ljava/lang/Object;)LV 错了
    //调用类实例对象mPackages(android.util.ArrayMap类型)的非静态成员方法get()函数
    jclass ArrayMap_cls = env->FindClass("android/util/ArrayMap");
    jmethodID Array_get = env->GetMethodID(ArrayMap_cls, "get","(Ljava/lang/Object;)Ljava/lang/Object;");
    if (Array_get != nullptr){ __android_log_print(4,"kanxue->homework", "get_mid is not null");}
    jobject wr = env->CallNonvirtualObjectMethod(mPackages_obj,ArrayMap_cls,Array_get,PackageName);
    /*jobject wr= env->GetObjectField(PackageName, reinterpret_cast<jfieldID>(mPackages_obj));*/
    if (wr != nullptr){ __android_log_print(4,"kanxue->homework", "wr is not null" );}
    
    //第9行, Object loadedApkObj = wr.get();
    //其中,我们首先需要获取类java.lang.ref.WeakReference的非静态成员方法get的调用id
    //错误写法:jobject loadedApkObj = env->GetObjectField(classloader, reinterpret_cast<jfieldID>(wr));【服了我自己,当时在想什么】
    //获取LoadedApk类对象实例的弱引用(WeakReference<LoadedApk>),弱引用的位置 :java/lang/ref/WeakReference
    jclass myWeakReference = env->FindClass("java/lang/ref/WeakReference");
    jmethodID WeakReference_get = env->GetMethodID( myWeakReference, "get", "()Ljava/lang/Object;");
    jobject loadedApkObj = env->CallObjectMethod(wr,WeakReference_get);
    if (loadedApkObj != nullptr){ __android_log_print(4,"kanxue->homework", "loadedApkObj is not null" );}
    
    //第10行, Class LoadedApkClazz = classloader.loadClass("android.app.LoadedApk");
    //第11行,Field mClassLoaderField = LoadedApkClazz.getDeclaredField("mClassLoader");
    //第12行,mClassLoaderField.setAccessible(true);
    //第13行,mClassLoaderField.set(loadedApkObj, classloader);
    //其实,这是在设置private 非静态的mClassLoader的属性值
    //首先我们需要获取类android.app.LoadedApk的非静态私有成员mClassLoader的fid
    jclass LoadedApk_cls = env->FindClass("android/app/LoadedApk");
    if (LoadedApk_cls != nullptr){ __android_log_print(4,"kanxue->homework", "LoadedApk_cls is not null" );}
    jfieldID mClassLoader_fid = env->GetFieldID(LoadedApk_cls,"mClassLoader","Ljava/lang/ClassLoader;");
    env->SetObjectField(loadedApkObj,mClassLoader_fid,classloader);
}

从以上第二部分的代码可以看出,如果我把注释和log打印信息全部删除,只保留精简部分,那么代码长度是远小于第一种方法的。

成功打印

16ad0f7610b59dae653343401daa432b.png

0x03 经验之谈

我在转化时遇到了大大小小的bug,我都是一步步打印log来观察正确与否的。

NDK开发当中有很多个地方需要加深学习,我的路还有很长,与君共勉!如果有幸能帮助到一些人,那我深感欣慰。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值