从无调试符号Swift二进制提取CryptoKit.SymmetricKey的技术问询
我要演示在二进制中存储/生成CryptoKit.SymmetricKey并不安全,为此编写了测试脚本:
import Foundation import CryptoKit let key = SymmetricKey(data: Data([0x18, 0xd3, 0x31, 0xd0, 0xb4, 0x42, 0x55, 0x71, 0xb5, 0xaa, 0xa9, 0xcd, 0xd3, 0x1b, 0xb4, 0xd1, 0xac, 0xd9, 0x29, 0xd6, 0x18, 0x88, 0xbc, 0xee, 0x3f, 0xdf, 0x5e, 0x62, 0x2d, 0xa4, 0x6c, 0x10])) let encryptedData = Data([0xa8, 0xf8, 0xf5, 0x79, 0x63, 0xb7, 0xb2, 0x7b, 0x00, 0xbe, 0x39, 0xf8, 0x2a, 0xb2, 0xeb, 0x4f, 0x6d, 0x77, 0x00, 0xe1, 0xd3, 0x7c, 0x15, 0x43, 0xa3, 0xb9, 0x50, 0xf1, 0x6d, 0x47, 0x3b, 0x79, 0xbc, 0x83, 0xee, 0xfe, 0x5e, 0xf1, 0xe5, 0x59, 0x85]) let sealedBox = try! ChaChaPoly.SealedBox(combined: encryptedData) let decryptedData = try! ChaChaPoly.open(sealedBox, using: key) let decryptedString = String(decoding: decryptedData, as: UTF8.self) print(decryptedString)
该脚本由以下代码生成:
import Foundation import CryptoKit let message = "Hello, world!" let nonce = ChaChaPoly.Nonce() let key = SymmetricKey(size: .bits256) let keyData = key.withUnsafeBytes { Data(Array($0)) } print("Key: \(keyData.map { String(format: "%02hhx", $0) }.joined())") let sealedBox = try! ChaChaPoly.seal(message.data(using: .utf8)!, using: key, nonce: nonce) print("Combined: \(sealedBox.combined.map { String(format: "%02hhx", $0) }.joined())")
我的目标是避免直接从二进制中提取密钥数组,而是通过内存转储或拦截CryptoKit调用的方式获取密钥,以此证明即使密钥经过混淆或来自其他来源,仍可被提取。具体要求是在无调试符号的任意Swift二进制中,至少获取十六进制格式的密钥,同时尽可能获取密文或明文。
LLDB
我尝试在LLDB中为CryptoKit.SymmetricKey.init<A where A: Foundation.ContiguousBytes>(data: A) -> CryptoKit.SymmetricKey和CryptoKit.ChaChaPoly.open(_: CryptoKit.ChaChaPoly.SealedBox, using: CryptoKit.SymmetricKey) throws -> Foundation.Data设置断点,但遇到以下问题:
获取内存地址
带调试符号编译(swiftc -g)时,能用frame var -L key拿到密钥地址,但无调试符号时,无法通过寄存器和反汇编确定密钥的内存地址。
获取密钥数据
即使通过调试符号拿到地址,手动强制转换结构体时也失败:
(lldb) expr -l Swift -- import CryptoKit (lldb) expr -l Swift -- import Foundation (lldb) frame var -L key 0x0000000100008028: (CryptoKit.SymmetricKey) key = { 0x0000000100008028: sb = { 0x0000000100008028: offset = 4296052176 } } (lldb) expr -l Swift -- let $foo = unsafeBitCast(0x0000000100008028, to: SymmetricKey.self) (lldb) expr -l Swift -- $foo.withUnsafeBytes { Data(Array($0)) }.map { String(format: "%02hhx", $0) }.joined() error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=1, address=0xfffffffffffffff8). The process has been returned to the state before expression evaluation.
不过直接调用expr -l Swift -- key.withUnsafeBytes { Data(Array($0)) }.map { String(format: "%02hhx", $0) }.joined()是有效的。
Frida
我编写了Frida脚本,但陷入瓶颈,不知道如何继续推进:
{ const getFunc = (name) => { if (name.includes("open_")) { return "open" } else if (name.includes("seal_")) { return "seal" } else { throw new Error("Unknown function") } } const module = Process.enumerateModules().find((module) => module.name === "CryptoKit") if (!module) { throw new Error("CryptoKit module not found") } for (const exp of module.enumerateExports()) { if (exp.name.includes("Symmetric") || exp.name.includes("Cha")) { Interceptor.attach(exp.address, { onEnter: (args) => { const func = getFunc(exp.name) switch (func) { case "open": // SealedBox, SymmetricKey console.log("Open called") break case "seal": console.log("Seal called") break } }, onLeave: (retval) => {}, }) } } }
内容的提问来源于stack exchange,提问作者Swifty

