NDK进阶

NDK与JNI基础

什么是JNI?:
JNI,全称为Java Native Interface,即Java本地接口,JNI是Java调用Native语言的一种特性.通过JNI可以使得Java与C/C++交互.
JNIEnv是什么?
JNIEnv是一个线程相关的结构体,该结构体代表了Java在本线程的执行环境.
JNIEnv与JavaVM的区别:

JNIEnv: JavaVM在线程中的代码,每个线程都有一个,JNI可能有非常多个JNIEnv.

1
2
3
4
5
6
7
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

JavaVM: 是Java虚拟机在JNI层的代表,JNI全局仅仅只有一个.

1
2
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved);
JNIEnv的作用:

调用Java函数: JNIEnv代表了Java执行环境,能够使用JNIEnv调用Java中的代码.

操作Java函数: Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象.

JNI接口追踪

jnitrace

https://github.com/chame1eon/jnitrace

1
2
3
4
5
#安装
pip install jnitrace

#使用
jnitrace -l libnative-lib.so com.example.myapplication

hook_art.js

https://github.com/lasting-yang/frida_hook_libart

1
2
#使用
frida -U --no-pause -f package_name -l hook_art.js

Frida查看Native层调用栈

1
2
3
4
#不是很准确
console.log('CCCryptorCreate called from:\n' +
        Thread.backtrace(this.context, Backtracer.ACCURATE)
        .map(DebugSymbol.fromAddress).join('\n') + '\n');

System.load函数追踪

Java层通过以下代码进行so的加载.

1
2
3
4
5
6
static {
   System.loadLibrary("luomd5");
}
public static void loadLibrary(String libname) {
   Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
}

接下来查看Android源码,跟踪Runtime类的loadLibrary0函数.

 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
//源码跟踪
//http://aospxref.com/android-8.1.0_r81/xref/libcore/ojluni/src/main/java/java/lang/Runtime.java

loadLibrary0(String libname, ClassLoader classLoader)
->doLoad
->nativeLoad
->JVM_NativeLoad
->LoadNativeLibrary
->OpenNativeLibrary

//http://aospxref.com/android-8.1.0_r81/xref/system/core/libnativeloader/native_loader.cpp?r=&mo=19529&fi=523#523
void *OpenNativeLibrary(JNIEnv *env,
                        int32_t target_sdk_version,
                        const char *path,
                        jobject class_loader,
                        jstring library_path,
                        bool *needs_native_bridge,
                        std::string *error_msg)
    if (class_loader == nullptr)
    {
        *needs_native_bridge = false;
        return dlopen(path, RTLD_NOW);

        //---

        void *handle = android_dlopen_ext(path, RTLD_NOW, &extinfo);
        if (handle == nullptr)
        {
            *error_msg = dlerror();
        }
    }
}

通过以上代码,我们可以知道Android系统有两种方式可以加载so文件.

  • dlopen
  • android_dlopen_ext

所以说,我们可以通过以下代码Hook上述两个函数,来查看我们想要的so是通过哪种方式加载的,进而执行下一步操作.

 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
function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "dlopen"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                console.log("dlopen:", path);

            }
        },
        onLeave: function(retval) {

        }
    })

    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                console.log("android_dlopen_ext:", path);

            }
        },
        onLeave: function(retval) {

        }
    });
}

setImmediate(hook_dlopen);
1
frida -U -f com.example.luomd5 -l .\LuoHook.js --no-pause

JNIHook

测试Apk

Java层:

 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
//Java层
package com.example.luomd5;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.example.luomd5.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'luomd5' library on application startup.
    static {
        System.loadLibrary("luomd5");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(mdString("Hello XiaLuoHun!"));
    }

    public static native String mdString(String string);
}

Native层:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <jni.h>
#include <string>

#include "md5.h"

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_luomd5_MainActivity_mdString(JNIEnv *env, jclass clazz, jstring string) {
    const char* szString = env->GetStringUTFChars(string, NULL);
    std::string strMd5 = MD5(szString).toStr();

    return  env->NewStringUTF(strMd5.c_str());
}

替换方式

 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
