- 文档
- 教程
- 网络安全
【免费下载链接】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 Mobile Application Security Testing Guide(MASTG)中的调试工具jdb(Java Debugger)。jdb 是随 JDK 一同发布的命令行调试器,基于 Java Debug Wire Protocol(JDWP)与 Android 的 Dalvik/ART 虚拟机通信,允许安全测试人员在命令行下设置断点、打印与修改应用变量。在 Android 逆向工程中,jdb 常用于对发布版(非 debuggable)应用进行运行时分析、绕过反调试/反篡改逻辑、提取明文敏感数据,并可作为执行追踪(Execution Tracing)的轻量级手段。读完本文,你将掌握 jdb 的完整使用流程:从adb jdwp定位进程、adb forward建立转发通道、jdb -attach挂接调试器,到使用stop in、locals、set、trace go methods等核心命令完成一次真实的逆向调试任务。
jdb 是什么:基于 JDWP 的 Java 层调试器
jdb 是 Java 平台自带的命令行调试器,其官方定义为:允许设置断点并打印应用变量,且使用 JDWP 协议与目标虚拟机通信。对应到 Android 平台,它调试的对象是运行在 Dalvik/ART 虚拟机上的 Java/Kotlin 字节码(DEX),而非 native 层代码。
Android 应用支持两种层级的调试(参见 MASTG-TECH-0031):
- Java 运行时层:通过 JDWP 协议调试,jdb、Android Studio 等工具均基于此协议;
- Native 层:基于 Linux 的
ptrace系统调用调试(如 lldb、gdb),适用于 ELF 共享库。
两者对逆向工程都有价值。jdb 属于前者,用于调试"普通" Android 应用(即不大量调用 native 库的应用)。一个 JDWP 调试器可以单步执行 Java 代码、在 Java 方法上设置断点、检查并修改局部变量与实例变量,这些能力正是动态分析的核心。
JDWP 线程与 debuggable 标志
JDWP 调试的前提是目标进程存在一个负责处理 JDWP 协议包的特殊线程。如 MASTG-KNOW-0007 所述,这个线程仅当应用在 AndroidManifest.xml 中声明android:debuggable="true"时才会启动。这也意味着:
- 对发布版(
android:debuggable缺失或为false)应用,直接附加 jdb 通常会失败; - 逆向测试中需要先通过补丁 Manifest 重打包、hook 框架改写
FLAG_DEBUGGABLE、或修改系统属性ro.debuggable等方式让进程变为可调试(详见 MASTG-TECH-0031 的 "Debugging Release Apps" 一节与 MASTG-KNOW-0028 的 JDWP 反调试讨论)。
环境准备与调试前提
确认应用是否可调试
在开始 jdb 会话前,先用静态与动态手段确认目标应用的调试状态(参考 MASTG-TEST-0039):
- 静态检查:查看
AndroidManifest.xml中是否包含android:debuggable="true"。可用aapt快速验证:
# 输出 1 表示存在 android:debuggable="true" 指令 $ aapt d xmltree sieve.apk AndroidManifest.xml | grep -Ec "android:debuggable\(0x[0-9a-f]+\)=\(type\s0x[0-9a-f]+\)0xffffffff" 1- 动态检查:通过
dumpsys查看应用包标志中是否含DEBUGGABLE:
# 输出大于 0 说明应用带调试标志 $ adb shell dumpsys package com.mwr.example.sieve | grep -c "DEBUGGABLE" 2另一种直接的方法就是尝试附加 jdb:如果附加成功,说明调试已激活——这正是本文的核心工作流。
让发布版应用变为可调试
如果应用不是 debuggable,MASTG 给出了多种方案(详见 MASTG-TECH-0031):
- 补丁 Manifest 并重打包:修改
android:debuggable="true"后重签名。代价是侵入性强,可能引发应用不稳定或触发完整性校验。 - Hook 框架改写标志:用 Xposed/LSPosed 等框架(如 MASTG-TOOL-0151)hook 对
ApplicationInfo中FLAG_DEBUGGABLE的检查,使应用"看似"可调试。需要 root 与 hook 框架,且可能被检测。 - 系统级开启调试:在 root 后的特权 ADB shell 中执行
resetprop ro.debuggable 1;若引起不稳定,可临时setenforce 0将 SELinux 设为 permissive。此方式较"粗暴",容易被应用检测。
jdb 完整调试工作流
第一步:列出 JDWP 进程并建立转发通道
使用 MASTG-TOOL-0004(adb)的jdwp子命令列出设备上所有托管 JDWP transport 的可调试进程 PID:
adb jdwp12167随后用adb forward在宿主机上打开一个监听 socket,并将该 socket 的入站 TCP 连接转发到所选进程的 JDWP transport:
adb forward tcp:7777 jdwp:12167这里7777是宿主机本地端口,12167是上一步查到的目标进程 PID。
第二步:附加 jdb 并保持进程挂起
直接jdb -attach会使应用恢复执行,这通常不是逆向分析想要的——我们希望在探索前保持进程挂起。解决办法是把suspend命令通过管道喂给调试器:
{ echo "suspend"; cat; } | jdb -attach localhost:7777 Initializing jdb ... > All threads suspended. >此时已成功挂接到挂起状态的进程。输入?可打印完整命令列表。
注意:Android 虚拟机并不支持全部 JDWP 特性。例如
redefine(重定义类代码)不可用;发布版字节码不含行号信息,因此行断点失效,但方法断点(method breakpoint)可用。
第三步:掌握核心调试命令
在 jdb 会话中,以下命令是 Android 逆向中最常用且可用的(来源:MASTG-TECH-0031):
| 命令 | 作用 |
|---|---|
classes | 列出所有已加载类 |
class/methods/fields<class id> | 打印类详情,列出其方法与字段 |
locals | 打印当前栈帧中的局部变量 |
print/dump<expr> | 打印对象信息 |
stop in <method> | 设置方法断点 |
clear <method> | 移除方法断点 |
set <lvalue> = <expr> | 为字段/变量/数组元素赋新值 |
resume | 恢复全部线程执行 |
cont | 继续执行当前线程 |
step up | 执行到当前方法返回 |
此外,jdb 还能用作执行追踪工具(详见 MASTG-TECH-0032):利用 Android 的 "Wait for Debugger" 功能或kill -STOP让应用在启动之初暂停,附加 jdb 后在任意初始化方法上设置延迟方法断点,命中后执行trace go methods并resume,jdb 便会从该点起转储所有方法的进入与退出记录,适合在混淆或动态加载场景下观察控制流。
$ adb forward tcp:7777 jdwp:7288 $ { echo "suspend"; cat; } | jdb -attach localhost:7777 Set uncaught java.lang.Throwable Set deferred uncaught java.lang.Throwable Initializing jdb ... > All threads suspended. > stop in com.acme.bob.mobile.android.core.BobMobileApplication.<clinit>() Deferring breakpoint com.acme.bob.mobile.android.core.BobMobileApplication.<clinit>(). It will be set after the class is loaded. > resume All threads resumed. Set deferred breakpoint com.acme.bob.mobile.android.core.BobMobileApplication.<clinit>() Breakpoint hit: "thread=main", com.acme.bob.mobile.android.core.BobMobileApplication.<clinit>(), line=44 bci=0 main[1] trace go methods main[1] resume Method entered: All threads resumed.实战案例一:绕过反篡改对话框并提取明文密钥
MASTG 文档以 MASTG-APP-0003(Android UnCrackable L1)为演示目标,展示如何仅用 jdb 完成一次完整的破解流程(详见 MASTG-TECH-0031)。该 crackme 的目标是:应用内隐藏了一个字符串,需要找到方法提取它。
说明:文档明确指出用 jdb 解决此 crackme 并非最"高效"的路径(用 Frida 等工具更快),但它非常适合演示 Java 调试器的能力。
步骤 1:定位目标逻辑
回顾反编译代码:sg.vantagepoint.uncrackable1.MainActivity.a方法负责显示 "This is unacceptable..." 消息框。它创建一个AlertDialog,为onClick事件设置监听类b(其回调会在用户点击OK时终止应用),并调用setCancelable防止用户直接取消对话框:
private void a(final String title) { final AlertDialog create = new AlertDialog$Builder((Context)this).create(); create.setTitle((CharSequence)title); create.setMessage((CharSequence)"This in unacceptable. The app is now going to exit."); create.setButton(-3, (CharSequence)"OK", (DialogInterface$OnClickListener)new b(this)); create.setCancelable(false); create.show(); }应用在启动时即执行 root/篡改检测,因此若不先处理反篡改逻辑,根本无法进入能读到明文密钥的状态。
步骤 2:运行时篡改setCancelable
在应用保持挂起的状态下,对android.app.Dialog.setCancelable设置方法断点并恢复执行:
> stop in android.app.Dialog.setCancelable Set breakpoint android.app.Dialog.setCancelable > resume All threads resumed. > Breakpoint hit: "thread=main", android.app.Dialog.setCancelable(), line=1,110 bci=0 main[1]应用现在停在setCancelable方法的第一条指令。用locals打印传入的参数(注意:参数会被错误地显示在 "Local variables" 之下):
main[1] locals Method arguments: Local variables: flag = truesetCancelable(true)不是我们要找的调用,resume继续:
main[1] resume Breakpoint hit: "thread=main", android.app.Dialog.setCancelable(), line=1,110 bci=0 main[1] locals flag = false这次参数是false。用set命令把它改为true再恢复执行:
main[1] set flag = true flag = true = true main[1] resume重复上述过程(每次断点命中都把flag设为true,断点大约会被命中五六次),直到消息框最终显示。此时对话框已变为可取消——点击对话框旁边区域即可关闭它,而不会终止应用。
步骤 3:在String.equals断点处读取明文
反篡改逻辑解除后,开始提取密钥。静态分析已知:密钥用 AES 解密后,会与输入框中的字符串在java.lang.String.equals方法内比较。因此:
> stop in java.lang.String.equals Set breakpoint java.lang.String.equals >在输入框输入任意文本并点击 verify 按钮,断点命中后执行locals:
Breakpoint hit: "thread=main", java.lang.String.equals(), line=639 bci=2 main[1] locals Method arguments: Local variables: other = "radiusGravity" main[1] cont Breakpoint hit: "thread=main", java.lang.String.equals(), line=639 bci=2 main[1] locals Method arguments: Local variables: other = "I want to believe" main[1] contequals的参数other就是应用持有的明文密钥字符串——这正是我们要找的答案。整个过程仅依赖 jdb 的断点、变量查看与变量改写三项能力,完整演示了"方法断点 + locals 读参 + set 改值"这一核心逆向调试套路。
实战案例二:混合调试中为 lldb 铺路
jdb 的价值还体现在与 native 调试器(如 MASTG-TOOL-0152,lldb)的配合上(详见 MASTG-TECH-0031 的 "Debugging Native Code" 一节)。
当目标 JNI 函数(如Java_sg_vantagepoint_helloworldjni_MainActivity_stringFromJNI)只在启动时执行一次时,直接附加 lldb 往往"为时已晚"——此时libnative-lib.so尚未映射进进程内存。解决办法是先用 jdb 把进程"温和地"调到理想状态:
adb jdwp adb forward tcp:7777 jdwp:14342 { echo "suspend"; cat; } | jdb -attach localhost:777714342然后在 Java 运行时加载 native 库的方法上设断点并恢复,命中后执行step up,让进程运行至loadLibrary返回——此时libnative-lib.so已被加载:
> stop in java.lang.System.loadLibrary > resume > step upAll threads resumed. Breakpoint hit: "thread=main", java.lang.System.loadLibrary(), line=988 bci=0 > step up main[1] step up > Step completed: "thread=main", sg.vantagepoint.helloworldjni.MainActivity.<clinit>(), line=12 bci=5随后再启动lldb-server附加,即可在 native 层对已加载的库设置断点。这是 "JDWP 控制 Java 层执行节奏 + ptrace 调试 native 层" 的典型组合用法。
进阶:基于 jdb 的可调试性判定与注意事项
用 jdb 附加判定应用是否可调试
在安全测试中,jdb 附加成功与否本身就是一条判定信号:如果 jdb 能成功挂接,说明应用的调试标志已激活(参考 MASTG-TEST-0039)。完整的判定流程:
# 1. 用 adb jdwp 识别目标应用 PID $ adb jdwp 2355 16346 <== 最后启动的进程,对应我们的应用 # 2. 建立本地端口与应用进程间的转发通道 # adb forward tcp:[LOCAL_PORT] jdwp:[APPLICATION_PID] $ adb forward tcp:55555 jdwp:16346 # 3. 用 jdb 附加本地端口,开始调试会话 $ jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=55555 Set uncaught java.lang.Throwable Set deferred uncaught java.lang.Throwable Initializing jdb ... > help常见问题排查
- "the connection to the debugger has been closed" 错误:当 jdb 绑定本地通信端口时报此错,可杀掉所有 adb 会话并重新开启一个全新会话再试(MASTG-TEST-0039)。
- 断点不生效:发布版字节码没有行号信息,行断点无效;应改用
stop in方法断点。 redefine不可用:Android 虚拟机不支持类代码重定义(JDWP 的该特性未实现)。- 反调试对抗:应用可能检测 JDWP 线程并主动退出(参考 MASTG-KNOW-0028 的 JDWP 反调试一节,以及 MASTG-TEST-0046 中 "Attaching jdb and ptrace-based debuggers fails or causes the app to terminate" 的观察点)。此时需先绕过反调试逻辑再附加。
适用范围与限制
- jdb 仅作用于Java 层(Dalvik/ART),对 native 库中的逻辑无能为力,需配合 lldb 等 native 调试器;
- 附加 jdb 会使进程整体挂起/恢复,对依赖时间敏感逻辑或强反调试的应用可能触发检测;
- 本文涉及的命令与行为以当前 MASTG 仓库所描述的 Android 平台(Dalvik/ART、JDWP 实现)为前提,不同 Android 版本对 JDWP 特性的支持可能存在差异。
总结
jdb 是 MASTG Android 逆向测试工具箱中一个轻量而关键的成员:它不依赖图形界面,仅需 JDK 与 adb 即可对 Java 层应用执行断点调试、变量读写与执行追踪。其完整工作流可概括为:adb jdwp定位进程 →adb forward建立 JDWP 转发 →{ echo "suspend"; cat; } | jdb -attach挂接并保持挂起 →stop in设方法断点 →locals/print/set观察与篡改运行时状态。无论是绕过反篡改对话框、在String.equals断点处提取明文密钥,还是为 native 层 lldb 调试铺路,jdb 都是值得优先掌握的入门动态分析工具。与之配套的更多调试与追踪手段(Android Studio Profiler、strace、ftrace、lldb 等)可进一步查阅 MASTG-TECH-0031 与 MASTG-TECH-0032。
- 文档
- 教程
- 网络安全
【免费下载链接】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 Android 可调试应用(Debuggable Apps)检测指南:从 Manifest 标志到 JDWP 反调试
OWASP MASTG Android 可调试应用(Debuggable Apps)检测指南:从 Manifest 标志到 JDWP 反调试 导读 本文基于 O
文档教程网络安全OWASP MASTG Android 反调试(Anti-Debugging)实战指南:从 JDWP 检测到 ptrace 对抗
OWASP MASTG Android 反调试(Anti Debugging)实战指南:从 JDWP 检测到 ptrace 对抗 导读 本文基于 OWASP M
文档教程网络安全OWASP MASTG 实战:用 Semgrep 静态检测 Android StrictMode 使用(MASTG-DEMO-0039)
OWASP MASTG 实战:用 Semgrep 静态检测 Android StrictMode 使用(MASTG DEMO 0039) 导读 :本文基于 OW
文档教程网络安全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考