从 0 到 1:用 C++ 破解 APK 签名提取难题,告别工具依赖

在Android安全领域,APK签名信息就像应用的“身份证”,无论是检测恶意篡改、追溯开发者身份,还是批量验证应用合法性,都离不开它。但多数人依赖的apksignerkeytool等工具,在跨平台集成、自动化批量处理场景下总有诸多限制。今天就带大家用C++从零实现APK签名提取,彻底掌握这一核心技术。

一、先搞懂:APK签名藏在哪?

很多人不知道,APK本质是个标准ZIP压缩包,签名信息并非藏在复杂的二进制结构里,而是集中在META-INF/目录下,主要以三种文件形式存在:

  • .RSA:最常见的签名文件,存储基于RSA算法的签名数据
  • .DSA:基于DSA算法的签名文件,多见于早期应用
  • .EC:基于椭圆曲线加密(ECC)的签名文件,轻量化场景常用

这些文件内部都遵循PKCS#7标准格式,把证书链、签名算法、公钥等关键信息打包存储。我们要做的,就是从APK中“挖”出这些文件,再解析出里面的核心数据。

二、核心工具:为什么选libzip+OpenSSL?

实现签名提取需要两个关键库,各自分工明确:

  • libzip:轻量级跨平台ZIP解析库,能高效打开APK文件、遍历目录条目,精准定位META-INF/下的签名文件,避免自己写复杂的ZIP解压逻辑
  • OpenSSL:业界标准的加密库,自带PKCS#7解析、X.509证书处理功能,能轻松提取证书主体、指纹、公钥等信息,省去手动解析加密格式的麻烦

这两个库都支持Windows、Linux、Android多平台,编译后体积小,非常适合嵌入到安全分析工具、自动化脚本中。

三、手把手实现:3步提取签名信息

第一步:用libzip定位并读取签名文件

先通过libzip打开APK,遍历所有文件条目,筛选出META-INF/目录下的签名文件,再读取其二进制数据:

#include <libzip/zip.h>
#include <vector>
#include <string>
#include <iostream>

// 从APK中提取签名文件二进制数据
std::vector<uint8_t> get_signature_from_apk(const std::string& apk_path) {
    // 1. 打开APK文件
    int zip_err = 0;
    zip_t* apk_zip = zip_open(apk_path.c_str(), ZIP_RDONLY, &zip_err);
    if (!apk_zip) {
        std::cerr << "APK打开失败!错误码:" << zip_err << std::endl;
        return {};
    }

    std::vector<uint8_t> sig_data;
    // 2. 遍历APK内所有文件条目
    zip_int64_t entry_count = zip_get_num_entries(apk_zip, 0);
    for (zip_int64_t i = 0; i < entry_count; ++i) {
        // 获取文件名称
        const char* entry_name = zip_get_name(apk_zip, i, 0);
        if (!entry_name) continue;
        std::string filename = entry_name;

        // 3. 筛选签名文件:META-INF目录下,后缀为.RSA/.DSA/.EC
        if (filename.find("META-INF/") == 0 && 
            (filename.ends_with(".RSA") || filename.ends_with(".DSA") || filename.ends_with(".EC"))) {
            
            // 获取文件大小
            zip_stat_t entry_stat;
            if (zip_stat_index(apk_zip, i, 0, &entry_stat) != 0) continue;
            
            // 读取文件内容到内存
            zip_file_t* sig_file = zip_fopen_index(apk_zip, i, 0);
            if (!sig_file) continue;
            
            sig_data.resize(entry_stat.size);
            zip_int64_t read_len = zip_fread(sig_file, sig_data.data(), entry_stat.size);
            if (read_len != entry_stat.size) {
                sig_data.clear();
            }
            
            zip_fclose(sig_file);
            break; // 通常一个APK只有一个签名文件,找到后直接退出循环
        }
    }

    // 关闭APK文件
    zip_close(apk_zip);
    return sig_data;
}

第二步:用OpenSSL解析PKCS#7结构

拿到签名文件的二进制数据后,用OpenSSL解析PKCS#7格式,提取证书链中的关键信息(主体、颁发者、SHA256指纹等):

#include <openssl/pkcs7.h>
#include <openssl/x509.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>

