1、申 请 I D:Colbert仔
2、个人邮箱:1285999474@qq.com
3、原创技术文章: 我发现有一种非常简单的方法,可以保护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文件都在附件。
|