本篇我们来看看android的签名机制。发布出来的apk都是有META-INF文件夹,里面包含如下三个文件:

  下面来一一解释这三个文件的作用(打包apk时签名过程):SignApk.main()

  1、MANIFEST.MF:/build/tools/signapk/SignApk.java-addDigestsToManifest()

    遍历APK包中除了META-INF\ 文件夹以外的所有文件,利用SHA1算法生成这些文件的消息摘要,然后转化为对应的base64编码。MANIFEST.MF存储的是文件的摘要值,保证完整性,防止文件被篡改。anzhi的MANIFEST.MF如下:

  1. // Manifest-Version: 1.0
  2. // Created-By: 1.0 (Android)
  3.  
  4. // Name: res/layout/act_header.xml
  5. // SHA1-Digest: tiVog/vCbIpPfnZbtZOxN28MKIE=
  6.  
  7. // Name: res/drawable-hdpi/bg_top_list_index_red.9.png
  8. // SHA1-Digest: Y91AQINPN6Y7pkZ6qnQuSVcwLfw=
  9. ......

  2、CERT.SF:/build/tools/signapk/SignApk.java-writeSignatureFile()

    xx.SF文件(xx为使用者证书的自定义别名,默认为CERT,即CERT.SF),保存的是MANIFEST.MF的摘要值, 以及MANIFEST.MF中每一个摘要项的摘要值,然后转化成对应的base64编码。虽然该文件的后缀名.sf(SignatureFile)看起来是签名文件,但是并没有私钥参与运算,也不保存任何签名内容。anzhi的CERT.SF:

  1. // Signature-Version: 1.0
  2. // Created-By: 1.0 (Android)
  3. // SHA1-Digest-Manifest: GBijl3ytIYpo7tJr1NgfkgssLWA=
  4.  
  5. // Name: res/layout/act_header.xml
  6. // SHA1-Digest: 2KdEJyEwgrLAHZTdwEpnH6Ud4pE=
  7.  
  8. // Name: res/drawable-hdpi/bg_top_list_index_red.9.png
  9. // SHA1-Digest: jfdrZJNisF8zAIexeGba0VuZSMU=
  10. ......

   3、CERT.RSA:/build/tools/signapk/SignApk.java-writeSignatureBlock()

    .RSA / .DSA文件(后缀不同采用的签名算法不同,.RSA使用的是RSA算法, .DSA使用的是数字签名算法DSA,目前APK主要使用的是这两种算法),保存的是第二项.SF文件的数字签名,同时还会包括签名采用的数字证书(公钥—参考资料1)。特别说明,当使用多重证书签名时,每一个.sf文件必须有一个.RSA/.DSA文件与之对应,也就是说使用证书CERT1签名时有CERT1.SF和CERT1.RSA,同时采用证书CERT2签名时又会生成CERT2.SF和CERT2.RSA。

  我们看到这三个文件层层关联,MANIFEST.MF保证apk完整性,CERT.SF对MANIFEST.MF hash来校验,CERT.RSA利用密钥对CERT.SF加密来校验CERT.SF(这里有个问题发现没,若CERT.RSA的密钥被更换,那么...)。但我们也必须认清几点

1、 Android签名机制其实是对APK包完整性和发布机构唯一性的一种校验机制。

2、 Android签名机制不能阻止APK包被修改,但修改后的再签名无法与原先的签名保持一致。(拥有私钥的情况除外)。

