Android应用安全之JNI混淆

简介

        JNI全称为Java Native Interface,是使Java方法与C\C++函数互通的一座桥梁。通俗的讲,它的作用就是使Java可以调用C\C++写的函数、使C\C++可以调用Java写的方法。

特点

1、性能

        众所周知,Android程序一般是基于Java语言开发的,虽然Google随后推出了Kotlin语言,但是Kotlin语言依然运行在JVM虚拟机上,所以其性能与采用Java开发的程序是没有任何区别的。

        由于Java是虚拟机语言(指需要被编译成虚拟机代码,由虚拟机执行的语言),所以无论是JVM虚拟机,还是Dalvik、Art虚拟机,在对性能要求较高的情况下,就显得有些不足了。此时,便需要编译型语言将源代码编译为机器码,直接由CPU执行代码,从而提升性能。

2、安全性

        相对于java代码容易被反编译,使用NDK开发出来的原生C++代码编译后,生成的so库是一个二进制文件,这无疑增加了破解的难度。利用这个特性,可以将客户端敏感信息写在C++代码中,增强应用的安全性。

逆向分析

        1、新建一个Native C++项目,将自动生成一个默认的JNI方法:

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

        2、上面是一个很简单的JNI方法,我们将生成的so库使用IDA工具进行反汇编,可以看到以下的内容:

 

        默认情况下,JNI函数名以Java为前缀,并且用“_”下划线将包名、类名以及native方法名连接起来。这种格式的so库被反汇编后,很容易就找到对应的方法。

动态注册

  • 原理

        利用 RegisterNatives 方法来注册 java 方法与 JNI 函数的一一对应关系;

  • 实现流程

        1.利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系;

// 第一个参数:Java层的方法名
// 第二个参数:方法的签名,括号内为参数类型,后面为返回类型
// 第三个参数:需要重新注册的方法名
static JNINativeMethod getMethods[] = {
      {"stringFromJNI",  "()Ljava/lang/String;", (void *) getStr1}
};
// 指定代码所在的段。在编译时,把该函数编译到自定义的section里。
// 由于在java层没有定义该函数,因此需要写到一个自定义的section里。
extern "C"
__attribute__((section(".mysection"))) JNICALL jstring getStr1(JNIEnv *env, jobject obj) {
  return env->NewStringUTF("Hello from C++");
}

        2.实现 JNI_OnLoad 方法,在加载动态库后,执行动态注册;

extern "C"
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
  JNIEnv *env;
  if (vm->GetEnv((void **) (&env), JNI_VERSION_1_6) != JNI_OK) {
      return -1;
  }
  if (!registerNatives(env)) {
      return -1;
  }
  return JNI_VERSION_1_6;
}

        3.调用 FindClass 方法,获取 java 对象;

extern "C"
int registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *getMethods, int numMethods) {
  jclass clazz;
  clazz = env->FindClass(className);
  if (clazz == NULL) {
      return JNI_FALSE;
  }
  if (env->RegisterNatives(clazz, getMethods, numMethods) < 0) {
      return JNI_FALSE;
  }
  return JNI_TRUE;
}

        4.调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册;

// 指定要注册的类
#define JNIREG_CLASS "com/example/jnilibrary/JniUtils"
extern "C"
int registerNatives(JNIEnv *env) {
    if (!registerNativeMethods(env, JNIREG_CLASS, getMethods,
                               sizeof(getMethods) / sizeof(getMethods[0]))) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

        将生成的so库使用IDA工具进行反汇编,可以看到函数被编译到自定义的section里面去了:

 

隐藏符号表

  • ndk-build

    在Android.mk文件中,添加 LOCAL_CFLAGS := -fvisibility=hidden

  • cmake

    在CmakeLists.txt文件中,添加 add_definitions("-fvisibility=hidden")

      我们将生成的so库使用IDA工具进行反汇编,发现找不到编写的JNI函数,其次也没有找到函数的符号表,而且整个逻辑是完全混淆的:

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值