原文:http://wenjh.com/?p=19

客户端软件发布后,可能会被他人破解使用。破解的目的可能是汉化、去广告、加入便捷功能,但也有可能是植入恶意模块。Androd 也存在这个问题。

Android 应用程序在发布前,都必须被签名。假设用于签名的密钥不被泄露或共享,我们可以认为,当被破解的Android软件重新打包后,其 “签名”必然与官方版本不同。

这个“签名”到底是什么东西?
如果你手头方便的话,可以随便解压一个APK。打开解压目录,“META-INF”里面就是签名内容保存的地方。签名过程的源码在这里,核心代码从(467行开始)。

首先,签名程序将遍历APK里的所有文件,并用SHA1算法得出每个文件的“摘要信息”。这些摘要信息就通通记录在“META-INF”目录的“MANIFEST.MF”文件中。(第170行)
所以得出解决方案一:可以比对MANIFEST.MF里各个摘要信息是否相同,来获知APK是否被修改。(比对样本是“官方”APK,下同)

真正的“签名”却还没开始,因为还没有用到公/私钥。MANIFEST.MF的生成,提供了用密钥进行签名的清单。
接下来,会对MANIFEST.MF里所有文件的“摘要信息”用私钥进行二次加密,采用的加密算法是“RSA”非对称加密算法。生成的信息记录在目录下".SF"结尾的文件里。(第475行,第259行)
和解决方案一相同,我们同样可以比对此文件,获知APK是否被篡改。姑且不占用“解决方案二”这个名头,继续往下分析。

最后,将公钥用PKCS#7算法加密保存在 “.RSA” 结尾的文件中(准确说是将包含了公钥等其它相关信息的数字证书加密保存。关于公/私钥、数字证书我搜了一篇文章)。这个文件如果直接用文本阅读器打开,会看到乱码。(第483行,第305行)
解决方案二:通过判断公钥是否一致,来获知软件是否被重新“打包”。

解决方案二看起来更简单一些:
1. 方案一,需要逐一比对apk里所有文件的摘要信息。
2. 软件正常升级后,apk里面的内容必然改变。因此方案一的比对只能在同版本之间进行。

OK。方案一的具体实现抛却不谈,因为有这个思路后,再没有任何技术困难。
方案二,需要突破的地方是:如何才能解码“.RSA” 文件,从而从中提取出公钥呢?

这里需要引入另一个源码文件:PackageParser.java。 核心代码是collectCertificates方法,能清楚看到通过数字证书解析公钥的过程。
但是这个类被加上了“hide”注解,所以并不能直接调用,但可以用反射去使用这些隐藏的API。
最后得出方法:

// 得到任意apk公钥信息的md5字符串public static String getApkSignatureMD5(String apkPath) throws Exception {Class clazz = Class.forName("android.content.pm.PackageParser");Method parsePackageMethod = clazz.getMethod("parsePackage", File.class, String.class, DisplayMetrics.class, int.class);Object packageParser = clazz.getConstructor(String.class).newInstance("");Object packag = parsePackageMethod.invoke(packageParser, new File(apkPath), null, getContext().getResources().getDisplayMetrics(), 0x0004);Method collectCertificatesMethod = clazz.getMethod("collectCertificates", Class.forName("android.content.pm.PackageParser$Package"), int.class);collectCertificatesMethod.invoke(packageParser, packag, PackageManager.GET_SIGNATURES);Signature mSignatures[] = (Signature[]) packag.getClass().getField("mSignatures").get(packag);Signature apkSignature = mSignatures.length > 0 ? mSignatures[0] : null;if(apkSignature != null) {                // 说明:没有提供md5的具体实现return StringUtils.md5(apkSignature.toCharsString());}return null;}

如果当前运行的软件要得到自身的公钥信息,只需要得到apk位置就可以了(每个软件(非内置)安装后,都会在/data/system里保存一份apk文件):

String path = context.getApplicationInfo()                     .publicSourceDir

接下来的事情就是找到比对时机。什么时候,采取什么形式来比对。
建议可以在一些关键步骤(如程序第一次启动、安装成功、登陆/注册等)启动比对。
若是b/s结构的程序,建议将比对放在服务器进行。也可以选择放在Native代码里进行比对(但是也更容易在破解后通过修改代码绕过)。

本文若有不周详之处,欢迎读者指正。

原创文章,转载请保留出处:http://wenjh.com/?p=19

更多相关文章

  1. 分享几点Android(安卓)开发中的小技巧吧。不知道算不算?
  2. 分享几点Android(安卓)开发中的小技巧吧。不知道算不算?
  3. Android中的签名验证(1)
  4. Android(安卓)事件全局监听(二)需要root权限 ,使用getevent监听Andr
  5. android 调用系统文件管理器
  6. Android菜鸟实训的第一天
  7. 分享几点Android(安卓)开发中的小技巧
  8. AndroidStudio Build过程解析
  9. NPM 和webpack 的基础使用

随机推荐

  1. HTML 5自动对焦会混乱CSS加载
  2. JSF和HTML表单的Unicode问题?
  3. SpringBoot + Thymeleaf + JPA创建一个we
  4. HTML 5是否需要``
  5. commons-fileupload文件上传、下载
  6. 在IE(所有版本)中打破JQuery自动完成组合框
  7. 【web】a标签点击时跳出确认框
  8. 针对不同的分辨率优化网站
  9. 为什么我的html页面的大小不固定啊?
  10. HTML5移动开发技术要点总结及各事件含义