// 解析签名数据,输出证书信息
void parse_signature_data(const std::vector<uint8_t>& sig_data) {
    if (sig_data.empty()) {
        std::cerr << "签名数据为空!" << std::endl;
        return;
    }

    // 1. 初始化内存BIO,加载签名数据
    BIO* mem_bio = BIO_new(BIO_s_mem());
    BIO_write(mem_bio, sig_data.data(), sig_data.size());
    
    // 2. 解析PKCS#7结构
    PKCS7* pkcs7 = d2i_PKCS7_bio(mem_bio, nullptr);
    BIO_free(mem_bio);
    if (!pkcs7) {
        std::cerr << "PKCS#7解析失败!" << std::endl;
        ERR_print_errors_fp(stderr);
        return;
    }

    // 3. 检查是否为签名类型(排除其他PKCS#7用途)
    if (!PKCS7_type_is_signed(pkcs7)) {
        std::cerr << "非签名类型的PKCS#7数据!" << std::endl;
        PKCS7_free(pkcs7);
        return;
    }

    // 4. 提取证书链
    STACK_OF(X509)* cert_chain = pkcs7->d.sign->cert;
    if (!cert_chain || sk_X509_num(cert_chain) == 0) {
        std::cerr << "证书链为空!" << std::endl;
        PKCS7_free(pkcs7);
        return;
    }

    // 5. 遍历证书链,输出每个证书的关键信息
    for (int i = 0; i < sk_X509_num(cert_chain); ++i) {
        X509* cert = sk_X509_value(cert_chain, i);
        std::cout << "=== 证书 " << (i + 1) << " ===" << std::endl;

        // 5.1 证书主体(开发者/机构信息)
        char subject[512] = {0};
        X509_NAME_oneline(X509_get_subject_name(cert), subject, sizeof(subject));
        std::cout << "主体:" << subject << std::endl;

        // 5.2 证书颁发者(CA机构信息)
        char issuer[512] = {0};
        X509_NAME_oneline(X509_get_issuer_name(cert), issuer, sizeof(issuer));
        std::cout << "颁发者:" << issuer << std::endl;

        // 5.3 证书序列号
        ASN1_INTEGER* serial = X509_get_serialNumber(cert);
        BIGNUM* bn_serial = ASN1_INTEGER_to_BN(serial, nullptr);
        char* serial_hex = BN_bn2hex(bn_serial);
        std::cout << "序列号:" << serial_hex << std::endl;
        OPENSSL_free(serial_hex);
        BN_free(bn_serial);

        // 5.4 SHA256指纹(应用身份唯一标识)
        unsigned char sha256_fingerprint[EVP_MAX_MD_SIZE] = {0};
        unsigned int fp_len = 0;
        if (X509_digest(cert, EVP_sha256(), sha256_fingerprint, &fp_len)) {
            std::cout << "SHA256指纹:";
            for (unsigned int j = 0; j < fp_len; ++j) {
                printf("%02X", sha256_fingerprint[j]);
                if (j < fp_len - 1) printf(":");
            }
            std::cout << std::endl;
        }

        // 5.5 签名算法
        const X509_ALGOR* sig_alg = X509_get0_signature(nullptr, nullptr, cert);
        int alg_nid = OBJ_obj2nid(sig_alg->algorithm);
        std::cout << "签名算法:" << OBJ_nid2ln(alg_nid) << std::endl;
    }

    // 释放资源
    PKCS7_free(pkcs7);
}

第三步:整合调用,测试效果

把两个核心函数整合,传入APK路径即可提取签名信息:

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cout << "用法:" << argv[0] << " <APK文件路径>" << std::endl;
        return 1;
    }

    // 1. 提取签名文件数据
    std::vector<uint8_t> sig_data = get_signature_from_apk(argv[1]);
    if (sig_data.empty()) {
        std::cerr << "未提取到签名数据!" << std::endl;
        return 1;
    }

    // 2. 解析签名数据并输出
    parse_signature_data(sig_data);
    return 0;
}

四、关键提醒:保护你的签名提取工具

如果你把这套代码做成安全分析工具、批量检测脚本,一定要注意自身安全——核心解析逻辑很容易被逆向破解,导致工具被篡改或滥用。

这里推荐用Virbox Protector对工具进行加固,它能提供多层保护:

  • 代码虚拟化:把核心的签名解析函数转换成虚拟机指令,逆向难度呈指数级提升
  • 反调试/反注入:阻止攻击者用调试器跟踪代码、注入恶意代码篡改逻辑
  • 资源加密:对编译后的二进制文件加密,防止被静态分析
  • 内存保护:防止运行时内存中的关键数据被dump或修改

加固后,工具的抗逆向能力会大幅提升,确保你的技术方案不被轻易破解。

五、为什么推荐这套方案?

相比传统工具,C+++libzip+OpenSSL的方案有三个核心优势:

  1. 跨平台:一次编写,可在Windows、Linux、macOS甚至嵌入式设备上编译运行
  2. 高集成度:能直接嵌入到安全引擎、自动化测试平台中,无需单独调用外部工具
  3. 灵活可控:可根据需求自定义提取字段(比如只需要SHA256指纹),减少冗余计算

无论是做APK批量检测、安全合规审查,还是开发自定义的Android工具,这套方案都能满足需求,让你彻底摆脱对系统工具的依赖。

posted @ 2025-10-11 10:53  VirboxProtector  阅读(54)  评论(0)    收藏  举报