android 防刷机

来源:互联网 发布:net和java哪个比较好 编辑:程序博客网 时间:2024/06/08 09:26

最近做的一款产品,由于前期失误使用的是 官方的testkey 签名rom包。所以别人rom可以随意刷我们产品盒子,为了改正这个bug我们使用了新的签名刷自己产品。防止别人盗刷我们盒子。
要用新签名rom包,先要理解android升级原理,
第一步:
当我们手动u盘升级或者ota升级下载好update.zip包后。当我们点击升级后,android framework的代码
./base/core/java/android/os/RecoverySystem.java
中函数会

 public static void verifyPackage(File packageFile,                                     ProgressListener listener,                                     File deviceCertsZipFile)

这个函数 会被上层 应用 调用用于检测 rom 包签名
RecoverySystem.verifyPackage(verifyPackage, listener,
new File(“/system/etc/security/otacerts.zip”)); 而otacerts.zip 就是我们的密钥,解压后发现就是 testkey.x509.pem,如果我们rom签名的密钥 用的是 testkey.x509.pem 和testkey.pk8 ,现在用testkey.x509.pem 检测update.zip就能通过。
第二步:
在 recovery 模式升级的时候。我们recovery模式会再一次验证update.zip签名。具体代码在install.cpp 中函数 really_install_package 中

   int numKeys;    RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);    if (loadedKeys == NULL) {        LOGE("Failed to load keys\n");        return INSTALL_CORRUPT;    }    LOGI("%d key(s) loaded from %s\n", numKeys, PUBLIC_KEYS_FILE);    // Give verification half the progress bar...    ui->Print("Verifying update package...\n");    ui->SetProgressType(RecoveryUI::DETERMINATE);    ui->ShowProgress(VERIFICATION_PROGRESS_FRACTION, VERIFICATION_PROGRESS_TIME);    int err;    err = verify_file(path, loadedKeys, numKeys);    free(loadedKeys);    LOGI("verify_file returned %d\n", err);    if (err != VERIFY_SUCCESS) {        LOGE("signature verification failed\n");        return INSTALL_CORRUPT;    }

首先 获得 public key 公钥,load_keys 返回值就是我们的 key指针地址 ,numKeys
是keys 数量。支持多个key。

