安卓逆向-NDK开发基础

下雨天
10月23日发布 /正在检测是否收录...

NDK开发基础

1. 基础

1.1 指定平台

  • 编译apk时可以指定编译哪些平台的so,在app下的build.gradle里配置 ndk 即可,默认不写则是四个平台都编译;
defaultConfig {
    applicationId "com.example.devicecheckfinger"
    minSdk 22
    targetSdk 34
    versionCode 1
    versionName "1.0"
    ndk{
        abiFilters 'armeabi-v7a', 'arm64-v8a'
    }
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

1.2 可选平台

  • 查看自己的cpu支持哪些平台,可以使用Build下的一个api,如下:
Log.d("sana abi support", Arrays.toString(Build.SUPPORTED_ABIS));
// [arm64-v8a, armeabi-v7a, armeabi]

2. jni相关

  • 首先,JNI是Java Native Interface缩写,JNI提供了一套标准API,允许Java代码与本地编译语言(如C/C++)交互;

    • Java调用本地代码:通过native关键字声明方法,由C/C++实现具体逻辑。
    • 本地代码调用Java:C/C++可通过JNI访问Java对象、调用Java方法;
  • 实际上ndk与jni是一回事,写的代码是c或者c++,而不是java的代码了;
  • 在生成的示例文件中,可以发现有些函数前会有 extern "C" 字样,他是什么意思?

    • extern "C"是为了解决C++ 的重载特性,会出现名称粉碎这个特性,C++编译器会把void foo(int)编译为类似_foo_int的符号,而C编译器只会生成 _foo ,若 C++ 直接调用 C 函数,会因符号名不匹配导致链接错误,这么写就是为了消除这种特性;
  • 众所周知,jni函数前两个参数都是固定的,第一个为jnienv,第二个则是jclass或者jobject,第二个参数分别是如下情况:

    • jclass对应的java方法是静态方法,有static修饰符;
    • jobject对应的java方法则是实例方法;
  • JVM虚拟机:JVM是Java虚拟机的简称,是Java字节码的运行环境,JVM标准实现是栈架构虚拟机,执行class文件,采用JIT即时编译,运行时效率不高;
  • ART虚拟机:ART是Android Runtime,是Android系统上的运行环境,ART处理的是dex字节码文件格式;Android早期确实使用JVM(具体是Dalvik虚拟机)来运行应用,但Dalvik采用JIT即时编译,运行时效率不高;2014年安卓4.4开始引入ART作为实验选项,5.0后全面取代Dalvik;ART最大的改变是采用AOT预先编译技术,把字节码提前编译成本地机器码;
  • so加载主要会执行的几个函数:首先是init、init_array、Jni_OnLoad;

3. java反射

资料:https://www.jianshu.com/p/9be58ee20dee
https://www.runoob.com/java/java-reflection.html

3.1 基本概念

  • 在Android开发中,反射(Reflection) 是Java语言的核心特性之一,它允许程序在运行时动态加载类、查看类的信息(比如类名、方法、字段、构造器等)、动态调用对象的方法、动态访问或修改对象的字段;
  • 或者说:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制;

3.2 重要api

  • 与Java反射相关的类如下:
类名用途
Class类代表类的实体,在运行的Java应用程序中表示类和接口
Field类代表类的成员变量(成员变量也称为类的属性)
Method类代表类的方法
Constructor类代表类的构造方法
  • Class代表类的实体,在运行的Java应用程序中表示类和接口,在这个类中提供了很多有用的方法,这里对他们简单的分类介绍;
  • 获得类相关的方法:
方法用途
asSubclass(Class clazz)把传递的类的对象转换成代表其子类的对象
Cast把对象转换成代表类或是接口的对象
getClassLoader()获得类的加载器
getClasses()返回一个数组,数组中包含该类中所有公共类和接口类的对象
getDeclaredClasses()返回一个数组,数组中包含该类中所有类和接口类的对象
forName(String className)根据类名返回类的对象
getName()获得类的完整路径名字
newInstance()创建类的实例
getPackage()获得类的包
getSimpleName()获得类的名字
getSuperclass()获得当前类继承的父类的名字
getInterfaces()获得当前类实现的类或是接口
  • 获得类中属性相关的方法:
方法用途
getField(String name)获得某个公有的属性对象
getFields()获得所有公有的属性对象
getDeclaredField(String name)获得某个属性对象
getDeclaredFields()获得所有属性对象
  • 获得类中注解相关的方法:
方法用途
getAnnotation(Class annotationClass)返回该类中与参数类型匹配的公有注解对象
getAnnotations()返回该类所有的公有注解对象
getDeclaredAnnotation(Class annotationClass)返回该类中与参数类型匹配的所有注解对象
getDeclaredAnnotations()返回该类所有的注解对象
  • 获得类中构造器相关的方法:
方法用途
getConstructor(Class...<?> parameterTypes)获得该类中与参数类型匹配的公有构造方法
getConstructors()获得该类的所有公有构造方法
getDeclaredConstructor(Class...<?> parameterTypes)获得该类中与参数类型匹配的构造方法
getDeclaredConstructors()获得该类所有构造方法
  • 获得类中方法相关的方法:
方法用途
getMethod(String name, Class...<?> parameterTypes)获得该类某个公有的方法
getMethods()获得该类所有公有的方法
getDeclaredMethod(String name, Class...<?> parameterTypes)获得该类某个方法
getDeclaredMethods()获得该类所有方法
  • 类中其他重要的方法:
方法用途
isAnnotation()如果是注解类型则返回true
isAnnotationPresent(Class<? extends Annotation> annotationClass)如果是指定类型注解类型则返回true
isAnonymousClass()如果是匿名类则返回true
isArray()如果是一个数组类则返回true
isEnum()如果是枚举类则返回true
isInstance(Object obj)如果obj是该类的实例则返回true
isInterface()如果是接口类则返回true
isLocalClass()如果是局部类则返回true
isMemberClass()如果是内部类则返回true
  • Field代表类的成员变量(成员变量也称为类的属性):
方法用途
equals(Object obj)属性与obj相等则返回true
get(Object obj)获得obj中对应的属性值
set(Object obj, Object value)设置obj中对应属性值
  • Methond代表类的方法:
方法用途
invoke(Object obj, Object... args)传递object对象及参数调用该对象对应的方法
  • Constructor代表类的构造方法:
方法用途
newInstance(Object... initargs)根据传递的参数创建类的对象
  • setAccessible方法,用于获得权限,是一个很重要的点;

4. 基础开发

  • 目标:读写SD卡文件;创建项目后需要先在静态资源里申请权限,这里分别是sd卡的读、写、操作权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
  • 读取sd卡的文件内容:
extern "C"
JNIEXPORT jstring JNICALL
Java_com_sana_kanxuendk_MainActivity_readSDcardFile(JNIEnv *env, jobject thiz, jstring path) {

    jstring jstring_ret = env->NewStringUTF("null");
    char * tmp = (char *)env->GetStringUTFChars(jstring_ret, 0);

    char* filePath = (char*)env->GetStringUTFChars(path, 0);
    FILE *fp;
    fp = fopen(filePath, "r");
    if (fp == NULL){
        LOGI("fp == NULL, %s", filePath);
        return jstring_ret;
    }

    char buff[1024];
    while (fgets(buff, 1024, fp) != NULL){
        LOGI("fgets :%s", buff);
    }

    env->ReleaseStringChars(jstring_ret, reinterpret_cast<const jchar *>(tmp));
    jstring_ret = env->NewStringUTF(buff);
    return jstring_ret;

}
  • 目标:练习反射,Student类如下:
package com.sana.kanxuendk;

import android.util.Log;

public class Student {
    private String studentName;
    private int studentAge = 22;

    public Student(String studentName, int studentAge) {
        this.studentName = studentName;
        this.studentAge = studentAge;
    }

    public Student(String studentName) {
        this.studentName = studentName;
    }

    public String study(int flag) {
        Log.i("sanaTag", "name:" + studentName + ",age:" + studentAge + ",flag:" + flag);
        getAge();
        return "studyRet";
    }

    public static int calcLength(String param) {
        return param.length();
    }

    private int getAge() {
        return studentAge;
    }
}
  • getDeclaredMethods:获得该类所有方法,但不包含构造方法:
public static void func_reflection(){
    Class clz = null;
    try {
        // 获取该类
        clz = Class.forName("com.sana.kanxuendk.Student");
        // getDeclaredMethods 获取该类的所有方法 但不包括构造方法
        Method[] declaredMethods = clz.getDeclaredMethods();
        for (Method method : declaredMethods) {
            Log.d("sanaTag", "method:" + method.getName());
            Log.d("sanaTag", "method getReturnType:" + method.getReturnType().getSimpleName());
        }
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    }
}
  • getDeclaredConstructors:获取该类所有构造方法:
// 获取该类所有构造方法
Constructor[] declaredConstructors = clz.getDeclaredConstructors();
for (Constructor constructor : declaredConstructors) {
    Log.d("sanaTag", "constructor:" + constructor.getName());
    Log.d("sanaTag", "constructor toGenericString:" + constructor.toGenericString());
}
  • 调用方法:
public static void func_reflection2() {
    // 获取类
    Class clz = Student.class;
    try {
        // 获取方法
        Method method_study = clz.getMethod("study", new Class[]{int.class});
        // 获取构造方法
        Constructor constructor = clz.getConstructor(new Class[]{String.class, int.class});
        // 实例化
        Student student = (Student) constructor.newInstance("xiayutian", 23);
        // 调用方法
        String res = (String) method_study.invoke(student, new Object[]{666});
        Log.d("sanaTag", "func_reflection2 res:" + res);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
  • 调用private方法getAge:
public static void func_reflection3() {
    Class clz = one.getClass();
    try {
        // Method method_getAge = clz.getMethod("getAge", new Class[]{});
        Method method_getAge = clz.getDeclaredMethod("getAge");
        // 设置权限
        method_getAge.setAccessible(true);
        int age = (int) method_getAge.invoke(one, new Object[]{});
        Log.d("sanaTag", "func_reflection3 age:" + age);

    }catch (Exception e) {
        e.printStackTrace();
    }
}

5. NDK反射

  • NDK 通过 JNI 提供以下关键函数实现反射:

    • FindClass():获取 Java 类对象(如 jclass cls = env->FindClass("com/example/MyClass"););
    • GetMethodID() / GetFieldID():获取方法或字段 ID(需指定方法名、签名和返回值类型);
    • Call<Type>Method() / Get<Type>Field():通过 ID 调用方法或读写字段;
  • 实例1:调用study方法;
extern "C"
JNIEXPORT jint JNICALL
Java_com_sana_kanxuendk_MainActivity_callJavaMethod(JNIEnv *env, jobject thiz, jobject student) {
    // 找到类 与下一句 FindClass 同等
    jclass jclazz_student = env->GetObjectClass(student);
    jclass jclazz_student2 = env->FindClass("com/sana/kanxuendk/Student");
    // 获取方法id ndk层面的获取方法 通过id去调用
    jmethodID jmethod_study = env->GetMethodID(jclazz_student, "study", "(I)Ljava/lang/String;");
    int flag = 34;
    // 调用 jmethod_study 方法
    jobject jstring_ret = env->CallObjectMethod(student, jmethod_study, flag);
    char * tmp = (char *)env->GetStringUTFChars((jstring)jstring_ret, 0);
    LOGI("jstring_ret : %s", tmp);
    return flag;
}
  • 实例2:调用static方法calcLength:
extern "C"
JNIEXPORT jint JNICALL
Java_com_sana_kanxuendk_MainActivity_callJavaMethod2(JNIEnv *env, jobject thiz) {
    jclass jclazz_student2 = env->FindClass("com/sana/kanxuendk/Student");
    jmethodID jmethod_study = env->GetStaticMethodID(jclazz_student2, "calcLength", "(Ljava/lang/String;)I");
    jstring jstring_name = env->NewStringUTF("sana");
    int length = env->CallStaticIntMethod(jclazz_student2, jmethod_study, jstring_name);
    LOGI("sana length : %d", length);
    return length;
}
© 版权声明
THE END
喜欢就支持一下吧
点赞 0 分享 收藏
评论 抢沙发
OωO
取消