3、 APK包加密的公钥就打包在APK包内,且不同的私钥对应不同的公钥。换句话言之,不同的私钥签名的APK公钥也必不相同。所以我们可以根据公钥的对比,来判断私钥是否一致。

  刚刚上面说了CERT.RSA的密钥的被更换,事情就大条了。现在我们看看在安装apk时android中是如何进行签名验证的。

  1. /libcore/luni/src/main/java/java/util/jar/JarVerifier.java
    synchronized boolean readCertificates() {
  2. ...
  3. Iterator<String> it = metaEntries.keySet().iterator();
  4. while (it.hasNext()) {
  5. String key = it.next();
  6. if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
  7. verifyCertificate(key);
  8. // Check for recursive class load
  9. if (metaEntries == null) {
  10. return false;
  11. }
  12. it.remove();
  13. }
  14. }
  15. return true;
  16. }

  readCertificates找以".DSA"、".RSA"、".EC"结尾的文件,让verifyCertificate来校验

  1. private void verifyCertificate(String certFile) {
  2. // Found Digital Sig, .SF should already have been read
  3. String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF";
  4. byte[] sfBytes = metaEntries.get(signatureFile);
  5. if (sfBytes == null) {
  6. return;
  7. }
  8.  
  9. byte[] manifest = metaEntries.get(JarFile.MANIFEST_NAME);
  10. // Manifest entry is required for any verifications.
  11. if (manifest == null) {
  12. return;
  13. }
  14.  
  15. byte[] sBlockBytes = metaEntries.get(certFile);
  16. try {//verifySignature验证SF文件
  17. Certificate[] signerCertChain = JarUtils.verifySignature(
  18. new ByteArrayInputStream(sfBytes),
  19. new ByteArrayInputStream(sBlockBytes));
  20. ......    
  21. // Verify manifest hash in .sf file
  22. Attributes attributes = new Attributes();
  23. HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
  24. try {
  25. ManifestReader im = new ManifestReader(sfBytes, attributes);
  26. im.readEntries(entries, null);
  27. } catch (IOException e) {
  28. return;
  29. }
  30. // Use .SF to verify the mainAttributes of the manifest
  31. // If there is no -Digest-Manifest-Main-Attributes entry in .SF
  32. // file, such as those created before java 1.5, then we ignore
  33. // such verification.
  34. if (mainAttributesEnd > 0 && !createdBySigntool) {
  35. String digestAttribute = "-Digest-Manifest-Main-Attributes";
  36. if (!verify(attributes, digestAttribute, manifest, 0, mainAttributesEnd, false, true)) {
  37. throw failedVerification(jarName, signatureFile);
  38. }
  39. }
  40.  
  41. // Use .SF to verify the whole manifest.
  42. String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";
  43. if (!verify(attributes, digestAttribute, manifest, 0, manifest.length, false, false)) {
  44. Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();
  45. while (it.hasNext()) {
  46. Map.Entry<String, Attributes> entry = it.next();
  47. Manifest.Chunk chunk = man.getChunk(entry.getKey());
  48. if (chunk == null) {
  49. return;
  50. }
  51. if (!verify(entry.getValue(), "-Digest", manifest,
  52. chunk.start, chunk.end, createdBySigntool, false)) {
  53. throw invalidDigest(signatureFile, entry.getKey(), jarName);
  54. }
  55. }
  56. }
  57. ......
  58. }

   代码流程很清晰,

   1、RSA验证SF不被篡改——verifySignature

   2、SF验证MF文件不被篡改

      在哪里验证apk文件有没有篡改啊?(即验证MF文件和app文件,等下分析哦)

  继续看verifySignature(不要忘了我们是来看RSA中的密钥如何认证的哦);但在分析源码之前你先看参考资料1和下面这幅证书链

                                                 证书链示意图

/libcore/luni/src/main/java/org/apache/harmony/security/utils/JarUtils.java

  1. public static Certificate[] verifySignature(InputStream signature, InputStream
  2. signatureBlock) throws IOException, GeneralSecurityException {
  3. ......
  4. return createChain(certs[issuerSertIndex], certs);
  5. }
  1. private static X509Certificate[] createChain(X509Certificate signer, X509Certificate[] candidates) {
  2. LinkedList chain = new LinkedList();
  3. chain.add(0, signer);
  4.  
  5. // Signer is self-signed
  6. if (signer.getSubjectDN().equals(signer.getIssuerDN())){
  7. return (X509Certificate[])chain.toArray(new X509Certificate[1]);
  8. }
  9.  
  10. Principal issuer = signer.getIssuerDN();
  11. X509Certificate issuerCert;
  12. int count = 1;
  13. while (true) {
  14. issuerCert = findCert(issuer, candidates);
  15. if( issuerCert == null) {
  16. break;
  17. }
  18. chain.add(issuerCert);
  19. count++;
  20. // 递归到根认证CA
  21. if (issuerCert.getSubjectDN().equals(issuerCert.getIssuerDN())) {
  22. break;
  23. }
  24. issuer = issuerCert.getIssuerDN();
  25. }
  26. return (X509Certificate[])chain.toArray(new X509Certificate[count]);
  27. }
  28.  
  29. private static X509Certificate findCert(Principal issuer, X509Certificate[] candidates) {
  30. for (int i = 0; i < candidates.length; i++) {
  31. // 只用字符串来判断
  32. if (issuer.equals(candidates[i].getSubjectDN())) {
  33. return candidates[i];
  34. }
  35. }
  36. return null;
  37. }

  看上图证书链我们可知,owner证书有效的前提是CA证书有效,而CA证书有效的前提是ROOT CA证书有效,ROOT CA证书的有效性由操作系统验证。而在android系统里,这部分由createChain函数来执行。createChain中用owner证书的IssuserDN—CA通过findCert函数来查找是否存在CA证书。findCert里遍历证书查看是否有证书的subjectDN == CA,如果有则表示此证书为CA证书(如果不理解请继续看参考资料1和证书链示意图)。看得出这个findCert太随意了值找证书而没有Verify signature,导致这里有bug,对此谷歌的修复方案如下

  1. private static X509Certificate findCert(Principal issuer, X509Certificate[] candidates, X509Certificate subjectCert, boolean chainCheck) {
  2. for (int i = 0; i < candidates.length; i++) {
  3. if (issuer.equals(candidates[i].getSubjectDN())) {
  4. if (chainCheck) {
  5. try {
  6. subjectCert.verify(candidates[i].getPublicKey());
  7. } catch (Exception e) {
  8. continue;
  9. }
  10. }
  11. return candidates[i];
  12. }
  13. }
  14. return null;
  15. }

  ok,签名原理搞清楚了,我们来看看上面提到的bug利用,此bug存在android4.4.1以下的所有版本中。

参考资料:

1、数字证书原理

2、【原创】Android证书验证存漏洞 开发者身份信息可被篡改

3、Android 签名验证机制

4、Android FakeID(Google Bug 13678484) 漏洞详解

5、FakeID签名漏洞分析及利用(Google Bug 13678484)

android签名分析及漏洞修复的更多相关文章

  1. android添加账户流程分析涉及漏洞修复

    android修复了添加账户代码中的2处bug,retme取了很酷炫的名字launchAnyWhere.broadAnywhere(参考资料1.2).本文顺着前辈的思路学习bug的原理和利用思路. 我 ...

  2. 2016/2/26Android实习笔记(Android签名和aapt)

    1. 我们平时用eclipse或Android Studio开发得到的android应用程序,其实已经添加有默认的debug签名了. Android系统要求所有的程序经过数字签名才能安装,如果没有可用 ...

  3. [Android Pro] Android签名与认证详细分析之二(CERT.RSA剖析)

    转载自: http://www.thinksaas.cn/group/topic/335449/ http://blog.csdn.net/u010571535/article/details/899 ...

  4. CVE-2011-0104:Microsoft Office Excel 栈溢出漏洞修复分析

    0x01 前言 上一篇讲到了 CVE-2011-0104 漏洞的成因和分析的方法,并没有对修复后的程序做分析.之后在一次偶然的情况下,想看一看是怎么修复的,结果却发现了一些问题 环境:修复后的 EXC ...

  5. [Android Pro] Android签名与认证详细分析之一(CERT.RSA剖析)

    转载自:http://www.thinksaas.cn/group/topic/335450/ 一.Android签名概述 我们已经知道的是:Android对每一个Apk文件都会进行签名,在Apk文件 ...

  6. Android证书验证存漏洞 开发者身份信息可被篡改(转)

    原帖地址:http://bbs.pediy.com/showthread.php?p=1335278#post1335278 近期在国内网易,雷锋网等网站爆出谷歌市场上的索尼官方的备份与恢复应用&qu ...

  7. Android签名机制

    Android APK 签名比对 发布过Android应用的朋友们应该都知道,Android APK的发布是需要签名的.签名机制在Android应用和框架中有着十分重要的作用. 例如,Android系 ...

  8. Android 热补丁和热修复

    参考: 各大热补丁方案分析和比较 Android App 线上热修复方案 1. Xposed Github地址:https://github.com/rovo89/Xposed 项目描述:Xposed ...

  9. Android签名机制---签名过程

    大神文章:http://blog.csdn.net/jiangwei0910410003/article/details/50402000 一.知识点 1.数据摘要(数据指纹).签名文件,证书文件 2 ...

随机推荐

  1. LNMP配置——Nginx配置 ——访问控制

    #vi /usr/local/nginx/conf/vhost/test.com.conf 写入: server { listen 80; server_name test.com test1.com ...

  2. 某SQL注入--报错注入payload

    1.证明存在sql注入,根据这个报错语句,,有'  有% 2.payload  闭合语句 %' or (select extractvalue("anything",concat( ...

  3. css盒布局-省份选择盘的实现

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...

  4. unbutu的dpkg被中断的解决办法

    直接sudo apt update进行重新配置就行

  5. postman接口自动化测试之添加Tests检查点

    一.概念 Postman的Tests本质上是JavaScript代码,通过我们编写测试代码,每一个Tests返回True,或是False,以判断接口返回的正确性. 其实,每一个Tests实际上就是一个 ...

  6. Java单链表反转图文详解

    Java单链表反转图文详解 最近在回顾链表反转问题中,突然有一些新的发现和收获,特此整理一下,与大家分享 背景回顾 单链表的存储结构如图: 数据域存放数据元素,指针域存放后继结点地址 我们以一条 N1 ...

  7. 前端 | JS Promise:axios 请求结果后面的 .then() 是什么意思?

    Promise 是JS中一种处理异步操作的机制,在现在的前端代码中使用频率很高.Promise 这个词可能有点眼生,但你肯定见过 axios.get(...).then(res => {...} ...

  8. 阳明-K8S训练营全部文档-2020年08月11日14:59:02更新

    阳明-K8S训练营全部文档 Docker 基础 简介 安装 基本操作 Dockerfile Dockerfile最佳实践 Kubernetes 基础 简介 安装 资源清单 Pod 原理 Pod 生命周 ...

  9. MacBook读写移动硬盘

    在MacBook上插入移动硬盘,只能读取,不能写入.这是因为移动硬盘的格式是NTFS,MacBook不支持写入,有三种方法: 1. 改变移动硬盘的格式,格式化为可以读写的exFAT等格式,但存储的文件 ...

  10. Chapter 2 简单DC-DC变换器稳态分析小结

    Chapter 2 简单DC-DC变换器稳态分析小结 1 本章重点 1.1 小纹波近似 所谓小纹波近似就是DC-DC变换器的稳态分析中,假定开关频率次的纹波相对于直流分量而言非常小,可以将其忽略进行各 ...