IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 016 Android之NDK开发 -> 正文阅读

[C++知识库]016 Android之NDK开发

NDK入门指南

原生开发套件(NDK)是一套工具,能够在Android应用中使用C和C++代码,并提供众多平台库,可以使用这些平台库管理原生Activity和访问物理设备组件。

与NDK密切相关的另一个词汇则是JNI,它是NDK开发中的枢纽,Java与底层交互大多数通过它来完成。

JNI: Java Native Interface 也就是java本地接口 ,它是一个协议,这个协议用来沟通Java代码和C++代码。通过这个协议 ,Java类的某些方法可以用原生实现,同时可以让他们像普通的Java方法一样被调用和使用。

也就是说使用JNI这种协议可以实现Java代码和C++代码的相互调用

那为什么要使用NDK开发呢?

  • Java是半解释型语言,容易被反汇编成源码,在开发一些重要协议时,为了安全起见,使用C语言来编写这些重要的部分,来增大系统的安全性
  • 在一些复杂性的计算中,要求高性能的场景中,C++更有效率,代码也便于复用

下载NDK和工具

在Android Studio中,点开Config

在这里插入图片描述

找到SKD Manager

在这里插入图片描述

勾选LLIB NDK和CMake

NDK工程说明

在这里插入图片描述

新建一个项目,选择Native C++

在这里插入图片描述

语言选择JAVA,后续所有操作默认即可

在这里插入图片描述

在默认生成的工程中有一个Native方法stringFromJNI

在这里插入图片描述

该方法的实现在native-lib.cpp里

Java_com_example_ndkdemo_MainActivity_stringFromJNI

当前这个函数的名称由以下几部分组成

Java_[包名]_[类名]_[函数名]

这个函数默认有两个参数,如果java中有参数,就继续在两个参数后面加。

其中参数一JNIEnv* env,是JNI的环境指针,我们用到的jni函数,都在这个指针中;参数二jobject是java对象的this

JNI数据类型

java在C++中的数据类型,分为两类

  1. 引用类型,在C++中以j开头,本质都是指针
  2. 基本数据类型,在C++中以j开头,本质上就是C++中的数据类型重定义

基本数据类型

在这里插入图片描述

引用类型

在这里插入图片描述

引用类型的继承关系

在这里插入图片描述

JNI中的描述符

描述符分为以下几类:类描述符 域描述符 方法描述符

  • 类描述符是类的完整名称(包名+类名),将原来的分隔符换成斜杠。例如:
