首页
论坛
课程
招聘
[原创]android JNI静态注册和动态注册
2021-9-28 20:46 5408

[原创]android JNI静态注册和动态注册

2021-9-28 20:46
5408

android JNI静态注册和动态注册

这几天在摸android的题目, 在XCTF里面做了一个ONLOAD来动态注册native的题目, 但是本身ida的export能够找到对应native函数名. 想找找ONLOAD注册方法和直接调用的区别, 就仔细去翻了一些相关的帖子.
这个OnLoad注册了一个名为CheckFlag的方法.

 

native方法分为静态注册和动态注册两种.(我的环境是java16)
因为注册的过程不太好调, 可能会有问题, 如果有错误请师傅们提出来, 我好及时改进.

静态注册

java的每个方法都会有一个Method对象, 而这个Method对象有两个成员:nativeFunc和insns.

 

当java构建这个native方法时, 会将该方法对应的Method对象的nativeFunc成员设置为dvmResolveNativeMethod, dvmResolveNativeMethod方法会按照方法的名称去查找对应的C方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
void dvmResolveNativeMethod(const u4* args, JValue* pResult,
    const Method* method, Thread* self)
{
    ClassObject* clazz = method->clazz;
 
    /*
     * If this is a static method, it could be called before the class
     * has been initialized.
     */
    if (dvmIsStaticMethod(method)) {
        if (!dvmIsClassInitialized(clazz) && !dvmInitClass(clazz)) {
            assert(dvmCheckException(dvmThreadSelf()));
            return;
        }
    } else {
        assert(dvmIsClassInitialized(clazz) ||
               dvmIsClassInitializing(clazz));
    }
 
    /* start with our internal-native methods */
    DalvikNativeFunc infunc = dvmLookupInternalNativeMethod(method);
    if (infunc != NULL) {
        /* resolution always gets the same answer, so no race here */
        IF_LOGVV() {
            char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
            LOGVV("+++ resolved native %s.%s %s, invoking",
                clazz->descriptor, method->name, desc);
            free(desc);
        }
        if (dvmIsSynchronizedMethod(method)) {
            ALOGE("ERROR: internal-native can't be declared 'synchronized'");
            ALOGE("Failing on %s.%s", method->clazz->descriptor, method->name);
            dvmAbort();     // harsh, but this is VM-internal problem
        }
        DalvikBridgeFunc dfunc = (DalvikBridgeFunc) infunc;
        dvmSetNativeFunc((Method*) method, dfunc, NULL);
        dfunc(args, pResult, method, self);
        return;
    }
 
    /* now scan any DLLs we have loaded for JNI signatures */
    void* func = lookupSharedLibMethod(method);
    if (func != NULL) {
        /* found it, point it at the JNI bridge and then call it */
        dvmUseJNIBridge((Method*) method, func);
        (*method->nativeFunc)(args, pResult, method, self);
        return;
    }
 
    IF_ALOGW() {
        char* desc = dexProtoCopyMethodDescriptor(&method->prototype);
        ALOGW("No implementation found for native %s.%s:%s",
            clazz->descriptor, method->name, desc);
        free(desc);
    }
 
    dvmThrowUnsatisfiedLinkError("Native method not found", method);
}

lookupSharedLibMethod函数用来查找SO层里面的方法, 再看下lookupSharedLibMethod代码

1
2
3
4
5
6
7
8
9
static void* lookupSharedLibMethod(const Method* method)
{
    if (gDvm.nativeLibs == NULL) {
        ALOGE("Unexpected init state: nativeLibs not ready");
        dvmAbort();
    }
    return (void*) dvmHashForeach(gDvm.nativeLibs, findMethodInLib,
        (void*) method);
}

dvmHashForeach的注释是

1
2
3
4
5
6
7
8
9
int dvmHashForeach(HashTable* pHashTable, HashForeachFunc func, void* arg);
/*
 * Execute "func" on every entry in the hash table.
 *
 * If "func" returns 1 detach the entry from the hash table. Does not invoke
 * the "free" function.
 *
 * Returning values other than 0 or 1 from "func" will abort the routine.
 */

