IOS逆向 CCCrypt技巧

Posted on Tue 05 January 2021 in SoftwareSecurity • 2 min read

CCCrypt原型接口

由于IOS/OSX相对闭源, 故⼤多数算法都是使⽤ObejectiveC 封装的⼀套Crypto库. 该库拥有⼀套统⼀的调⽤ ⼊⼝ CCCrypt. 其具体定义如下

CCCryptorStatus CCCrypt( CCOperation op, 
CCAlgorithm alg, 
CCOptions options, 
const void *key, 
size_t keyLength, 
const void *iv, 
const void *dataIn, 
size_t dataInLength, 
void *dataOut,
size_t dataOutAvailable, 
size_t *dataOutMoved)

# 参数意义
opkCCEncrypt(加密)/ kCCDecrypt(解密)
alg:加密算法 
options:加密⽅式
key:加密密钥
keyLength:密钥⻓度
iv:初始化向量
dataIn:加密数据
dataInLength:加密数据⻓度
dataOut:加密结果
dataOutAvailable:缓冲区⼤⼩
dataOutMoved:加密结果⼤⼩

# 部分参数Enum列表
# alg
enum {
kCCAlgorithmAES128 = 0,
kCCAlgorithmAES = 0,
kCCAlgorithmDES,
kCCAlgorithm3DES, 
kCCAlgorithmCAST, 
kCCAlgorithmRC4,
kCCAlgorithmRC2, 
kCCAlgorithmBlowfish 
};
typedef uint32_t CCAlgorithm;

#options
enum {
/* options for block ciphers */
kCCOptionPKCS7Padding = 0x0001,
kCCOptionECBMode = 0x0002
/* stream ciphers currently have no options */
};
typedef uint32_t CCOptions;

#keyLength
enum {
kCCKeySizeAES128 = 16,
kCCKeySizeAES192 = 24,
kCCKeySizeAES256 = 32,
kCCKeySizeDES = 8,
kCCKeySize3DES = 24,
kCCKeySizeMinCAST = 5,
kCCKeySizeMaxCAST = 16,
kCCKeySizeMinRC4 = 1,
kCCKeySizeMaxRC4 = 512,
kCCKeySizeMinRC2 = 1,
kCCKeySizeMaxRC2 = 128,
kCCKeySizeMinBlowfish = 8,
kCCKeySizeMaxBlowfish = 56,
};

利用Frida 快速返回CCCrypt相关调用信息

首先运行命令

frida-trace -U -p 14534 -i "CCCrypt"

生成一个默认脚本, 随后修改脚本使之能够返回更多信息. 脚本如下

/*
* Auto-generated by Frida. Please modify to match the signature of CCCrypt.
* This stub is currently auto-generated from manpages when available.
*
* For full API reference, see: http://www.frida.re/docs/javascript-api/
*/

{
    /**
    * Called synchronously when about to call CCCrypt.
    *
    * @this {object} - Object allowing you to store state for use in onLeave.
    * @param {function} log - Call this function with a string to be presented to the user.
    * @param {array} args - Function arguments represented as an array of NativePointer objects.
    * For example use args[0].readUtf8String() if the first argument is a pointer to a C string encoded as UTF-8.
    * It is also possible to modify arguments by assigning a NativePointer object to an element of this array.
    * @param {object} state - Object allowing you to keep state across function calls.
    * Only one JavaScript function will execute at a time, so do not worry about race-conditions.
    * However, do not use this to store function arguments across onEnter/onLeave, but instead
    * use "this" which is an object for keeping state local to an invocation.
    */
    onEnter: function (log, args, state) {
    log('CCCrypt(' +
        'op=' + args[0] +
        ', alg=' + args[1] +
        ', options=' + args[2] +
        ', key=' + args[3] +
        ', keyLength=' + args[4] +
        ', iv=' + args[5] +
        ', dataIn=' + args[6] +
        ', dataInLength=' + args[7] +
        ', dataOut=' + args[8] +
        ', dataOutAvailable=' + args[9] +
        ', dataOutMoved=' + args[10] +
    ')');
    //保存参数
    this.operation   = args[0]
    this.CCAlgorithm = args[1]
    this.CCOptions   = args[2]
    this.keyBytes    = args[3]
    this.keyLength   = args[4]
    this.ivBuffer    = args[5]
    this.inBuffer    = args[6]
    this.inLength    = args[7]
    this.outBuffer   = args[8]
    this.outLength   = args[9]
    this.outCountPtr = args[10]
    //this.operation == 0 
    if (this.operation == 0) {
        //打印加密前的原文
        console.log("In buffer:")
        console.log(hexdump(ptr(this.inBuffer), {
            length: this.inLength.toInt32(),
            header: true,
            ansi: true
        }))
        //打印密钥
        console.log("Key: ")
        console.log(hexdump(ptr(this.keyBytes), {
            length: this.keyLength.toInt32(),
            header: true,
            ansi: true
        }))
        //打印 IV
        console.log("IV: ")
        if(this.ivBuffer != 0){
        console.log(hexdump(ptr(this.ivBuffer), {
            length: this.keyLength.toInt32(),
            header: true,
            ansi: true
        }))}
        else{console.log("None")}
    }
    },

    /**
    * Called synchronously when about to return from CCCrypt.
    *
    * See onEnter for details.
    *
    * @this {object} - Object allowing you to access state stored in onEnter.
    * @param {function} log - Call this function with a string to be presented to the user.
    * @param {NativePointer} retval - Return value represented as a NativePointer object.
    * @param {object} state - Object allowing you to keep state across function calls.
    */
    onLeave: function (log, retval, state) {
      if (this.operation == 1) {
        //打印解密后的原文
        console.log("out buffer:")
        console.log(hexdump(ptr(this.outBuffer), {
            length: this.outLength.toInt32(),
            header: true,
            ansi: true
        }))
        //打印密钥
        console.log("Key: ")
        console.log(hexdump(ptr(this.keyBytes), {
            length: this.keyLength.toInt32(),
            header: true,
            ansi: true
        }))
        //打印 IV
        console.log("IV: ")
        if(this.ivBuffer != 0){
        console.log(hexdump(ptr(this.ivBuffer), {
            length: this.keyLength.toInt32(),
            header: true,
            ansi: true
        }))}
        else{console.log("None")}
    }
    }
}

将上述代码保存后, 利⽤frida对⽬标应⽤进⾏插桩处理, 并调⽤该脚本, 便可以⾃动化处理使⽤该应⽤

frida-trace -U -p 14534 -i "CCCrypt"