Android 運行中效驗檔案完整合法性

來源:互聯網
上載者:User

Android 運行中效驗檔案完整合法性
一.概述因為之前項目有動態熱修複的功能,在修複的過程中會從伺服器上下載一個新的dex檔案來替換老的dex檔案,所以就牽扯到檔案身份效驗的問題.通常介面會下發一個MD5值,只是一個MD5值的話就只能做一個完整性效驗,並不能確定檔案的合法性,如果攻擊者類比介面下發一個正確的MD5值,照樣可以替換檔案.所以這裡就在效驗MD5完整性之後再根據簽名做合法性效驗.二.實現

1.檔案完整性效驗

這裡字串取MD5就不做贅述了.既然要效驗檔案的完整性,那麼就牽扯到取檔案MD5摘要,這裡是用JDK中的MessageDigest通過讀取檔案的二進位流,使用update累計更新檔案流的MD5摘要來擷取整個檔案的MD5摘要.拿到檔案MD5之後跟介面下發的對比就OK了.所以單純做檔案完整性效驗還是很簡單的.

 

    /**     * get file md5     * @param file     * @return     * @throws NoSuchAlgorithmException     * @throws IOException     */    private static String getFileMD5(File file) throws NoSuchAlgorithmException, IOException {        if (!file.isFile()) {            return null;        }        MessageDigest digest;        FileInputStream in;        byte buffer[] = new byte[1024];        int len;        digest = MessageDigest.getInstance(MD5);        in = new FileInputStream(file);        while ((len = in.read(buffer, 0, 1024)) != -1) {            digest.update(buffer, 0, len);        }        in.close();        BigInteger bigInt = new BigInteger(1, digest.digest());        return bigInt.toString(16);    }
2.檔案合法性效驗

 

檔案合法性的效驗相比完整性效驗就要複雜不少,這裡合法性是根據簽名來做的,所以起碼要簡單瞭解簽名的過程,簽名之後檔案發生了什麼變化和怎麼擷取檔案的簽名資訊等.
1)簽名後產出

產生簽名檔案和簽名的過程這裡就不細說了,主要分析一下籤名之後的一些情況.解壓簽名之後的檔案可以看到經過簽名產生出來了一個META-INF檔案夾,裡面一般都是三個檔案用來存放所有檔案的效驗以及簽名資訊.

MANIFEST.MF:可以明文查看,對所有檔案取BASE64雜湊值.

ANDROIDK.SF:可以明文查看,對所有檔案的前三行取BASE64雜湊值.

ANDROIDK.RSA:前面兩個檔案都只是對檔案做了一個雜湊摘要,並沒有簽名公開金鑰等資訊,RSA檔案裡面就包含了我們所需要的資訊,其中包含有開發人員資訊,開發人員公開金鑰以及CA根據前兩個檔案的摘要資訊經過私密金鑰加密後的密文.RSA檔案是不能查看明文的,這裡可以使用openssl的命令來輸出該檔案的資訊.openssl pkcs7 -inform DER -in ANDROIDK.RSA -noout -print_certs -text, 從這個檔案的資料結構來看本章需要用到的其實就是公開金鑰,擷取檔案自身認證資訊的公開金鑰,然後對比app自身的簽名公開金鑰就可以判斷檔案的合法性.

2)擷取app自身簽名

通過上面的介紹可以瞭解到簽過名的檔案都可以擷取到一個基於RSA演算法的RSA public key,這裡就通過效驗這個公開金鑰的方式來驗證合法性,app自身的簽名資訊可以通過PackageInfo擷取,擷取到之後經過字串轉換和截取,將RSA public key部分摘取出來就OK了.

 

    /**     * get local app rsa public key     * @param ctx     * @return     * @throws IOException     * @throws PackageManager.NameNotFoundException     * @throws CertificateException     */    private static String getLocalSignature(Context ctx) throws IOException,            PackageManager.NameNotFoundException, CertificateException {        String signCode = null;        //get signature info depends on package name        PackageInfo packageInfo = ctx.getPackageManager().getPackageInfo(                ctx.getPackageName(), PackageManager.GET_SIGNATURES);        Signature[] signs = packageInfo.signatures;        Signature sign = signs[0];        CertificateFactory certFactory = CertificateFactory                .getInstance(X.509);        X509Certificate cert = (X509Certificate) certFactory                .generateCertificate(new ByteArrayInputStream(sign.toByteArray()));        String pubKey = cert.getPublicKey().toString();        String ss = subPublicSignature(pubKey);        ss = ss.replace(,, );        ss = ss.toLowerCase();        int aa = ss.indexOf(modulus);        int bb = ss.indexOf(publicexponent);        signCode = ss.substring(aa + 8, bb);        return signCode;    }
3)擷取外部檔案簽名
擷取外部檔案簽名的過程其實可以參考Android內部效驗apk檔案的過程,Android源碼中的PackageParser類會在安裝apk之前對apk檔案做合法性效驗,但是遺憾的是這個類被標註為hide了,所以我們不能直接使用.那麼就只剩下兩個方法了,一個是通過反射使用PackageParser的方法,一個看源碼中效驗這部分的實現然後摳出來自己實現.

 

/** * Package archive parsing * * {@hide} */public class PackageParser {    //source/frameworks/base/core/java/android/content/pm}
在當前的使用情境下不推薦使用反射,一是使用反射降低效率還有風險,二是效驗這部分並沒有依賴Android其他部分,只是依賴了JDK中的JarFile.所以扣源碼自己實現來得比較實在,這裡就不分析使用反射驗證的過程了,直接上源碼.

 

從PackageParser類中的collectCertificates方法中可以看到如下程式碼片段.首先根據檔案路徑將簽名後的apk,jar或zip檔案裝載到JarFile中(JarFile是繼承自ZipFile),然後擷取檔案內容部的某個檔案(這部分代碼塊是擷取的manifest檔案),再擷取到該檔案的認證資訊.只要能拿到認證資訊,那麼拿到公開金鑰什麼的都是小case了.

 

    public boolean collectCertificates(Package pkg, int flags) {        //.................        JarFile jarFile = new JarFile(mArchiveSourcePath);        //.................        JarEntry jarEntry = jarFile.getJarEntry(ANDROID_MANIFEST_FILENAME);        //.................        certs = loadCertificates(jarFile, jarEntry, readBuffer);        pkg.mSignatures = null;        //.................    }
這個loadCertificates方法需要特別說一下,因為一開始我是看完源碼之後自己寫實現的,看這個方法的時候每注意注釋,就把讀檔案流什麼也沒乾的步驟略過了,直接通過JarEntry.getCertificates擷取認證.結果換了好幾個簽過名的檔案都擷取不到認證,重新看了下源碼才發現注釋中的必須使用JarEntry讀檔案流才能接收到認證資訊......不作死就不會死.拿到認證之後就跟之前2)中的步驟一樣了,直接get公開金鑰,然後截取字串將RSA public key截出來,最後跟2)中的結果比對就可以做合法性效驗了.

 

    private static Certificate[] loadCertificates(JarFile jarFile, JarEntry je,                                                  byte[] readBuffer) throws IOException {        // We must read the stream for the JarEntry to retrieve        // its certificates.        InputStream is = new BufferedInputStream(jarFile.getInputStream(je));        while (is.read(readBuffer, 0, readBuffer.length) != -1) {            // not using        }        is.close();        return je != null ? je.getCertificates() : null;    }

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.