- 文档
- 教程
- 网络安全
【免费下载链接】mastg
The OWASP Mobile Application Security Testing Guide (MASTG) is a comprehensive manual for mobile app security testing and reverse engineering. It describes technical processes for verifying the OWASP Mobile Security Weakness Enumeration (MASWE) weaknesses, which are in alignment with the OWASP MASVS.
本文以 OWASP MASTG 官方演示用例 MASTG-DEMO-0014 为主线,完整演示如何对使用 CryptoKit 的 iOS 应用进行静态分析,定位其二进制中硬编码的 P-256 ECDSA 私钥。读者将掌握「解包 IPA → radare2 符号与交叉引用分析 → DATA 段密钥定位」的完整检测链路,并能将这套方法直接复用于任何基于 Security / CommonCrypto / CryptoKit 的 iOS 应用。
背景与测试目标:MASTG-TEST-0213 与 MASWE-0003
本演示对应的测试用例是 MASTG-TEST-0213「Use of Hardcoded Cryptographic Keys in Code」,属于 iOS 平台的静态、代码级、人工确认型(type: [static, code, manual])测试,关联的弱点是 MASWE-0003(硬编码加密密钥)。该测试明确指出了 iOS 上最常出现硬编码密钥的三个框架入口:
- Security Framework:
SecKeyCreateWithData允许开发者直接从原始数据创建密钥; - CommonCrypto:
CCCrypt的key参数可直接传入原始密钥数据; - CryptoKit:虽然提供了高层抽象,开发者仍可能以各种格式硬编码密钥并交给
P256.Signing.PrivateKey.init(rawRepresentation:)之类的初始化方法使用——这正是本演示样本的做法。
测试还归纳了硬编码密钥的三种典型表现形式,在分析时可以作为搜索线索:
| 表现形式 | 说明 |
|---|---|
| 原始字节数组(Raw Byte Arrays) | 密钥直接以[UInt8]数组或Data对象内嵌在代码中,例如 32 字节的 P-256 私钥、256 位 AES 密钥 |
| Base64 编码字符串 | 密钥以 Base64 字符串形式写入代码,运行时再解码 |
| Hex 编码字符串 | 密钥存为十六进制字符串,运行时转换为Data再参与密码运算 |
该测试同时取代了旧版 MASTG-TEST-0062「Testing Key Management」(原 MSTG-CRYPTO-1 / MSTG-CRYPTO-5,状态为 deprecated),在 MASTG V2 中由 MASTG-TEST-0213 与 MASTG-TEST-0214 共同覆盖。
样本剖析:在 CryptoKit 中使用硬编码 P-256 私钥
演示样本的完整 Swift 源码位于 MastgTest.swift,其核心逻辑是:把一个写死在代码里的 32 字节 P-256 私钥交给 CryptoKit 完成签名与验签,再把结果拼成字符串输出。完整代码如下:
import Foundation import CryptoKit struct MastgTest { static func mastgTest(completion: @escaping (String) -> Void) { // Step 1: Use a hardcoded ECDSA P-256 private key (32 bytes for P-256) in bytes let privateKeyBytes: [UInt8] = [ 0x7c, 0x02, 0x2a, 0x7e, 0x53, 0x7e, 0x1a, 0x2d, 0x44, 0x77, 0xd4, 0xf6, 0x20, 0x8b, 0x14, 0xdb, 0x4e, 0x8d, 0x84, 0x19, 0xd6, 0x23, 0x5f, 0xf2, 0x4e, 0x4b, 0x8d, 0x18, 0xf4, 0x2c, 0x76, 0xe2 ] let privateKeyData = Data(privateKeyBytes) guard let privateKey = try? P256.Signing.PrivateKey(rawRepresentation: privateKeyData) else { completion("Failed to create private key.") return } let publicKey = privateKey.publicKey // Data to sign let dataToSign = "This is a sample text".data(using: .utf8)! // Step 2: Sign the data with the hardcoded private key let signature = try! privateKey.signature(for: dataToSign) // Convert signature to hex string for display let signatureHex = signature.rawRepresentation.map { String(format: "%02hhx", $0) }.joined() // Step 3: Verify the signature with the public key let verificationStatus = publicKey.isValidSignature(signature, for: dataToSign) let verificationResult = verificationStatus ? "Signature is valid." : "Signature is invalid." let value = """ Original: \(String(data: dataToSign, encoding: .utf8)!) Public Key (Hex): \(publicKey.rawRepresentation.map { String(format: "%02hhx", $0) }.joined()) Signature (Hex): \(signatureHex) Verification: \(verificationResult) """ completion(value) } }从源码层面可以提炼出检测时关注的几个关键点:
- 硬编码密钥本身:
privateKeyBytes是一个 32 字节(P-256 曲线私钥的标准长度)的[UInt8]数组,属于上述「原始字节数组」表现形式; - 密钥的消费入口:MastgTest.swift 通过
P256.Signing.PrivateKey(rawRepresentation: privateKeyData)把字节数据还原成私钥对象,rawRepresentation正是分析时最关键的符号; - 密钥的派生使用:
privateKey.publicKey导出公钥、privateKey.signature(for:)生成 ECDSA 签名、publicKey.isValidSignature(_:for:)完成验签,这些 CryptoKit 符号都会在二进制中留下可检索的导入记录。
理解样本后,下面进入「编译产物」层面的检测流程——即对同目录下的主二进制 MASTestApp 做静态分析。
检测步骤总览
原文档的检测流程包含两个核心步骤:
Step 1:解包应用,定位主二进制
按照 MASTG-TECH-0058「Exploring the App Package」 的方法,先用unzip解包 IPA,在Payload目录下找到 Application Bundle(.app),主二进制即./Payload/MASTestApp.app/MASTestApp。MASTG-TECH-0058中给出的一般形式如下:
unzip <AppName>.ipa ls -1 Payload/<AppName>.appStep 2:用 radare2 打开二进制并执行分析脚本
使用 radare2(MASTG-TOOL-0073) 以-i选项加载本演示配套的 radare2 脚本cryptokit_hardcoded_ecdsa.r2(-i表示执行脚本后进入交互模式,这里结合-q静默退出并配合-A全量分析)。对应的运行命令记录在 run.sh 中:
r2 -q -i cryptokit_hardcoded_ecdsa.r2 -A MASTestApp > output.asm标准输出被重定向到output.asm,供后续人工审阅。
radare2 脚本逐条解析
分析脚本 cryptokit_hardcoded_ecdsa.r2 是一个 radare2 命令序列,先做输出环境设置,再依次执行符号检索、交叉引用、反汇编与内存转储,核心逻辑如下:
e asm.bytes=false e scr.color=false e asm.var=false ?e Uses of CryptoKit.P256.Signing.PrivateKey: afl~CryptoKit.P256.Signing.PrivateKey ?e ?e xrefs to CryptoKit.P256.Signing.PrivateKey.rawRepresentation: axt @ 0x100007388 ?e ?e Use of CryptoKit.P256.Signing.PrivateKey.rawRepresentation: pd-- 9 @ 0x1000048d4 pdf @ sym.func.1000046dc > function.asm px 32 @ 0x1000100c8 > key.asm各条命令的作用如下:
| 命令 | 作用 |
|---|---|
e asm.bytes=false | 反汇编输出中不显示原始指令字节,保持输出简洁可读 |
e scr.color=false | 关闭颜色,便于重定向到文件后阅读 |
e asm.var=false | 隐藏反汇编中的局部变量名,聚焦指令序列 |
afl~CryptoKit.P256.Signing.PrivateKey | 在函数列表(afl)中用~过滤出与CryptoKit.P256.Signing.PrivateKey相关的符号,即二进制中导入/引用了该类型的哪些成员 |
axt @ 0x100007388 | 在地址0x100007388上执行交叉引用查询(axt),找出该导入符号(PrivateKey 元数据符号)被哪些代码位置引用 |
pd-- 9 @ 0x1000048d4 | 在0x1000048d4处向前反汇编 9 条指令(pd--),还原rawRepresentation调用点的上下文 |
pdf @ sym.func.1000046dc > function.asm | 对包含该调用点的函数sym.func.1000046dc做完整反汇编(pdf),结果保存到 function.asm |
px 32 @ 0x1000100c8 > key.asm | 在0x1000100c8处转储 32 字节内存(px 32),结果保存到 key.asm,用于直接查看硬编码密钥的原始字节 |
这套脚本的巧妙之处在于:先用afl/axt从符号层确认「应用使用了 CryptoKit 私钥 API」,再用pdf还原调用上下文,最后用px直接把候选密钥字节dump出来——三层递进,从「用了什么」到「在哪用」再到「密钥是什么」。
观察结果解读
符号层:CryptoKit.P256.Signing.PrivateKey 的四处使用
脚本第一段查询的输出(见 output.asm)列出了二进制中与CryptoKit.P256.Signing.PrivateKey相关的全部符号:
Uses of CryptoKit.P256.Signing.PrivateKey: 0x100007364 1 12 sym.imp.CryptoKit.P256.Signing.PrivateKey.public...F0AE06PublicF0Vvg.Public...F0Vvg...Vvg 0x100007370 1 12 sym.imp.CryptoKit.P256.Signing.PrivateKey.rawRepresentation.Foundation.ContiguousBytes...lufC 0x10000737c 1 12 sym.imp.CryptoKit.P256.Signing.PrivateKey.signature.for.ECDSASignature.Foundation.DataProtocol...lF 0x100007388 1 12 sym.imp.CryptoKit.P256.Signing.PrivateKey...VMa尽管 Swift 符号被编译期改名(mangling)得面目全非,但仍能清晰还原出样本源码中的四个 API 使用点:
PrivateKey.public→ 对应源码privateKey.publicKey(公钥导出);PrivateKey.rawRepresentation→ 对应P256.Signing.PrivateKey(rawRepresentation:)初始化,这是把硬编码字节喂给 CryptoKit 的关键入口;PrivateKey.signature(for:)→ 对应privateKey.signature(for: dataToSign)(ECDSA 签名);PrivateKey...VMa→ 类型元数据符号,表明二进制中确实实例化了该类型。
交叉引用层:rawRepresentation 的调用点
第二段查询用axt @ 0x100007388反查元数据符号的引用位置,输出指向了函数sym.func.1000046dc:
xrefs to CryptoKit.P256.Signing.PrivateKey.rawRepresentation: sym.func.1000046dc 0x1000047f0 [CALL:--x] bl sym.imp.CryptoKit.P256.Signing.PrivateKey...VMa说明sym.func.1000046dc(即样本中mastgTest编译后的函数)在0x1000047f0处调用了 PrivateKey 的构造/元数据符号,随后pd-- 9 @ 0x1000048d4还原出该函数内调用rawRepresentation的指令序列:
│ 0x1000048c8 sub x0, x29, 0x80 │ 0x1000048cc mov x8, x20 │ 0x1000048d0 mov x21, 0 │ 0x1000048d4 bl sym CryptoKit.P256.Signing.PrivateKey.rawRepresentation.Foundation.ContiguousBytes...lufC ; sym.imp.CryptoKit.P256.Signing.PrivateKey.rawRepresentation.Foundation.ContiguousBytes...lufC │ ┌─< 0x1000048d8 cbz x21, 0x100004900bl(branch with link)到rawRepresentation的实现符号,cbz x21是 Swift 可失败初始化器(try?)的典型错误分支检查——这与源码中guard let privateKey = try? P256.Signing.PrivateKey(rawRepresentation:)的写法一一对应。
数据层:DATA 段中的 32 字节私钥
脚本的最后一步px 32 @ 0x1000100c8把密钥候选内存 dump 到了 key.asm:
- offset - C8C9 CACB CCCD CECF D0D1 D2D3 D4D5 D6D7 89ABCDEF01234567 0x1000100c8 7c02 2a7e 537e 1a2d 4477 d4f6 208b 14db |.*~S~.-Dw.. ... 0x1000100d8 4e8d 8419 d623 5ff2 4e4b 8d18 f42c 76e2 N....#_.NK...,v.对照源码中privateKeyBytes数组,二者逐字节完全一致:
源码: 0x7c 0x02 0x2a 0x7e 0x53 0x7e 0x1a 0x2d 0x44 0x77 0xd4 0xf6 0x20 0x8b 0x14 0xdb 内存: 7c02 2a7e 537e 1a2d 4477 d4f6 208b 14db 源码: 0x4e 0x8d 0x84 0x19 0xd6 0x23 0x5f 0xf2 0x4e 0x4b 0x8d 0x18 0xf4 0x2c 0x76 0xe2 内存: 4e8d 8419 d623 5ff2 4e4b 8d18 f42c 76e2地址0x1000100c8位于segment.__DATA(段基址0x100010000,偏移0xc8),即密钥被编译器作为静态数据存进了二进制的 DATA 段。这与 evaluation.txt 中对0x1000047b4附近指令的注释一致:adrp x0, segment.__DATA加载 DATA 段基址,add x0, x0, 0xe8定位到密钥相关数据所在偏移,随后调用PrivateKey初始化器完成密钥装载。
函数层:完整反汇编与 AI 辅助反编译
pdf @ sym.func.1000046dc生成的 function.asm 长达 1840 字节,是mastgTest函数的完整 AArch64 反汇编。其中可以清楚看到与源码一一对应的调用序列:
0x100004780处bl sym CryptoKit.P256.Signing.PublicKey...VMa—— PublicKey 类型元数据;0x1000047f0处bl sym CryptoKit.P256.Signing.PrivateKey...VMa—— PrivateKey 类型元数据(构造入口);0x1000048d4处调用rawRepresentation(上文已述);0x1000049e8处bl sym CryptoKit.P256.Signing.PrivateKey.public...—— 对应privateKey.publicKey;0x100004aa8处bl sym CryptoKit.P256.Signing.PrivateKey.signature.for...—— 对应privateKey.signature(for:);0x100004b6c处bl sym CryptoKit.P256.Signing.PublicKey.isValidSignature.for...—— 对应publicKey.isValidSignature(_:for:);- 字符串区
0x100007f20「This is a sample text」、0x100007f60「Signature is invalid.」、0x100007fe0「Signature is valid.」等常量也都完整保留在二进制中。
由于 AArch64 汇编可读性有限,演示还借助人工智能对反汇编进行了二次反编译,产物是 decompiled-o1-review.swift。该文件把上述汇编还原成了可读的 Swift 逻辑:从内嵌数据装载 P-256 私钥、导出公钥、对「This is a sample text」签名并验签、最后把密钥与签名转成 Hex 拼接输出。需要注意,原文档明确提示:AI 反编译结果可能不完美、可能存在错误,但在本例中它清晰地印证了CryptoKit.P256.Signing.PrivateKey的使用以及关联私钥的存在,可作为人工复核汇编代码的高效辅助。
评估结论
按照 MASTG-TEST-0213 的评估标准——「只要在二进制中发现携带硬编码密钥的密码学函数调用,测试即失败」——本样本测试失败,原因非常明确:
在二进制中发现了一个硬编码的 ECDSA 私钥(32 字节 P-256),它被直接存储于 DATA 段,并通过
CryptoKit.P256.Signing.PrivateKey.rawRepresentation初始化器装载参与签名运算。
整个证据链是完整闭环的:
- 符号证据:
afl过滤出PrivateKey.rawRepresentation、signature(for:)等导入符号; - 调用证据:
axt+pd--定位到sym.func.1000046dc内的bl rawRepresentation调用点; - 数据证据:
px 32 @ 0x1000100c8dump 出的 32 字节与源码硬编码数组逐字节一致。
修复与加固方向
从防御者视角,修复此类问题的思路可以对照旧版 MASTG-TEST-0062 中给出的密钥管理原则:密钥不得硬编码、不得在无额外保护的情况下存储、不得从不可信位置导入、不得用底层语言(C/C++)隐藏密钥。落到 iOS 工程实践上,通常的做法包括:
- 将私钥放入 Keychain(可结合 Secure Enclave 支持),运行时从 Keychain 读取而非写死在代码中;
- 签名私钥等敏感材料由服务端动态下发或通过受控的密钥协商流程获取,避免随应用包分发;
- 至少也应把密钥以环境变量、配置文件等方式外置,并配合访问控制,而不是作为字节数组常量编译进二进制——因为本文已经证明,这类常量在 DATA 段中一查便知。
相关资源索引
- 演示主文档:MASTG-DEMO-0014.md
- 样本源码:MastgTest.swift
- 分析脚本:cryptokit_hardcoded_ecdsa.r2 与运行命令:run.sh
- 分析产物:output.asm、key.asm、function.asm、evaluation.txt、decompiled-o1-review.swift
- 关联测试用例:MASTG-TEST-0213 与已废弃的 MASTG-TEST-0062
- 配套技术文档:MASTG-TECH-0058(探索应用包)、MASTG-TOOL-0073(radare2)
同目录下的 MASTG-DEMO-0013 同样覆盖 MASTG-TEST-0213,可作为本演示的对照案例进一步阅读。
- 文档
- 教程
- 网络安全
【免费下载链接】mastg
The OWASP Mobile Application Security Testing Guide (MASTG) is a comprehensive manual for mobile app security testing and reverse engineering. It describes technical processes for verifying the OWASP Mobile Security Weakness Enumeration (MASWE) weaknesses, which are in alignment with the OWASP MASVS.
相关推荐
MASTG 安卓硬编码加密密钥检测:用 semgrep 与 SecretKeySpec 静态分析实战(MASTG-DEMO-0017)
MASTG 安卓硬编码加密密钥检测:用 semgrep 与 SecretKeySpec 静态分析实战(MASTG DEMO 0017) 本篇文章基于 OWASP
文档教程网络安全使用 radare2 静态分析 iOS Keychain 生物认证回退:kSecAccessControlUserPresence 实战解析(MASTG-DEMO-0043)
使用 radare2 静态分析 iOS Keychain 生物认证回退:kSecAccessControlUserPresence 实战解析(MASTG DEM
文档教程网络安全OWASP MASTG 实战:用 Semgrep 静态检测 Android StrictMode 使用(MASTG-DEMO-0039)
OWASP MASTG 实战:用 Semgrep 静态检测 Android StrictMode 使用(MASTG DEMO 0039) 导读 :本文基于 OW
文档教程网络安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考