美文网首页
JNI 开发,JNI开发

JNI 开发,JNI开发

作者: that_is_this | 来源:发表于2018-03-24 15:45 被阅读100次

    0. LOG 打印

    首先:Android.mk 中添加 llog

    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_LDLIBS += -llog
    LOCAL_MODULE := demo
    LOCAL_SRC_FILES := demo.c
    include $(BUILD_SHARED_LIBRARY)
    

    再:

    include <android/log.h>

    然后:
    __android_log_print(ANDROID_LOG_INFO,"Wooo","--------1--------");

    #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
    #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
    #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)
    

    调用:
    LOGI("gMethods %s,%s,%p\n ",gMethods[0].name,gMethods[0].signature,gMethods[0].fnPtr);

    1. 声明

    导入

    #include <jni.h>
    #include <android/log.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include "mycommom.h"
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <android/asset_manager.h>
    #include <android/asset_manager_jni.h>
    
    #include "packer.h"
    #include <jni.h>
    #include <dlfcn.h>
    #include <android/log.h>
    #include <errno.h>
    #include <assert.h>
    #include <sys/system_properties.h>
    #include <string>
    #include <cstdlib>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <dirent.h>
    #include <fcntl.h>
    #include <sys/mman.h>
    #include <vector>
    #include <bits/unique_ptr.h>
    

    基本类型

    jclass myLoadedApk;     // 声明类
    jfieldID  LoadedApk_mClassLoader;   // 声明 FieldID
    jmethodID Application_onCreate;     // 声明 MethodID
    jint sdk_int;       // 声明 int
    jobject mObj;   // 声明 Object
    JNINativeMethod *dvm_dalvik_system_DexFile;     // 声明本地方法
    JValue* pResult;    // 声明的序列化
    

    还有基本类型:

    jbyte、jboolean、jchar、jshort、jint、jlong、jfloat、jdouble

    void 标识无类型

    对应的数组类型有:

    jstring、jobjectArray、jbooleanArray、jbyteArray、jcharArray、jshortArray、jintArray、jlongArray、jfloatArray、jdoubleArray、jthrowable

    以上类型对应的 smali 及对应的签名:

    char[]      [C
    float[]     [F
    double[]    [D
    long[]      [J
    String[]    [Ljava/lang/String;
    Object[]    [Ljava/lang/Object;
    
    剩余的签名:
    boolean     Z
    short   S
    byte    B
    int     I
    void    V
    
    函数示例:
    int fun1()      对应签名()I
    int fun1(int i) 对应签名(I)I
    

    2. JNINativeMethod 声明

    这个对应这是一个结构体:

    typedef struct {
    const char* name;
    const char* signature;
    void* fnPtr;
    } JNINativeMethod;
    

    第一个变量name是Java中函数的名字。
    第二个变量signature,用字符串是描述了函数的参数和返回值 ((II)V)
    第三个变量fnPtr是函数指针,指向C函数。

    • 示例一(这个是自己声明的本地方法):
    void native_attachContextBaseContext(JNIEnv *env, jclass clazz,jobject ctx);
    void native_onCreate(JNIEnv *env, jclass clazz);
    
    static JNINativeMethod method_table[] = {
        { "attachBaseContext", "(Landroid/content/Context;)V", (void*)native_attachContextBaseContext},     // 声明本地方法,在类前面声明
        { "onCreate","()V",(void*)native_onCreate}, // 观察三个参数
    };
    

    以上返回的是 method_table 数组,元素是 JNINativeMethod

    • 示例二(这里是调用第三方的 so 文件里的函数):
    void (*openDexFile)(const u4* args,union  JValue* pResult); // 声明方法类型,JValue 序列化声明
    
    void *ldvm = (void*) dlopen("libdvm.so", 1);    // 这个通过 dlopen 是找到 so 文件
    JNINativeMethod* dvm_dalvik_system_DexFile = (JNINativeMethod*) dlsym(ldvm,"dvm_dalvik_system_DexFile");    // 这个是通过 dlsym 找到方法指针
    if(0 == lookup(dvm_dalvik_system_DexFile, "openDexFile", "([B)I", &openDexFile))    // lookup 函数寻找函数,并赋予到 openDexFile 指针
    {
        openDexFile = NULL;
        LOGI("method does not found ");
        return ;
    }
    else
    {
        LOGI("openDexFile method found ! HAVE_BIG_ENDIAN");      
    }
    
    char* dexbase = (char*)mmap(0, dexLen, 3, 2, handle, 0);    // mmap 映射,handle 为文件描述符
    char* arr;
    arr=(char*)malloc(16+dexLen);
    ArrayObject *ao=(ArrayObject*)arr;
    ao->length=dexLen;
    memcpy(arr+16,dexbase,dexLen);
    munmap(dexbase, dexLen);    // 内存映射解除
    u4 args[] = { (u4) ao };
    union JValue pResult;       // 声明传入参数
    jint result;            // 返回值
    if(openDexFile != NULL)
    {
        openDexFile(args,&pResult);     // 函数调用
        result = (jint) pResult.l;      // 获取返回值
    }
    
    int lookup(JNINativeMethod *table, const char *name, const char *sig,void (**fnPtrout)(u4 const *, union JValue *)) 
    {
        int i = 0;
        while (table[i].name != NULL)   // 这个方法名字不等于 name
        {
            LOGI("lookup %d %s" ,i,table[i].name);
            if ((strcmp(name, table[i].name) == 0)
                && (strcmp(sig, table[i].signature) == 0)) 
            {
                *fnPtrout = table[i].fnPtr;     // 赋予函数指针
                return 1;
            }
            i++;
        }
        return 0;
    }
    

    3. 方法注册

    首先声明方法,然后调用函数注册

    JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);      // 在 JNI_OnLoad 内开始注册
    #define JNIREG_CLASS "com/example/unpack/StubApplication"   // 声明类
    # define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))      // 用于算出方法数组包含参数
    
    JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
    {
        JNIEnv* env = 0;
        jint result = -1;
        if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {  // 从当前运行环境中取出版本字符串,判断 jni 合法性
            return result;
        }
        int status=register_ndk_load(env);
        if(!status)
        {
            LOGI("register call failed");
        }
        return JNI_VERSION_1_4;
    }
    
    static JNINativeMethod method_table[] = {       // 方法数组
        { "attachBaseContext", "(Landroid/content/Context;)V", (void*)native_attachContextBaseContext},
        { "onCreate","()V",(void*)native_onCreate},
    };
    
    int register_ndk_load(JNIEnv *env)
    {
        
        return registerNativeMethods(env, JNIREG_CLASS, method_table, NELEM(method_table)); // 传入参数,环境、类名、方法数组、方法数量
    }
    
    static int registerNativeMethods(JNIEnv* env, const char* className,JNINativeMethod* gMethods, int numMethods)
    {
        jclass clazz;
        clazz = (*env)->FindClass(env, className);
        if (clazz == 0) {
            return JNI_FALSE;
        }
        LOGI("gMethods  %s,%s,%p\n ",gMethods[0].name,gMethods[0].signature,gMethods[0].fnPtr);
        if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {    // 方法注册
            return JNI_FALSE;
        }
        return JNI_TRUE;
    }
    

    4. 寻找类

    1. 函数调用,返回类对象到 myContextWrapper 指针内
    if( !myFindClass(env,&Build_version,"android/os/Build$VERSION"))
    {
        LOGI("ERROR:Build$VERSION");
        return;
    }
    
    jfieldID fieldID= ((*env)->GetStaticFieldID)(env, Build_version, "SDK_INT", "I");   // 函数调用,获取 field
    sdk_int=(*env)->GetStaticIntField(env,Build_version,fieldID);       // 获取 version_id
    LOGI("sdk_int %d\n",sdk_int); 
    
    实现函数:
    jclass myFindClass(JNIEnv *env,jclass* ptr,char* name)
    {
         jobject g_cls_string;
         jclass clazz = (*env)->FindClass(env,name);
         if(clazz)
         {
            g_cls_string = (*env)->NewGlobalRef(env,clazz);
            *ptr=g_cls_string;
            return g_cls_string;
         }
         else
         {
              return 0;
         }
    }
    
    1. 根据 object 获取类
    jclass v19 = (*env)->GetObjectClass(env, application_obj);  //获取 application_obj 为类对象
    
    1. 获取父类
      jclass BaseDexClassLoader = env->GetSuperclass(PathClassLoader);

    2. 新建一个对象
      由方法 ID 和 class 对象获取一个新的 obiect 对象

    jstring newapp= (*env)->NewStringUTF(env, "android.app.Application");
    jobject findclass_classz = (*env)->CallObjectMethod(env, classLoader,classLoader_findClass, newapp);
    jmethodID initMethod = (*env)->GetMethodID(env, findclass_classz, "<init>", "()V");
    onCreateObj = (*env)->NewObject(env, findclass_classz, initMethod);
    

    5. 获取 MethodID 并调用

    获取 MethodID
    Application_onCreate = (*env)->GetMethodID(env, myApplication, "onCreate", "()V");
    // myApplication 指的是 Application 类,onCreate 方法名, ()V 方法签名

    获取 StaticMethodID
    currentActivityThread = (*env)->GetStaticMethodID(env, ActivityThread,"currentActivityThread","()Landroid/app/ActivityThread;");
    // ActivityThread 类,currentActivityThread 方法名, ()Landroid/app/ActivityThread; 方法签名

    调用返回值是 int 类型的函数
    int count = (*env)->CallIntMethod(env, v11, arraylist_size);

    调用返回值是 Object 类型的静态函数
    jobject tmp= (*env)->CallStaticObjectMethod(env, mySystem, system_getProperty, vmname);
    // mySystem 指 java/lang/System 类,system_getProperty 指 getProperty(Ljava/lang/String;)Ljava/lang/String; 方法,vmname 为传入的字符串参数

    调用非静态函数
    jobject v9 = (*env)->CallObjectMethod(env, arraymap_mPackages, ArrayMap_get, thePackagename);
    // arraymap_mPackages 为数组,ArrayMap_get 为方法 ID,thePackagename 是 key 值

    调用无返回值函数
    (*env)->CallNonvirtualVoidMethod(env, application_obj, myContextWrapper, ContextWrapper_attachBaseContext,ctx);
    // application_obj 为默认传入的 obj,myContextWrapper 是 "android/content/ContextWrapper" 类,ContextWrapper_attachBaseContext 是 attachBaseContext(Landroid/content/Context;)V 方法,ctx 是传入参数

    调用无返回值函数
    (*env)->CallVoidMethod(env, onCreateObj, Application_onCreate);
    // onCreateObj 是 Application 对象,Application_onCreate 是方法 ID

    6. 获取 FieldID 并设置

    获取一个类内的一个变量 FieldID
    mPackages=(*env)->GetFieldID(env, ActivityThread, "mPackages", "Landroid/util/ArrayMap;");
    // ActivityThread 类对象,mPackages 变量名,Landroid/util/ArrayMap 变量类型、签名

    根据 ID 获取一个类实例内的变量值
    jobject arraymap_mPackages =(*env)->GetObjectField(env, theCurrentActivityThread, mPackages);
    // theCurrentActivityThread 为一个类调用方法返回的对象,mPackages 为 FieldID
    // theCurrentActivityThread 是一个对象

    设置 Field 变量
    (*env)->SetObjectField(env, DexFile, mCookie, art_MarCookie);
    // DexFile 是要设置到的主体对象, mCookie 是对应的 FieldID(对应 DexFile 内的一个属性),art_MarCookie 是要设置到 mCookie 的对象。就等同于:DexFile.mCookie.set(art_MarCookie)
    示例如下:

    DexPathList_Element_dexFile = (*env)->GetFieldID(env,myElement,"dexFile","Ldalvik/system/DexFile;");
    jobject Element = (*env)->GetObjectArrayElement(env, v15, i);
    jobject DexFile = (*env)->GetObjectField(env, Element, DexPathList_Element_dexFile);
    mCookie = (*env)->GetFieldID(env,myDexFile, "mCookie", "I");
    art_MarCookie=(*env)->CallStaticObjectMethod(env, myDexFile, myOpenDexFile, inPath,0,0);
    (*env)->SetObjectField(env, DexFile, mCookie, art_MarCookie);
    

    设置 int
    env->SetIntField(mini_dex_obj, cookie_field, mCookie);

    7. 字符串处理

    格式标准化
    jstring thePackagename=(*env)->NewStringUTF(env,mPackageName);

    将字符串转为 char 数组
    const char* v22 = (*env)->GetStringUTFChars(env, tmp, 0); // tmp 为字符串

    LOGI("------- vmNameStr:%s", v22);
    (*env)->ReleaseStringUTFChars(env, tmp, v22); // 函数通知 JVM 这块内存已经不使用了,你可以清除了

    拼接字符串
    char szPath[260]={0}; // 申请空间
    sprintf(szPath,"/data/data/%s/files/dump.dex",mPackageName); // 字符串输出为 szPath
    // const char* mPackageName; 类型

    设置 vm 的 APKPATH
    setenv("APKPATH", mPackageResourePath, 1); // mPackageResourePath 为路径字符串

    8. 数组的获取处理

    获取一个数组的长度
    int count = (*env)->GetArrayLength(env, v15);
    // v15 是一个数组

    遍历一个数组
    while(i<count)
    {
    jobject Element = (*env)->GetObjectArrayElement(env, v15, i);
    }

    9. 删除占用内存

    env->DeleteLocalRef(DexFileClass); // DexFileClass 是 jclass
    env->DeleteLocalRef(apk); // apk 是 jstring

    10. 一些小功能

    1. 读取 maps 获取信息

    char* get_path_frommaps(const char* pkgName,char* filter1,char* filter2)
    {
    int pid=getpid();
    char map[256]={0};
    sprintf(map,"/proc/%d/maps",pid);
    FILE* fd=fopen(map,"r");
    if(!fd){
        LOGE("[-]open %s failed",map);
        return NULL;
    }
    while(true){
        char line_buffer[256]={0};
        if(!fgets(line_buffer,255,fd))
            break;
        //寻找带有包名和.dex或.odex的行
        if (strstr(line_buffer,pkgName) && (strstr(line_buffer,filter1)|| strstr(line_buffer,filter2))) {
            LOGD("[+]targt line buffer:%s",line_buffer);
            char* p =strchr(line_buffer,'/');
            //获取需要复制的长度
            //line_buffer结尾是一个换行符
            int copy_len=strlen(line_buffer)-(p-(char*)line_buffer)-1;
            memcpy((void*)g_fake_dex_path,p,copy_len);
            LOGD("[+]find dex path:%s from maps",g_fake_dex_path);
            return g_fake_dex_path;
        }
    }
    fclose(fd);
    return NULL;
    }
    

    2. 调用 libart.so 内的方法

    {
    std::string location = "";
    std::string err_msg;
    void* g_ArtHandle=NULL;
    g_ArtHandle=get_lib_handle("libart.so");    // 获取 so 句柄
    
    org_artDexFileOpenMemory22 func = (org_artDexFileOpenMemory22)dlsym(g_ArtHandle, "_ZN3art7DexFile10OpenMemoryEPKhjRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPNS_6MemMapEPKNS_7OatFileEPS9_");
    
    if(!func){
        LOGE("[-]sdk_int:%d dlsym openMemory failed:%s",g_sdk_int,dlerror());
        return NULL;
    }
    const Header* dex_header = reinterpret_cast<const Header*>(base);
    void *value = func((const unsigned char *)base, size, location, dex_header->checksum_, NULL, NULL, &err_msg);       // 方法调用。size 是 dex 的大小
    if(!value){
        LOGE("[-]call artDexFileOpenMemory22 failed");
        return NULL;
    }
    LOGD("[+]openMemory value:%p",value);
    return value;
    }
    
    void* get_lib_handle(const char* lib_path)
    {
        void *handle_art = dlopen(lib_path, RTLD_NOW);
        if (!handle_art) {
            LOGE("[-]get %s handle failed:%s",dlerror());
            return NULL;
        }
        return handle_art;
    }
    

    .h 内声明方法类型

    typedef void* (*org_artDexFileOpenMemory22)(const uint8_t* base, size_t size,const std::string& location, uint32_t location_checksum,void* mem_map, const void*oat_file, std::string* error_msg);
    

    相关文章

      网友评论

          本文标题:JNI 开发,JNI开发

          本文链接:https://www.haomeiwen.com/subject/xoiqcftx.html