本篇为 tales 移动安全专题第三篇。
1、移动应用安全基础篇——Android、ios 环境准备 https://www.freebuf.com/column/199666.html
2、移动应用安全基础篇——绕过 iOS 越狱检测 https://www.freebuf.com/column/201114.htm
介绍
如今,在做 APP 安全测试的时候,越来越多的 APP 数据使用加密传输,一般的做法都需要去逆向 APP 并寻找到加解密算法。今天主要介绍一下 iOS 的一些逆向基础知识,教大家碰到加密数据的 APP 后该如何去解密。
今天主要是针对两款有不同加密方式的 iOS 应用,难度由低到高。
案例一:
首先解决挂代理抓不到包的问题
使用 objection ios sslpinning disable 绕过证书绑定
在登录处抓包发现,request 包和 response 包都为加密传输:
通过参考 github 上的 js 脚本,改写了个较为全面的 hook.js 脚本:
// Intercept the CCCrypt call.Interceptor.attach(Module.findExportByName('libcommonCrypto.dylib', 'CCCrypt'), {
onEnter: function (args) { // Save the arguments
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]
console.log('CCCrypt(' +
'operation: ' + this.operation +', ' + 'CCAlgorithm: ' + this.CCAlgorithm +', ' + 'CCOptions: ' + this.CCOptions +', ' + 'keyBytes: ' + this.keyBytes +', ' + 'keyLength: ' + this.keyLength +', ' + 'ivBuffer: ' + this.ivBuffer +', ' + 'inBuffer: ' + this.inBuffer +', ' + 'inLength: ' + this.inLength +', ' + 'outBuffer: ' + this.outBuffer +', ' + 'outLength: ' + this.outLength +', ' + 'outCountPtr: ' + this.outCountPtr +')') if (this.operation == 0) { // Show the buffers here if this an encryption operation
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
}))
console.log("IV: ")
console.log(hexdump(ptr(this.ivBuffer), {
length: this.keyLength.toInt32(),
header: true,
ansi: true
}))
}
},
onLeave: function (retVal) { if (this.operation == 1) { // Show the buffers here if this a decryption operation
console.log("Out buffer:")
console.log(hexdump(ptr(this.outBuffer), {
length: Memory.readUInt(this.outCountPtr),
header: true,
ansi: true
}))
console.log("Key: ")
console.log(hexdump(ptr(this.keyBytes), {
length: this.keyLength.toInt32(),
header: true,
ansi: true
}))
console.log("IV: ")
console.log(hexdump(ptr(this.ivBuffer), {
length: this.keyLength.toInt32(),
header: true,
ansi: true
}))
}
}
})
使用 frida hook CCCrypt 函数
可以直观的看到加密请求数据和解密返回数据为明文。
operation: 0x0 代表加密,0x1 代表解密,CCAlgorithm: 0x0 指加密方式是 kCCAlgorithmAES128,CCOptions: 0x1 指模式是 cbc,key=DATA_KEY20150116 和 iv=20150116
案例二:
在登录处抓包发现,request 包和 response 包都为加密传输: 使用 hook.js 脚本发现 hook 不到 老方法,首先使用 frida-ios-dump 对该 APP 进行一键 dump
frida-ios-dump,该工具基于 frida 提供的强大功能通过注入 js 实现内存 dump 然后通过 python 自动拷贝到电脑生成 ipa 文件,通过配置完成之后真的就是一条命令砸壳。
砸壳完成后会生成 ipa 文件,我们解压缩然后使用 IDA 加载完二进制文件 然后在 String 窗口搜索 loginbypassword(这个是登录时的信息),搜索后进入对应的类,接下来我们进入这个类看它用了哪些方法
找到这个字符串引用的代码位置 之后双击 callWebAPI:data:method:ssl:completionHandler: 找到 [WebService callWebAPI:data:method:ssl:completionHandler:] 然后 F5 一下 浏览该类发现可以看到 data 等关键加密信息,接着我们尝试搜索 data 前面的 setValue:forKey [_priv_NBSSafeMutableDictionary setValue:forKey:] 查看该类发现无结果,返回上一步重新查看加密所在的类
v87 由 v86 = -[WebService returnDictionaryWithDataPath:](v11,「returnDictionaryWithDataPath:」, v201) 返回
查看 returnDictionaryWithDataPath:
v8 = +[RSA encryptString:privateKey:](&OBJC_CLASS___RSA,「encryptString:privateKey:」, v4, v6); v4 由 convertToJsonData:返回(明文)v6 由 AppPrivate 返回(密钥)
查看密钥返回函数 AppPrivate 和 encryptString:privateKey 函数 然后使用 frida 进行 hook 可以看到解密后的信息
使用 objection
ios hooking watch method 「+[RSA encryptString:privateKey:]」 –dump-args
ios hooking watch method 「+[RSA encryptString:privateKey:]」 –dump-return
直接使用 objection 的这两句命令可以达到同样的效果
附 JS:
if (ObjC.available){ try{ var className = "RSA"; var funcName = "+ encryptString:privateKey:"; var hook = eval('ObjC.classes.' + className + '["' + funcName + '"]'); console.log("[*] Class Name: " + className); console.log("[*] Method Name: " + funcName);
Interceptor.attach(hook.implementation, { onEnter: function(args) { var param1 = new ObjC.Object(args[2]); console.log("args[2] -> " + param1); var param2 = new ObjC.Object(args[3]); console.log("args[3] -> " + param2);
}, onLeave: function(retval) { var retur = new ObjC.Object(retval); console.log("retval -> " + retur);
}
});
} catch(err){ console.log("[!] Exception2: " + err.message);
}
}else{ console.log("Objective-C Runtime is not available!");
}