function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "dlopen"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("dlopen:", path);

            }
        },
        onLeave: function (retval) {

        }
    })

    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("android_dlopen_ext:", path);

                if (path.indexOf("libluomd5.so") >= 0) {
                    this.target = true;
                    console.log("found [android_dlopen_ext:]", path);
                }

            }
        },
        onLeave: function (retval) {
            if (this.target) {
                //找到要Hook函数的地址
                var mdString_addr = Module.findExportByName("libluomd5.so", "Java_com_example_luomd5_MainActivity_mdString")
                console.log("mdString_addr: ", mdString_addr)

                var md5string = new NativeFunction(mdString_addr, "pointer", ["pointer", "pointer", "pointer"]);
                Interceptor.replace(mdString_addr, new NativeCallback((env, jclass, jstring) => {
                    //打印原参数
                    console.log(Java.vm.tryGetEnv().getStringUtfChars(jstring, null).readCString())
                    //构造新参数
                    var newJSTRING = Java.vm.tryGetEnv().newStringUtf('XiaLuoHun 1234');
                    //调用
                    var retval = md5string(env, jclass, newJSTRING);
                    //打印返回值
                    console.log("retval: ", Java.vm.tryGetEnv().getStringUtfChars(retval, null).readCString());
                    return retval

                }, "pointer", ["pointer", "pointer", "pointer"]))

            }

        }
    });
}

setImmediate(hook_dlopen);

Hook方式

也可以通过下述代码进行Hook,查看Native层函数的参数和返回值.

 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
function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "dlopen"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("dlopen:", path);

            }
        },
        onLeave: function (retval) {

        }
    })

    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var pathptr = args[0];
            if (pathptr !== undefined && pathptr != null) {
                var path = ptr(pathptr).readCString();
                //console.log("android_dlopen_ext:", path);

                if (path.indexOf("libluomd5.so") >= 0) {
                    this.target = true;
                    console.log("found [android_dlopen_ext:]", path);
                }

            }
        },
        onLeave: function (retval) {
            if (this.target) {
                var mdString_addr = Module.findExportByName("libluomd5.so", "Java_com_example_luomd5_MainActivity_mdString")
                console.log("mdString_addr: ", mdString_addr)
                Interceptor.attach(mdString_addr, {
                    onEnter: function (args) {
                        var env = Java.vm.getEnv();
                        console.log("arg2 jstring: ", env.getStringUtfChars(args[2], NULL).readCString())

                    },
                    onLeave: function (retval) {
                        var env = Java.vm.getEnv();
                        console.log("retval jstring: ", env.getStringUtfChars(retval, NULL).readCString())
                    }
                })

            }

        }
    });
}

setImmediate(hook_dlopen);

动态注册

动态注册的JNI函数在native层实现的函数名称不定,并且不一定要求相应函数是导出类型,比静态注册更安全.

通过以下函数即可完成动态注册.

1
2
3
4
//第一个参数clazz, native函数所在的类,可通过FindClass这个JNI函数获取(将类名的"."符号换成"/")
//第二个参数methods, 是一个数组,其中包含函数的一些签名信息以及对应在native层的函数指针
//第三个参数nMethods, 是methods数组的数量
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)

示例代码

Java层:

 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
package com.example.luomd5;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.example.luomd5.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'luomd5' library on application startup.
    static {
        System.loadLibrary("luomd5");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(mdString("Hello XiaLuoHun!"));
    }

    public static native String mdString(String string);
}

Native层:

 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
#include <jni.h>
#include <string>

#include "md5.h"

#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))

jstring LuoNativeFunc(JNIEnv *env, jclass clazz, jstring string) {
    const char *szString = env->GetStringUTFChars(string, NULL);
    std::string strMd5 = MD5(szString).toStr();

    return env->NewStringUTF(strMd5.c_str());
}

static JNINativeMethod method_table[] = {
        {"mdString", "(Ljava/lang/String;)Ljava/lang/String;", (void *) LuoNativeFunc},
};

static int registerMethods(JNIEnv *env, const char *className,
                           JNINativeMethod *gMethods, int numMethods) {
    jclass clazz = env->FindClass(className);
    if (clazz == nullptr) {
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env = nullptr;
    if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
        return JNI_ERR;
    }

    // 注册native方法
    if (!registerMethods(env, "com/example/luomd5/MainActivity", method_table,
                         NELEM(method_table))) {
        return JNI_ERR;
    }

    return JNI_VERSION_1_6;
}

源码分析

动态注册的主要函数是RegisterNatives,下面从Android源码中分析该函数流程.

 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