java.lang.String--->java/lang/String
  • 数组类型的描述符为:[+类型描述符
int[]--->[I
float[]--->[F
String[]--->[Ljava/lang/String;
  • 方法描述符

将参数类型的域描述符按照声明顺序放入一对括号中后跟返回值类型的域描述符,例如:

String test()---->()Ljava/lang/String;
int f(int i,Object object)---->(ILjava/lang/Object;)I
void set(byte[] bytes)---->([B)V

JNI基本使用

JAVA代码调用C++代码

先来写一个最简单的Crackme,体验NDK编程的完整流程

在这里插入图片描述

界面代码如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

   <EditText
       android:id="@+id/edt_user"
       android:hint="请在此输入用户名"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"></EditText>

    <EditText
        android:id="@+id/edt_pass"
        android:hint="请在此输入密码"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"></EditText>

    <Button
        android:id="@+id/btn1"
        android:text="注册"
        android:onClick="onClick"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"></Button>

</LinearLayout>

接着新增一个Native函数

public native boolean stringFromJNI2(String user,String pass);

然后在C++中编写实现代码

extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_ndkdemo_MainActivity_stringFromJNI2(JNIEnv *env, jobject thiz, jstring user,
                                                     jstring pass) {

    bool bRet=false;
    const char* pUser=env->GetStringUTFChars(user,JNI_FALSE);
    const char* pPass=env->GetStringUTFChars(pass,JNI_FALSE);

    if (strcmp(pUser,pPass)==0){
        bRet= true;
    }

    //释放字符串
    env->ReleaseStringUTFChars(user,pUser);
    env->ReleaseStringUTFChars(pass,pPass);

    return bRet;

}

然后在按钮点击事件中调用C接口

public void onClick(View view) {
        EditText editText=findViewById(R.id.edt_user);
        EditText editText1=findViewById(R.id.edt_pass);
        String user=editText.getText().toString();
        String pass=editText1.getText().toString();

        boolean bRet=stringFromJNI2(user,pass);
        if (bRet) {
            Toast.makeText(this,"恭喜 注册成功",Toast.LENGTH_LONG).show();
        }
        else {
            Toast.makeText(this,"注册失败",Toast.LENGTH_LONG).show();
        }

    }

这样就完成了一个完整的JAVA代码调用C代码的过程

C++代码调用JAVA代码

上面的Demo显然安全性不够,因为只要反编译JAVA代码直接修改返回值就能直接破解,不需要分析C++代码,所以再进一步进行修改。

public native void stringFromJNI2(String user,String pass);

首先修改方法原型,让函数返回空

 public void onClick(View view) {
        EditText editText=findViewById(R.id.edt_user);
        EditText editText1=findViewById(R.id.edt_pass);
        String user=editText.getText().toString();
        String pass=editText1.getText().toString();
        stringFromJNI2(user,pass);
    }

在onClick方法内直接调用stringFromJNI2函数

 public void ShowText()
    {
        Toast.makeText(this,"恭喜 注册成功",Toast.LENGTH_LONG).show();
    }

然后封装一个ShowText方法

extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndkdemo_MainActivity_stringFromJNI2(JNIEnv *env, jobject thiz, jstring user,
                                                     jstring pass) {


    const char* pUser=env->GetStringUTFChars(user,JNI_FALSE);
    const char* pPass=env->GetStringUTFChars(pass,JNI_FALSE);

    if (strcmp(pUser,pPass)==0){
       //获取类类型
        jclass jclass1=env->GetObjectClass(thiz);

       //获取方法ID
        jmethodID jmethodId=env->GetMethodID(jclass1,"ShowText","()V");

       //调用方法
        env->CallVoidMethod(thiz,jmethodId);
    }

    //释放字符串
    env->ReleaseStringUTFChars(user,pUser);
    env->ReleaseStringUTFChars(pass,pPass);
}

接着在C++代码中调用JAVA成员函数

C++代码修改JAVA字段

接着再对上面的代码进行修改

public String mString="不好意思 出错了";
    public void ShowError()
    {
        Toast.makeText(this,mString,Toast.LENGTH_LONG).show();
    }

首先封装ShowError方法,弹出错误提示。接着修改C++的Native方法,如果输入错误则弹出错误提示,如果正确则将提示字符串修改为注册成功。

extern "C"
JNIEXPORT void JNICALL
Java_com_example_ndkdemo_MainActivity_stringFromJNI3(JNIEnv *env, jobject thiz, jstring user,
                                                     jstring pass) {
    const char* pUser=env->GetStringUTFChars(user,JNI_FALSE);
    const char* pPass=env->GetStringUTFChars(pass,JNI_FALSE);

    if (strcmp(pUser,pPass)==0){
        //修改JAVA中的字段
        //获取类类型
        jclass jclass1=env->GetObjectClass(thiz);

        //获取字段ID
        jfieldID jfieldId=env->GetFieldID(jclass1,"mString","Ljava/lang/String;");

        //修改字段
        env->SetObjectField(thiz,jfieldId,env->NewStringUTF("恭喜 注册成功"));
    }

    //获取类类型
    jclass jclass1=env->GetObjectClass(thiz);

    //获取方法ID
    jmethodID jmethodId=env->GetMethodID(jclass1,"ShowError","()V");

    //调用方法
    env->CallVoidMethod(thiz,jmethodId);

    //释放字符串
    env->ReleaseStringUTFChars(user,pUser);
    env->ReleaseStringUTFChars(pass,pPass);

}

这样就完成了用C++代码修改JAVA字段。

但是这样就引出了一个问题

在这里插入图片描述

我们只要直接用IDA加载so文件,在导出表中搜索类名,就能很容易找到编写的Native方法

在这里插入图片描述

然后直接通过F5查看C++源码。这里我们想让分析人员不那么容易找到对应的Native方法,这样就引入了动态注册的概念

动态注册

public native void Check(String user,String pass);

新增一个Native方法,命名为Check

extern "C"
JNIEXPORT void JNICALL
AAA(JNIEnv *env, jobject thiz, jstring user, jstring pass) {
    // TODO: implement Check()
}

然后在生成的C++代码中,将名字修改为AAA。接下来我们利用动态注册的方式将Check方法和AAA进行绑定

JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)

在C++代码中实现一个onload方法,在so文件加载时,在JNI_OnLoad方法中进行注册,将Check方法和AAA进行绑定,实现代码如下:

extern "C"
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    JNIEnv* env;
    //获取JNI环境指针
    jint jret = vm->GetEnv((void **)&env,JNI_VERSION_1_6);
    if (jret!=JNI_OK){
        return JNI_ERR;
    }

    //获取类类型 package com.example.ndkdemo;
    jclass jclass1=env->FindClass("com/example/ndkdemo/MainActivity");
    //准备结构体数组
    const JNINativeMethod method={
            "Check",
            "(Ljava/lang/String;Ljava/lang/String;)V",
            (void *)AAA
    };
    //注册
    env->RegisterNatives(jclass1,&method,1);

    return JNI_VERSION_1_6;
}

注册完整之后就可以在JAVA代码中直接调用Check方法

在这里插入图片描述

这种方法的好处在于直接搜索函数名称是无法搜索到的

在这里插入图片描述

通过这种方法动态注册的函数只能通过JNI_Onload找到实现代码

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-09 17:27:32  更:2021-07-09 17:29:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/4 7:22:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码