Java so文件混淆

So文件混淆

一、 混淆目的

JNI开发过程中利用javah生成本地层对应的函数名类似于java_com_XX这种形式,很容易被逆向者在逆向so的时候在IDA的Exports列表中找到

如下:
2989495-373cd501649d4ee3.png
image.png

我们的目的就是让这个函数在IDA中不能轻易找出,增加破解难度。

二、 混淆方法

1. 原理

当我们在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

2. 实现

(C++为例,C的需要稍微改下语法)

1) 在jni文件夹中新建一个任意名称的cpp文件

2) 复制如下代码

//
// Created by libb on 2019/5/8.
//
#include<jni.h>
#include <stdio.h>
#include <log.h>
#include <assert.h>
#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;

  • 第一个变量name是Java中函数的名字。
  • 第二个变量signature,用字符串是描述了Java中函数的参数和返回值
  • 第三个变量fnPtr是函数指针,指向native函数。前面都要接 (void *)
  • 第一个参数就是我们写的方法,第三个就是.h文件里面的方法,主要是第二个参数比较复杂.括号里面表示参数的类型,括号后面表示返回值。

“()” 中的字符表示参数,后面的则代表返回值。例如:

”()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区域才能进行下一步解析
2989495-ace80d6e9b139dfe.png
image.png

猜你喜欢

转载自blog.csdn.net/weixin_33814685/article/details/90799045