//http://aospxref.com/android-8.1.0_r81/xref/art/runtime/jni_internal.cc#2241
RegisterNatives(JNIEnv* env,jclass java_class,const JNINativeMethod* methods,jint method_count){
	//---
	for (jint i = 0; i < method_count; ++i) {
      const char* name = methods[i].name;
      const char* sig = methods[i].signature;
      const void* fnPtr = methods[i].fnPtr;
      if (UNLIKELY(name == nullptr)) {
        ReportInvalidJNINativeMethod(soa, c.Get(), "method name", i);
        return JNI_ERR;
      } else if (UNLIKELY(sig == nullptr)) {
        ReportInvalidJNINativeMethod(soa, c.Get(), "method signature", i);
        return JNI_ERR;
      } else if (UNLIKELY(fnPtr == nullptr)) {
        ReportInvalidJNINativeMethod(soa, c.Get(), "native function", i);
        return JNI_ERR;
    }
	//---
	ArtMethod* m = nullptr;  
	m = FindMethod<...>(current_class.Ptr(), name, sig);
	//---
	const void* final_function_ptr = m->RegisterNative(fnPtr, is_fast);
}

-> RegisterNative(fnPtr, is_fast);
-> SetEntryPointFromJni(new_native_method);
-> SetEntryPointFromJniPtrSize(entrypoint, kRuntimePointerSize);
-> SetDataPtrSize(entrypoint, pointer_size);
-> SetNativePointer(DataOffset(pointer_size), data, pointer_size);

SetNativePointer里面的代码,意思就是将上述找到的Native函数指针fnPtr赋值给下方ArtMethod类中的ptr_sized_fields结构体中的data字段.

ArtMethod结构体:

 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
//http://aospxref.com/android-8.1.0_r81/xref/art/runtime/art_method.h
class ArtMethod{
 protected:
  // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
  // The class we are a part of.
  GcRoot<mirror::Class> declaring_class_;

  // Access flags; low 16 bits are defined by spec.
  // Getting and setting this flag needs to be atomic when concurrency is
  // possible, e.g. after this method's class is linked. Such as when setting
  // verifier flags and single-implementation flag.
  std::atomic<std::uint32_t> access_flags_;

  /* Dex file fields. The defining dex file is available via declaring_class_->dex_cache_ */

  // Offset to the CodeItem.
  uint32_t dex_code_item_offset_;

  // Index into method_ids of the dex file associated with this method.
  uint32_t dex_method_index_;

  /* End of dex file fields. */

  // Entry within a dispatch table for this method. For static/direct methods the index is into
  // the declaringClass.directMethods, for virtual methods the vtable and for interface methods the
  // ifTable.
  uint16_t method_index_;

  // The hotness we measure for this method. Managed by the interpreter. Not atomic, as we allow
  // missing increments: if the method is hot, we will see it eventually.
  uint16_t hotness_count_;

  // Fake padding field gets inserted here.

  // Must be the last fields in the method.
  struct PtrSizedFields {
    // Short cuts to declaring_class_->dex_cache_ member for fast compiled code access.
    mirror::MethodDexCacheType* dex_cache_resolved_methods_;

    // Pointer to JNI function registered to this method, or a function to resolve the JNI function,
    // or the profiling data for non-native methods, or an ImtConflictTable, or the
    // single-implementation of an abstract/interface method.
    void* data_;

    // Method dispatch from quick compiled code invokes this pointer which may cause bridging into
    // the interpreter.
    void* entry_point_from_quick_compiled_code_;
  } ptr_sized_fields_;
};

二次注册

Native层Hook思路:通过调用RegisterNatives方法注册函数,那我们可以再次调用RegisterNatives方法进行注册,覆盖掉先前注册的函数,从而实现Hook.

一种通用超简单的Android Java Native方法Hook

fake-linker-main

调用JNI中的方法

Frida

准备一个so文件:

Java层:

 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
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.example.luodst.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'luodst' library on application startup.
    static {
        System.loadLibrary("luodst");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }
    
    public native String stringFromJNI();

    public native int LuoAdd(int n1, int n2);
    public static native String mdString(String string);
}

Native层:

 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
#include <jni.h>
#include <string>

#include "md5.h"

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_luodst_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_luodst_MainActivity_LuoAdd(JNIEnv *env, jobject thiz, jint n1, jint n2) {
    return n1 + n2;
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_luodst_MainActivity_mdString(JNIEnv *env, jclass clazz, jstring string) {
    const char *szString = env->GetStringUTFChars(string, NULL);
    std::string strMd5 = MD5(szString).toStr();

    return env->NewStringUTF(strMd5.c_str());
}

开始调用:

下述代码,用来示例抽取原Apk的so库,用Frida直接来调用其中的方法.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function main(){
    Java.perform(function(){
        var module_luomd5 = Module.load("/data/app/libluodst.so");
        var mdString_address = module_luomd5.findExportByName("Java_com_example_luodst_MainActivity_mdString");
        console.log("mdString_address: ", mdString_address)
    
        var mdString = new NativeFunction(mdString_address, "pointer", ["pointer", "pointer", "pointer"]);
        var env = Java.vm.getEnv();

        //mdString是个静态Native函数
        var result = mdString(env, NULL, env.newStringUtf("Hello XiaLuoHun!"))
        console.log("result: ", env.getStringUtfChars(result, NULL).readCString())
    })
}

setImmediate(main);
1
frida -U com.android.settings -l .\LuoHook.js

Unidbg

Unidbg是一个基于Unicorn的逆向工具,可以直接调用Android和iOS中的so文件.

https://github.com/zhkl0228/unidbg

测试

将Unidbg整个项目下载下来,用IDEA打开,运行其中一个测试项目,若能正常输出,则配置正确.

Unidbg测试

调用so中的静态方法

准备一个so文件:

Java层:

 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
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.TextUtils;
import android.widget.TextView;

import com.example.luodst.databinding.ActivityMainBinding;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'luodst' library on application startup.
    static {
        System.loadLibrary("luodst");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;

        String strSample = "Hello XiaLuoHun!";
        tv.setText("mdString: " + mdString(strSample));
    }

    public static native String mdString(String string);
}

