android签名分析及漏洞修复
本篇我们来看看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如下:
// Manifest-Version: 1.0
// Created-By: 1.0 (Android) // Name: res/layout/act_header.xml
// SHA1-Digest: tiVog/vCbIpPfnZbtZOxN28MKIE= // Name: res/drawable-hdpi/bg_top_list_index_red.9.png
// SHA1-Digest: Y91AQINPN6Y7pkZ6qnQuSVcwLfw=
......
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:
// Signature-Version: 1.0
// Created-By: 1.0 (Android)
// SHA1-Digest-Manifest: GBijl3ytIYpo7tJr1NgfkgssLWA= // Name: res/layout/act_header.xml
// SHA1-Digest: 2KdEJyEwgrLAHZTdwEpnH6Ud4pE= // Name: res/drawable-hdpi/bg_top_list_index_red.9.png
// SHA1-Digest: jfdrZJNisF8zAIexeGba0VuZSMU=
......
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中是如何进行签名验证的。
/libcore/luni/src/main/java/java/util/jar/JarVerifier.java
synchronized boolean readCertificates() {
...
Iterator<String> it = metaEntries.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {
verifyCertificate(key);
// Check for recursive class load
if (metaEntries == null) {
return false;
}
it.remove();
}
}
return true;
}
readCertificates找以".DSA"、".RSA"、".EC"结尾的文件,让verifyCertificate来校验
private void verifyCertificate(String certFile) {
        // Found Digital Sig, .SF should already have been read
        String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF";
        byte[] sfBytes = metaEntries.get(signatureFile);
        if (sfBytes == null) {
            return;
        }
       byte[] manifest = metaEntries.get(JarFile.MANIFEST_NAME);
       // Manifest entry is required for any verifications.
       if (manifest == null) {
           return;
       }
        byte[] sBlockBytes = metaEntries.get(certFile);
        try {//verifySignature验证SF文件
            Certificate[] signerCertChain = JarUtils.verifySignature(
                    new ByteArrayInputStream(sfBytes),
                    new ByteArrayInputStream(sBlockBytes));
         ......    
    // Verify manifest hash in .sf file
        Attributes attributes = new Attributes();
        HashMap<String, Attributes> entries = new HashMap<String, Attributes>();
        try {
            ManifestReader im = new ManifestReader(sfBytes, attributes);
            im.readEntries(entries, null);
        } catch (IOException e) {
        return;
        }
        // Use .SF to verify the mainAttributes of the manifest
        // If there is no -Digest-Manifest-Main-Attributes entry in .SF
        // file, such as those created before java 1.5, then we ignore
        // such verification.
        if (mainAttributesEnd > 0 && !createdBySigntool) {
            String digestAttribute = "-Digest-Manifest-Main-Attributes";
            if (!verify(attributes, digestAttribute, manifest, 0, mainAttributesEnd, false, true)) {
                throw failedVerification(jarName, signatureFile);
            }
        }
        // Use .SF to verify the whole manifest.
        String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";
        if (!verify(attributes, digestAttribute, manifest, 0, manifest.length, false, false)) {
            Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();
            while (it.hasNext()) {
                Map.Entry<String, Attributes> entry = it.next();
                Manifest.Chunk chunk = man.getChunk(entry.getKey());
                if (chunk == null) {
                    return;
                }
                if (!verify(entry.getValue(), "-Digest", manifest,
                        chunk.start, chunk.end, createdBySigntool, false)) {
                    throw invalidDigest(signatureFile, entry.getKey(), jarName);
               }
            }
        }
        ......
   }
代码流程很清晰,
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
public static Certificate[] verifySignature(InputStream signature, InputStream
signatureBlock) throws IOException, GeneralSecurityException {
......
return createChain(certs[issuerSertIndex], certs);
}
private static X509Certificate[] createChain(X509Certificate  signer, X509Certificate[] candidates) {
        LinkedList chain = new LinkedList();
        chain.add(0, signer);
        // Signer is self-signed
        if (signer.getSubjectDN().equals(signer.getIssuerDN())){
            return (X509Certificate[])chain.toArray(new X509Certificate[1]);
        }
        Principal issuer = signer.getIssuerDN();
        X509Certificate issuerCert;
        int count = 1;
        while (true) {
            issuerCert = findCert(issuer, candidates);
            if( issuerCert == null) {
                break;
            }
            chain.add(issuerCert);
            count++;
            // 递归到根认证CA
            if (issuerCert.getSubjectDN().equals(issuerCert.getIssuerDN())) {
                break;
            }
            issuer = issuerCert.getIssuerDN();
        }
        return (X509Certificate[])chain.toArray(new X509Certificate[count]);
    }
    private static X509Certificate findCert(Principal issuer, X509Certificate[] candidates) {
        for (int i = 0; i < candidates.length; i++) {
            // 只用字符串来判断
            if (issuer.equals(candidates[i].getSubjectDN())) {
                return candidates[i];
            }
        }
        return null;
    }
看上图证书链我们可知,owner证书有效的前提是CA证书有效,而CA证书有效的前提是ROOT CA证书有效,ROOT CA证书的有效性由操作系统验证。而在android系统里,这部分由createChain函数来执行。createChain中用owner证书的IssuserDN—CA通过findCert函数来查找是否存在CA证书。findCert里遍历证书查看是否有证书的subjectDN == CA,如果有则表示此证书为CA证书(如果不理解请继续看参考资料1和证书链示意图)。看得出这个findCert太随意了值找证书而没有Verify signature,导致这里有bug,对此谷歌的修复方案如下
private static X509Certificate findCert(Principal issuer, X509Certificate[] candidates, X509Certificate subjectCert, boolean chainCheck) {
    for (int i = 0; i < candidates.length; i++) {
        if (issuer.equals(candidates[i].getSubjectDN())) {
            if (chainCheck) {
                try {
                    subjectCert.verify(candidates[i].getPublicKey());
                } catch (Exception e) {
                    continue;
                }
            }
            return candidates[i];
        }
    }
    return null;
} 
ok,签名原理搞清楚了,我们来看看上面提到的bug利用,此bug存在android4.4.1以下的所有版本中。
参考资料:
1、数字证书原理
2、【原创】Android证书验证存漏洞 开发者身份信息可被篡改
4、Android FakeID(Google Bug 13678484) 漏洞详解
5、FakeID签名漏洞分析及利用(Google Bug 13678484)
android签名分析及漏洞修复的更多相关文章
- android添加账户流程分析涉及漏洞修复
		
android修复了添加账户代码中的2处bug,retme取了很酷炫的名字launchAnyWhere.broadAnywhere(参考资料1.2).本文顺着前辈的思路学习bug的原理和利用思路. 我 ...
 - 2016/2/26Android实习笔记(Android签名和aapt)
		
1. 我们平时用eclipse或Android Studio开发得到的android应用程序,其实已经添加有默认的debug签名了. Android系统要求所有的程序经过数字签名才能安装,如果没有可用 ...
 - [Android Pro]   Android签名与认证详细分析之二(CERT.RSA剖析)
		
转载自: http://www.thinksaas.cn/group/topic/335449/ http://blog.csdn.net/u010571535/article/details/899 ...
 - CVE-2011-0104:Microsoft Office Excel 栈溢出漏洞修复分析
		
0x01 前言 上一篇讲到了 CVE-2011-0104 漏洞的成因和分析的方法,并没有对修复后的程序做分析.之后在一次偶然的情况下,想看一看是怎么修复的,结果却发现了一些问题 环境:修复后的 EXC ...
 - [Android Pro]   Android签名与认证详细分析之一(CERT.RSA剖析)
		
转载自:http://www.thinksaas.cn/group/topic/335450/ 一.Android签名概述 我们已经知道的是:Android对每一个Apk文件都会进行签名,在Apk文件 ...
 - Android证书验证存漏洞 开发者身份信息可被篡改(转)
		
原帖地址:http://bbs.pediy.com/showthread.php?p=1335278#post1335278 近期在国内网易,雷锋网等网站爆出谷歌市场上的索尼官方的备份与恢复应用&qu ...
 - Android签名机制
		
Android APK 签名比对 发布过Android应用的朋友们应该都知道,Android APK的发布是需要签名的.签名机制在Android应用和框架中有着十分重要的作用. 例如,Android系 ...
 - Android 热补丁和热修复
		
参考: 各大热补丁方案分析和比较 Android App 线上热修复方案 1. Xposed Github地址:https://github.com/rovo89/Xposed 项目描述:Xposed ...
 - Android签名机制---签名过程
		
大神文章:http://blog.csdn.net/jiangwei0910410003/article/details/50402000 一.知识点 1.数据摘要(数据指纹).签名文件,证书文件 2 ...
 
随机推荐
- CNN结构演变总结(二)轻量化模型
			
CNN结构演变总结(一)经典模型 导言: 上一篇介绍了经典模型中的结构演变,介绍了设计原理,作用,效果等.在本文,将对轻量化模型进行总结分析. 轻量化模型主要围绕减少计算量,减少参数,降低实际运行时间 ...
 - C#的foreach遍历循环和隐式类型变量
			
C#的foreach遍历循环和隐式类型变量 foreach遍历循环 foreach (<baseType> <name> in <array>>) { //c ...
 - CVE-2019-2618 任意文件上传
			
漏洞描述:CVE-2019-2618漏洞主要是利用了WebLogic组件中的DeploymentService接口,该接口支持向服务器上传任意文件.攻击者突破了OAM(Oracle Access Ma ...
 - CMDB项目要点之技术点(面试题)
			
1.单例模式 日志对象用单例模式 django admin中注册类是,用到单例模式 为什么要用单例模式 同一个对象操作 维护全局变量 + 对全局变量做一些操作 # __new__ import thr ...
 - css3中的渐变效果
			
大家好,这里是demo软件园,今天为大家分享的是css3中的渐变效果. css3中的渐变需要注意的是渐变的是图片而不是颜色,而渐变又分为两种:线性渐变与径向渐变,今天我们重点介绍的是线性渐变. 1.线 ...
 - mysql最经典的语句
			
一.基础1.说明:创建数据库CREATE DATABASE database-name2.说明:删除数据库drop database dbname3.说明:备份sql server--- 创建 备份数 ...
 - 一些DevTools的小技巧-让你不止会console.log()
			
转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 原文参考:https://www.sitepoint.com/beyond-console-log-leve ...
 - c/s应用程序自动更新组件GeneralUpdate3.2.1发布
			
一.组件简介 GeneralUpdate是基于.net standard 开发的一款(c/s应用)自动升级程序.该组件将更新的核心部分抽离出来方便应用于多种项目当中目前适用于wpf,控制台应用,win ...
 - 运维小姐姐说这篇Consul集群和ACL配置超给力(保姆级)
			
前言 上一篇简单介绍了Consul,并使用开发模式(dev)进行流程演示,但在实际开发中需要考虑Consul的高可用和操作安全性,所以接着来聊聊集群和ACL的相关配置,涉及到的命令会在环境搭建过程中详 ...
 - 【odoo14】第二十三章、管理邮件
			
邮件集成是odoo最重要的特性.我们可以通过odoo收发邮件.我们甚至可以管理业务文档上的电子邮件,如潜在客户.销售订单和项目.本章,我们将探讨在odoo中处理邮件的方式. 配置邮件服务器 管理文档中 ...