findMethodInLib:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
static int findMethodInLib(void* vlib, void* vmethod)
{
    const SharedLib* pLib = (const SharedLib*) vlib;
    const Method* meth = (const Method*) vmethod;
    char* preMangleCM = NULL;
    char* mangleCM = NULL;
    char* mangleSig = NULL;
    char* mangleCMSig = NULL;
    void* func = NULL;
    int len;
    if (meth->clazz->classLoader != pLib->classLoader) {
        ALOGV("+++ not scanning '%s' for '%s' (wrong CL)",
            pLib->pathName, meth->name);
        return 0;
    } else
        ALOGV("+++ scanning '%s' for '%s'", pLib->pathName, meth->name);
    /*
     * First, we try it without the signature.
     */
    preMangleCM =
        createJniNameString(meth->clazz->descriptor, meth->name, &len);
    if (preMangleCM == NULL)
        goto bail;
    mangleCM = mangleString(preMangleCM, len);
    if (mangleCM == NULL)
        goto bail;
    ALOGV("+++ calling dlsym(%s)", mangleCM);
    func = dlsym(pLib->handle, mangleCM);
    if (func == NULL) {
        mangleSig =
            createMangledSignature(&meth->prototype);
        if (mangleSig == NULL)
            goto bail;
        mangleCMSig = (char*) malloc(strlen(mangleCM) + strlen(mangleSig) +3);
        if (mangleCMSig == NULL)
            goto bail;
        sprintf(mangleCMSig, "%s__%s", mangleCM, mangleSig);
        ALOGV("+++ calling dlsym(%s)", mangleCMSig);
        func = dlsym(pLib->handle, mangleCMSig);
        if (func != NULL) {
            ALOGV("Found '%s' with dlsym", mangleCMSig);
        }
    } else {
        ALOGV("Found '%s' with dlsym", mangleCM);
    }
bail:
    free(preMangleCM);
    free(mangleCM);
    free(mangleSig);
    free(mangleCMSig);
    return (int) func;
}

最终通过调用dlsym来查找so文件里面的方法, 这和windows的GetProAddress()很类似了.
在dvmResolveNativeMethod找到对应的方法后, 会对native方法对应的方法对象进行再一次赋值,
nativeFunc = dvmCallJNIMethod, insns = 找到的so中的方法地址.
dvmCallJNIMethod方法是一个bridge方法, 这个方法的作用就是调用insns对应的方法
当native方法对象构建好了之后, nativeFunc中已经有值, 这样就会直接执行nativeFunc方法

动态注册

动态注册是将RegisterNatives放在OnLoad方法里面, 在加载库的时候, 会通过dlsym调用Onload方法.

 

System.java:

1
2
3
public static void loadLibrary(String libname) {
    Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}

Runtime.class:

1
2
3
4
5
6
7
8
9
10
11
void loadLibrary0(Class<?> fromClass, String libname) {
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkLink(libname);
    }
    if (libname.indexOf((int)File.separatorChar) != -1) {
        throw new UnsatisfiedLinkError(
            "Directory separator should not appear in library name: " + libname);
    }
    ClassLoader.loadLibrary(fromClass, libname);
}

ClassLoader.loadLibrary:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static NativeLibrary loadLibrary(Class<?> fromClass, String name) {
    ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader();
    if (loader == null) {
        NativeLibrary nl = BootLoader.getNativeLibraries().loadLibrary(fromClass, name);
        if (nl != null) {
            return nl;
        }
        throw new UnsatisfiedLinkError("no " + name +
                " in system library path: " + StaticProperty.sunBootLibraryPath());
    }
 
    NativeLibraries libs = loader.libraries;
    // First load from the file returned from ClassLoader::findLibrary, if found.
    String libfilename = loader.findLibrary(name);
    if (libfilename != null) {
        File libfile = new File(libfilename);
        if (!libfile.isAbsolute()) {
            throw new UnsatisfiedLinkError(
                    "ClassLoader.findLibrary failed to return an absolute path: " + libfilename);
        }
        NativeLibrary nl = libs.loadLibrary(fromClass, libfile);
        if (nl != null) {
            return nl;
        }
        throw new UnsatisfiedLinkError("Can't load " + libfilename);
    }
    // Then load from system library path and java library path
    NativeLibrary nl = libs.loadLibrary(fromClass, name);
    if (nl != null) {
        return nl;
    }
 
    // Oops, it failed
    throw new UnsatisfiedLinkError("no " + name +
            " in java.library.path: " + StaticProperty.javaLibraryPath());
}