Native层:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <jni.h>
#include <string>

#include "md5.h"

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_luodst_MainActivity_mdString(JNIEnv *env, jclass clazz, jstring string) {
    const char *szString = env->GetStringUTFChars(string, NULL);
    std::string strMd5 = MD5(szString).toStr();

    return env->NewStringUTF(strMd5.c_str());
}

模拟调用:

 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
package com.example.luodst;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class MainActivity extends AbstractJni {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        MainActivity mainActivity = new MainActivity();
        System.out.println("load offset=" + (System.currentTimeMillis() - start) + "ms");
        mainActivity.crack();
    }

    private final AndroidEmulator emulator;
    private final VM vm;

    private MainActivity() {
        //1.创建Android模拟器实例
        emulator = AndroidEmulatorBuilder
                .for64Bit()
                .addBackendFactory(new DynarmicFactory(true))
                .build();

        //2.获取操作内存的接口
        Memory memory = emulator.getMemory();
        //3.设置Android SDK 版本
        LibraryResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);
        //4.创建虚拟机
        vm = emulator.createDalvikVM();
        //5.是否打印日志
        vm.setVerbose(false);
        //6.设置jni
        vm.setJni(this);
        //7.加载目标so文件
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/example/luodst/files/libluodst.so"), false);
        //8.调用JNI_OnLoad
        dm.callJNI_OnLoad(emulator);
    }

    private void crack() {
        //9.执行JNI方法

        //示例
        DvmClass dvmClass = vm.resolveClass("com/example/luodst/MainActivity");
        DvmObject result1 = dvmClass.callStaticJniMethodObject(emulator, "mdString(Ljava/lang/String;)Ljava/lang/String;", "Hello XiaLuoHun!");
        System.out.println("result1 => " + result1.getValue());
    }
}

调用so中的实例方法-无java方法

准备一个so文件:

Java层:

 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
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.TextUtils;
import android.widget.TextView;

import com.example.luodst.databinding.ActivityMainBinding;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'luodst' library on application startup.
    static {
        System.loadLibrary("luodst");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;

        tv.setText("LuoAdd: " + LuoAdd(10, 20));
    }

    public native int LuoAdd(int n1, int n2);
}

Native:

1
2
3
4
5
6
7
8
#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_luodst_MainActivity_LuoAdd(JNIEnv *env, jobject thiz, jint n1, jint n2) {
    return n1 + n2;
}

模拟调用:

 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
package com.example.luodst;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class MainActivity extends AbstractJni {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        MainActivity mainActivity = new MainActivity();
        System.out.println("load offset=" + (System.currentTimeMillis() - start) + "ms");
        mainActivity.crack();
    }

    private final AndroidEmulator emulator;
    private final VM vm;

    private MainActivity() {
        //1.创建Android模拟器实例
        emulator = AndroidEmulatorBuilder
                .for64Bit()
                .addBackendFactory(new DynarmicFactory(true))
                .build();

        //2.获取操作内存的接口
        Memory memory = emulator.getMemory();
        //3.设置Android SDK 版本
        LibraryResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);
        //4.创建虚拟机
        vm = emulator.createDalvikVM();
        //5.是否打印日志
        vm.setVerbose(false);
        //6.设置jni
        vm.setJni(this);
        //7.加载目标so文件
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/example/luodst/files/libluodst.so"), false);
        //8.调用JNI_OnLoad
        dm.callJNI_OnLoad(emulator);
    }

    private void crack() {
        //9.执行JNI方法

        //示例
        DvmObject<?> obj1 = ProxyDvmObject.createObject(vm, this);
        int result = obj1.callJniMethodInt(emulator, "LuoAdd(II)I", 10, 20);
        System.out.println("result => " + result);
    }
}
注意

在Unidbg中新建的测试类,要和so中的Native方法所在的类要一致.

