这几年随着互联网的不断发展,Android App 也越来越多!但是随之而来的问题也越来越多,这其中比较令人头疼的问题就是:有些不法分子利用反编译技术破解 App,修改代码,之后再重新编译上传到应用商店!到时候,如果用户下载了这些应用,就可能造成个人信息或者金钱的丢失!所以,作为开发者的我们,有必要学习一些防止被破解的技术手段了!
现在想要破解一个 App, 大多数都是遵循着以下的几个步骤:
目前来讲大多数的破解只存在一二两步中,但是后面的几个步骤还是会有人弄的,所以我们不能掉以轻心呀!当然最后一步我们姑且不谈了!下面我就来一个一个的介绍防止这些步骤的方法!
目前来说,大多数人用的反编译工具都是:jdax,dex2jar,jd-gui等工具,但是对于我们来说有个好消息就是 这些工具很多都是开源的,我们可以在 Github
上找到这些工具的源码,我们可以通过阅读这些源码,从而找出防止他们反编译的方法,也就是让他在反编译你的 App 的时候,让他们的软件崩溃!对于这个我们可以忽略,因为去阅读这些代码并理解他们,是一项比较耗时的工作甚至有可能都读不通他们的程序,所以我们尽量把经历放在下面的几个步骤上!
其实并不能真正的做到防止别人查看我们的源代码,我们只是通过代码混淆的技术来使别人在查阅我们的源代码时变得更加的困难而已。在 Android 中我们可以使用 ProGuard 来混淆我们的代码。在这里先说明一下代码混淆的优缺点:
优点:
缺点:
对于第一个缺点的问题的解决方案是:保存 mappinp.txt 这个保存映射关系的文件,当 app 在混淆运行之后可能出现的问题,可以根据这个映射关系来快速定位出 bug 所在的位置!对于第二个缺点的问题的解决方案是:不混淆该类,如果不知道是哪个类那只能运行的时候调试了!接下来简要介绍一下 ProGuard 的常用语法:
-libraryjars class_path 应用的依赖包,如android-support-v4 -keep [,modifier,...] class_specification 不混淆某些类 -keepclassmembers [,modifier,...] class_specification 不混淆类的成员 -keepclasseswithmembers [,modifier,...] class_specification 不混淆类及其成员 -keepnames class_specification 不混淆类及其成员名 -keepclassmembernames class_specification 不混淆类的成员名 -keepclasseswithmembernames class_specification 不混淆类及其成员名 -assumenosideeffects class_specification 假设调用不产生任何影响,在proguard代码优化时会将该调用remove掉。如system.out.println和Log.v等等 -dontwarn [class_filter] 不提示warnning
在这里在给出 android 中标准的 proguard.cfg
文件内容
-optimizationpasses 5 -dontusemixedcaseclassnames -dontskipnonpubliclibraryclasses -dontpreverify -verbose -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* -keep public class * extends android.app.Activity -keep public class * extends android.app.Application -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class com.android.vending.licensing.ILicensingService -keepclasseswithmembernames class * { native <methods>; } -keepclasseswithmembernames class * { public <init>(android.content.Context, android.util.AttributeSet); } -keepclasseswithmembernames class * { public <init>(android.content.Context, android.util.AttributeSet, int); } -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); }
大家可以依据此模版在进行增删改查。另外,我还可以通过代码混淆来控制是否打印日志输出:
-assumenosideeffects class android.util.Log { public static *** d(...); public static *** v(...); }
在我写此文章是,有些牛逼的公司已经可以进行资源文件名的混淆了,这样就更加增大了代码阅读难度了!在这里给出该文章: 美团Android资源混淆保护实践
所谓的动态调试就是使用 检测调试器
这个检测很简单:就是当我们的软件检测到连接到调试器的时候,就终止程序!
首先,我们在 AndroidManifest.xml
中的 Application
标签中添加 android:debuggable="false"
这个属性,使程序不可调试,如果有人想调试,必定要修改这个值,之后我们只要在代码中添加下面短代码来检测该值是否变改变过,如果改变过就停止程序。
if((getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABL)!=0){ android.os.Process.killProcess(android.os.Process.myPid()); }
这样就可以解决这个问题了
代码在上传的时候是需要对 app 进行的签名的,而每个签名文件只有 app 开发者才有,所以我们可以根据签名的内容来进行对比。但是签名的文件过长不利于对比,我们用见面签名内容的 hashCode
来比较。获取这个值的代码:
public int getsignature(String packageName) { try { android.content.pm.Signature[] signatures= getPackageManager().getPackageInfo(packageName.toString(), PackageManager.GET_SIGNATURES).signatures; return signatures[0].hashCode(); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); return 0; } }
我们只要在本地打包之后获取到这个值,之后在每次程序运行的时候,在获取签名的 hashCode
的来与我们之前在本地打包的值进行比较就行了!如果不同就直接关闭程序就行了!
就是关键代码使用 C/C++ 代码来写,没有研究过
在现有的代码外加一层 C/C++ 代码来保护,也没有研究过