Android JNI接口混淆方案

来源:互联网 发布:淘宝店铺起名字大全 编辑:程序博客网 时间:2024/06/03 03:50

一. 背景

由于项目有一些比较敏感的判断函数放在了native层,比如是否被改包,是否被Hook,是否被监听,是否有代理,是否运行了一些敏感程序等等,这种接口在编写的时候为了方便一般会写成isModifyPkg,isHook,isListening等等这样的函数,由于是JNI方法,因此也不能混淆,所以很容易被居心不良者直接hook住Java层或者反编译后通过关键字查找到函数调用,从而改变判断逻辑。因此需要对这些接口进行一层混淆,增加被Hook或者被发现的难度。

二. 未混淆的JNI接口

我们可以先看看未被混淆的JNI接口有多容易被Hook或者被识别。我模拟了一个类

package cn.zarathustra.obscure;public class NotObscureJNI {    public static native boolean isModifyPkg();    public static native boolean isHook();    public static native boolean isListening();    public static native boolean hasSensitiveProcess();}

这就是我们一般声明的JNI方法,因为JNI方法不能被混淆,所以这样调用的意图很容易被人识破,反编译成smali之后就是这样的代码

.line 19invoke-static {}, Lcn/zarathustra/obscure/NotObscureJNI;->isHook()Z.line 20invoke-static {}, Lcn/zarathustra/obscure/NotObscureJNI;->hasSensitiveProcess()Z.line 21invoke-static {}, Lcn/zarathustra/obscure/NotObscureJNI;->isListening()Z.line 22invoke-static {}, Lcn/zarathustra/obscure/NotObscureJNI;->isModifyPkg()Z

甚至都不用hook native层都可以轻易的识破代码逻辑,再利用idap看看so


也很浅显易懂

三. 混淆JNI接口的方案

问题来了,怎么样能达到混淆的效果呢?

3.1 java层使用一个调用接口,利用opcode区分不同功能

由于Java的JNI方法和类是不能混淆的,因此需要让类的方法名尽可能的不好读,可以把类声明成A或者B这样,方法同理。如果每个功能使用一个接口,首先是会让代码更加难度,其次容易让别人逐个函数去hook来猜测用途,如果将所有需要的方法都写在一个函数里面,通过一个标识变量来区分具体的调用的功能,并且在Native层再进行区分,这样就能加大别破解的难度。

public static native boolean a(String opcode);
3.2 opcode进行加密,让意图不容易猜测

接下来就是Opcode怎么样去定义,首先不可能让其直接用”isListening”这样意图明显的字符串去定义,其次要保证opcode不会冲突,Hash算法就是一个不错的方案,比如OPCODE1代表isModifyPkg这个方法,利用Hash算法算出字符串“OPCODE1”的值,Hash算法我这里用了MD5(当然SHA256,SM3等更佳)。

//isModifyPkgpublic static String OPCODE1 = "805af30bbb66cb65accdea8b37890ec3";//isHookpublic static String OPCODE2 = "8727fd99374b1989fe854785b48f8dc6";//isListeningpublic static String OPCODE3 = "a2a5ef0cc5017e0c4da97a8e61705196";//hasSensitiveProcesspublic static String OPCODE4 = "dc2ad8b00cac1e98f15e67fd7f9248ab";

在Native层只要相应的用MD5去Hash出相应Opcode的值即可,如下:

//isModifyPkg#define OPCODE1 "OPCODE1"//isHook#define OPCODE2 "OPCODE2"//isListening#define OPCODE3 "OPCODE3"//hasSensitiveProcess#define OPCODE4 "OPCODE4"bool result = false;if (opcodeStr == MD5(OPCODE1)) {    result = true;} else if (opcodeStr == MD5(OPCODE2)) {    result = true;} else if (opcodeStr == MD5(OPCODE3)) {    result = true;} else if (opcodeStr == MD5(OPCODE4)) {    result = true;}

调用的方式变为:

ObscureJNI.a(ObscureJNI.OPCODE1);ObscureJNI.a(ObscureJNI.OPCODE2);ObscureJNI.a(ObscureJNI.OPCODE3);ObscureJNI.a(ObscureJNI.OPCODE4);

四. 总结

在进行了上面的混淆方式之后,可以看到反编译到Smali之后已经不能很清楚的看到函数意图,增加了篡改代码逻辑的难度

.line 25sget-object v0, Lcn/zarathustra/obscure/ObscureJNI;->OPCODE1:Ljava/lang/String;invoke-static {v0}, Lcn/zarathustra/obscure/ObscureJNI;->a(Ljava/lang/String;)Z.line 26sget-object v0, Lcn/zarathustra/obscure/ObscureJNI;->OPCODE2:Ljava/lang/String;invoke-static {v0}, Lcn/zarathustra/obscure/ObscureJNI;->a(Ljava/lang/String;)Z.line 27sget-object v0, Lcn/zarathustra/obscure/ObscureJNI;->OPCODE3:Ljava/lang/String;invoke-static {v0}, Lcn/zarathustra/obscure/ObscureJNI;->a(Ljava/lang/String;)Z.line 28sget-object v0, Lcn/zarathustra/obscure/ObscureJNI;->OPCODE4:Ljava/lang/String;invoke-static {v0}, Lcn/zarathustra/obscure/ObscureJNI;->a(Ljava/lang/String;)Z

native层也只有一个入口函数,内部的逻辑也没有之前那么好修改,当然要花心思还是能动的,但是如果配合一些so加壳,vmp,乱序插花,压缩之类的操作,这些逻辑就会变得坚不可摧了。
最后附上demo的代码:

github

csdn 文件共享

原创粉丝点击