libs.loadLibrary():

1
2
3
4
5
6
7
8
9
public NativeLibrary loadLibrary(Class<?> fromClass, String name) {
    assert name.indexOf(File.separatorChar) < 0;
 
    NativeLibrary lib = findFromPaths(LibraryPaths.SYS_PATHS, fromClass, name);
    if (lib == null && searchJavaLibraryPath) {
        lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, name);
    }
    return lib;
}

findFromPaths():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private NativeLibrary findFromPaths(String[] paths, Class<?> fromClass, String name) {
    for (String path : paths) {
        File libfile = new File(path, System.mapLibraryName(name));
        NativeLibrary nl = loadLibrary(fromClass, libfile);
        if (nl != null) {
            return nl;
        }
        libfile = ClassLoaderHelper.mapAlternativeName(libfile);
        if (libfile != null) {
            nl = loadLibrary(fromClass, libfile);
            if (nl != null) {
                return nl;
            }
        }
    }
    return null;
}

loadLibrary():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public NativeLibrary loadLibrary(Class<?> fromClass, File file) {
    // Check to see if we're attempting to access a static library
    String name = findBuiltinLib(file.getName());
    boolean isBuiltin = (name != null);
    if (!isBuiltin) {
        name = AccessController.doPrivileged(new PrivilegedAction<>() {
            public String run() {
                try {
                    return file.exists() ? file.getCanonicalPath() : null;
                } catch (IOException e) {
                    return null;
                }
            }
        });
        if (name == null) {
            return null;
        }
    }
    return loadLibrary(fromClass, name, isBuiltin);
}
1
2
3
4
5
6
7
8
private NativeLibrary loadLibrary(Class<?> fromClass, String name, boolean isBuiltin){
    ...
     try {
         if (!lib.open()) {
             return null;    // fail to open the native library
     }
     ...
}
1
2
3
4
5
6
7
boolean open() {
            if (handle != 0) {
                throw new InternalError("Native library " + name + " has been loaded");
            }
 
            return load(this, name, isBuiltin, isJNI);
        }

然后发现居然最后调用了一个native :

1
private static native boolean load(NativeLibraryImpl impl, String name, boolean isBuiltin, boolean isJNI);

NativeLibraries.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
Java_jdk_internal_loader_NativeLibraries_load
  (JNIEnv *env, jobject this, jobject lib, jstring name, jboolean isBuiltin, jboolean isJNI)
{
    const char *cname;
    jint jniVersion;
    jthrowable cause;
    void * handle;
    jboolean loaded = JNI_FALSE;
 
    if (!initIDs(env))
        return JNI_FALSE;
 
    cname = JNU_GetStringPlatformChars(env, name, 0);
    if (cname == 0)
        return JNI_FALSE;
    handle = isBuiltin ? procHandle : JVM_LoadLibrary(cname);
    if (isJNI) {
        if (handle) {
            JNI_OnLoad_t JNI_OnLoad;
            JNI_OnLoad = (JNI_OnLoad_t)findJniFunction(env, handle,
                                                       isBuiltin ? cname : NULL,
                                                       JNI_TRUE);
            if (JNI_OnLoad) {
                JavaVM *jvm;
                (*env)->GetJavaVM(env, &jvm);
                jniVersion = (*JNI_OnLoad)(jvm, NULL); //应该是执行JNI_Onload的地方
            } else {
                jniVersion = 0x00010001;
            }
 
            cause = (*env)->ExceptionOccurred(env);
            if (cause) {
                (*env)->ExceptionClear(env);
                (*env)->Throw(env, cause);
                if (!isBuiltin) {
                    JVM_UnloadLibrary(handle);
                }
                goto done;
            }
 
            if (!JVM_IsSupportedJNIVersion(jniVersion) ||
                (isBuiltin && jniVersion < JNI_VERSION_1_8)) {
                char msg[256];
                jio_snprintf(msg, sizeof(msg),
                             "unsupported JNI version 0x%08X required by %s",
                             jniVersion, cname);
                JNU_ThrowByName(env, "java/lang/UnsatisfiedLinkError", msg);
                if (!isBuiltin) {
                    JVM_UnloadLibrary(handle);
                }
                goto done;
            }
            (*env)->SetIntField(env, lib, jniVersionID, jniVersion);
        } else {
            cause = (*env)->ExceptionOccurred(env);
            if (cause) {
                (*env)->ExceptionClear(env);
                (*env)->SetLongField(env, lib, handleID, (jlong)0);
                (*env)->Throw(env, cause);
            }
            goto done;
        }
    }
    (*env)->SetLongField(env, lib, handleID, ptr_to_jlong(handle));
    loaded = JNI_TRUE;
 
 done:
    JNU_ReleaseStringPlatformChars(env, name, cname);
    return loaded;
}

findJniFunction:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
static void *findJniFunction(JNIEnv *env, void *handle,
                                    const char *cname, jboolean isLoad) {
    const char *onLoadSymbols[] = JNI_ONLOAD_SYMBOLS;
    const char *onUnloadSymbols[] = JNI_ONUNLOAD_SYMBOLS;
    const char **syms;
    int symsLen;
    void *entryName = NULL;
    char *jniFunctionName;
    int i;
    size_t len;
 
    // Check for JNI_On(Un)Load<_libname> function
    if (isLoad) {
        syms = onLoadSymbols;
        symsLen = sizeof(onLoadSymbols) / sizeof(char *);
    } else {
        syms = onUnloadSymbols;
        symsLen = sizeof(onUnloadSymbols) / sizeof(char *);
    }
    for (i = 0; i < symsLen; i++) {
        // cname + sym + '_' + '\0'
        if ((len = (cname != NULL ? strlen(cname) : 0) + strlen(syms[i]) + 2) >
            FILENAME_MAX) {
            goto done;
        }
        jniFunctionName = malloc(len);
        if (jniFunctionName == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            goto done;
        }
        buildJniFunctionName(syms[i], cname, jniFunctionName); //jniEntryName = _JNI_OnLoad
        entryName = JVM_FindLibraryEntry(handle, jniFunctionName);
        free(jniFunctionName);
        if(entryName) {
            break;
        }
    }
 
 done:
    return entryName;
}

buildJniFunctionName:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void buildJniFunctionName(const char *sym, const char *cname,
                          char *jniEntryName) {
    if (cname != NULL) {
        char *p = strrchr(sym, '@');
        if (p != NULL && p != sym) {
            // sym == _JNI_OnLoad@8
            strncpy(jniEntryName, sym, (p - sym));
            jniEntryName[(p-sym)] = '\0';
            // jniEntryName == _JNI_OnLoad
            strcat(jniEntryName, "_");
            strcat(jniEntryName, cname);
            strcat(jniEntryName, p);
            //jniEntryName == _JNI_OnLoad_cname@8
        } else {
            strcpy(jniEntryName, sym);
            strcat(jniEntryName, "_");
            strcat(jniEntryName, cname);
        }
    } else {
        strcpy(jniEntryName, sym);
    }
    return;
}

JVM_FindLibraryEntry中调用了dlsym()

1
2
3
4
5
6
7
JNIEXPORT void* JNICALL JVM_FindLibraryEntry(void* handle, const char* name) {
    ...
    else {
        return dlsym(handle, name);
    }
    ...
}

然后在Onload里面写RegisterNatives方法就可以进行方法注册:

 

RegisterNatives的第二个参数为JNINativeMethod类型, 这个类型的定义为:

1
2
3
4
5
typedef struct {
    const char* name;  //Java层native函数名
    const char* signature;
    void*       fnPtr; //so层对应的方法指针
} JNINativeMethod;

最后对native方法对应的方法对象进行赋值, 和静态注册类似

 

nativeFunc = dvmCallJNIMethod, insns = 找到的so中的方法地址.

 

最后可以总结一些, 在静态注册的时候,感觉和windows的iat加载有点类似, 一个是通过GetProAddress()查找DLL的EAT来实现,一个是通过通过dlsym寻找动态符号表.动态注册的时候, 是事先对结构体中的函数指针进行赋值, 通过RegisterNatives进行注册.当注册好了之后, native方法对应的方法对象的成员nativeFunc==dvmCallJNIMethod, 就不会在再设置为dvmResolveNativeMethod, 而是直接执行vmCallJNIMethod, 调用insns的方法.


【公告】欢迎大家踊跃尝试高研班11月试题,挑战自己的极限!

最后于 2021-9-28 20:48 被margina1编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回