JNI开发过程中利用javah生成本地层对应的函数名类似于java_com_XX这种形式,很容易被逆向者在逆向so的时候在IDA的Exports列表中找到
如下:我们的目的就是让这个函数在IDA中不能轻易找出,增加破解难度。
当我们在Java中调用System.loadLibrary(xxx)方法时候,会告诉虚拟机去加载libxxx.so链接库。虚拟机加载这个so库的时候,从Java层进入本地层首先会执行JNI_Onload函数完成一些初始化的工作。同时,在这个函数中会注册Java层的native方法,最终会调用RegisterNatives方法能帮助我们把c/c++中的方法隐射到Java中的native方法,而无需遵循特定的方法命名格式。
传统java Jni方式:1.编写带有native方法的Java类;--->2.使用javah命令生成.h头文件;--->3.编写代码实现头文件中的方法,这样的“官方” 流程,是我们认识到这样会带来java_com_xxxx这样很容易被逆向者发现的弊端
因此我们混淆JNI本地函数的方法就是调用JNI提供的RegisterNatives方法动态的将native方法注册到JVM中
动态注册步骤:
l 自定义JNI_Onload函数,通过registerNativeMethods()函数来更换本地函数指针并加入头文件。
l 所更换的本地函数所对应的函数的实现。
l 隐藏符号表,在Android.mk文件里面添加一句LOCAL_CFLAGS := -fvisibility=hidden
(C++为例,C的需要稍微改下语法)
1) 在jni文件夹中新建一个任意名称的cpp文件
2) 复制如下代码
//
// Created by libb on 2019/5/8.
//
#include
#include
#include
#include
#include "com_limushan_decomplieso_JniTest.h"
#define JNIREG_CLASS "com/limushan/decomplieso/JniTest"//指定要注册的类
jobject getApplication1(JNIEnv* env) {
jclass localClass = (env)->FindClass("android/app/ActivityThread");
if (localClass != NULL) {
// LOGI("class have find");
jmethodID getapplication = env->GetStaticMethodID(localClass, "currentApplication",
"()Landroid/app/Application;");
if (getapplication != NULL) {
jobject application = (env)->CallStaticObjectMethod(localClass, getapplication);
return application;
}
return NULL;
}
return NULL;
}
extern "C"
__attribute__((section (".mytext"))) JNICALL jobject _xxx_yyy1(JNIEnv *env, jclass obj) {
return getApplication1(env);
}
extern "C"
__attribute__((section (".mytext"))) JNICALL void _xxx_yyy2(JNIEnv *env, jclass obj,jint flag) {
jclass temp_clazz = NULL;
jmethodID mid_static_method;
// 1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象
temp_clazz = env->FindClass("java/lang/System");
mid_static_method = env->GetStaticMethodID(temp_clazz, "exit", "(I)V");
(env)->CallStaticVoidMethod( temp_clazz, mid_static_method, flag);
(env)->DeleteLocalRef(temp_clazz);
}
extern "C"
__attribute__((section (".mytext"))) JNICALL jstring _xxx_yyy3(JNIEnv *env, jclass obj) {
jobject context = getApplication1(env);
jclass class_system = (env)->FindClass( "java/lang/System");
if (class_system == NULL) {
LOGD("class system is null");
}
jmethodID method_get_property = (env)->GetStaticMethodID(class_system, "getProperty", "(Ljava/lang/String;)Ljava/lang/String;");
if (method_get_property != NULL) {
LOGD("method is found...");
} else {
LOGD("method not found...");
}
jstring host = (env)->NewStringUTF("http.proxyHost");
jstring port = (env)->NewStringUTF("http.proxyPort");
jstring hostIp = (jstring)(env)->CallStaticObjectMethod(class_system, method_get_property, host);
jstring hostPort = (jstring)(env)->CallStaticObjectMethod(class_system, method_get_property, port);
if (hostIp != NULL || hostPort != NULL) {
LOGD("有代理,好危险!");
} else {
LOGD("环境正常,可以操作");
}
return hostPort;
}
/**
* Table of methods associated with a single class.
*/
//绑定,注意,V,Z签名的返回值不能有分号“;”
//这里就是把JAVA层的getStringFromC()函数绑定到Native层的getStringc()函数,就无需使用原生的Java_com_xx_xx_classname_methodname这种恶心的函数命名方式了
static JNINativeMethod gMethods[] = {
{ "getApplication", "()Ljava/lang/Object;", (void*)_xxx_yyy1},
{ "exitApplication", "(I)V", (void*)_xxx_yyy2},
{ "checkProxyExist", "()Ljava/lang/String;", (void*)_xxx_yyy3},
};
/*
* Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = (env)->FindClass(className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((env)->RegisterNatives(clazz, gMethods, numMethods) < 0) {
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
* Register native methods for all classes we know about.
*/
static int registerNatives(JNIEnv* env)
{
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,
sizeof(gMethods) / sizeof(gMethods[0])))
return JNI_FALSE;
return JNI_TRUE;
}
/*
* Set some test stuff up.
*
* Returns the JNI version on success, -1 on failure.
*/
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if ((vm)->GetEnv( (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) {//注册
return -1;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
return result;
我们用RegisterNatives动态获取本地方法
3) 修改方法对应表
/**
* Table of methods associated with a single class.
*/
//绑定,注意,V,Z签名的返回值不能有分号“;”
//这里就是把JAVA层的getApplication函数绑定到Native层的_xxx_yyy1函数,
//就无需使用原生的Java_com_xx_xx_classname_methodname这种恶心的函数命名方式了
static JNINativeMethod gMethods[] = {
{ "getApplication", "()Ljava/lang/Object;", (void*)_xxx_yyy1},
{ "exitApplication", "(I)V", (void*)_xxx_yyy2},
{ "checkProxyExist", "()Ljava/lang/String;", (void*)_xxx_yyy3},
};
这是一个数组,对应这我们Java native方法和本地函数的映射关系。具体每个函数表示一个JNINativeMethod
结构体,官方定义如下:
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
“()” 中的字符表示参数,后面的则代表返回值。例如:
”()V” 就表示void xxx();
“(I)V” 表示 void xxx(int a);
“(II)I” 表示 int xxx(int a, int b);
"()Ljava/lang/String;" 表示String xxx();
这些字符与函数的参数类型的映射表如下:
···
V void void
Z jboolean boolean
I jint int
J jlong long
D jdouble double
F jfloat float
B jbyte byte
C jchar char
S jshort short
数组则以”[“开始,用两个字符表示 n维数组的话,则是前面多少个”[“而已,如”[[[D”表示“double[][][]”)
[I jintArray int[]
[F jfloatArray float[]
[B jbyteArray byte[]
[C jcharArray char[]
[S jshortArray short[]
[D jdoubleArray double[]
[J jlongArray long[]
[Z jbooleanArray boolean[]
引用类型:以”L”开头,以”;”结尾,中间是用”/” 隔开。”;”也是多个类名的分隔符
Ljava/lang/String; jstring String
Ljava/lang/Object; jobject Object
···
4) 更换本地函数对应的函数实现
示例如下:这里的名字可以随意取,里面实现需要替换成你自己的实现
extern "C"
__attribute__((section (".mytext"))) JNICALL void _xxx_yyy2(JNIEnv *env, jclass obj,jint flag) {
jclass temp_clazz = NULL;
jmethodID mid_static_method;
// 1、从classpath路径下搜索ClassMethod这个类,并返回该类的Class对象
temp_clazz = env->FindClass("java/lang/System");
mid_static_method = env->GetStaticMethodID(temp_clazz, "exit", "(I)V");
(env)->CallStaticVoidMethod( temp_clazz, mid_static_method, flag);
(env)->DeleteLocalRef(temp_clazz);
}
在函数前加上attribute((section (“.mytext”))),这样的话,编译的时候就会把这个函数编译到自定义的名叫”.mytext“的section里面,由于我们在java层没有定义这个函数因此要写到一个自定义的section里面
5) 声明待注册类目
···
#define JNIREG_CLASS "com/limushan/decomplieso/JniTest"//指定要注册的类
···
6) 在Android.mk文件里面添加一句LOCAL_CFLAGS := -fvisibility=hidden 隐藏符号表
7) 其他步骤和JNI开发一致
8) ndk-build构建so文件
具体结果如下,在方法名索引表中找不到调用的函数名称。只有找到自定的section区域才能进行下一步解析