简单快捷的JNI接口函数混淆

前一段时间学习了ThomasKing大神的【简单粗暴的so加解密实现】,链接如下:
http://bbs.pediy.com/showthread.php?t=191649

实践出真知,在研究的过程中,我突然发现有一种非常简单的方法,可以保护so中的JNI接口函数(有源码情况下)。

这种方法的特点是:
1.源码改动少,只需要添加JNI_Onload函数
2.无需加解密so,就可以实现混淆so中的JNI函数
3.后续可以添加so加解密,使破解难度更大



下面开始讲一下这种方法的几个关键的实现过程:
1.添加JNI_Onload函数,自定义JNI的函数名(无需使用Java_com_xx_xx_classname_methodname)
2.在JNI接口函数的定义加上__attribute__((section (".mytext"))),把JNI添加到自定义的section
3.在Android.mk文件加上LOCAL_CFLAGS := -fvisibility=hidden隐藏符号表


实际上这种方法的原理是使用了JNI_Onload 混淆了函数名,并且把目标函数放到自定义的section里面,并且使用code32(ARM)模式,对付一般的破解者还是挺有效的。

下面我们直接看一个例子:
1.假设我们JAVA层的代码如下:

static {
    System.loadLibrary("check7");
  }
public native String check7(String name);


chek7是一个简单的白名单函数,输入string,返回string.

2.再来看看Native层的check7.c是怎么写的:

第一步:首先我们要写一个JNI_Onload,来自定义JNI函数的函数名,要加入头文件#include <assert.h>

代码如下:
//自定义的JNI_OnLoad,用于混淆native函数名
#define JNIREG_CLASS "com/xx/xx/xxService"//指定要注册的类

     /**
     * Table of methods associated with a single class.
     */

     static JNINativeMethod gMethods[] = {//绑定,注意,V,Z签名的返回值不能有分号“;”
     //这里就是把JAVA层的check7()函数绑定到Native层的check8()函数,就无需使用原生的Java_com_xx_xx_classname_methodname这种恶心的函数命名方式了
     { "check7", "(Ljava/lang/String;)Ljava/lang/String;", (void*)check8},

     };

     /*
     * 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) < 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(vm, (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;
     }

第二步:看完JNI_Onload的实现,我们再看一下chek8函数的实现:

//JNI
__attribute__((section (".mytext")))jstring check8( JNIEnv* env,jobject thiz,jstring name)
{
    const char *str;
    str= (*env)->GetStringUTFChars(env,name,NULL);

  if (strcmp(str,"mahuateng")==0){
    return name;
  }
    else {
      return (*env)->NewStringUTF(env, "cann't find this account");
    }
}

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

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

例子:
include $(CLEAR_VARS)
LOCAL_LDLIBS := -llog
LOCAL_MODULE    := check7
LOCAL_CFLAGS := -fvisibility=hidden
#用来隐藏符号表
LOCAL_SRC_FILES  := check7.c
include $(BUILD_SHARED_LIBRARY)

这样就大功告成了!

下面我们用IDA来看一下混淆的效果:
在IDA的Exports里面看不到check8函数,其次check8函数的符号表是没有的,这个函数放在.mytext里面,而且整个逻辑是完全混淆的,数据和代码混在一起了(其实是IDA以为是ARM指令)

.mytext:00002064 loc_2064                                ; DATA XREF: .data:0000400Co
.mytext:00002064                 LDCNE   p5, c11, [R5], {0x38}
.mytext:00002068                 MOVCS   R6, #0x20000
.mytext:0000206C                 LDMPLIA R3, {R0,R1,R3,R4,R7}^
.mytext:00002070                 ANDCS   R1, R0, #0x2900
.mytext:00002074                 LDRMI   R1, [R8,R4,LSL#24]
.mytext:00002078                 LDRMIBT R4, [R9],#-0x908
.mytext:0000207C                 STC     p7, c15, [R0,#0x3F8]
.mytext:00002080                 ANDLE   R2, R8, R0,LSL#16
.mytext:00002084                 STMMIDB R6, {R1,R5,R11,SP,LR}
.mytext:00002088                 ADDEQS  R2, R11, R7,LSR#7
.mytext:0000208C                 LDMPLIA R3, {R0,R3-R6,R10,LR}^
.mytext:00002090                 LDRMI   R1, [R8,R0,LSR#24]
.mytext:00002094                 STCNE   p12, c1, [R8],#-0x14
.mytext:00002094 ; ---------------------------------------------------------------------------
.mytext:00002098                 DCB 0x38 ; 8
.mytext:00002099                 DCB 0xBD ; 
.mytext:0000209A                 DCB 0xC0 ; 
.mytext:0000209B                 DCB 0x46 ; F
.mytext:0000209C                 DCB 0x4A ; J
.mytext:0000209D                 DCB    1
.mytext:0000209E                 DCB    0
.mytext:0000209F                 DCB    0
.mytext:000020A0                 DCB 0x42 ; B
.mytext:000020A1                 DCB    1
.mytext:000020A2                 DCB    0
.mytext:000020A3                 DCB    0
.mytext:000020A3 ; .mytext       ends

大家可以试一下这种JNI接口函数混淆的方法,不仅简单快捷,而且扩展性良好

例子的c文件和so文件都在附件。

大家喜欢的话多支持一下我哈,后续还有更多好玩的加密混淆方案放出来,敬请期待!

posted @ 2016-09-30 10:37  飞晨信息  阅读(972)  评论(0)    收藏  举报