前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >android 防止反编译的若干方法

android 防止反编译的若干方法

作者头像
xiangzhihong
发布2018-02-01 16:39:02
2.3K0
发布2018-02-01 16:39:02
举报
文章被收录于专栏:向治洪向治洪向治洪

第一种方式:混淆策略

混淆策略是每个应用必须增加的一种防护策略,同时他不仅是为了防护,也是为了减小应用安装包的大小,所以他是每个应用发版之前必须要添加的一项功能,现在混淆策略一般有两种:

对代码的混淆

我们在反编译apk之后,看到的代码类名,方法名,已经代码格式看起来不像正常的Android项目代码,那么这时候就会增加阅读难度,增加破解难度,像这样的代码混淆:

我们一般现在的破解查看Java层代码就是两种方式:

一种是直接先解压classes.dex文件出来,使用dex2jar工具转化成jar文件,然后再用jd-gui工具进行查看类结构

一种是使用apktool工具直接反编译apk,得到smali源码,阅读smali源码

不过这种代码混淆有时候在一定程度上能够增加混淆策略,但是有时候也不是很安全,因为我们知道我们在破解的过程中一般是找程序的入口,那么这些入口一般都是Application或者是MainActivity之类的,但是这些Android中的组件类是不能进行混淆的,所以我们还是有入口可寻,能够找到入口代码,然后进行跟踪。

2、对工程资源的混淆

我们上面说到了对代码的混淆能够增加一定的代码阅读难度,有时候我们为了防止资源的保护也是可以做混淆的,这个资源混淆原理这里就不多解释了,微信团队已经将这个功能开源,不了解的同学可以转战github查看:

https://github.com/shwenzhang/AndResGuard

主要是对资源的混淆

第二种方式:应用的签名

我们知道Android中的每个应用都是有一个唯一的签名。但是这个签名在之前是可以被伪造,并实现为此打包的。为了防止应用被二次打包,或者是需要破解我们的apk的操作,在入口处添加签名验证,如果发现应用的签名不正确就立即退出程序,我们可以在应用启动的时候获取应用的签名值,然后和正规的签名值作比对,如果不符合就直接退成程序即可。

 public static String getSign(){
      Context context= StockApplication.getInstance();
	   try {
		   PackageInfo packageInfo=context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
		   Signature[] signatures=packageInfo.signatures;
		   StringBuilder builder=new StringBuilder();
		   for (Signature signature:signatures){
			   builder.append(signature.toCharsString());
		   }
		   return builder.toString();
	   } catch (PackageManager.NameNotFoundException e) {
		   e.printStackTrace();
	   }
	   return "";
   }

我们可以在application启动的时候,如果不是我们app的签名,那么app直接退出

 private void checkSign() {
      if (!Utils.isMyApp()){
         //直接退出
        }
    }
public static boolean isMyApp(){
     String signStr=getSign();
     return SIGN.equals(signStr);
   }

第三种方式:修改Naitve函数名

这个方法其实不太常用,因为他的安全措施不是很强大的,但是也是可以起到一定的障眼法策略,在说这个知识点的时候,我们先来了解一下so加载的流程:

在Android中,当程序在java层运行System.loadLibrary("jnitest");这行代码后,程序会去载入libjnitest.so文件,与此同时,产生一个"Load"事件,这个事件触发后,程序默认会在载入的.so文件的函数列表中查找JNI_OnLoad函数并执行,与"Load"事件相对,当载入的.so文件被卸载时,“Unload”事件被触发,此时,程序默认会去在载入的.so文件的函数列表中查找JNI_OnUnload函数并执行,然后卸载.so文件。需要注意的是,JNI_OnLoad与JNI_OnUnload这两个函数在.so组件中并不是强制要求的,用户也可以不去实现,java代码一样可以调用到C组件中的函数,之所以在C组件中去实现这两个函数(特别是JNI_OnLoad函数),往往是做一个初始化工作或“善后”工作。可以这样认为,将JNI_ONLoad看成是.so组件的初始化函数,当其第一次被装载时被执行(window下的dll文件也可类似的机制,在_DLL_Main()函数中,通过一个swith case语句来识别当前是载入还是卸载)。将JNI_OnUnload函数看成是析构函数,当其被卸载时被调用。由此看来,就不难明白为什么很多jni C组件中会实现JNI_OnLoad这个函数了。 一般情况下,在C组件中的JNI_OnLoad函数用来实现给VM注册接口,以方便VM可以快速的找到Java代码需要调用的C函数。(此外,JNI_OnLoad函数还有另外一个功能,那就是告诉VM此C组件使用那一个JNI版本,如果未实现JNI_OnLoad函数,则默认是JNI 1.1版本)。

应用层的Java类别通过VM而调用到native函数。一般是通过VM去寻找*.so里的native函数。如果需要连续呼叫很多次,每次都需要寻找一遍,会多花许多时间。此时,C组件开发者可以将本地函数向VM进行注册,以便能加快后续调用native函数的效率.可以这么想象一下,假设VM内部一个native函数链表,初始时是空的,在未显式注册之前此native函数链表是空的,每次java调用native函数之前会首先在此链表中查找需要查找需要调用的native函数,如果找到就直接使用,如果未找到,得再通过载入的.so文件中的函数列表中去查找,且每次java调用native函数都是进行这样的流程,因此,效率就自然会下降,为了克服这样现象,我们可以通过在.so文件载入初始化时,即JNI_OnLoad函数中,先行将native函数注册到VM的native函数链表中去,这样一来,后续每次java调用native函数时都会在VM中的native函数链表中找到对应的函数,从而加快速度

第四种方式:反调试异常检测

这种方式其实是为了应对现在很多破解者使用IDA进行动态方式调试so文件,从而获取重要的信息,如果还不知道如何使用IDA进行动态调试so文件的同学可以查看这篇文章:Android中使用IDA进行动态调试so文件 ,看完这篇文章之后,我们可以知道IDA进行so动态调试是基于进程的注入技术,然后使用Linux中的ptrace机制,进行调试目标进程的,那么ptrace机制有一个特点,就是如果一个进程被调试了,在他进程的status文件中有一个字段TracerPid会记录调试者的进程id值,比如:

查看文件:/proc/[myPid]/status

在第六行,有一个TracerPid字段,就是记录了调试者的进程id

那么我们就可以这么做来达到反调试的功效了,就是我们可以轮训的遍历自己进程的status文件,然后读取TracerPid字段值,如果发现他大于0,那么就代表着自己的应用在被人调试,所以就立马退出程序。原理知道了,代码实现也很简单,这里用pthread创建一个线程,然后进行轮训操作:

使用pthread_create创建一个线程,线程启动之后执行thread_function函数

看看thread_funcation函数:

开始轮训,读取TracerPid字段的值,发现大于0,就立马退出程序,我们运行结果看看:

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-05-31 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第一种方式:混淆策略
  • 第二种方式:应用的签名
  • 第三种方式:修改Naitve函数名
  • 第四种方式:反调试异常检测
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档