转载自:http://www.liuling123.com/2016/06/so_method_mix.html

感谢原作者 侵删

  默认情况下,使用JNI时与native对应的JNI函数名都是Java包名(点替换为)类名方法名,使用javah生成的头文件函数名就是这样的格式。这样的格式的so库被反汇编时很容易就找到对应的方法。

JNIEXPORT jstring JNICALL Java_com_liuling_ndkjnidemo_JniUtils_getStringFromC
(JNIEnv *env, jclass obj) {
return (jstring)(*env)-> NewStringUTF(env, "I am string from jni");
}

  

上面是简单的一个JNI方法,我们将生成的so库使用IDA工具进行反汇编之后就能看到如下的内容:

在左边很容易就能找到Java_com_liuling_ndkjnidemo_JniUtils_getStringFromC这个方法:

双击该方法就能看到该方法反汇编之后的内容,这里返回的字符串”I am string from jni”就暴露出来了,如果是一些敏感信息比如一些key之类的东西,这样就存在着风险。

经上网搜索,发现有一种方法可以让JNI中的方法名不适用javah生成的风格,方法名随便取,并且可以将方法隐藏起来,反汇编之后找不到对应的方法,类似于Android中的混淆,加大了破解的难度。

这种方法的特点是:

  • 源码改动少,只需要添加JNI_Onload函数
  • 无需加解密so,就可以实现混淆so中的JNI函数
  • 后续可以添加so加解密,使破解难度更大

下面来看一个例子:

Java层代码

public class JniUtils {
static {
System.loadLibrary("NDKJNIDemo");//与build.gradle里面设置的so名字,必须一致
}
public static native String getStringFromC();
}

JNI层代码

第一步:我们要写一个JNI_Onload,来自定义JNI函数的函数名,要加入头文件#include

#include <assert.h>
#include "com_liuling_ndkjnidemo_JniUtils.h" #define JNIREG_CLASS "com/liuling/ndkjnidemo/JniUtils"//指定要注册的类 /**
* Table of methods associated with a single class.
*/
//绑定,注意,V,Z签名的返回值不能有分号“;”
//这里就是把JAVA层的getStringFromC()函数绑定到Native层的getStringc()函数,就无需使用原生的Java_com_xx_xx_classname_methodname这种恶心的函数命名方式了
static JNINativeMethod gMethods[] = {
{ "getStringFromC", "()Ljava/lang/String;", (void*)getStringc}, }; /*
* Register several native methods for one class.
*/ static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < ) {
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[])))
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 = -; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -;
}
assert(env != NULL); if (!registerNatives(env)) {//注册
return -;
} /* success -- return valid version number */ result = JNI_VERSION_1_4; return result;
}

第二步:Java层函数所对应的函数的实现:

__attribute__((section (".mytext"))) JNICALL jstring getStringc(JNIEnv *env, jclass obj) {
return (jstring)(*env)-> NewStringUTF(env, "I am string from jni22222");
}

这里的关键是,在函数前加上attribute((section (“.mytext”))),这样的话,编译的时候就会把这个函数编译到自定义的名叫”.mytext“的section里面去了。

最后一步:隐藏符号表,在Android.mk文件里面添加一句LOCAL_CFLAGS := -fvisibility=hidden

LOCAL_PATH := $(call my-dir)

local_c_includes := \
$(NDK_PROJECT_PATH) \ include $(CLEAR_VARS) LOCAL_CFLAGS := -fvisibility=hidden #隐藏符号表 LOCAL_MODULE := NDKJNIDemo LOCAL_SRC_FILES := com_liuling_ndkjnidemo_JniUtils.c
P
include $(BUILD_SHARED_LIBRARY)

这样就OK了,程序跑起来的效果和之前没有任何区别。

下面我们用IDA来看一下混淆后的效果:

在IDA里面看不到getStringc()函数,其次getStringc()函数的符号表是没有的,这个函数放在.mytext里面,而且整个逻辑是完全混淆的,数据和代码混在一起了(其实是IDA以为是ARM指令),这样就加大了so库破解的难度。

上面混淆方案的实现原理其实很简单,当在系统中调用System.loadLibrary函数时,该函数会找到对应的so库,然后首先试图找到”JNI_OnLoad”函数,如果该函数存在,则调用它。

JNI_OnLoad可以和JNIEnv的registerNatives函数结合起来,实现动态的函数替换。如果在so库中没有找到”JNI_OnLoad”函数,则会在调用的时候解析javah风格的函数。

