本篇文章是Android逆向系列的第六篇,涉及到JNI静态注册的概念和基础知识。初步学习JNI的使用,主要是了解一些JNI接口函数的使用,并且使用JNI调用普通变量和静态变量,其中涉及到JNI接口函数中的GetObjectField、GetFieldID、FindClass等方法,此外也涉及到函数嵌套和参数问题,在下文中将详细地讲解学习到。
一、JNI使用简述
本节以安卓逆向的角度介绍JNI入门使用,主要涉及到JNI的使用书写步骤,最后再通过NDK静态注册上传至手机上测试。主要步骤如下:
- 编写带有native声明的方法的Java类,这里的native声明为本地方法且不要实现,其中的参数和方法体留在后面实现
- 使用javah命令生成.h头文件,该.h头文件相当于在Java层中的接口,里面存在的方法在编写C/C++中实现。
- C/C++中编写本地方法及.h头文件中存在的方法
- 生成动态库.so文件,之后运行即可
二、使用JNI-书写步骤介绍及简单Toast弹窗
1、Eclipse创建Android工程
1)创建Android工程
2)项目设置
3)编写MainActivity.java
在MainActivity.java
文件中编写一个Getstring()
方法,并在onCreate()
方法中添加Toast弹窗显示,便于测试
前面把程序放到Jadx工具里面进行反编译处理后,发现很多使用native修饰的方法,这些方法的特点是不可见性,即在jadx反编译时看不到其逻辑代码,只能看到一个空的方法名,而且使用native修饰的方法具体实现在java层是看不到的。这时就需要ndk开发的知识,在.so程序找到其对应的.so文件,然后去.so里面分析它的一个逻辑。
这里已经定义了一个被native修饰的方法,那么修饰这个方法就要做一些操作,在java层用Toast弹窗展示出来
package com.example.anquantest;
import android.os.Bundle;
import android.app.Activity;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(this, Getstring(), 1).show();//Toast弹窗
}
public native CharSequence Getstring();
}
4)设置编码格式
2、javah生成JNI样式的标头文件
这部分是基于MainActivity.java文件中的类,通过javah命令生成JNI样式的头部文件。
1)前往文件目录下
该项目的资源src目录位置
cd D:\EveryCode\Eclipse\data\anquantest\src
2)javah命令生成JNI头文件
javah -jni 包名+类
javah -jni com.example.anquantest.MainActivity
可以在src目录下看到生成了一个com_example_anquantest_MainActivity.h
文件
3、分析javah生成的.h文件
这部分简单介绍及分析了通过javah生成的.h头文件的内容
.h文件内容如下
第一行代码引入jni头文件,也就是之前介绍的jin文件
#include <jni.h>
预处理功能中的条件编译,根据是否已经定义了一个变量来进行分支选择,用于防止重名和重复导入。
#ifndef
#endif
jobject
类型的方法
JNIEXPORT jobject JNICALL Java_com_example_anquantest_MainActivity_Getstring
(JNIEnv *, jobject);
4、ndk编译前准备
这部分是编写三个文件,用于NDK编译.so文件,分别是JNI_anquan.c、Application.mk、Android.mk文件。
1)主目录下创建jni文件夹,并将.h文件移入其中
改为JNI_anquan.h
并移动到jni文件夹中
2)创建JNI_anquan.c文件
#include "JNI_anquan.h"
JNIEXPORT jobject JNICALL Java_com_example_anquantest_MainActivity_Getstring
(JNIEnv *env, jobject obj){
jstring str = (*env)->NewStringUTF(env, "testtest");
return str;
}
首先是引入头文件JNI_anquan.h
,接下来的.._Getstring(){}
方法是在JNI_anquan.h
中获取(使用javah生成的文件),添加两个参数env和obj,在方法体中添加NewSreingUTF()
方法定义jstring类型的变量str,接收字符串testtest,最后将该字符串传回。
3)创建Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= JNI_anquan
LOCAL_SRC_FILES := JNI_anquan.c
LOCAL_ARM_MODE := arm
LOCAL_LDLIBS:= -llog
include $(BUILD_SHARED_LIBRARY)
4)创建Application.mk文件
APP_ABI := armeabi-v7a
5、生成.so文件
在cmd命令行中输入命令进行编译
ndk-build
6、调用载入so库并连接手机测试
在MainActivity.java
文件中添加代码至MainActivity类中,达到调用so库的作用
static{
System.loadLibrary("JNI_anquan");
}
package com.example.anquantest;
import android.os.Bundle;
import android.app.Activity;
import android.widget.Toast;
public class MainActivity extends Activity {
static{
System.loadLibrary("JNI_anquan");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(this, Getstring(), Toast.LENGTH_SHORT).show();
}
public native CharSequence Getstring();
}
保存后,连接手机测试
可以在手机上看到
小结:
JNI:返回值类型 方法名称 参数类型
jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...);
C层:返回值 参数 方法体
JNIEXPORT jobject JNICALL Java_com_example_anquantest_MainActivity_Getstring
(JNIEnv *, jobject){
}
NewStringUTF
三、使用JNI-调用普通和静态变量
1、编写MainActivity.java
package com.example.anquantest;
import android.os.Bundle;
import android.app.Activity;
import android.widget.Toast;
public class MainActivity extends Activity {
public String car1 = "yeah yeah !!!!";
public static String car2 = "static static static static";
static{
System.loadLibrary("JNI_anquan");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toast.makeText(this, Getstring(), Toast.LENGTH_SHORT).show();
Toast.makeText(this, Getstr(), Toast.LENGTH_SHORT).show();
Toast.makeText(this, GetStatic_str(), Toast.LENGTH_SHORT).show();
}
public native CharSequence Getstring();
public native CharSequence Getstr();
public native CharSequence GetStatic_str();
}
2、通过javah命令生成MainActivity.h文件
javah -jni com.example.anquantest.MainActivity
将其改名为JNI_anquan.h
并移动到jni目录下
3、编写JNI_anquan.c文件
在之前的基础上需要新加两个函数方法,分别是Getstr()
方法体和Getstatic_str()
方法体,下面先详细介绍编写Getstr()
方法体,后面的Getstatic_str()
方法体类似。
1)编写Getstatic_str()方法体
0x01 GetObjectField方法
首先获取普通字段方法需要用到JNI接口中的Getobjectfield
方法
jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID);
注:返回值-方法名称-参数类型
添加了GetObjectField方法后,发现jfieldID未知,需要通过其他方法获取
那么jfieldID值需要如何获取呢?在jni.h
中提供了GetFieldID方法获取jfieldID值
0x02 GetFieldID方法
GetFieldID
方法用于获取jfieldID
jfieldID(*GetFieldID)(JNIEnv*, jclass, const char*, const char*);
第一个参数:JNI接口对象 env
第二个参数:Java类对象jclass
第三个参数:Java中的变量名 car1
第四个参数:Java变量签名 Ljava/lang/String; (变量返回值类型,注意有分号)
添加了GetFieldID方法后,发现还有个jclass参数未知,也是需要其他方法来获取。
同样我们可以在jni.h
中找到FindClass方法来得到jclass参数的值
这里
Ljava/lang/String
后面需要加上一个分号;
0x03 FindClass方法
FindClass
方法用于获取jclass值
jclass (*FindClass)(JNIEnv*, const char*);
第一个参数:JNI接口对象 env
第二个参数:Java类的完整路径 com/example/anquantest/MainActivity
这里的完整路径是包名+类名,将点换成斜杆
完整代码如下
0x04 完整方法体
JNIEXPORT jobject JNICALL Java_com_example_anquantest_MainActivity_Getstr
(JNIEnv *env, jobject obj){
jclass _jclass = (*env)->FindClass(env, "com/example/anquantest/MainActivity");
jfieldID _jfieldID = (*env)->GetFieldID(env, _jclass, "car1", "Ljava/lang/String;");
jobject str1 = (*env)->GetObjectField(env, obj, _jfieldID);
return str1;
}
2)编写Getstatic_str()方法体
类似Getstr()
方法,Getstatic_str()
方法内容构造类似
0x01 GetStaticObjectField方法
jobject (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID);
参数中的jclass
和jfieldID
未知,可以通过上述方法获取,代码类似,这里就不展开
0x02 GetStaticFieldID方法
jfieldID(*GetStaticFieldID)(JNIEnv*, jclass, const char*,const char*);
0x03 完整方法体
JNIEXPORT jobject JNICALL Java_com_example_anquantest_MainActivity_GetStatic
(JNIEnv *env, jobject obj){
jclass _jclass = (*env)->FindClass(env, "com/example/anquantest/MainActivity");
jfieldID _jfieldID = (*env)->GetStaticFieldID(env, _jclass, "car2", "Ljava/lang/String;");
jobject str2 = (*env)->GetStaticObjectField(env, _jclass, _jfieldID);
return str2;
}
3)JNI_anquan.c完整版
#include "JNI_anquan.h"
JNIEXPORT jobject JNICALL Java_com_example_anquantest_MainActivity_Getstring
(JNIEnv *env, jobject obj){
jstring str = (*env)->NewStringUTF(env, "testtest");
return str;
}
// 获取Java层普通变量
JNIEXPORT jobject JNICALL Java_com_example_anquantest_MainActivity_Getstr
(JNIEnv *env, jobject obj){
// FindClass方法返回jclass,第二个参数是包名+类名以斜杆隔开
jclass _jclass = (*env)->FindClass(env, "com/example/anquantest/MainActivity");
// GetFieldID方法获取jfieldID,第三个参数是变量名
// 第四个参数是变量签名,也就是变量的类型,L表示类类型,后面加上分号; !!!~~~
jfieldID _jfieldID = (*env)->GetFieldID(env, _jclass, "car1", "Ljava/lang/String;");
// GetObjectField获取变量值,其中未知的参数通过上面的方法均可找到
jobject str1 = (*env)->GetObjectField(env, obj, _jfieldID);
// 返回获取到的变量值
return str1;
}
JNIEXPORT jobject JNICALL Java_com_example_anquantest_MainActivity_GetStatic
(JNIEnv *env, jobject obj){
jclass _jclass = (*env)->FindClass(env, "com/example/anquantest/MainActivity");
jfieldID _jfieldID = (*env)->GetStaticFieldID(env, _jclass, "car2", "Ljava/lang/String;");
jobject str2 = (*env)->GetStaticObjectField(env, _jclass, _jfieldID);
return str2;
}
4、创建Android.mk文件
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:= JNI_anquan
LOCAL_SRC_FILES := JNI_anquan.c
LOCAL_ARM_MODE := arm
LOCAL_LDLIBS:= -llog
include $(BUILD_SHARED_LIBRARY)
5、Application.mk文件
APP_ABI := armeabi-v7a
6、生成.so文件
ndk-build
7、连接手机测试
- 本文作者: xigua
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/687
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!