Java so文件混淆

So文件混淆

一、 混淆目的

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

如下:
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区域才能进行下一步解析
image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容