一种JNI混淆方案的更多相关文章

  1. Android 代码混淆 混淆方案

    本篇文章:自己在混淆的时候整理出比较全面的混淆方法,比较实用,自己走过的坑,淌出来的路.请大家不要再走回头路,可能只要我们代码加混淆,一点不对就会导致项目运行崩溃等后果,有许多人发现没有打包运行好好地 ...

  2. 正确修改MySQL最大连接数的三种好用方案

    以下的文章主要介绍的是正确修改MySQL最大连接数的三种好用方案,我们大家都知道MySQL数据库在安装完之后,默认的MySQL数据库,其最大连接数为100,一般流量稍微大一点的论坛或网站这个连接数是远 ...

  3. 最经常使用的两种C++序列化方案的使用心得(protobuf和boost serialization)

    导读 1. 什么是序列化? 2. 为什么要序列化?优点在哪里? 3. C++对象序列化的四种方法 4. 最经常使用的两种序列化方案使用心得 正文 1. 什么是序列化? 程序猿在编写应用程序的时候往往须 ...

  4. 最常用的两种C++序列化方案的使用心得(protobuf和boost serialization)

    导读 1. 什么是序列化? 2. 为什么要序列化?好处在哪里? 3. C++对象序列化的四种方法 4. 最常用的两种序列化方案使用心得 正文 1. 什么是序列化? 程序员在编写应用程序的时候往往需要将 ...

  5. OAuth2 RFC 6749 规范提供的四种基本认证方案

    OAuth2 RFC 6749 规范提供了四种基本认证方案,以下针对这四种认证方案以及它们在本实现中的使用方式进行分别说面. 第一种认证方式: Authorization Code Grant (授权 ...

  6. Python几种并发实现方案的性能比较

    http://blog.csdn.net/permike/article/details/54846831 Python几种并发实现方案的性能比较 2017-02-03 14:33 1541人阅读 评 ...

  7. objc单例的两种安全实现方案

    所有转出博客园,请您注明出处:http://www.cnblogs.com/xiaobajiu/p/4122034.html objc的单例的两种安全实现方案 首先应该知道单例的实现有两大类,一个是懒 ...

  8. SSO的几种跨域方案

    在此只是记录一下自己在尝试SSO跨域实现的过程中学到的几种跨域方案,不包含任何例子和具体的实现方法. 最近在尝试SSO的跨域,看了好多资料,然后自己记录了一下可以实现的方法: ①跳转所有站点设置coo ...

  9. 一文掌握Redis的三种集群方案

    在开发测试环境中,我们一般搭建Redis的单实例来应对开发测试需求,但是在生产环境,如果对可用性.可靠性要求较高,则需要引入Redis的集群方案.虽然现在各大云平台有提供缓存服务可以直接使用,但了解一 ...

随机推荐

  1. 数位dp 的简单入门

    时间紧张,就不讲那么详细了. 之前一直被深搜代码误解,以为数位dp 其实就是记忆化深搜...(虽说爆搜确实很舒服而且还好想) 但是后来发现数位dp 的标准格式其实是 预处理 + dp ...... 数 ...

  2. Codeforces 877E - Danil and a Part-time Job 线段树+dfs序

    给一个有根树,1e5个节点,每个节点有权值0/.1,1e5操作:1.将一个点的子树上所有点权值取反2.查询一个点的子树的权值和   题解: 先深搜整颗树,用dfs序建立每个点对应的区间,等于把树拍扁成 ...

  3. sublime自动保存设置

    首选项——用户设置 (Preferences:Settings - User) 行末添加"save_on_focus_lost": true 注意用逗号分隔 保存即可 save_o ...

  4. VMware虚拟机安装Linux系统后IP配置(二)

    1.在NAT模式下自动获取IP 2.编辑网卡配置文件(通用) 3.Ctrl+L 清屏.重启网络服务后检查网络是否正常上网 修改后本机IP地址在linux中ping通本机IP 然而在本机却ping不通l ...

  5. VMware安装CentOS7系统

  6. 点击页面上的元素,页面删除removeChild()

    简单描述:最近做了一个图片上传,上传完成回显图片的时候,需要用到点击图片,从页面删除的效果,然后就找到了removeChild()方法,说实话,我刚看到的时候,就觉得这个问题已经解决了,但是却发现这个 ...

  7. cc.Node 的坐标空间与ACTION的学习

    1.创建二维的向量坐标 //创建向量坐标方法一 let new_pos1 = new cc.Vec2(100, 100); //创建向量坐标方法二 let new_pos2 = cc.v2(200, ...

  8. Softmax 回归 vs. k 个二元分类器

    如果你在开发一个音乐分类的应用,需要对k种类型的音乐进行识别,那么是选择使用 softmax 分类器呢,还是使用 logistic 回归算法建立 k 个独立的二元分类器呢? 这一选择取决于你的类别之间 ...

  9. Tomcat目录结构详解

    1.bin: 该目录下存放的是二进制可执行文件,如果是安装版,那么这个目录下会有两个exe文件:tomcat6.exe.tomcat6w.exe,前者是在控制台下启动Tomcat,后者是弹出UGI窗口 ...

  10. Linux 上使用LVM 扩展磁盘Size

    第一步:使用 fdisk -l 查看当前磁盘容量 fdisk -l 第二步: 使用 fdisk /dev/sda/ 为free space 添加新的分区 fdisk /dev/sda m :列出所有命 ...