首页
社区
课程
招聘
[原创]简单快捷的JNI接口函数混淆
2015-2-19 14:16 24909

[原创]简单快捷的JNI接口函数混淆

2015-2-19 14:16
24909
标 题: 【原创】简单快捷的JNI接口函数混淆
作 者: Colbert仔
时 间: 2015-02-19,14:15:58
链 接:http://bbs.pediy.com/showthread.php?p=1354517#post1354517

今天大年初一,大家新年好!
前一段时间学习了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文件都在附件。

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

[培训]《安卓高级研修班(网课)》月薪三万计划,掌 握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

上传的附件:
收藏
点赞1
打赏
分享
最新回复 (8)
雪    币: 2321
活跃值: (4028)
能力值: ( LV12,RANK:530 )
在线值:
发帖
回帖
粉丝
熊猫正正 9 2015-2-19 15:37
2
0
谢谢分享,一起学习研究
雪    币: 370
活跃值: (1181)
能力值: ( LV9,RANK:310 )
在线值:
发帖
回帖
粉丝
ThomasKing 6 2015-2-19 15:49
3
0
楼主新年快乐!
个人觉得楼主这样做有点欠妥。 想要函数没有导出名字,直接用static声明即可。另外,图中IDA修改成T模式并修正,就可以把函数给还原出来。f5后的代码:
int __fastcall sub_2064(int a1, int a2, int a3)
{
  int v3; // r5@1
  int v4; // r4@1
  const char *v5; // r0@1

  v3 = a3;
  v4 = a1;
  v5 = (const char *)(*(int (**)(void))(*(_DWORD *)a1 + 676))();
  if ( strcmp(v5, "mahuateng") )
    v3 = (*(int (__fastcall **)(int, signed int))(*(_DWORD *)v4 + 668))(v4, 8658);
  return v3;
}
雪    币: 228
活跃值: (50)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
Colbert仔 2015-2-19 16:18
4
0
谢谢大神指导!
你说的对,只能混淆函数名,而且只要修正为code 16模式(也就是T模式)就可以静态分析。
所以这种只是简单的对抗初级逆向人员的方法,后续可以加密自定义section,还有自定义elf head,加强保护。
雪    币: 18
活跃值: (41)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
deadxing 2015-4-13 18:51
5
0
请问,自定义section,还有自定义elf head,怎么恢复,让IDA可以静态分析?
雪    币: 244
活跃值: (26)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ShadoWWinL 2015-4-16 22:09
6
0
等他们解密的时候dump出来。
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
iewcwu 2016-7-8 15:27
7
0
请问怎么修正为code 16模式(也就是T模式) 呢? 谢谢指教
雪    币: 42
活跃值: (406)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
DragKing 2016-7-30 16:04
8
0
alt + g  把 0x0 改成0x1
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
小李葛格 2016-9-6 17:12
9
0
static JNINativeMethod gMethods[] = {
        { "jurl", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", (void*)check8},

};

的方法处,报错:

'check8' undeclared here (not in a function)

是怎么回事呢?
游客
登录 | 注册 方可回帖
返回