如:apk中的com.example.luodst.MainActivity中有一个Native方法为LuoAdd,

现在要在Unidbg中加载so文件,模拟调用Native方法LuoAdd,那么首先要在Unidbg中新建一个类为com.example.luodst.MainActivity,然后写代码模拟调用so中的Native方法.

上述Unidbg中调用so中的Native方法代码如下:

1
2
3
DvmObject<?> obj1 = ProxyDvmObject.createObject(vm, this);
int result = obj1.callJniMethodInt(emulator, "LuoAdd(II)I", 10, 20);
System.out.println("result => " + result);

可以看到上述调用,代码中只写了LuoAdd(II)I,但是so中的LuoAdd方法导出函数名为Java_com_example_luodst_MainActivity_LuoAdd

Unidbg会根据当前类名全称和上述方法签名进行拼接.

调用so中的实例方法注意点

调用so中的实例方法-有Java方法

准备一个so文件:

Java层:

 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
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.TextUtils;
import android.widget.TextView;

import com.example.luodst.databinding.ActivityMainBinding;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'luodst' library on application startup.
    static {
        System.loadLibrary("luodst");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;

        String strSample = "Hello XiaLuoHun!";
        tv.setText("Refmd5: " + Refmd5(strSample));
    }

    public String Javamd5(String string) {
        if (TextUtils.isEmpty(string)) {
            return "";
        }
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
            byte[] bytes = md5.digest(string.getBytes());
            StringBuilder result = new StringBuilder();
            for (byte b : bytes) {
                String temp = Integer.toHexString(b & 0xff);
                if (temp.length() == 1) {
                    temp = "0" + temp;
                }
                result.append(temp);
            }
            return result.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

    public native String Refmd5(String string);
}

Native层:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_luodst_MainActivity_Refmd5(JNIEnv *env, jobject thiz, jstring string) {
    jclass clsMainActivity = env->FindClass("com/example/luodst/MainActivity");
    jmethodID Javamd5 = env->GetMethodID(clsMainActivity, "Javamd5",
                                         "(Ljava/lang/String;)Ljava/lang/String;");
    jstring strMd5 = (jstring) env->CallObjectMethod(thiz, Javamd5, string);
    return strMd5;
}

模拟调用:

上述样例so中的Native方法调用了apk中的Java方法,直接在Unidbg中模拟调用是不行的,会报以下错误.

调用so中的实例方法2

需要在Unidbg的AbstractJni.java文件中补Java方法或者重载callObjectMethodV函数.

 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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
package com.example.luodst;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;

import java.io.File;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MainActivity extends AbstractJni {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        MainActivity mainActivity = new MainActivity();
        System.out.println("load offset=" + (System.currentTimeMillis() - start) + "ms");
        mainActivity.crack();
    }

    private final AndroidEmulator emulator;
    private final VM vm;

    private MainActivity() {
        //1.创建Android模拟器实例
        emulator = AndroidEmulatorBuilder
                .for64Bit()
                .addBackendFactory(new DynarmicFactory(true))
                .build();

        //2.获取操作内存的接口
        Memory memory = emulator.getMemory();
        //3.设置Android SDK 版本
        LibraryResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);
        //4.创建虚拟机
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/java/com/example/luodst/files/luoDst.apk"));
        //5.是否打印日志
        vm.setVerbose(false);
        //6.设置jni
        vm.setJni(this);
        //7.加载目标so文件
        DalvikModule dm = vm.loadLibrary("luodst", false);
        //8.调用JNI_OnLoad
        dm.callJNI_OnLoad(emulator);
    }

    public String Javamd5(String string) {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
            byte[] bytes = md5.digest(string.getBytes());
            StringBuilder result = new StringBuilder();
            for (byte b : bytes) {
                String temp = Integer.toHexString(b & 0xff);
                if (temp.length() == 1) {
                    temp = "0" + temp;
                }
                result.append(temp);
            }
            return result.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature) {
            case "com/example/luodst/MainActivity->Javamd5(Ljava/lang/String;)Ljava/lang/String;":
                StringObject jstring = vaList.getObjectArg(0);
                String result = Javamd5(jstring.getValue());
                return new StringObject(vm, result);
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

    private void crack() {
        //9.执行JNI方法

        //示例
        DvmClass dvmClass = vm.resolveClass("com/example/luodst/MainActivity");
        DvmObject result = dvmClass.newObject(null).callJniMethodObject(emulator, "Refmd5(Ljava/lang/String;)Ljava/lang/String;", "Hello XiaLuoHun!");
        System.out.println("result3 => " + result.getValue());
    }
}

Hook初窥

准备一个so文件:

Java层:

 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
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

