016 Android之NDK开发
文章目录
- NDK入门指南
- 下载NDK和工具
- NDK工程说明
- JNI数据类型
- JNI中的描述符
- JNI基本使用
- JAVA代码调用C++代码
- C++代码调用JAVA代码
- C++代码修改JAVA字段
- 动态注册
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++中的数据类型,分为两类
- 引用类型,在C++中以j开头,本质都是指针
- 基本数据类型,在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"><EditTextandroid:id="@+id/edt_user"android:hint="请在此输入用户名"android:layout_width="match_parent"android:layout_height="wrap_content"></EditText><EditTextandroid:id="@+id/edt_pass"android:hint="请在此输入密码"android:layout_width="match_parent"android:layout_height="wrap_content"></EditText><Buttonandroid: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);//获取方法IDjmethodID 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);//获取字段IDjfieldID jfieldId=env->GetFieldID(jclass1,"mString","Ljava/lang/String;");//修改字段env->SetObjectField(thiz,jfieldId,env->NewStringUTF("恭喜 注册成功"));}//获取类类型jclass jclass1=env->GetObjectClass(thiz);//获取方法IDjmethodID 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找到实现代码
016 Android之NDK开发相关推荐
- Android下NDK开发环境搭建
Android下NDK开发环境搭建 1. AndroidNDK安装与配置 1.1 NDK简介 Android NDK是一套允许开发人员使用本地代码(如C/C++)进行Android APP部 ...
- Android之NDK开发
一.NDK产生的背景 Android平台从诞生起,就已经支持C.C++开发.众所周知,Android的SDK基于Java实现,这意味着基于Android SDK进行开发的第三方应用都必须使用Java语 ...
- Android之NDK开发学习总结
Android之NDK开发 http://www.cnblogs.com/devinzhang/archive/2012/02/29/2373729.html 一.NDK产生的背景 Android平台 ...
- JNI编程基础(二)——Android Studio NDK开发
由于笔者目前的工作是Android开发,所以在JNI开发系列博客中穿插一篇AndroidStudio NDK开发介绍. 随着Android Studio 2.2的推出,Android Studio的N ...
- Android之NDK开发初体验
记得前年开始自己在项目中使用第三方so库的时候就接触NDK编程开发了,只不过哪个时候自己是输出了"Hello Wrold~!".如今一年多的时间过去了,回头拾起之前的代码再次翻看. ...
- 【Android】NDK开发Crash分析
NDK开发Crash问题分析 手机user版本还是userdebug或是eng版本:adb shell getprop ro.build.type 因为使用的user版本的手机,所有没有权限读取到/d ...
- Android Studio NDK 开发
1.SDK Tools下载DNK,如果已经有下载有DNK的开发工具的话,可以直接导入 当然,AS后面推荐使用CMake方式开发NDK,这个后面再说,这里还是先讲ndk+javah+Android.mk ...
- Android之NDK开发的简单实例
NDK全称为Native Development Kit,是本地开发工具集.在Android开发中,有时为了能更好的重用以前的C/C++的代码,需要将这些代码编译成相应的so,然后通地JNI以供上层J ...
- Android Studio NDK 开发配置
一:使用gradle experimental plugin 首先,我们不能用android studio默认的gradle插件,我们需要把android studio的插件改为gradle expe ...
最新文章
- Android之EditText的各种使用
- 探秘腾讯Android手机游戏平台之不安装游戏APK直接启动法
- Nginx +uwsgi+django配置
- android dialog 隐藏状态栏_Android开发不得不收集的工具类集合
- 分布式基础篇总结(谷粒商城)
- opencv基础知识及其一些例子
- 我的家庭私有云计划-2
- 假期数据结构学习总结
- mysql脚本中如何写判断_mysql中如何写判断语句
- 《算法与数据结构---C语言描述》优先队列
- iphone升级ios7之后出现蓝框框一直跳的问题
- GNU C 的 __attribute__ 机制
- c语言中为什么无法输入文件,Devc写C语言时无法输入文件内容
- 手机网站注册页面html模板,手机网页登录注册自适应模版
- 传奇服务器常见的网络攻击方式有哪些?-版本被攻击
- 网络安全学习和CTF必不可少的一些网站
- win2003 服务器设置 完全版 作者:冰盾防火墙
- VIM 参 考 手 册
- (新)B站视频播放源地址获取及B站视频下载
- gammatone 滤波器详解及其MATLAB代码实现
热门文章
- DL之VGG16:基于VGG16(Keras)利用Knifey-Spoony数据集对网络架构进行迁移学习
- ML之Xgboost:利用Xgboost模型(7f-CrVa+网格搜索调参)对数据集(比马印第安人糖尿病)进行二分类预测
- Dataset:数据集集合(综合性)——机器学习、深度学习算法中常用数据集大集合(建议收藏,持续更新)
- Android中广播接收者BroadcastReceiver详解
- JavaEE5种常见的设计模式
- MS Chart 学习心得
- 拔掉网线时Socket的检查方法
- keil编译出错关于__use_no_semihosting_swi的使用
- linux下安装或升级GCC 4.8以上版本(包括),以支持C++11
- ajax头文件报错,AJAX的CSRF保护