DIVA 通关笔记 — Android 逆向漏洞方法论
核心理念只有一句话:
不读判断逻辑,找 API 特征。
每类漏洞都对应一组固定的 Android API。搜到 API,就确定了攻击方向;剩下的,只是确认路径。
本文按 DIVA 各关卡整理一套「API 特征驱动」的 Android 漏洞方法论,覆盖存储泄露、输入验证、访问控制和 Native 层问题。
Level 1 — Insecure Logging(日志泄露)
| 项目 | 内容 |
|---|---|
| 靶心 API | Log.e() / Log.d() / Log.v() / Log.w() |
| 数据终点 | logcat(系统级环形缓冲区,所有 APP 共享) |
| 发现手法 | 反编译搜 Log\.,或 strings classes.dex | grep -i "Log\." |
| 攻击命令 | adb logcat -d | grep <tag>,或直接 adb logcat -d dump 全部 |
平台特征: logcat 是 Android 的内核级日志设施,不区分进程。任何能执行 adb logcat 的人,都能读到所有 APP 写的日志。APP 自身 Log.i/d/e 调用的目标就是 logcat——这是平台决定的,不是开发者发明的。
逆向链路:
看到 Log.e("diva-log", ...) → 知道数据流向 logcat → adb logcat -d | grep diva-log 直接收数据。
Level 2 — Hardcoding Issues(硬编码凭证)
| 项目 | 内容 |
|---|---|
| 靶心 API | String.equals("硬编码字面量") / setText("硬编码字面量") |
| 数据终点 | DEX 文件中的字符串常量池 |
| 发现手法 | 不要读 if-else 判断逻辑;用 strings classes.dex | grep -i "key|secret|password|api|vendor" |
| 攻击命令 | strings classes.dex | grep -i "vendor|secret" |
为什么 strings 有效: Java 字面量编译后会进入 DEX 常量池,strings 直接从二进制提取所有可读字符串。任何硬编码字符串都会裸露出现,不需要理解控制流。
示例:
strings classes.dex | grep vendor→vendorsecretkeystrings classes.dex | grep API→API Key: 123secretapikey123、diva、p@ssword
Level 3 — Insecure Data Storage(不安全数据存储)
这一关包含 4 种存储方式,每种都对应不同的 Android 存储 API 和固定系统路径。
Part 1 — SharedPreferences
| 项目 | 内容 |
|---|---|
| 靶心 API | PreferenceManager.getDefaultSharedPreferences() / getSharedPreferences() |
| 存储路径 | /data/data/<pkg>/shared_prefs/*.xml |
| 存储格式 | 明文 XML |
| 发现手法 | 搜 SharedPreferences 或 getDefaultSharedPreferences |
| 攻击命令 | cat /data/data/<pkg>/shared_prefs/*.xml |
SharedPreferences 是 Android SDK 内置键值存储,写入位置由平台固定。看到这个 API,就知道数据一定在 shared_prefs/*.xml。
逆向链路: 反编译看到 getDefaultSharedPreferences → 攻击路径确定 → 无需分析保存/读取逻辑细节。
Part 2 — SQLite 数据库
| 项目 | 内容 |
|---|---|
| 靶心 API | openOrCreateDatabase() / SQLiteOpenHelper |
| 存储路径 | /data/data/<pkg>/databases/* |
| 存储格式 | SQLite 二进制,可用 strings 或 sqlite3 读取 |
| 发现手法 | 搜 openOrCreateDatabase 或 SQLiteOpenHelper |
| 攻击命令 | strings /data/data/<pkg>/databases/*,或 sqlite3 <db> "SELECT * FROM <table>" |
与 Web 开发不同,Android 数据库路径是框架固化的:看到 openOrCreateDatabase("ids2", ...),攻击路径就是 /data/data/<pkg>/databases/ids2。
Part 3 — 内部存储文件(无固定文件名)
| 项目 | 内容 |
|---|---|
| 靶心 API | File.createTempFile() / FileWriter / FileOutputStream |
| 存储路径 | /data/data/<pkg>/ 内部,文件名由代码定义 |
| 发现手法 | 搜 FileWriter、FileOutputStream、createTempFile、dataDir |
| 攻击命令 | 先读代码确认文件名,再 strings /data/data/<pkg>/<file_name>* |
这是开发者自定义文件操作,文件名不是平台固定的,必须看源码确认写到了哪个文件;但攻击方向(搜索文件 I/O API)是确定的。
一个实用技巧:
看到 File.createTempFile("prefix", "suffix", dir) + getApplicationInfo().dataDir
→ 搜索 strings /data/data/<pkg>/prefix*suffix。
Part 4 — 外部存储文件
| 项目 | 内容 |
|---|---|
| 靶心 API | Environment.getExternalStorageDirectory() |
| 存储路径 | /sdcard/ 或 /storage/emulated/0/ |
| 发现手法 | 搜 getExternalStorageDirectory / getExternalStoragePublicDirectory |
| 攻击命令 | ls -la /sdcard/,注意隐藏文件,再 cat /sdcard/<file> |
外部存储是共享区域,无 root 也可能读。API 29+ 的 Scoped Storage 会加限制,但低版本和部分 ROM 仍较宽松。注意隐藏文件(如 .uinfo.txt)要用 ls -la 才能看到。
Level 7 — SQL Injection(SQL 注入)
| 项目 | 内容 |
|---|---|
| 靶心 API | SQLiteDatabase.rawQuery() + 字符串拼接 |
| 攻击面 | 搜索输入框,输入拼进 SQL |
| 数据终点 | /data/data/<pkg>/databases/* |
| 发现手法 | 搜 rawQuery / execSQL,看参数是否用 + 拼接 |
| 攻击命令 | 搜索框输入 ' OR 1=1 --;或用 sqlite3 直接查库验证 |
Android 版 SQL 注入和 Web 版本质一样:用户输入拼进查询语句。区别只在于 Android 用 rawQuery(),数据库是本地 SQLite。
通用 payload:
1 | |
漏洞确认后,建议直接 dump 数据库对比:
1 | |
Level 8 — URI Scheme Bypass(URL Scheme 绕过)
| 项目 | 内容 |
|---|---|
| 靶心 API | WebView.loadUrl() 无 scheme 白名单 |
| 攻击面 | URL 输入框 → WebView 加载 |
| 发现手法 | 搜 loadUrl / WebView,看是否有 scheme 校验 |
| 攻击命令 | 输入 file:///etc/hosts 或 content://<provider_uri> |
loadUrl() 若接受任意 URL scheme,http://、file://、content:// 都会进来。开发者只想着加载网页,却可能没拦住本地文件读取,也没禁止访问 ContentProvider。
常见攻击:
file:///etc/hosts→ 读系统敏感文件content://jakhar.aseem.diva.provider.notesprovider/notes→ 跨 Provider 数据泄露
这关还要和 Level 11 联动:如果 APP 有 exported ContentProvider,loadUrl("content://...") 可以绕过 PIN 等前端保护直接访问 Provider 数据。
Level 9 — Exported Activity(导出组件 — Activity)
| 项目 | 内容 |
|---|---|
| 靶心 API | Manifest 中 <intent-filter> → android:exported="true"(隐式默认) |
| 攻击面 | 带 intent-filter 的 Activity 可被外部调用 |
| 发现手法 | 读 Manifest,或 adb shell dumpsys package <pkg> |
| 攻击命令 | adb shell am start -n <pkg>/<Activity> |
Android 四大组件里,一旦声明 <intent-filter>,android:exported 默认就会变成 true,意味着外部可通过 am start 或 Intent 调用它。很多开发者并不清楚这个默认行为。
攻击流程(API 特征法):
- 不先读
APICredsActivity的业务代码 - Manifest 看到它有
<intent-filter action="jakhar.aseem.diva.action.VIEW_CREDS"/> - 执行:
adb shell am start -n jakhar.aseem.diva/.APICredsActivity - 直接显示 API Key
Activity 名本身(APICredsActivity)往往就是线索——不一定要先看代码。
Level 10 — Intent Data Forging(Intent 数据伪造)
| 项目 | 内容 |
|---|---|
| 靶心 API | Intent.getBooleanExtra() 信任客户端传入的值 |
| 攻击面 | Intent extra 由发送方控制,不可信 |
| 发现手法 | 搜 getBooleanExtra / getStringExtra,看是否用于权限判断 |
| 攻击命令 | 正常流程中不勾选 RadioButton → 绕过 PIN 检查 |
Intent extras 完全由调用方设置,就像 HTTP 请求参数由客户端控制。把 putExtra("chk_pin", false) 当作安全校验,等价于把 ?is_admin=true 放在 URL 参数里。
常见漏洞模式:
1 | |
攻击思路:
- Manifest 看到
APICreds2Activity无 intent-filter → 只能内部调用 - 反编译搜
startActivity,找到调用方AccessControl2Activity - 看到
getBooleanExtra("chk_pin")→ 权限判断依赖 Intent 数据 - 追溯到 RadioButton → 不勾选即可绕过
补充:getBooleanExtra("chk_pin", true) 默认值是 true。有时直接构造 --ez chk_pin false 会因传递问题不稳定,但 UI 流程(不勾选)通常更稳。
Level 11 — Exported ContentProvider(导出组件 — ContentProvider)
| 项目 | 内容 |
|---|---|
| 靶心 API | Manifest <provider android:exported="true"> + ContentProvider.query() |
| 攻击面 | ContentProvider 是跨 APP 数据共享接口 |
| 发现手法 | 读 Manifest,找 exported="true" 的 provider |
| 攻击命令 | adb shell content query --uri content://<authority>/<path> |
ContentProvider 是 Android 特有 IPC 组件。content 命令是系统提供的命令行工具,标准 Linux 没有它。
关键特征:
- Activity 层有 PIN 保护,看起来安全
- Provider 自身没有认证,
NotesProvider.query()直接返回数据 - 前端防御 ≠ 后端防御
攻击链路:
- Manifest 看到
android:exported="true"authorities="jakhar.aseem.diva.provider.notesprovider" - 执行:
1 | |
- 私密笔记直接返回,完全绕过 PIN
而且不仅能读:content insert / update / delete 同样可用。
Level 12 — Native Hardcoding(Native 层硬编码)
| 项目 | 内容 |
|---|---|
| 靶心 API | System.loadLibrary() + .so 中的 strcmp / 字符串常量 |
| 数据终点 | ELF 的 .rodata 段(不是 DEX 常量池) |
| 发现手法 | strings lib/*/lib*.so | grep -i "key|secret|password" |
| 攻击命令 | unzip -p app.apk lib/*/lib*.so | strings |
和 Level 2 的区别只是存放位置:
- Level 2:密钥在 DEX(Java)层
- Level 12:密钥被搬到 Native(
.so)层
开发者常以为编译成 .so 就等于“加密”,但 ELF 的 .rodata 仍然明文存放字符串常量,strings 照读不误。
1 | |
strings 是万能钥匙: 不管是 DEX、ELF、资源文件还是数据库,它都能抽出可读字符串。
Level 13 — Native Buffer Overflow(Native 层缓冲区溢出)
| 项目 | 内容 |
|---|---|
| 靶心 API | strcpy() 无长度检查 |
| 攻击面 | 用户输入 → JNI → Native strcpy → 栈溢出 |
| 发现手法 | 搜 strcpy / strcat / gets / sprintf |
| 攻击命令 | 输入 500+ 字符 → 观察 SIGSEGV |
Java 本身没有缓冲区溢出,但 JNI 把参数送到 C 层后,安全边界就断了。
不读 C 源码也能确认漏洞:
- 反编译看到 JNI 方法接收用户输入
- 输入超长字符串
- logcat 出现
SIGSEGV,且 crash 地址落在libdivajni.so - 即可确认缓冲区溢出
1 | |
在 DIVA 这类靶场里,PoC 通常是 DoS;真实场景中,精确控制溢出可进一步劫持控制流做 ROP。
方法论总结
Level 7–13 速查
| 关卡 | 漏洞类型 | 靶心 API |
|---|---|---|
| Level 7 | SQL 注入 | rawQuery() + 字符串拼接 |
| Level 8 | URL Scheme 绕过 | WebView.loadUrl() 无 scheme 校验 |
| Level 9 | 导出 Activity | <intent-filter> → exported=true |
| Level 10 | Intent 数据伪造 | getBooleanExtra() 信任客户端 |
| Level 11 | 导出 ContentProvider | <provider exported="true"> |
| Level 12 | Native 硬编码 | System.loadLibrary() + .so 字符串 |
| Level 13 | Native 缓冲区溢出 | strcpy() 无边界检查 |
Level 1–13 分类
- 存储类:Level 1(logcat)/ Level 2(DEX 常量池)/ Level 3(SharedPrefs、SQLite、文件)/ Level 12(
.so的.rodata) - 输入验证类:Level 7(SQL 注入)/ Level 8(URL Scheme)/ Level 13(Buffer Overflow)
- 访问控制类:Level 9(导出 Activity)/ Level 10(Intent 伪造)/ Level 11(导出 ContentProvider)
贯穿 13 关的核心原则
- 搜 API,不读逻辑 — 每类漏洞都有固定 Android API 特征
strings是万能钥匙 — DEX / ELF / DB 都能抽字符串- Manifest 是第一入口 — 导出组件一览无余
- 平台特征决定攻击路径 — 存储位置、IPC、导出规则由 Android 固化
- JNI 打破 Java 安全边界 — Native 引入 C 类漏洞分析方法
- 先搜
strings,再反编译 — 命中方向 → 定位代码 → 构造攻击,效率最高
写给以后的自己:
Android 漏洞分析最省时间的路径,往往不是“先读懂业务”,而是“先识别平台 API 暴露的攻击面”。DIVA 这 13 关,本质就是在反复练习这件事。