0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

前言
当执行一个 Java 的 native 方法时,虚拟机是怎么知道该调用 so 中的哪个方法呢?这就需要用到注册的概念了,通过注册,将指定的 native 方法和 so 中对应的方法绑定起来(函数映射表),这样就能够找到相应的方法了。
注册分为 静态注册 和 动态注册 两种。默认的实现方式即静态注册。
安卓逆向-Native 方法的静态注册和动态注册详解与实战(上)
紧接上话继续开始
一、静态注册-案例三
目标:使用C层调用java层次的静普通和静态的方法。
1、修改java文件
现在在java层写入普通方法和静态方法
//定义普通方法
public void _method(){
Toast.makeText(this, "我是普通方法",1).show();
}
//定义静态方法
public void _staticmethod(){
Toast.makeText(this, "我是静态方法",1).show();
}
创建好两个方法还需要调用这一个native修饰过的方法(这一次是要java层的普通方法和静态方法两个方法,在javaToc()中实现调用
//调用静态和普通两个方法
private native String javaToc();
private:在同一类内可见。使用对象:变量、方法、注意:不能修饰类(外部类)
Native方法一般用于两种情况:在方法中调用一些不是由java语言写的代码;在方法中用java语言直接操纵计算机硬件。
java称代码写完,就要写C文件了,首先生成.h文件
javah -encoding UTF-8 -jni com.example.yunjian.MainActivity
或
javah -jni com.example.yunjian.MainActivity
重命名名字,删除之前的JNI_yunj.h文件,移动到jni目录
2、修改调用普通方法的JNI接口
1、修改.c文件
将.h文件中的Java_com_example_yunjian_MainActivity_javaToc复制到c文件中,补全参数,方法体等
这里用到jni表里找接口:CallVoidMethod
void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...);
补全接口的参数,指出等,因为返回值为void,意思没有返回值
JNIEnv* 默认,填写yunj
jobject 默认,填写obj
jmethodID 需要调用GetMethodID获取
... 参数列表,删除就可以
第三个参数jmethodID需要调用GetMethodID获取
jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);
补全GetMethodID的参数等信息
JNIEnv* 默认 填写yunj
jclass 调用FindClass获取
const char* 为java层的方法名:”_method“
const char* 第四个参数为java层的方法的返回值,但是_method返回值为空,所以这里填写:"()V"
发现GetMethodID第二参数jclass,这里需要调用FindClass
jclass (*FindClass)(JNIEnv*, const char*);
补充FindClass的参数等信息
JNIEnv* 默认 填写yunj
const char* java的类的全名:"com/example/yunjiananqian/MainActivity"
FindClass的第二个参数const char*也上面一样,java的类的全名
com/example/yunjiananqian/MainActivity
继续补充GetMethodID的参数
jclass第二个参数为FindClass方法返回值:j_class
const char*第三个参数为java层的方法名:”_method“
const char*第四个参数为java层的方法的返回值,但是_method返回值为空,所以这里填写:"()V"
最后FindClass方法完成状态为:
补全GetMethodID方法,完整变调用普通方法代码
2、ndk编译so文件
C语言文件完成了,下面开始通过ndk编译so文件了
cd E:\data\Yunjian\jni\
ndk-build
代码没有问题,看来调用普通方法的jni接口写好了,开始静态方法调用把
3、修改调用静态方法的JIN接口
1、修改java文件
静态方法需要在重新修改下java层代码
getApplicationContext() 是通过makeApplication创建的 ,所以说你使用getApplicationContext()返回的是一个对象,返回应用的上下文,生命周期是整个应用,应用摧毁它才摧毁。getApplicationContext()是在Context 定义的,所以说只要继承Context的子类都可以调用getApplicationContext()方法.
因为java没有新增native修饰的方法,.h文件中不会新增方法,所以不用新生成.h文件
2、修改.c文件
直接在c文件中增加方法,在之前获取普通方法的方法上加上static即可CallStaticVoidMethod,后面的参数列表用不上,删除...就可以了:
void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...);
补充CallStaticVoidMethod方法参数等,因为返回值为void,意思没有返回值
JNIEnv* 默认填写junj
jclass 使用FindClass返回值,但是这个前面已经写了吗
jmethodID:需要调用GetStaticMethodID方法
jclass在一个前面FindClass方法了,直接将FindClass返回值放进来就可以
GetStaticMethodID方法参数
JNIEnv* 默认 输入:yunj
jclass 使用FindClass返回值,输入j_class
const char* 和上面的GetMethodID方法一样,这里是java层静态方法的方法名:"_staticmethod"
const char* 和上面的GetMethodID方法一样,java层静态方法的返回值,这里返回值为空void,输入:"()V"
C语言文件完成了,下面开始通过ndk编译so文件了
cd E:\data\Yunjian\jni\
ndk-build
这个是没有回显的,因为这是个调用的方法
静态注册我们已经可以很清晰的明白概念了,下面让我们开始动态注册的学习
二、动态注册-计算器案例一
目标:编写一个可以加减乘除的简单计算器APP
1、创建项目
创建一个项目
一直默认即可,这时候创建成功一个工程项目
2、编辑app界面
放入两个纯文本框 plain Text
放入两个文本视图,TextView
然后改下名称
修改完名称,有个输入框和描述后,还需要加减乘除的四个框架:Button
修改名称
修改完四个框架为加减乘除符号!
双击四个框架进入,在id处修改:
加:add 减:sub 乘:mul 除:div
修改之后记得保存哦~~~
继续给最初的Text赋予个初始值0:
那么这时候第一部分就完成了,接下来开始写java代码
3、java层编写
首先创建个方法来初始化控件,在MainActivity.java编辑
初始化控件后,绑定之前,先定义一个编辑框的变量
这里先一个变量first,然后first绑定第一个编辑框,但是报错了,是因为没有定义他的类型
定义好第一个编辑框,继续定义第二个编辑框
定义完成两个编辑框后,开始绑定按钮,和上面编辑框差不多,只是类型不同,绑定加减乘除四个按钮
这时候按钮界面就绑定写完了!绑定完成后需要设置按钮监听获取编辑框的值,定义几个加减乘除就需要用到native修饰,实现在so层,先是定义方法定义两个全局变量
定义好两个全家变量,在这是输入加减乘除的输入参数,还需要定义方法来进行加减乘除
定义native方法,实现java与c之间的联动
定义好了四种方法后,接下来要获取两个编辑框中的值,就要用到gettext的tostring方法了
parseFloat()方法是将String 类型装换成 float 类型
toString 能将引用型转换成String 类型
这就话的意思是出入first(输入框)中内容转换为string类型,在转化为Float类型,赋予one变量
继续添加second第二个即可
获得编辑框里的内容后,就要进行一个运算了,定义一个运算方法
创建简单的运算方法,先给add这个方法进行一个按钮绑定的监听,new了一个OnClickListener类,这里需要导入OnClickListener类(点击一下即可),导入进来后,onClick监听按钮,逻辑是通过按钮的ID来执行一个命令操作。
使用swith 对接收的按钮的ID来进行执行不同操作!
将add.setOnClickListener()删除即可,这里的key的view V中,这个V的getid按钮,value的按钮的ID值:
获取之后要进行一个对用的操作,因为获取完之后,需要调用这个方法显示到界面上,那么继续写:
Toast.makeText()三个参数:
context:填写MainActivity.this,代表MainActivity使用中的上下文
text:第二个参数就是我们要获取的add(one,two),代表之前我们定义的add()方法,后面加+"",add返回值为float格式,但是这里需要string格式,所以加个字符拼接。
第三个参数时间改为1,
.show显示下!
完成加法后,开始减乘除的编写
设置完后需要进行一个默认值的绑定:OnClickListener
这里使用OnClickListener进行默认值绑定,定义cl,用final表示这个字段就是最终字段不可修改,然后绑定了加减乘除cl!
这时候编辑框的值要放入case里面,不然点不动会数据异常:
这里就可以动态获取信息了,java层代码告一段落,保存
package com.example.yunjiandongtai;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
public class MainActivity extends Activity {
//第一个编辑框变量
private EditText first;
//第二个编辑框变量
private EditText second;
//加减乘除四个变量
private Button add;
private Button sub;
private Button mul;
private Button div;
//编辑框内容的变量
private float one;
private float two;
static{
System.loadLibrary("jisuanqi");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控件
init();
//这就话的意思是出入first(输入框)中内容转换为string类型,在转化为Float类型,赋予one变量
one = Float.parseFloat(first.getText().toString());
two = Float.parseFloat(second.getText().toString());
//开始运算
yunsuan();
}
//运算方法
private void yunsuan() {
// TODO Auto-generated method stub
final OnClickListener cl = new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.add:
one = Float.parseFloat(first.getText().toString());
two = Float.parseFloat(second.getText().toString());
Toast.makeText(MainActivity.this, add(one,two)+"", 1).show();
break;
case R.id.sub:
one = Float.parseFloat(first.getText().toString());
two = Float.parseFloat(second.getText().toString());
Toast.makeText(MainActivity.this, sub(one,two)+"", 1).show();
break;
case R.id.mul:
one = Float.parseFloat(first.getText().toString());
two = Float.parseFloat(second.getText().toString());
Toast.makeText(MainActivity.this, mul(one,two)+"", 1).show();
break;
case R.id.div:
one = Float.parseFloat(first.getText().toString());
two = Float.parseFloat(second.getText().toString());
Toast.makeText(MainActivity.this, div(one,two)+"", 1).show();
break;
}
}
};
add.setOnClickListener(cl);
sub.setOnClickListener(cl);
mul.setOnClickListener(cl);
div.setOnClickListener(cl);
}
//初始化控件
private void init() {
// TODO Auto-generated method stub
//绑定第一个编辑框对象
first = (EditText) findViewById(R.id.editText1);
//绑定第二个编辑框对象
second = (EditText) findViewById(R.id.editText2);
//绑定加按钮的对象
add = (Button) findViewById(R.id.add);
//绑定减按钮的对象
sub = (Button) findViewById(R.id.sub);
//绑定乘按钮的对象
mul = (Button) findViewById(R.id.mul);
//绑定除按钮的对象
div = (Button) findViewById(R.id.div);
}
//定义native方法,实现与c层联动
public native float add(float one,float tow);
public native float sub(float one,float tow);
public native float mul(float one,float tow);
public native float div(float one,float tow);
}
4、C层编写
创建jni文件夹
创建jisuanqi.c
开始写C代码,写头文件
头文件就用自带的.h即可
在JNI接口文件中看到有jfloat返回值,那么使用jfloat然后方法add传入四个参数,前面两个是固定的,后面两个才是最重要的需要传入的参数
那么这里创建好了加法,在依此创建减乘除:
这里有个问题,C语言的代码中add、sub、mul、div加减乘除四个方法名和java层需要一样吗?
这里是不需要一样的,因为会只用到jni接口进行java层和c层的方法关联,这里为了区分,修改一下c层的方法名称
接下来使用结构体定义结构体数据:JNINativeMethod
这个数组大括号就是用来绑定c层和java层
可以看到里面三个参数:
第一个是java方法名称
第二个是参数、返回值、签名信息,
第三个是函数指针!(F就是Smali代码里的)
接下来用RegisterNatives方法
需要先定义一个函数和一个参数:
在定义函数中将RegisterNatives方法放进去
env自定义即可,补全指出
补全参数
JNIEnv* 默认 修改为:env
jclass 需要FindClass方法返回值
const JNINativeMethod* 定义的结构体的名称
jint 放入的数组数量
第一个参数
第二个参数涉及FindClass方法返回值
jclass (*FindClass)(JNIEnv*, const char*);
补全FindClass方法
JNIEnv* 默认 填写env
const char* 填写java文件的全部名称"com.example.yunjiandongtai.MainActivity"
第三个参数
第四个参数,数量
这时候写完参数之后,还需要进行一个判断使用if,如果不等于(!=)零(JNI_OK),就返回一个-1(JNI_ERR)
registerNative方法在返回一个JNI_OK零!
这里是为了写法严谨改为:
sizeof是一个操作符(operator),其作用是返回一个对象或类型所占的内存字节数。
sizeof(nativeMethod)/sizeof(nativeMethod[0])
这时候注册函数就写完了,开始写动态注册用到的:JNI_OnLoad
先定义一个方法体给:JNI_OnLoad,这里使用GetEnv,
这里GetEnv有三个参数,
第一个参数vm补全
第二个参数void**二级指针指向(&)指针的地址env
第三个参数是版本
这里参数补全后,还需要指出
接下来判断这个env是否获取成功,就要写if条件,这里如果不等于0就返回JNI_ERR(-1)
还需要对上面的注册函数进行判断:registerNative判断,如果registerNative注册不等于0就返回-1,还需要加上env参数
成功最终返回一个版本
这里就完成使用JNI_OnLoad进行动态注册,这里的JNl_OnLoad类似Java里面的类,系统会自动调用!所以逆向在JNl_OnLoad下断点即可分析找->registerNative->RegisterNatives三个参数->Java层和C层绑定的一个逻辑关系!
最终代码
#include <jni.h>
jfloat addc(JNIEnv * env,jobject obj,jfloat a,jfloat b){
return a+b;
}
jfloat subc(JNIEnv * env,jobject obj,jfloat a,jfloat b){
return a-b;
}
jfloat mulc(JNIEnv * env,jobject obj,jfloat a,jfloat b){
return a*b;
}
jfloat divc(JNIEnv * env,jobject obj,jfloat a,jfloat b){
return a/b;
}
JNINativeMethod nativeMethod[]={
{"add","(FF)F",(void*)addc},
{"sub","(FF)F",(void*)subc},
{"mul","(FF)F",(void*)mulc},
{"div","(FF)F",(void*)divc}
};
jint registerNative(JNIEnv* env){
//获取类
jclass j_class=(*env)->FindClass(env, "com/example/yunjiandongtai/MainActivity");
//注册
if((*env)->RegisterNatives(env, j_class,nativeMethod,sizeof(nativeMethod)/sizeof(nativeMethod[0]))!=JNI_OK){
return JNI_ERR;
}
return JNI_OK;
}
//使用JNI_OnLoad进行动态注册
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved){
JNIEnv* env;
//如果不等于0
if((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_4)!=JNI_OK){
return JNI_ERR;
}
if(registerNative(env)!=JNI_OK){
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
5、编写Android.mk
LOCAL_PATH := $(call my-dir) #获取jni文件路径
include $(CLEAR_VARS)
LOCAL_MODULE := jisuanqi #模块名称
LOCAL_SRC_FILES := jisuanqi.c #源文件 .c或者.cpp
LOCAL_ARM_MODE := arm #编译后的指令集ARM指令
LOCAL_LDLIBS += -llog #依赖库
include $(BUILD_SHARED_LIBRARY) #指定编译文件的类型.so
6、编辑Application.mk
APP_ABI := armeabi-v7a
7、修改java层代码与真机联动
在java层新增代码块,将java层联动jni
运行程序,在真机上测试
完成~~~
三、总结
1. jni接口:java native interface 2.作用:用于javahe/ c++代码的交互
3.使用方法:jni静态注册和jni动态方法 (1). jni静态注册流程 a.在java代码中定义nativef修饰的方法; b.来到指定路径(src路径)执行javah -jni,根据java中native修饰的方法生成.h头文件; c.编写C、C++代码,导入头文件,同时实现我们.h头文件中的方法; d.编写两个mk文件: android.mk文件, application.mk文件(把四个文件放到jni目录); e.来到指定路径(jni文件夹所在路径)ndk-build生成so文件; (2)动态注册流程 a.在java代码中定义nativef修饰的方法; b.新建C/C++文件,导入jni.h头文件,编写c代码,实现java层被native修饰的方法; c.通过JNINativeMethod结构体绑定java和c、c++方法 d.通过RegisterNatives方法注册java相应的类以及方法; e.把C/C++注册方法写入到JNI_onload(两个参数VM、保留参数)﹔注意:JNI_inload,是系统调用的; f.来到指定路径(jni文件夹所在路径)ndk-build生成so文件; 4.两种注册方法有确定的对比: (1).静态注册: a.编写不方便,jni方法名字必须遵守规则而且名字长; b.运行效率不高,不安全; (2)动态注册: 流程清晰可控;运行效率高;
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