load_keys(const char* filename, int* numKeys) {    RSAPublicKey* out = NULL;    *numKeys = 0;    FILE* f = fopen(filename, "r");    if (f == NULL) {        LOGE("opening %s: %s\n", filename, strerror(errno));        goto exit;    }    {        int i;        bool done = false;        while (!done) {            ++*numKeys;            out = (RSAPublicKey*)realloc(out, *numKeys * sizeof(RSAPublicKey));            RSAPublicKey* key = out + (*numKeys - 1);            char start_char;            if (fscanf(f, " %c", &start_char) != 1) goto exit;            if (start_char == '{') {                // a version 1 key has no version specifier.                key->exponent = 3;            } else if (start_char == 'v') {                int version;                if (fscanf(f, "%d {", &version) != 1) goto exit;                if (version == 2) {                    key->exponent = 65537;                } else {                    goto exit;                }            }            if (fscanf(f, " %i , 0x%x , { %u",                       &(key->len), &(key->n0inv), &(key->n[0])) != 3) {                goto exit;            }            if (key->len != RSANUMWORDS) {                LOGE("key length (%d) does not match expected size\n", key->len);                goto exit;            }            for (i = 1; i < key->len; ++i) {                if (fscanf(f, " , %u", &(key->n[i])) != 1) goto exit;            }            if (fscanf(f, " } , { %u", &(key->rr[0])) != 1) goto exit;            for (i = 1; i < key->len; ++i) {                if (fscanf(f, " , %u", &(key->rr[i])) != 1) goto exit;            }            fscanf(f, " } } ");            // if the line ends in a comma, this file has more keys.            switch (fgetc(f)) {            case ',':                // more keys to come.                break;            case EOF:                done = true;                break;            default:                LOGE("unexpected character between keys\n");                goto exit;            }            LOGI("read key e=%d\n", key->exponent);        }    }

PUBLIC_KEYS_FILE = /res/keys
在recovery 下面用 busybox cat /res/keys 发现都是数字

{64,0xc926ad21,{1795090719,2141396315,950055447,2581568430,4268923165,1920809988,546586521,3498997798,1776797858,3740060814,1805317999,1429410244,129622599,1422441418,1783893377,1222374759,2563319927,323993566,28517732,609753416,1826472888,215237850,4261642700,4049082591,3228462402,774857746,154822455,2497198897,2758199418,3019015328,2794777644,87251430,2534927978,120774784,571297800,3695899472,2479925187,3811625450,3401832990,2394869647,3267246207,950095497,555058928,414729973,1136544882,3044590084,465547824,4058146728,2731796054,1689838846,3890756939,1048029507,895090649,247140249,178744550,3547885223,3165179243,109881576,3944604415,1044303212,3772373029,2985150306,3737520932,3599964420},{3437017481,3784475129,2800224972,3086222688,251333580,2131931323,512774938,325948880,2657486437,2102694287,3820568226,792812816,1026422502,2053275343,2800889200,3113586810,165549746,4273519969,4065247892,1902789247,772932719,3941848426,3652744109,216871947,3164400649,1942378755,3996765851,1055777370,964047799,629391717,2232744317,3910558992,191868569,2758883837,3682816752,2997714732,2702529250,3570700455,3776873832,3924067546,3555689545,2758825434,1323144535,61311905,1997411085,376844204,213777604,4077323584,9135381,1625809335,2804742137,2952293945,1117190829,4237312782,1825108855,3013147971,1111251351,2568837572,1684324211,2520978805,367251975,810756730,2353784344,1175080310}}

可能别的keys值和这个不一样。这没关系。
这个keys怎么得来的呢??
在 源码目录build/target/product/security下执行命令
java -jar ../../../../out/host/linux-x86/framework/dumpkey.jar testkey.x509.pem > keys

具体文件 在 build/core/Makefile 中

$(RECOVERY_INSTALL_OTA_KEYS): $(OTA_PUBLIC_KEYS) $(DUMPKEY_JAR) $(extra_keys)    @echo "DumpPublicKey: $@ <= $(PRIVATE_OTA_PUBLIC_KEYS) $(extra_keys)"    @rm -rf $@    @mkdir -p $(dir $@)    java -jar $(DUMPKEY_JAR) $(PRIVATE_OTA_PUBLIC_KEYS) $(extra_keys) > $@

上面源码就是 build/core/Makefile

RECOVERY_INSTALL_OTA_KEYS 就是 /res/keysOTA_PUBLIC_KEYS =  $(SRC_TARGET_DIR)/product/security/testkey.x509.pem DUMPKEY_JAR := $(HOST_OUT_JAVA_LIBRARIES)/dumpkey.jar

load_keys获得 RSAPublicKey之后,我们通过 verify_file(path, loadedKeys, numKeys); 验证 update.zip签名。

验证通过就升级update.zip,不通过就失败。

用新的签名当然先新建新的签名:
文件 development/tools/make_key 就是用于做新签名的工具
执行 命令

./make_key onekey  '/C=CN/ST=ShangHai/L=ShangHai/O=PPTV/OU=Department/CN=Your Name/emailAddress=YourE-mailAddress'
key的名字很好理解,就是前面提到的4中类型的key,公司信息的参数比较多,它们的含义如下:C   --->  Country Name (2 letter code)ST  --->  State or Province Name (full name)L   --->  Locality Name (eg, city)O   --->  Organization Name (eg, company)OU  --->  Organizational Unit Name (eg, section)CN  --->  Common Name (eg, your name or your server’s hostname)emailAddress --->  Contact email address

执行上面命令提示你输入密码。输入你的密码后就会在当前目录下生成 onekey.x509.pem 和 onekey.pk8 就是新签名 密钥。
原来签名 用的是 testkey签名 update.zip,现在我们要用 onekey 签名。所有我们要有一个过度updat.zip包,update.zip 签名是用 testkey 签名。但是这个update.zip 包含的签名文件(otacert.zip 和 recovery 模式下的res/keys)都是 onekey 的。
打开 build/core/Makefile

DEFAULT_KEY_CERT_PAIR := $(DEFAULT_SYSTEM_DEV_CERTIFICATE)

DEFAULT_KEY_CERT_PAIR 是用于签名 update.zip 文件。

java -Xmx1536m -jar out/host/linux-x86/framework/signapk.jar -w build/target/product/security/testkey.x509.pem build/target/product/security/testkey.pk8 /tmp/tmpg95cOx out/target/product/g18ref/g18ref-ota-20150508.zip

就是上面的testkey.pk8 和testkey.x509.pem
我们改为

DEFAULT_KEY_CERT_PAIR := $(DEFAULT_SYSTEM_DEV_CERTIFICATE) DEFAULT_KEY_CERT_PAIR := $(SRC_TARGET_DIR)/product/security/onetkey $(DEFAULT_KEY_CERT_PAIR) 

上面展开 makefile 就是 otakey 和 testkey

$(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(firstword $(DEFAULT_KEY_CERT_PAIR))

上面 签名时候选择 第一个签名 onetkey

$(TARGET_OUT_ETC)/security/otacerts.zip: $(addsuffix .x509.pem,$(DEFAULT_KEY_CERT_PAIR)) $(hide) rm -f $@ $(hide) mkdir -p $(dir $@) $(hide) zip -qj $@ $<

将最后一句改为

$(hide) zip -qj $@ $^

上面用于 生成 otacerts.zip,我们让 otacerts.zip 包含 testkey 和 onekey 新旧 update.zip 在 都能验证通过。

ifeq ($(BUILD_SECURE),true)    OTA_PUBLIC_KEYS := device/*/$(TARGET_DEVICE)/releasekey.x509.pemelse    OTA_PUBLIC_KEYS := $(SRC_TARGET_DIR)/product/security/testkey.x509.pemendif

后面添加这句

OTA_PUBLIC_KEYS := $(SRC_TARGET_DIR)/product/security/otakey.x509.pem

这句话用于 生成 recovery 模式下 /res/keys

cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys 添加后面一句cp $(TARGET_OUT_ETC)/security/otacerts.zip $(TARGET_RECOVERY_ROOT_OUT)/res/otacerts.zip 

为什么加这句。比如我现在版本(老的签名)用新的签名updtae.zip包(不是过度包)升级之后(otacert.zip包含testkey和 onekey)。我又想回到这个老版本,升级后otacerts.zip 只包含testkey,这时候我再想升级新的update.zip包(新签名包),如果不加这句就会还要用过度包。这个 otacerts.zip 会被在recovery 使用。

在 文件 recovery.cpp 中 添加

static  char *INSTALL_RECOVRY_SH = "/system/etc"; static  char *SOURCE_OTACERTZIP = "/res/otacerts.zip"; static  char *DEST_OTACERTZIP = "/system/etc/security/otacerts.zip";  int old_updatezip_flag = 0;

添加函数 用于 将 res目录下的 /res/otacerts.zip 拷贝到 /system/etc/security/otacerts.zip

 void      copy_otacert_file(const char* source, const char* destination) {        FILE *des = fopen(destination, "w");       FILE *tmplog = fopen(source, "r");        if (tmplog != NULL) {             fseek(tmplog, 0, SEEK_SET);                char buf[4096]; 456            while(fread(buf,1,sizeof(buf),tmplog)){               fwrite(buf,sizeof(buf),1,des);               }        }        fclose(tmplog);        fclose(des);    } 
finish_recovery(send_intent);        if(old_updatezip_flag==1)       {            if (ensure_path_mounted(INSTALL_RECOVRY_SH) == 0) {                unlink("/system/etc/install-recovery.sh");                  copy_otacert_file(SOURCE_OTACERTZIP,DEST_OTACERTZIP);            }            ensure_path_unmounted(INSTALL_RECOVRY_SH);        } 

如果是 在 新签名 recovery 下升级 旧签名 update.zip ,需要在升级完成后 删除 install-recovery.sh ,不然 在正常启动后,install-recovery.sh 会把 当面新签名recovery 换成老的recovery。
copy_otacert_file(SOURCE_OTACERTZIP,DEST_OTACERTZIP); 替换
旧的 otacerts.zip
if(old_updatezip_flag==1) 表示 当前要升级 旧签名的update.zip包。这个值确定在文件verifier.cpp中。

在 recovery 模式下。我们将我们之前 发布的 rom 的sha散列值 都计算 好保存到我们recovery 中。

head -c -1740 update.zip | sha1sum

用上面函数计算,去掉 尾部 1740个字节这些都是签名文件 自己加上去的。

    size_t comment_size = footer[4] + (footer[5] << 8);    size_t signature_start = footer[0] + (footer[1] << 8);    LOGI("comment is %d bytes; signature %d bytes from end\n",

上面函数打印出signature = 1740个字节 ,所以计算 sha值时候去掉 1740

extern int old_updatezip_flag;  声明外部变量char *known_sha1s[] = {            "01c041ee4803755cc4bc00e108e0a613dcc782ab",            "a23f051c805b586608ccd63d0257ad867f577bdf",            "5acb8d649a0416ae1730e2a3b45aafb1727bb9be",            "4bfe92f6f2bafb20979d881b773933dcc438f88b",            "82fb1103c4e8d281e891bae12d26283332cb74ac",            "6349f6ec3794e799b52c87eee674dc3ad2435c79",            "dc2a66de723aa1889eaa6b7cef2fb05d8d9a7590",            "9406448609c52c0e8109afbfb77110f838f41c4e",            "a589842eb4ab97e45dc1d68041e77dd72a71a63c",            "b6e27016ff3eb502c634b753427d9f179e15f2ff",            "a6277d7ef65dda2a061aa7cd1620d82e787f9de4",            "903f681c6c4e177007b1653128f61b19d247444f",            "3715d801b92a5f1edbbcc819b1f57f2def1c85de",            "a694007371fd0e11daef3a72b8fefe071ca61a28",            "a623c066541f0ea8476e8b39ccbd4dcc7dac96ec",            "9affed9d8ddf7e0748b4698080924f0e03aee405",            "6514aba7766a47a8c42cd72ca015879be6480742",            "bf7f2cfb83036679f322e3799c160ad05f8cf57d",            "96987b650e13add4afc91da7e41c0dba3edaf389",            "NULL"};
const uint8_t* sha1 = SHA_final(&ctx);    for (i = 0; i < numKeys; ++i) {        // The 6 bytes is the "(signature_start) $ff $ff (comment_size)" that        // the signing tool appends after the signature itself.        if (RSA_verify(pKeys+i, eocd + eocd_size - 6 - RSANUMBYTES,                       RSANUMBYTES, sha1)) {            LOGI("whole-file signature verified against key %d\n", i);            fclose(f);            free(eocd);            return VERIFY_SUCCESS;        }    }    下面都是新加的代码     /*verify old public rom */    char tmp_sha[42] = {0};    char const * hex = "0123456789ABCDEF";    for(i=0;i<20;i++)    {           tmp_sha[i*2] = hex[sha1[i] >> 4];        tmp_sha[i*2 + 1] = hex[sha1[i] & 0x0f];    }    将 uint8_t* 16进制 字符转成字符串     char **p ;    p= known_sha1s;    while(strcasecmp(*p,"NULL")!=0)    {        if(strcasecmp(*p,tmp_sha)==0)        {            old_updatezip_flag =1;            这个值等于1 表明是 旧签名的发布update包,我们在升级旧rom包             fclose(f);              return VERIFY_SUCCESS;        }        p++;    } 先校验是不是 旧签名 发布 rom 包        /*校验 过度 rom*/ 如果是过度 rom  我们 查找某个字符是否相等 就让过度包通过校验可以升级。    if (fseek(f, -comment_size, SEEK_END) != 0) {        LOGE("failed to seek in %s (%s)\n", path, strerror(errno));        fclose(f);        return VERIFY_FAILURE;    }    unsigned char sig[1];    if (fread(sig, 1, 1, f) != 1) {        LOGE("failed to read footer from %s (%s)\n", path, strerror(errno));        fclose(f);        return VERIFY_FAILURE;    }    if(sig[0]==83)    {        fclose(f);        return VERIFY_SUCCESS;    }

上面思路是 先用新的签名 校验 rom 包 。不能通过再比较rom 包sha 散列值。不通过再检验是不是过度包。
如果编译不通过 可能错误原因 是
verifier_test.cpp 中少 old_updatezip_flag
在文件中加入 int old_updatezip_flag;
整个 防刷机原理是 过度包刷完之后,
正常模式下 可以让old 签名包 和别人家的包和我们新签名包都通过验证。因为我们 otacerts.zip 包含 新旧 key,但是 我们返回旧key 签名 rom public 包。升级完成后 我们删除 install-recovert.sh ,并将 recovery 下的otacerts.zip拷贝到 system 下面。这样旧 public rom 和 新签名包可以来回 刷。但是 别家的却不能再刷通过 我们盒子。因为我们recovery 模式下面没有包含他们 sha值 known_sha1s 。有关过度包我们在验证通过我们做了 校验 update.zip 1740 字节某个字符是否相等

1 0
原创粉丝点击