百趣云 百趣云的博客

hook CCCrypt:iOS 加密参数的万能钥匙

为什么 CCCrypt 是 iOS 加密的"总开关"

iOS App 的对称加密(AES/DES/3DES)绝大多数最终汇聚到系统库 libcommonCrypto 的 CCCrypt 函数——无论上层封装多花哨(自研 SDK、三方库),真正干活的都是它。hook 住这一个函数,密钥、IV、明文、密文全部现形

CCCryptorStatus CCCrypt(
    CCOperation op,        // kCCEncrypt=0 / kCCDecrypt=1
    CCAlgorithm alg,       // kCCAlgorithmAES=0 / DES=1 / 3DES=2
    CCOptions options,     // kCCOptionPKCS7Padding=1 / ECBMode=2
    const void *key,       // ← 密钥
    size_t keyLength,
    const void *iv,        // ← IV
    const void *dataIn,    // ← 明文(加密时)
    size_t dataInLength,
    void *dataOut,         // ← 密文输出
    size_t dataOutAvailable,
    size_t *dataOutMoved
);

参数设计得太"贴心"了——加密所需的一切都在参数里。

Frida hook 完整脚本

var CCCrypt = Module.findExportByName('libcommonCrypto.dylib', 'CCCrypt');

Interceptor.attach(CCCrypt, {
    onEnter: function(args) {
        this.op = args[0].toInt32();
        this.alg = args[1].toInt32();
        this.options = args[2].toInt32();
        this.keyLen = args[4].toInt32();
        this.dataInLen = args[6].toInt32();
        this.dataOut = args[7];

        var opStr = this.op === 0 ? '加密' : '解密';
        var algStr = {0: 'AES', 1: 'DES', 2: '3DES'}[this.alg] || this.alg;
        var modeStr = (this.options & 2) ? 'ECB' : 'CBC';

        // 密钥和 IV 转 hex
        this.key = hexdump(args[3], {length: this.keyLen, header: false, ansi: false});
        this.iv = args[5].isNull() ? 'null' : hexdump(args[5], {length: 16, header: false, ansi: false});
        // 输入数据
        this.dataIn = args[6].readUtf8String(this.dataInLen) || '(二进制)';

        console.log(`\n[CCCrypt] ${opStr} ${algStr} ${modeStr}`);
        console.log('  key: ' + this.key.replace(/\n/g, ''));
        console.log('  iv:  ' + this.iv.replace(/\n/g, ''));
        console.log('  in:  ' + String(this.dataIn).substring(0, 500));
    },
    onLeave: function(retval) {
        // 输出数据(加密后的密文/解密后的明文)
        try {
            var out = this.dataOut.readUtf8String(this.dataInLen);
            console.log('  out: ' + String(out).substring(0, 500));
        } catch(e) {
            // 二进制输出就打 hex
        }
        // 调用栈:看谁调的加密
        console.log('  stack:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE)
            .map(DebugSymbol.fromAddress).slice(0, 5).join('\n'));
    }
});

输出怎么读

[CCCrypt] 加密 AES CBC
  key: 31323334353637383930616263646566    ← "1234567890abcdef" 的 hex
  iv:  30313233343536373839616263646566
  in:  {"username":"test","timestamp":1787968800}
  out: <二进制密文>
  stack:
    0x100abcdef TargetApp!-[CryptoUtils aesEncrypt:]
    0x100123456 TargetApp!-[SignManager makeSign]

一次 hook 拿到:算法(AES-CBC)、密钥明文、IV、输入输出、调用方。密钥直接就是硬编码的 1234567890abcdef——这种案例在真实逆向中占比高得惊人。

精髓一:从调用栈定位上层逻辑

CCCrypt 的日志里最有价值的不是密钥,是调用栈DebugSymbol.fromAddress 解析出的符号告诉你:

  • 哪个类哪个方法发起的加密(CryptoUtils aesEncrypt:
  • 顺着栈帧往上就是业务逻辑(SignManager makeSign

拿着这些符号去 Hopper 里跳过去,加密的上层组装逻辑(密钥从哪来、明文怎么拼)一目了然。

精髓二:CCCryptorCreate 系列也要覆盖

部分 App 不用一次性的 CCCrypt,而用分段式 API:

CCCryptorCreate → CCCryptorUpdate(可多次) → CCCryptorFinal → CCCryptorRelease

这种要 hook CCCryptorCreate(拿算法/密钥/IV)+ CCCryptorUpdate(拿数据流)。脚本结构类似,注意用 cryptor 句柄关联同一次加密的多段数据。

精髓三:不用 CCCrypt 的怎么办

两套常见例外:

  1. 自研加密/SO 内实现:金融 App 把 AES 实现在自己的 SO 里(防的就是 CCCrypt hook)。识别:hook 无输出但流量确实加密。对策:IDA 里搜 AES 特征常量(S 盒 0x63, 0x7c, 0x77...)定位自研实现,hook 其输入输出
  2. CryptoKit/CommonCrypto 的 Swift 封装AES.GCM.seal 等新 API 底层还是 libcommonCrypto,但 GCM 模式走 CCCryptorGCM 系列函数,hook 点要换成它们

总结

  1. CCCrypt 是 iOS 对称加密的总开关,参数里密钥 IV 明文全有
  2. 调用栈是黄金信息,直接指路上层组装逻辑
  3. 分段式 API 要 hook Create+Update 组合
  4. 自研加密搜 AES S 盒常量定位,GCM 换 hook CCCryptorGCM

交流微信:run1255

By 百趣云 阅读量:4 On