import com.example.luodst.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'luodst' library on application startup.
    static {
        System.loadLibrary("luodst");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());

        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(mdString("Hello XiaLuoHun!"));
    }

    public static native String mdString(String string);
}

Native层:

 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
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <jni.h>
#include <stdio.h>
#include <string.h>
#include<android/log.h>
#include <unistd.h>
#include <bitset>
#include <fstream>

#include "md5.h"

#define TAG "[LuoHun]: " // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__) // 定义LOGD类型
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__) // 定义LOGI类型
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__) // 定义LOGE类型
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__) // 定义LOGF类型

bool function_check_tracerPID() {
    bool b = false ;
    int pid = getpid();
    std::string file_name = "/proc/pid/status";
    std::string line;
    file_name.replace(file_name.find("pid"), 3, std::to_string(pid));
    LOGE("replace file name => %s", file_name.c_str());
    std::ifstream myfile(file_name, std::ios::in);
    if (myfile.is_open()) {
        while (getline(myfile, line)) {
            size_t TracerPid_pos = line.find("TracerPid");
            if (TracerPid_pos == 0) {
                line = line.substr(line.find(":") + 1);
                LOGE("file line => %s", line.c_str());
                if (std::stoi(line.c_str()) != 0) {
                    LOGE("trace pid => %s, i want to exit.", line.c_str());
                    b = true ;
//                    kill(pid, 9);
                    break;
                }
            }
        }
        myfile.close();
    }
    return b ;
}

bool system_getproperty_check() {
    char man[256], mod[156];
    /* A length 0 value indicates that the property is not defined */
    int lman = __system_property_get("ro.product.manufacturer", man);
    int lmod = __system_property_get("ro.product.model", mod);
    int len = lman + lmod;
    char *pname = NULL;
    if (len > 0) {
        pname = static_cast<char *>(malloc(len + 2));
        snprintf(pname, len + 2, "%s/%s", lman > 0 ? man : "", lmod > 0 ? mod : "");
    }

    bool b = false;
    if(strstr(pname,"Nexus")  || strstr(pname,"OnePlus") )
        b=true;
    LOGE("[LuoHun device]: [%s] result is => %d\n", pname ? pname : "N/A",b);
    return b;
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_luodst_MainActivity_mdString(JNIEnv *env, jclass clazz, jstring string) {
    const char *szString = env->GetStringUTFChars(string, NULL);
    std::string strMd5 = MD5(szString).toStr();

    jclass buildClazz = env->FindClass("android/os/Build");
    jfieldID FINGERPRINT = env->GetStaticFieldID(buildClazz,"FINGERPRINT","Ljava/lang/String;");
    jstring fingerprint = static_cast<jstring>(env->GetStaticObjectField(buildClazz, FINGERPRINT));

    if( function_check_tracerPID() ||  system_getproperty_check() || strstr( env->GetStringUTFChars(fingerprint, JNI_FALSE),"aosp")){
        kill(getpid(),9);
/*        int a,b,c;
        a=1;
        b=0;
        c=a/b;*/
    }

    return env->NewStringUTF(strMd5.c_str());
}

上述Narive层添加了反调试代码,如下:

1
2
3
4
5
6
7
if( function_check_tracerPID() ||  system_getproperty_check() || strstr( env->GetStringUTFChars(fingerprint, JNI_FALSE),"aosp")){
        kill(getpid(),9);
/*      int a,b,c;
        a=1;
        b=0;
        c=a/b;*/
    }

模拟调用:

首先需要在AbstractJni.java文件中补方法.

1
2
case "android/os/Build->FINGERPRINT:Ljava/lang/String;":
	return new StringObject(vm, "LuoHun");

直接在Unidbg中调用so的mdString方法是不行的,因为so层有反调试,从上面Native层代码可以看到function_check_tracerPID这个函数检测TracerPid,这在Unidbg中是没用的,system_getproperty_check这个函数是检测手机型号,Unidbg这里是"Nexus",这个函数会返回真,进而执行kill函数.下面我们在Unidbg中Hook这个函数,让他返回False.

 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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package com.example.luodst;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.LibraryResolver;
import com.github.unidbg.Module;
import com.github.unidbg.arm.HookStatus;
import com.github.unidbg.arm.backend.DynarmicFactory;
import com.github.unidbg.hook.HookContext;
import com.github.unidbg.hook.ReplaceCallback;
import com.github.unidbg.hook.hookzz.Dobby;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.jni.ProxyDvmObject;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class MainActivity extends AbstractJni {

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        MainActivity mainActivity = new MainActivity();
        System.out.println("load offset=" + (System.currentTimeMillis() - start) + "ms");
        mainActivity.crack();
    }

    private final AndroidEmulator emulator;
    private final VM vm;
    private final DvmClass dvmClass;
    private final Module module;

    private MainActivity() {
        //1.创建Android模拟器实例
        emulator = AndroidEmulatorBuilder
                .for32Bit()
                .addBackendFactory(new DynarmicFactory(true))
                .build();

        //2.获取操作内存的接口
        Memory memory = emulator.getMemory();
        //3.设置Android SDK 版本
        LibraryResolver resolver = new AndroidResolver(23);
        memory.setLibraryResolver(resolver);
        //4.创建虚拟机
        vm = emulator.createDalvikVM();
        //5.是否打印日志
        vm.setVerbose(false);
        //6.设置jni
        vm.setJni(this);
        //7.加载目标so文件
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/resources/example_binaries/armeabi-v7a/libluodst.so"), false);

        //开始Hook
        module = dm.getModule();
        Dobby dobby = Dobby.getInstance(emulator);
        //使用ida pro查看导出方法名,尝试hook
        dobby.replace(module.findSymbolByName("_Z24system_getproperty_checkv"), new ReplaceCallback() { // 使用Dobby inline hook导出函数
            @Override
            //contextk可以拿到参数,originFunction是原方法的地址
            public HookStatus onCall(Emulator<?> emulator, HookContext context, long originFunction) {
                //System.out.println("create_thread_check_traceid.onCall function address => 0x" + Long.toHexString(originFunction));
                System.out.println("_Z24system_getproperty_checkv");
                //return HookStatus.RET(emulator, originFunction);
                //return null;
                return HookStatus.LR(emulator, 0);
            }

            @Override
            public void postCall(Emulator<?> emulator, HookContext context) {
                System.out.println(" calling _Z14function_checkv .... return false");

               //context.getIntArg(0);
            }
        }, false);


        //8.调用JNI_OnLoad
        dm.callJNI_OnLoad(emulator);
    }

    private void crack() {
        //9.执行JNI方法

        //示例
        DvmObject<?> obj2 = ProxyDvmObject.createObject(vm, this);
        DvmObject result = obj2.callJniMethodObject(emulator, "mdString(Ljava/lang/String;)Ljava/lang/String;", "Hello XiaLuoHun!");
        System.out.println("result => " + result.getValue());
    }

}

