
文档教程网络安全【免费下载链接】mastgThe 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.项目地址https://gitcode.com/gh_mirrors/ow/mastg点击查看免费下载本篇技术指南以 OWASP Mobile Application Security Testing GuideMASTG仓库中的演示样本 MASTG-DEMO-0108 为主体完整解析一次检测—绕过—数据提取的真实攻防对抗目标 App 通过扫描/proc/self/maps识别 Frida 注入并调用Process.killProcess()自杀防御而攻击脚本通过 HookBufferedReader.readLine()在数据源头过滤 Frida 内存映射使检测函数永远返回false同时借Cipher.doFinal()Hook 在加解密边界截获明文 API Key。读者将掌握 artifact 型检测的实现原理、Java 层 I/O 接口 Hook 的绕过手法以及运行时完整性验证Runtime Integrity Verification的攻防边界。测试样本与系列攻防脉络MASTG-DEMO-0108 位于demos/android/MASVS-RESILIENCE/目录归属于 MASVS-RESILIENCE对抗逆向工程类别对应测试用例 MASTG-TEST-0341Runtime Use of Hook Detection Techniques测试结果为kind: fail——即防御失效、攻击成功。它不是孤立样本而是一组攻防对抗系列测试中的一环MASTG-DEMO-0106失败测试防御失败/攻击成功。App 对 Frida Hook 毫无检测Cipher.doFinal()被直接 Hook明文 API Key 被提取。MASTG-DEMO-0107成功测试防御成功/攻击失败。同一 App 增加运行时 Hook 检测——扫描/proc/self/maps发现 Frida 痕迹即终止进程防御住了 0106 的攻击。MASTG-DEMO-0108本文失败测试。使用更复杂的攻击手法绕过 0107 的检测不碰检测函数本身而是 Hook 其底层读取接口BufferedReader.readLine()将 Frida 相关行从/proc/self/maps的输出中过滤掉让检测形同虚设最终重新提取出敏感数据。这条 0106 → 0107 → 0108 的演进链条恰好复现了真实世界中最典型的检测-绕过循环防御方基于工具特征做检测攻击方随即针对检测的读取路径做 Hook。源码剖析检测机制与加密流程样本代码在 MastgTest.kt 中与 MASTG-DEMO-0107 完全一致。它包含两条互相关联的主线运行时 Hook 检测与基于 Android KeyStore 的加密/解密。基于 /proc/self/maps 的 Hook 检测init { if (detectHooking()) { android.os.Process.killProcess(android.os.Process.myPid()) } } private fun detectHooking(): Boolean { try { BufferedReader(FileReader(/proc/self/maps)).use { reader - var line: String? while (reader.readLine().also { line it } ! null) { val l line!!.lowercase() if (l.contains(frida) || l.contains(gadget)) { return true } } } } catch (_: Exception) { // Unable to read maps } return false }/proc/self/maps是 Linux 内核暴露给进程自身的虚拟内存映射表每一行描述一段映射的地址范围、权限、偏移、设备号和对应的文件路径或匿名映射来源。当 Frida 通过 spawn 或 attach 模式注入目标进程时其frida-agent二进制会以可执行映射的形式驻留在进程内存中若 App 内置 Frida Gadget则表现为libfrida-gadget.so的加载。因此逐行扫描该文件并匹配frida/gadget关键字是最直接的 artifact 型检测参见 MASTG-KNOW-0030 中 Loaded Library and Memory Mapping Checks 一节。该检测在两处被调用类构造init块中MastgTest.kt——实例创建时立即检查mastgTest()方法开头MastgTest.kt——执行敏感操作前再次检查。任一检查命中都会直接调用android.os.Process.killProcess(android.os.Process.myPid())杀死当前进程使后续加密操作和任何 Frida Hook 回调都来不及执行——这正是 0107 能防御成功的原因。值得注意的是该检测采用BufferedReader(FileReader(...))的 Java 高层 I/O 链读取文件内容而不是直接打开文件描述符逐字节解析。这个实现细节正是 0108 绕过的突破口。Android KeyStore AES/GCM 加解密检测通过后mastgTest()执行密钥管理与加解密private fun getOrCreateSecretKey(): SecretKey { val keyStore KeyStore.getInstance(AndroidKeyStore).apply { load(null) } return if (keyStore.containsAlias(keyAlias)) { (keyStore.getEntry(keyAlias, null) as KeyStore.SecretKeyEntry).secretKey } else { KeyGenerator.getInstance( KeyProperties.KEY_ALGORITHM_AES, AndroidKeyStore ).apply { init( KeyGenParameterSpec.Builder( keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT ) .setBlockModes(KeyProperties.BLOCK_MODE_GCM) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) .build() ) }.generateKey() } }密钥以别名mastgCipherKey存放在AndroidKeyStore硬件/系统级密钥库中generateKey()仅在别名不存在时执行算法为AES/GCM/NoPaddingGCM 模式下不填充KeyGenParameterSpec同时授予 ENCRYPT 与 DECRYPT 两种用途。加密与解密流程MastgTest.kt// Encrypt the sensitive API key val encryptCipher Cipher.getInstance(AES/GCM/NoPadding) encryptCipher.init(Cipher.ENCRYPT_MODE, key) val iv encryptCipher.iv val encryptedBytes encryptCipher.doFinal(sensitiveApiKey.toByteArray(Charsets.UTF_8)) val encryptedData Base64.encodeToString(iv encryptedBytes, Base64.DEFAULT) // Decrypt to verify val decodedData Base64.decode(encryptedData, Base64.DEFAULT) val ivFromData decodedData.copyOfRange(0, 12) val ciphertext decodedData.copyOfRange(12, decodedData.size) val decryptCipher Cipher.getInstance(AES/GCM/NoPadding) decryptCipher.init(Cipher.DECRYPT_MODE, key, GCMParameterSpec(128, ivFromData)) val decryptedBytes decryptCipher.doFinal(ciphertext)加密时随机生成 12 字节 IVCipher.doFinal()以明文sensitiveApiKeysk-OWASP-MAS-SuperSecretKey-1234567890为输入输出密文IV 与密文拼接后 Base64 编码存储。解密时前 12 字节切为 IV其余作为密文GCMParameterSpec(128, ivFromData)指定 GCM 认证标签长度为 128 位doFinal()返回解密明文。虽然密钥本身受 AndroidKeyStore 保护、密钥材料不可直接导出但加解密调用点的明文输入/输出完全暴露在 Java 层这决定了Cipher.doFinal()是数据提取的最佳 Hook 点——测试用例 MASTG-TEST-0341 的 Overview 中也将Cipher.doFinal()列为典型可 Hook 目标Ephemeral/Session Keys could be extracted ifCipher.doFinal()is hooked。绕过思路从特征检测到读取接口MASTG-DEMO-0107 的检测依赖一条完整的读取链路/proc/self/maps → FileReader → BufferedReader → readLine() → 小写匹配 frida/gadget攻击者有两种绕过方向Hook 检测函数本身直接替换detectHooking()的返回值。简单粗暴但容易被更复杂的完整性校验识破MASTG-KNOW-0032 描述的 ART entry point 校验、内存校验和等即针对此类修改。Hook 数据读取路径0108 采用让检测代码看到的是过滤后的内容。既然 App 用BufferedReader.readLine()逐行读取 maps就在这个 Java 方法上做文章每次读到的行若包含frida/gadget就自动跳过继续读下一行直到读到干净的行为止。这样detectHooking()遍历完整个文件也匹配不到任何特征老老实实返回false终止路径永远不被触发。第二种方式更优雅防御代码本身没有被篡改只是它观察世界的窗口被过滤了。这正是 MASTG-KNOW-0030 反复强调的结论——artifact 型检测天然脆弱Tool names, ports, file paths, strings, and process artifacts can often be changed by an attackerAttackers can rename binaries and libraries, change paths, patch Frida builds, hide mappings, or hook the detection code itself。完整绕过与数据提取脚本绕过脚本位于 bypass.js由两个 Hook 组成。第一部分Hook BufferedReader.readLine() 过滤 Frida 痕迹Java.perform(() { // --- Bypass: Hook BufferedReader.readLine() to hide Frida from /proc/self/maps --- let BufferedReader Java.use(java.io.BufferedReader); BufferedReader[readLine].overload().implementation function () { let line this[readLine](); while (line ! null) { let lower line.toString().toLowerCase(); if (lower.indexOf(frida) ! -1 || lower.indexOf(gadget) ! -1) { console.log([*] Hiding line from /proc/self/maps: line); line this[readLine](); } else { break; } } return line; }; console.log([] BufferedReader.readLine() hooked: filtering Frida-related entries from /proc/self/maps);实现要点Java.use(java.io.BufferedReader)获取类引用overload()精确匹配无参readLine()自定义实现内部仍调用this[readLine]()即原方法取真实行随后做与 App 完全一致的toLowerCase()小写化匹配命中frida/gadget时打印日志并继续调用原方法读下一行形成 while 循环直至读到非特征行为止返回值直接透传给调用者detectHooking()的 while 条件App 的检测循环看到的是一份净化过的 maps。注意这里匹配逻辑与 App 侧逐字符对称App 用lowercase()contains()脚本用toLowerCase()indexOf() ! -1保证过滤判定与检测判定覆盖同一特征集不会漏过。第二部分Hook Cipher.doFinal() 提取敏感数据// --- Hook Cipher.doFinal() to extract sensitive data --- function printBacktrace(maxLines 8) { let Exception Java.use(java.lang.Exception); let stackTrace Exception.$new().getStackTrace().toString().split(,); console.log(\nBacktrace:); for (let i 0; i Math.min(maxLines, stackTrace.length); i) { console.log(stackTrace[i]); } } function bytesToHex(bytes) { let hex []; for (let i 0; i bytes.length; i) { hex.push((0 (bytes[i] 0xFF).toString(16)).slice(-2)); } return hex.join(); } let Cipher Java.use(javax.crypto.Cipher); let StringClass Java.use(java.lang.String); // Hook Cipher.doFinal(byte[]) Cipher[doFinal].overload(B).implementation function (input) { let result this[doFinal; let algorithm this.getAlgorithm(); // Cipher.ENCRYPT_MODE 1, Cipher.DECRYPT_MODE 2 let opmode this.opmode.value; let modeStr opmode 1 ? ENCRYPT_MODE : opmode 2 ? DECRYPT_MODE : UNKNOWN( opmode ); console.log(\n\n[*] Cipher.doFinal(byte[]) called); console.log( Algorithm: algorithm); console.log( Mode: modeStr); if (opmode 1) { try { console.log( Input (plaintext): StringClass.$new(input)); } catch (e) { console.log( Input (hex): bytesToHex(input)); } console.log( Output (ciphertext hex): bytesToHex(result)); } if (opmode 2) { console.log( Input (ciphertext hex): bytesToHex(input)); try { console.log( Output (plaintext): StringClass.$new(result)); } catch (e) { console.log( Output (hex): bytesToHex(result)); } } printBacktrace(); return result; }; console.log([] Cipher.doFinal() hooked: extracting sensitive data\n); });实现要点Cipher[doFinal].overload([B)匹配单字节数组参数的重载Frida 类型签名为[B调用原方法获得真实结果后原样返回保证加解密功能不受影响通过this.opmode.value读取 Cipher 的操作模式字段1为ENCRYPT_MODE2为DECRYPT_MODE加密方向输入是明文用StringClass.$new(input)直接还原字符串失败时回退为 hex输出是密文 hex解密方向输入是密文 hex输出是明文同样优先还原字符串每条记录附带回栈printBacktrace()便于定位调用来源脚本自带的bytesToHex()将任意字节数组转为十六进制字符串作为明文还原失败的兜底方案。实战复现步骤启动脚本位于 run.sh本质是一条 Frida CLI 命令#!/bin/bash frida -U -f org.owasp.mastestapp -l bypass.js -o output.txt命令参数含义参数作用-U通过 USB 连接设备usb transport-f org.owasp.mastestappspawn 模式启动目标 Apppackage 为org.owasp.mastestapp在入口点之前注入脚本保证 Hook 先于 App 自身的检测逻辑注册-l bypass.js加载绕过/提取脚本-o output.txt将脚本日志输出重定向到文件便于事后分析完整复现步骤对应 MASTG-DEMO-0108 的 Steps在设备上安装目标 App安装方法参见 MASTG-TECH-0005确保宿主机安装 Frida CLI对应工具条目 MASTG-TOOL-0145且设备上frida-server已以 root 运行spawn 注入需要0107 中同样要求这一前置条件运行run.sh以 spawn 方式加载bypass.js启动 App在 App 界面点击Start按钮触发MastgTest.mastgTest()的检测与加解密流程观察脚本输出结束后按CtrlC或q退出 Frida CLI。spawn 模式在这里至关重要若采用 attach 模式App 的init块可能已经执行完第一次detectHooking()并杀死了进程而 spawn 模式保证注入发生在 App 任何业务代码运行之前readLineHook 先行就位。运行结果分析仓库保存的真实运行输出见 output.txt。日志可分为两个阶段。阶段一检测绕过生效。脚本成功隐藏了全部 Frida 内存映射共 8 条frida-agent-64.so记录分属两次扫描每次 4 条——对应init块与mastgTest()开头的两次detectHooking()调用[*] Hiding line from /proc/self/maps: 6d69a0b000-6d6a351000 r--p 00000000 00:01 188 /memfd:frida-agent-64.so (deleted) [*] Hiding line from /proc/self/maps: 6d6a352000-6d6b092000 r-xp 00946000 00:01 188 /memfd:frida-agent-64.so (deleted) [*] Hiding line from /proc/self/maps: 6d6b092000-6d6b163000 r--p 01685000 00:01 188 /memfd:frida-agent-64.so (deleted) [*] Hiding line from /proc/self/maps: 6d6b164000-6d6b17f000 rw-p 01756000 00:01 188 /memfd:frida-agent-64.so (deleted)细节值得注意Frida agent 在这里并非以普通文件路径出现而是(deleted)的/memfd:frida-agent-64.so——即通过memfd_create创建的匿名内存文件映射地址位于用户空间高位0x6d69a0b000起权限覆盖r--p/r-xp/rw-p。这意味着简单按可疑路径如/data/local/tmp做白名单过滤的检测方案MASTG-KNOW-0032 的 Path Validation对默认 Frida 注入并不奏效关键字匹配反而是更常见的防御写法——而它恰被readLineHook 精准抵消。阶段二加密操作被截获。检测被绕过后App 正常执行加解密两次Cipher.doFinal(byte[])调用被完整记录[*] Cipher.doFinal(byte[]) called Algorithm: AES/GCM/NoPadding Mode: ENCRYPT_MODE Input (plaintext): sk-OWASP-MAS-SuperSecretKey-1234567890 Output (ciphertext hex): e9ff1fd068d108fad6e3e64aca3ce460c169d956123a119ab596887d7f62d14245a24eeb93da5cb8395bbab4f8bc637d0852221fc96e Backtrace: javax.crypto.Cipher.doFinal(Native Method) org.owasp.mastestapp.MastgTest.mastgTest(MastgTest.kt:79) org.owasp.mastestapp.MainActivityKt.MainScreen$lambda$12$lambda$11(MainActivity.kt:101) ...第一次调用ENCRYPT_MODE输入明文sk-OWASP-MAS-SuperSecretKey-1234567890即sensitiveApiKey输出 41 字节密文 hex对应 MastgTest.kt第二次调用DECRYPT_MODE输入为前一次的密文 hex输出明文sk-OWASP-MAS-SuperSecretKey-1234567890对应 MastgTest.kt回栈清晰指向MastgTest.mastgTest(MastgTest.kt:79)与MastgTest.kt:89帮助分析者快速定位到源码行。两次调用覆盖了数据的进出两个方向加密前的明文入口 解密后的明文出口。无论防御方如何保护密钥存储只要 App 自己在 Java 层处理明文doFinal边界就是数据泄漏点。测试判定与标准根据 MASTG-TEST-0341 的判定准则测试通过passHook 尝试因 App 的防御响应而失败——如会话意外终止、Hook 回调从未执行、进程退出测试失败failHook 成功执行并返回了预期数据说明 App 缺乏有效的运行时完整性验证。MASTG-DEMO-0108 的结果是failBufferedReader.readLine()Hook 成功隐藏了/proc/self/maps中全部 Frida 内存段detectHooking()返回false终止路径Process.killProcess()从未执行App 正常完成加解密敏感 API Keysk-OWASP-MAS-SuperSecretKey-1234567890以明文形式在两次Cipher.doFinal()调用中被提取。值得注意的是0107 的评估部分也预先指出了这一可能性an attacker could hook thedetectHooking()method itself or lower level APIs such as the file reading APIs to hide Frida from the process memory map. MASTG-DEMO-0108 demonstrates such a bypass.——0108 正是这句话的完整落地。这一系列演示同时说明单条 artifact 检测通过测试绝不代表防御固若金汤。防御启示为何 artifact 检测脆弱从 MASTG-KNOW-0030 与 MASTG-KNOW-0032 的视角回看本次对抗可以提炼出三条明确的工程结论1. 特征检测只是信号而非证明。检查/proc/self/maps中的frida/gadget关键字属于 artifact-based detection它不证明代码或内存被篡改只说明运行环境可疑。此类指标极易被改名、移除、延迟或伪造单独使用时应视为弱信号MASTG-KNOW-0030 的 Effectiveness and Limitations 一节明确建议避免仅凭单一弱信号立即终止进程。2. 检测链路本身是攻击面。本次绕过没有篡改任何 App 代码只替换了读取路径上的一个 Java 方法。防御方应意识到readLine()这类高层 I/O 接口是 Hook 的首选目标若要提高对抗成本可将检测下沉到 native 层直接open() 逐字节解析并结合 MASTG-KNOW-0032 中描述的方案——如校验rwxp可疑可执行映射、对关键函数做内存校验和、检测 inline hook trampoline 特征字节、验证 ART entry point 归属等——把看特征升级为验完整性。3. 分层防御是唯一现实策略。本次演示中即使detectHooking()被绕过还有一层隐藏的防线值得推敲密钥材料被 AndroidKeyStore 保护无法直接导出攻击者只能搭便车截获 App 自己的加解密调用。若 App 将敏感操作收敛到更小的受控代码路径、对调用点做更细致的完整性校验或在 native 层完成加解密同样的攻击成本会显著上升。这与 MASTG 一贯强调的 defense-in-depth 一致——检测、完整性校验、混淆、native 化彼此互补任何单一手段都经不起专门针对它的攻击。总结MASTG-DEMO-0108 用一份约 100 行的 Kotlin 样本和一份约 80 行的 Frida 脚本完整演示了基于/proc/self/maps的运行时 Hook 检测及其针对读取接口的绕过这一经典对抗闭环防御侧与 MASTG-DEMO-0107 共用代码detectHooking()扫描 maps 匹配frida/gadget命中即killProcess攻击侧本样本readLine()Hook 过滤 Frida 行 → 检测返回false→ 加解密正常执行 →doFinal()Hook 截获sk-OWASP-MAS-SuperSecretKey-1234567890明文结果防御失败kind: fail同时为读者提供了完整的可复现命令、脚本与真实运行输出。对安全测试人员而言本样本是验证反 Frida 检测是否经得起针对性绕过的标准靶子对 App 开发者而言它是一份反例教材——不要在 Java 层用简单的关键字扫描做检测更不要指望单点检测能拦住有耐心的攻击者。完整的样本代码、脚本、日志与相关知识点均可在仓库对应目录中查看与复现样本 MASTG-DEMO-0108、源码 MastgTest.kt、脚本 bypass.js、运行脚本 run.sh、输出日志 output.txt以及背景知识 MASTG-KNOW-0030 与 MASTG-KNOW-0032。赞分享文档教程网络安全【免费下载链接】mastgThe 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.项目地址https://gitcode.com/gh_mirrors/ow/mastg点击查看免费下载相关推荐Android 运行时 Hook 检测实战基于 /proc/self/maps 的 Frida 检测与进程终止MASTG-DEMO-0107 深度剖析Android 运行时 Hook 检测实战基于 /proc/self/maps 的 Frida 检测与进程终止MASTG DEMO 0107 深度剖析 本文档教程网络安全使用 Frida 动态检测 Android Keystore 中非对称密钥对的多用途滥用MASTG-DEMO-0072使用 Frida 动态检测 Android Keystore 中非对称密钥对的多用途滥用MASTG DEMO 0072 导读 本文围绕 OWASP MAST文档教程网络安全MASTG 安卓硬编码加密密钥检测用 semgrep 与 SecretKeySpec 静态分析实战MASTG-DEMO-0017MASTG 安卓硬编码加密密钥检测用 semgrep 与 SecretKeySpec 静态分析实战MASTG DEMO 0017 本篇文章基于 OWASP文档教程网络安全创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考