反射

可以从下述源码进行学习

GravityBox

Java层:

 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
package com.example.luomd5;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.text.TextUtils;
import android.widget.TextView;

import com.example.luomd5.databinding.ActivityMainBinding;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'luomd5' library on application startup.
    static {
        System.loadLibrary("luomd5");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;

        String strSample = "XiaLuoHun";
        String str = "mdString: " + mdString(strSample) + "\n" +
                "Javamd5: " + Javamd5(strSample) + "\n" +
                "Refmd5: " + Refmd5(strSample) + "\n" +
                "Refmd5Sec: " + Refmd5Sec(strSample);

        tv.setText(str);
    }

    public String Javamd5(String string) {
        if (TextUtils.isEmpty(string)) {
            return "";
        }
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
            byte[] bytes = md5.digest(string.getBytes());
            StringBuilder result = new StringBuilder();
            for (byte b : bytes) {
                String temp = Integer.toHexString(b & 0xff);
                if (temp.length() == 1) {
                    temp = "0" + temp;
                }
                result.append(temp);
            }
            return result.toString();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

    public static native String mdString(String string);

    public native String Refmd5(String string);

    public native String Refmd5Sec(String string);
}

Native层:

 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
#include <jni.h>
#include <string>

#include "md5.h"

__attribute__ ((visibility("hidden")))
jstring JNICALL LuoDynamicNative(JNIEnv *env,
                                 jobject /* this */) {

    std::string hello = "Hello from C++ LuoHun";
    return env->NewStringUTF(hello.c_str());
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_luomd5_MainActivity_mdString(JNIEnv *env, jclass clazz, jstring string) {
    const char *szString = env->GetStringUTFChars(string, NULL);
    std::string strMd5 = MD5(szString).toStr();

    return env->NewStringUTF(strMd5.c_str());
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_luomd5_MainActivity_Refmd5(JNIEnv *env, jobject thiz, jstring string) {
    //md5 = MessageDigest.getInstance("MD5");
    jclass clsMessageDigest = env->FindClass("java/security/MessageDigest");
    jmethodID getInstance = env->GetStaticMethodID(clsMessageDigest, "getInstance",
                                                   "(Ljava/lang/String;)Ljava/security/MessageDigest;");
    jstring strMD5 = env->NewStringUTF("MD5");
    jobject objMD5 = env->CallStaticObjectMethod(clsMessageDigest, getInstance, strMD5);

    //byte[] bytes = md5.digest(string.getBytes());
    jclass clsString = env->FindClass("java/lang/String");
    jmethodID getBytes = env->GetMethodID(clsString, "getBytes", "()[B");
    jbyteArray jbAry = (jbyteArray) env->CallObjectMethod(string, getBytes);
    jmethodID digest = env->GetMethodID(clsMessageDigest, "digest", "([B)[B");
    jbyteArray jbAryResult = (jbyteArray) env->CallObjectMethod(objMD5, digest, jbAry);

    char *cmd5 = reinterpret_cast<char *>(env->GetByteArrayElements(jbAryResult, 0));
    int i;
    char dest[32] = {0};
    for (i = 0; i < 16; i++) {
        sprintf(dest + i * 2, "%02x", (unsigned int) cmd5[i]);
    }
    return env->NewStringUTF(dest);
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_luomd5_MainActivity_Refmd5Sec(JNIEnv *env, jobject thiz, jstring string) {
    jclass clsMainActivity = env->FindClass("com/example/luomd5/MainActivity");
    jmethodID Javamd5 = env->GetMethodID(clsMainActivity, "Javamd5",
                                         "(Ljava/lang/String;)Ljava/lang/String;");
    jstring strMd5 = (jstring) env->CallObjectMethod(thiz, Javamd5, string);
    return strMd5;
}

OnCreate函数Native化

Java层:

 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
package com.example.luodst;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;

import com.example.luodst.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'luodst' library on application startup.
    static {
        System.loadLibrary("luodst");
    }

    private ActivityMainBinding binding;

/*    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());

        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }*/

    protected native void onCreate(Bundle savedInstanceState);

    /**
     * A native method that is implemented by the 'luodst' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

Native层:

 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
#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_luodst_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_luodst_MainActivity_onCreate(JNIEnv *env, jobject thiz,
                                              jobject saved_instance_state) {
    //super.onCreate(savedInstanceState);
    //方式一
    jclass clsAppCompatActivity1 = env->FindClass("androidx/appcompat/app/AppCompatActivity");
    //方式二
    jclass clsMainActivity = env->GetObjectClass(thiz);
    jclass clsAppCompatActivity2 = env->GetSuperclass(clsMainActivity);

    jmethodID onCreate = env->GetMethodID(clsAppCompatActivity1, "onCreate",
                                          "(Landroid/os/Bundle;)V");
    //调用父类方法
    env->CallNonvirtualVoidMethod(thiz, clsAppCompatActivity1, onCreate, saved_instance_state);

    //binding = ActivityMainBinding.inflate(getLayoutInflater());
    jmethodID getLayoutInflater = env->GetMethodID(clsMainActivity, "getLayoutInflater",
                                                   "()Landroid/view/LayoutInflater;");
    jclass clsActivityMainBinding = env->FindClass(
            "com/example/luodst/databinding/ActivityMainBinding");
    jmethodID inflate = env->GetStaticMethodID(clsActivityMainBinding, "inflate",
                                               "(Landroid/view/LayoutInflater;)Lcom/example/luodst/databinding/ActivityMainBinding;");
    jobject jbinding = env->CallStaticObjectMethod(clsActivityMainBinding, inflate,
                                                   env->CallObjectMethod(thiz, getLayoutInflater));

    //setContentView(binding.getRoot());
    jmethodID getRoot = env->GetMethodID(clsActivityMainBinding, "getRoot",
                                         "()Landroid/view/View;");
    jmethodID setContentView = env->GetMethodID(clsMainActivity, "setContentView",
                                                "(Landroid/view/View;)V");
    env->CallVoidMethod(thiz, setContentView, env->CallObjectMethod(jbinding, getRoot));

    //TextView tv = binding.sampleText;
    jfieldID sampleText = env->GetFieldID(clsActivityMainBinding, "sampleText",
                                          "Landroid/widget/TextView;");
    jobject tv = env->GetObjectField(jbinding, sampleText);

    //tv.setText(stringFromJNI());
    jclass clsTextView = env->FindClass("android/widget/TextView");
    jmethodID setText = env->GetMethodID(clsTextView, "setText", "(Ljava/lang/CharSequence;)V");
    jmethodID stringFromJNI = env->GetMethodID(clsMainActivity, "stringFromJNI",
                                               "()Ljava/lang/String;");
    env->CallVoidMethod(tv, setText, env->CallObjectMethod(thiz, stringFromJNI));
}

参考链接

Android JNI(一)——NDK与JNI基础

Unidbg文档更新(一)

Unidbg文档慢更(二)

Java高级特性——反射

Android JNI学习(四)——JNI的常用方法的中文API


相关内容

0%