Drozer指北

Drozer 适合回答一个很具体的问题:一个装在同一台手机上的普通应用,能通过 Android IPC 调用目标应用的哪些功能? Console 提供模块和交互界面,Agent 在手机上发起调用。这个分工决定了如何解读它的结果。

这份笔记最早使用 Drozer 2.4.4 和 Python 2。原来的安装日志、组件分类和命令速查都保留;新版环境与 Sieve 验证放在对应章节补充。新增分析没有在手机上重跑,旧终端输出与新增加的原理图分开标明。

先分清调用身份

Console 经过 ADB 转发连接 Agent,Agent 以自己的 UID 调用目标组件

Agent 和被测 App 通常处于不同 UID。发现导出组件后,它会尝试通过 Activity、Binder、ContentResolver 等系统接口访问目标;目标仍然可以用组件权限、签名权限和业务逻辑拒绝访问。

adb shell am start 的调用方是 shell,Drozer 发起的调用则来自 Agent。两种结果不能直接互换。尤其在检查自定义权限、系统权限、调用 UID 时,需要在报告中写明是谁发出的请求。Agent 自己也可能声明或获得权限,开始前应执行 permissions,不要笼统地把它当作“没有任何权限的 App”。

另一个常见误解是把 shell 当作 root:Drozer 的 shell 默认在 Agent 的进程身份下运行。它能读取一个路径,并不自动代表突破了目标 App 的沙箱。

安装与连接

Console:把旧环境与当前安装方法分开

官方仓库已迁移到 ReversecLabs/drozer。Python 3 分支的安装说明推荐 pipx,并要求 JDK 11 或更高版本;Python 与依赖版本应以所安装版本的元数据为准。原文的 pip2、旧 wheel 和逐项降级依赖只适合复原当年的环境。

1
2
3
4
5
python --version
java -version
pipx install drozer
pipx ensurepath
drozer --help

如果 PATH 刚被修改,重新打开终端。需要复现实验时,保存实际安装版本、wheel 哈希和 Agent APK 哈希,避免下次拿到不同版本却沿用同一份记录。

官方指南目前不支持在 Windows 原生运行 Console;Windows 主机可使用 WSL 或官方 Docker 镜像。使用 WSL/Docker 时尤其要确认:adb 运行在哪个系统、31415 转发在哪个网络命名空间、Console 连接的 localhost 又指向哪里。

Agent:装好以后还要启动服务

从官方 Agent 发布页取得 APK,安装到测试设备:

1
2
3
4
5
adb devices
adb install drozer-agent.apk
adb forward tcp:31415 tcp:31415
adb forward --list
drozer console connect

安装后先在手机中打开 Agent,启用 Embedded Server,再连接 Console。多台设备时,用 adb -s SERIAL ... 为选定设备建立转发。默认服务端口是 31415;这个数字指 Agent 的监听端口,不是目标 App 的端口。

USB 转发减少了网络环境的不确定性,但不会替你关闭 Agent 的网络监听。测试结束后关闭 Embedded Server,并清理本次转发:

1
adb forward --remove tcp:31415

连接失败时按链路检查:adb 是否授权、Agent 服务是否启动、转发是否指向正确设备、宿主机端口是否被占用、Console 与 Agent 是否兼容。此时还没有开始测目标 App,不能把连接错误解释成目标的防护效果。

原来的 2.4.4 安装记录

旧笔记的安装输出保留在这里,便于回查当时的依赖、报错和 Agent 连接过程。Python 2 已停止维护,这部分用于复原历史环境;新安装使用上一节的官方 Python 3 路径。

原记录中有几处需要单独纠正:#assume-yes 不是 apt 的选项,正确形式是 --assume-yes 或 -y;java -Version 大小写错误,应为 java -version;帮助参数同样应写 --help。下方作为当时终端记录保留,不要把失败行当成推荐安装命令。

Linux 旧环境

展开原始安装命令、依赖日志、报错与连接输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
┌──(g0at㉿kali)-[~/Desktop]
└─$ wget https://github.com/FSecureLABS/drozer/releases/download/2.4.4/drozer-2.4.4-py2-none-any.whl


┌──(g0at㉿kali)-[~/Desktop]
└─$ sudo apt-get #assume-yes install python-pip


┌──(g0at㉿kali)-[~/Desktop]
└─$ pip2 install wheel
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: wheel in /usr/local/lib/python2.7/dist-packages (0.37.1)

┌──(g0at㉿kali)-[~/Desktop]
└─$ pip2 install pyyaml
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Defaulting to user installation because normal site-packages is not writeable
Collecting pyyaml
Downloading PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl (574 kB)
|████████████████████████████████| 574 kB 436 kB/s
Installing collected packages: pyyaml
Successfully installed pyyaml-5.4.1

┌──(g0at㉿kali)-[~/Desktop]
└─$ pip2 install pyhamcrest
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Defaulting to user installation because normal site-packages is not writeable
Collecting pyhamcrest
Downloading PyHamcrest-1.10.1.tar.gz (43 kB)
|████████████████████████████████| 43 kB 252 kB/s
Collecting six
Downloading six-1.16.0-py2.py3-none-any.whl (11 kB)
Building wheels for collected packages: pyhamcrest
Building wheel for pyhamcrest (setup.py) ... done
Created wheel for pyhamcrest: filename=PyHamcrest-1.10.1-py2-none-any.whl size=48898 sha256=7d5493ff342fd5ea5ec10a6d8713f7519bfcf4cc98d1b101cb75039f15e76e43
Stored in directory: /home/g0at/.cache/pip/wheels/f5/8c/e2/f0cea19d340270166bbfd4a2e9d8b8c132e26ef7e1376a0890
Successfully built pyhamcrest
Installing collected packages: six, pyhamcrest
Successfully installed pyhamcrest-1.10.1 six-1.16.0

┌──(g0at㉿kali)-[~/Desktop]
└─$ pip2 install protobuf
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Defaulting to user installation because normal site-packages is not writeable
Collecting protobuf
Downloading protobuf-3.17.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl (1.0 MB)
|████████████████████████████████| 1.0 MB 349 kB/s
Requirement already satisfied: six>=1.9 in /home/g0at/.local/lib/python2.7/site-packages (from protobuf) (1.16.0)
Installing collected packages: protobuf
Successfully installed protobuf-3.17.3

┌──(g0at㉿kali)-[~/Desktop]
└─$ pip2 install pyopenssl
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Defaulting to user installation because normal site-packages is not writeable
Collecting pyopenssl
Downloading pyOpenSSL-21.0.0-py2.py3-none-any.whl (55 kB)
|████████████████████████████████| 55 kB 304 kB/s
Collecting cryptography>=3.3
Downloading cryptography-3.3.2-cp27-cp27mu-manylinux2010_x86_64.whl (2.6 MB)
|████████████████████████████████| 2.6 MB 686 kB/s
Requirement already satisfied: six>=1.5.2 in /home/g0at/.local/lib/python2.7/site-packages (from pyopenssl) (1.16.0)
Collecting cffi>=1.12
Downloading cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl (390 kB)
|████████████████████████████████| 390 kB 4.8 MB/s
Collecting enum34; python_version < "3"
Downloading enum34-1.1.10-py2-none-any.whl (11 kB)
Collecting ipaddress; python_version < "3"
Downloading ipaddress-1.0.23-py2.py3-none-any.whl (18 kB)
Collecting pycparser
Downloading pycparser-2.21-py2.py3-none-any.whl (118 kB)
|████████████████████████████████| 118 kB 18.2 MB/s
Installing collected packages: pycparser, cffi, enum34, ipaddress, cryptography, pyopenssl
Successfully installed cffi-1.15.1 cryptography-3.3.2 enum34-1.1.10 ipaddress-1.0.23 pycparser-2.21 pyopenssl-21.0.0

┌──(g0at㉿kali)-[~/Desktop]
└─$ pip2 install twisted
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Defaulting to user installation because normal site-packages is not writeable
Collecting twisted
Downloading Twisted-20.3.0-cp27-cp27mu-manylinux1_x86_64.whl (3.2 MB)
|████████████████████████████████| 3.2 MB 396 kB/s
Collecting zope.interface>=4.4.2
Downloading zope.interface-5.5.2.tar.gz (300 kB)
|████████████████████████████████| 300 kB 2.5 MB/s
Collecting attrs>=19.2.0
Downloading attrs-21.4.0-py2.py3-none-any.whl (60 kB)
|████████████████████████████████| 60 kB 3.8 MB/s
Collecting hyperlink>=17.1.1
Downloading hyperlink-21.0.0-py2.py3-none-any.whl (74 kB)
|████████████████████████████████| 74 kB 1.0 MB/s
Collecting constantly>=15.1
Downloading constantly-15.1.0-py2.py3-none-any.whl (7.9 kB)
Collecting Automat>=0.3.0
Downloading Automat-22.10.0-py2.py3-none-any.whl (26 kB)
Requirement already satisfied: PyHamcrest!=1.10.0,>=1.9.0 in /home/g0at/.local/lib/python2.7/site-packages (from twisted) (1.10.1)
Collecting incremental>=16.10.1
Downloading incremental-22.10.0-py2.py3-none-any.whl (16 kB)
Requirement already satisfied: setuptools in /usr/local/lib/python2.7/dist-packages (from zope.interface>=4.4.2->twisted) (44.1.1)
Collecting idna>=2.5
Downloading idna-2.10-py2.py3-none-any.whl (58 kB)
|████████████████████████████████| 58 kB 4.6 MB/s
Collecting typing; python_version < "3.5"
Downloading typing-3.10.0.0-py2-none-any.whl (26 kB)
Requirement already satisfied: six in /home/g0at/.local/lib/python2.7/site-packages (from Automat>=0.3.0->twisted) (1.16.0)
Building wheels for collected packages: zope.interface
Building wheel for zope.interface (setup.py) ... done
Created wheel for zope.interface: filename=zope.interface-5.5.2-cp27-cp27mu-linux_x86_64.whl size=198975 sha256=c18b24a79ba85066a8b982b2f624c23b285a56618aae08f309f200f02383a5f2
Stored in directory: /home/g0at/.cache/pip/wheels/8f/60/b0/736e361327f969f38ad9ee2e24132600a82fac63ab43b7082e
Successfully built zope.interface
Installing collected packages: zope.interface, attrs, idna, typing, hyperlink, constantly, Automat, incremental, twisted
WARNING: The script automat-visualize is installed in '/home/g0at/.local/bin' which is not on PATH.
Consider adding this directory to PATH or, if you prefer to suppress this warning, use #no-warn-script-location.
WARNING: The scripts cftp, ckeygen, conch, mailmail, pyhtmlizer, tkconch, trial, twist and twistd are installed in '/home/g0at/.local/bin' which is not on PATH.
Consider adding this directory to PATH or, if you prefer to suppress this warning, use #no-warn-script-location.
Successfully installed Automat-22.10.0 attrs-21.4.0 constantly-15.1.0 hyperlink-21.0.0 idna-2.10 incremental-22.10.0 twisted-20.3.0 typing-3.10.0.0 zope.interface-5.5.2

┌──(g0at㉿kali)-[~/Desktop]
└─$ pip2 install service_identity
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Defaulting to user installation because normal site-packages is not writeable
Collecting service_identity
Downloading service_identity-21.1.0-py2.py3-none-any.whl (12 kB)
Collecting pyasn1-modules
Downloading pyasn1_modules-0.3.0-py2.py3-none-any.whl (181 kB)
|████████████████████████████████| 181 kB 456 kB/s
Collecting pyasn1
Downloading pyasn1-0.5.1-py2.py3-none-any.whl (84 kB)
|████████████████████████████████| 84 kB 3.5 MB/s
Requirement already satisfied: cryptography in /home/g0at/.local/lib/python2.7/site-packages (from service_identity) (3.3.2)
Requirement already satisfied: six in /home/g0at/.local/lib/python2.7/site-packages (from service_identity) (1.16.0)
Requirement already satisfied: attrs>=19.1.0 in /home/g0at/.local/lib/python2.7/site-packages (from service_identity) (21.4.0)
Requirement already satisfied: ipaddress; python_version < "3.3" in /home/g0at/.local/lib/python2.7/site-packages (from service_identity) (1.0.23)
Requirement already satisfied: cffi>=1.12 in /home/g0at/.local/lib/python2.7/site-packages (from cryptography->service_identity) (1.15.1)
Requirement already satisfied: enum34; python_version < "3" in /home/g0at/.local/lib/python2.7/site-packages (from cryptography->service_identity) (1.1.10)
Requirement already satisfied: pycparser in /home/g0at/.local/lib/python2.7/site-packages (from cffi>=1.12->cryptography->service_identity) (2.21)
Installing collected packages: pyasn1, pyasn1-modules, service-identity
Successfully installed pyasn1-0.5.1 pyasn1-modules-0.3.0 service-identity-21.1.0

┌──(g0at㉿kali)-[~/Desktop]
└─$ sudo apt-get install #assume-yes openjdk-8-jdk-headless
正在读取软件包列表... 完成
正在分析软件包的依赖关系树... 完成
正在读取状态信息... 完成
E: 无法定位软件包 openjdk-8-jdk-headless

┌──(g0at㉿kali)-[~/Desktop]
└─$ java -Version
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Unrecognized option: -Version
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

┌──(g0at㉿kali)-[~/Desktop]
└─$ javac -version
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
javac 17.0.8

┌──(g0at㉿kali)-[~/Desktop]
└─$ sudo pip2 install drozer-2.4.4-py2-none-any.whl
DEPRECATION: Python 2.7 reached the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 is no longer maintained. pip 21.0 will drop support for Python 2.7 in January 2021. More details about Python 2 support in pip can be found at https://pip.pypa.io/en/latest/development/release-process/#python-2-support pip 21.0 will remove support for this functionality.
Processing ./drozer-2.4.4-py2-none-any.whl
Collecting pyyaml>=3.11
Downloading PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl (574 kB)
|████████████████████████████████| 574 kB 466 kB/s
Collecting protobuf>=2.6.1
Downloading protobuf-3.17.3-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl (1.0 MB)
|████████████████████████████████| 1.0 MB 6.3 MB/s
Collecting pyopenssl>=16.2
Downloading pyOpenSSL-21.0.0-py2.py3-none-any.whl (55 kB)
|████████████████████████████████| 55 kB 2.2 MB/s
Collecting six>=1.9
Downloading six-1.16.0-py2.py3-none-any.whl (11 kB)
Collecting cryptography>=3.3
Downloading cryptography-3.3.2-cp27-cp27mu-manylinux2010_x86_64.whl (2.6 MB)
|████████████████████████████████| 2.6 MB 2.3 MB/s
Collecting cffi>=1.12
Downloading cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl (390 kB)
|████████████████████████████████| 390 kB 22.1 MB/s
Collecting enum34; python_version < "3"
Downloading enum34-1.1.10-py2-none-any.whl (11 kB)
Collecting ipaddress; python_version < "3"
Downloading ipaddress-1.0.23-py2.py3-none-any.whl (18 kB)
Collecting pycparser
Downloading pycparser-2.21-py2.py3-none-any.whl (118 kB)
|████████████████████████████████| 118 kB 9.4 MB/s
Installing collected packages: pyyaml, six, protobuf, pycparser, cffi, enum34, ipaddress, cryptography, pyopenssl, drozer
Successfully installed cffi-1.15.1 cryptography-3.3.2 drozer-2.4.4 enum34-1.1.10 ipaddress-1.0.23 protobuf-3.17.3 pycparser-2.21 pyopenssl-21.0.0 pyyaml-5.4.1 six-1.16.0

┌──(g0at㉿kali)-[~/Desktop]
└─$ drozer
usage: drozer [COMMAND]

Run `drozer [COMMAND] #help` for more usage information.

Commands:
console start the drozer Console
module manage drozer modules
server start a drozer Server
ssl manage drozer SSL key material
exploit generate an exploit to deploy drozer
agent create custom drozer Agents
payload generate payloads to deploy droze

┌──(g0at㉿kali)-[~/Desktop]
└─$ adb install drozer-agent-2.3.4.apk #安装完APP后需要在Android侧打开drozer
Performing Streamed Install
Success

┌──(g0at㉿kali)-[~/Desktop]
└─$ adb forward tcp:31415 tcp:31415 #设置端口转发
31415

┌──(g0at㉿kali)-[~/Desktop]
└─$ drozer console connect #启动Drozer
/home/g0at/.local/lib/python2.7/site-packages/OpenSSL/crypto.py:14: CryptographyDeprecationWarning: Python 2 is no longer supported by the Python core team. Support for it is now deprecated in cryptography, and will be removed in the next release.
from cryptography import utils, x509
Selecting 2abfc75c6bf43736 (Qualcomm Zeekr DHU for arm64 12)

.. ..:.
..o.. .r..
..a.. . ....... . ..nd
ro..idsnemesisand..pr
.otectorandroidsneme.
.,sisandprotectorandroids+.
..nemesisandprotectorandroidsn:.
.emesisandprotectorandroidsnemes..
..isandp,..,rotectorandro,..,idsnem.
.isisandp..rotectorandroid..snemisis.
,andprotectorandroidsnemisisandprotec.
.torandroidsnemesisandprotectorandroid.
.snemisisandprotectorandroidsnemesisan:
.dprotectorandroidsnemesisandprotector.

drozer Console (v2.4.4)
dz>

报错及相应的解决方案:https://blog.csdn.net/Jession_Ding/article/details/82528142

Windows 旧环境

下面是原笔记中 Drozer 2.4.4 的 Windows 安装方式,不适用于当前 Python 3 版本。旧环境使用 Python 2.7,安装时选择自动添加环境变量,安装drozer时指定Python2安装路径,drozer 成功安装后,软件位于 Python 2.7 下的 Scripts 目录下。

1
2
3
4
5
pip2 install protobuf -i https://pypi.tuna.tsinghua.edu.cn/simple
pip2 install pyopenssl -i https://pypi.tuna.tsinghua.edu.cn/simple
pip2 install pyyaml -i https://pypi.tuna.tsinghua.edu.cn/simple
pip2 install twisted -i https://pypi.tuna.tsinghua.edu.cn/simple
pip2 install service_identity -i https://pypi.tuna.tsinghua.edu.cn/simple

后面具体运行就和Linux一样了,更推荐在Linux下运行。

四大组件:保留原来的风险分类

原笔记没有公开实际目标的测试流程,下面保留当时整理的组件风险类别。具体复现使用后面的 Sieve 教学例子;表格描述的是可能的问题类型,不是看到 exported 就确认存在的漏洞。

Activity

漏洞种类 危害
越权绕过 Activity 用户界面绕过会造成用户信息窃取
拒绝服务 通过 Intent 给 Activity 传输畸形数据使得程序崩溃从而影响用户体验
Activity 劫持 组件导出导致钓鱼欺诈,Activity 界面被劫持产生欺诈等安全事件
隐式启动 Intent 包含敏感数据 敏感信息泄露

Content Provider

数据共享组件,用于向其他组件乃至其他应用共享数据,所以该组件漏洞与数据相关。

漏洞种类 危害
信息泄露 查看组件数据信息
SQL注入 注入获取相关数据
目录遍历 访问任意可读文件

Broadcast Receiver

漏洞种类 危害
敏感信息泄露 发送的intent没有明确指定接收者,而是简单的通过action进行匹配,恶意应用便可以注册一个广播接收者嗅探拦截到这个广播,如果这个广播存在敏感数据,就被恶意应用窃取了。
权限绕过 可以通过两种方式注册广播接收器,一种是在AndroidManifest.xml文件中通过 <receiver> 标签静态注册,另一种是通过Context.registerReceiver()动态注册,指定相应的intentFilter参数,运行时注册的导出行为需要结合系统版本、targetSdk 和注册标志判断,如果导出的BroadcastReceiver没有做权限控制,导致BroadcastReceiver组件可以接收一个外部可控的url、或者其他命令,导致攻击者可以越权利用应用的一些特定功能,比如发送恶意广播、伪造消息、任意应用下载安装、打开钓鱼网站等
消息伪造 暴露的Receiver对外接收Intent,如果构造恶意的消息放在Intent中传输,被调用的Receiver接收可能产生安全隐患
拒绝服务 如果敏感的BroadcastReceiver没有设置相应的权限保护,很容易受到攻击。最常见的是拒绝服务攻击。拒绝服务攻击指的是,传递恶意畸形的intent数据给广播接收器,广播接收器无法处理异常导致crash。 拒绝服务攻击的危害视具体业务场景而定,比如一个安全防护产品的拒绝服务、锁屏应用的拒绝服务、支付进程的拒绝服务等危害就是巨大的。

Service

Service(服务)是一个一种可以在后台执行长时间运行操作而没有用户界面的应用组件。服务可由其他应用组件启动(如Activity),其生命周期可以独立于启动它的 Activity,但并不保证永久存活;进程回收、后台执行限制与停止条件都要考虑。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。

漏洞种类 危害
权限提升 当 Service 对外导出,却未对调用者做有效权限和身份检查,那么恶意构造的APP都可以对此Service传入恰当的参数进行调用,导致恶意行为发生比如调用具有system权限的删除卸载服务删除卸载其他应用。
service劫持 旧式隐式 Service 调用存在解析歧义;现代 Android 对相关调用有明确限制,应使用显式目标,不能假设由安装先后保证安全
消息伪造 暴露的Service对外接收Intent,如果构造恶意的消息放在Intent中传输,被调用的Service接收可能产生安全隐患
拒绝服务 Service的拒绝服务主要来源于Service启动时对接收的Intent等没有做异常情况下的处理,导致程序崩溃

用 Sieve 走一遍组件验证

Sieve 是配套的故意脆弱应用。以下采用当前官方指南中的包名 com.withsecure.example.sieve;旧材料可能使用 MWR 包名,组件路径也会不同,应先枚举安装包,不能只替换字符串就假定命令适用。

1. 列包,再读攻击面

1
2
3
4
run app.package.list -f sieve
run app.package.info -a com.withsecure.example.sieve
run app.package.attacksurface com.withsecure.example.sieve
run app.package.manifest com.withsecure.example.sieve

app.package.attacksurface 接收位置参数,而 app.package.info 使用 -a,这是很容易混用的一处。各模块参数不是统一模板,有疑问就执行 help 模块名。

导出的 Activity、Service、Receiver、Provider 数量只是入口清单。继续记录每个入口的权限、可控参数、对应代码和最终动作,才能判断是否存在越权。合法的分享 Activity 也会导出,不能按 exported 数量给漏洞定级。

2. Activity:验证是否真的绕过认证

1
2
3
run app.activity.info -a com.withsecure.example.sieve
help app.activity.start
run app.activity.start --component com.withsecure.example.sieve com.withsecure.example.sieve.activity.PWList

官方 Sieve 示例用 PWList 演示绕过入口界面的访问。复现时,应先在正常流程中创建一条无敏感内容的测试记录,再退回未解锁状态,从 Agent 发起调用,确认是否仍能读到那条记录。

这里至少有三种不同结果:系统拒绝启动;页面启动但业务层拒绝读取;页面启动并返回本不应暴露的数据。只有最后一种才能支撑对应的数据访问结论。若页面只显示了缓存内容,还要区分它与实时读取受保护资源的路径。

修复后保持相同初始状态和输入再试一次,并验证正常解锁后的合法流程仍能工作。单独补一个首页登录判断,无法保护直接访问的内部 Activity。

3. Provider:URI、权限与查询参数分别看

1
2
3
run app.provider.info -a com.withsecure.example.sieve
run scanner.provider.finduris -a com.withsecure.example.sieve
run app.provider.query content://com.withsecure.example.sieve.provider.DBContentProvider/Passwords/ --vertical

这条 URI 来自官方 Sieve 示例。换目标时应先获得实际 authority、path 和匹配规则,再构造查询。扫描器没有发现 URI,可能只是猜测范围或可见性受限,不能据此证明 Provider 安全。

层次 需要确认的内容
组件 exported、全局 readPermission/writePermission
路径 path-permission、UriMatcher、末尾斜杠、编码与规范化
数据操作 query/insert/update/delete/openFile 是否各自受控
SQL 构造 projection、selection、sortOrder 是否有白名单和正确绑定
返回结果 实际返回哪些列、行、文件;是否超出调用者权限

Sieve 的教学价值之一,是说明“有权限声明”与“每条访问路径都被权限覆盖”之间的差别。例如精确路径 /Keys 与 /Keys/ 是否落入同一权限规则,必须结合 Manifest 和 URI 路由分析,不能凭界面显示的一个权限名下结论。

检查 SQL 注入时,可以从单引号等最小输入观察参数是否进入 SQL 语法,但 SQL 报错本身不足以证明已越权读取数据。还需要结合源代码或可重复的、无敏感数据的对照查询确认影响。selectionArgs 可以绑定值,不能替代对表名、列名和排序表达式的约束。

文件型 Provider 的 openFile 又是另一条路径:核对传入 URI 如何转换成文件路径、是否限制打开模式、规范化后是否仍位于允许目录。只读到 /etc/hosts 这类公开文件,不能直接写成“任意私有文件读取”;应在明确授权的目标测试版中,让 Provider 自己的后端目录包含一份非敏感 marker,再用它验证目录边界。

4. Receiver 与 Service:输入到达哪里

1
2
3
4
run app.broadcast.info -a com.withsecure.example.sieve
run app.service.info -a com.withsecure.example.sieve
help app.broadcast.send
help app.service.send

Receiver 要同时看静态声明和运行时注册,关注 action、permission、export 标志,以及收到 extras 后触发的操作。能伪造一个 action,并不意味着接收器会执行敏感动作;缺失字段导致崩溃,也需要确认影响范围与可重复性。

Service 则先判断它通过启动 Intent、Messenger 还是 AIDL/Binder 暴露能力。app.service.send 适合它支持的消息交互方式,并不是任意 AIDL 接口的通用调用器。遇到自定义接口,应根据接口定义编写最小测试客户端,并记录调用者 UID 和权限。

新版 Android 对后台执行、导出声明、运行时 Receiver 注册都有版本条件,详见 Android 安全笔记。旧系统上的成功路径不能直接外推到新版本。

从一条模块命令追到 Android API

理解 Drozer 不必一上来读完整框架。先挑自己正在使用的一个模块,沿着三步往下追:参数怎样解析、Console 向 Agent 请求什么、目标 App 最终收到什么 Android 调用。

以 app.provider.query 为例,URI、projection、selection、selectionArgs 和 sortOrder 最终对应 ContentResolver 查询的几个独立参数。模块输出表格是返回 Cursor 的一种显示方式,表格为空不等于系统拒绝了调用:可能查询成功但没有匹配记录,也可能 Provider 自己返回空 Cursor。异常、空 Cursor、null 返回值和无响应要分别保存。

再看 app.provider.info。它读取 PackageManager 返回的 ProviderInfo,显示 authority、读写权限、URI grant 与 path-permission 等元数据。这些数据很有用,但它没有替你执行 Provider 的每一种操作,更不会自动证明某条 query()、openFile() 分支有业务鉴权。info 与实际调用回答的是两个不同问题。

这两个模块可直接在 官方 provider.py 中对照。阅读时先检查 add_arguments(),再看 execute();不需要根据网上的命令截图猜参数位置。本节核对的是 2026-09-22 官方 develop 源码,安装其他版本时仍应以本机 help 为准。

模块声明的权限是什么

源码中的 permissions = [...] 用来描述模块与 Agent 能力之间的要求,不是向目标 App 临时申请一个同名业务权限。Android 在实际 IPC 发生时仍然检查调用身份、组件配置及当前授权。

因此记录“Agent 的权限”时,至少区分 Manifest 声明、系统实际授予、临时 URI grant 和目标自己的账户状态。只把源码中的权限字符串复制进报告,无法说明这次调用究竟拥有什么能力。

调用身份:检查发生在哪个线程,结果就可能不同

权限分析不能只看一个 getCallingUid() 函数名。Binder 服务在正在处理的入站事务内读取调用 UID,才有对应远端调用者的意义;如果把工作排进线程池,之后才读取,看到的可能已经是服务自身 UID。

下面是一个只展示检查顺序的 AIDL 服务端片段,enforceCallerMayRead 和 readOwnedRecord 是应用自己的策略函数,不是可以直接复制运行的 Android API:

1
2
3
4
5
public String readRecord(long recordId) {
final int uid = Binder.getCallingUid();
enforceCallerMayRead(uid, recordId); // 仍在入站事务内做授权
return readOwnedRecord(uid, recordId);
}

若确实需要异步执行,应把经过验证的主体和资源范围显式传给后台任务,并在实际操作前考虑授权状态是否已经改变。不要把未经验证的 callerPackage extra 当成 UID 证明;它只是调用方能填写的数据。

Binder 入口先确定调用主体,再授权并传递给后台工作

Binder.clearCallingIdentity() 又会改变当前线程的身份上下文。审计时查看它之前是否已经完成授权,结束后是否用 finally 恢复;不能用“调用 clear 后权限检查通过”来证明原始外部调用者具有权限。UID/PID 的具体语义见 Binder API。PID 会复用,并且某些事务下拿不到可靠值,不适合作为持久授权凭据。

这也解释了为什么 Drozer 和 adb shell 结果不同不一定是工具故障。两者的调用身份、授予权限、用户/profile 和启动环境都可能不同,应该分别记录,不能拿 shell 成功替普通 App 成功。

Activity:把认证状态做成一组对照

直接启动 Activity 的实验,先定义“原本不该允许什么”。以 Sieve 的教学页面为例,目标可以是未解锁状态下读取一条自己创建的 marker。仅仅出现页面或组件启动日志,不能替代数据访问证据。

我会把同一个入口在下面几种状态下分别记录:

前置状态 相同输入下要观察的结果
从未登录/解锁 页面是否自行转回认证入口,数据是否仍能加载
已解锁 正常合法流程能否继续
主动退出后 旧页面、缓存、Intent extras 是否还能恢复受限数据
进程被重新创建后 状态恢复是否跳过了必要检查

这些状态不是所有 App 都有,但一旦存在,就应明确是哪一种。Activity 生命周期重入、onNewIntent() 与首次 onCreate() 可能走不同分支;只修首次启动的判断,未必覆盖再次收到 Intent 的路径。

修复位置也要放对。页面入口可以拒绝未授权启动,但真正返回敏感数据的 repository/service 仍需要按主体检查资源归属。否则换一个导出入口调用相同后端,问题依旧存在。

Provider:把 authority、路径与操作方法拆开验证

一个 Provider 可以有多条 URI 路径、不同方法和不同权限。query 被拒绝,不表示 openFile 一定也被拒绝;读取需要权限,不表示更新自然拥有相同保护。

下面是一份适用于自建教学 Provider 的设计矩阵,URI 是说明用占位地址,不是 Sieve 的实际接口:

URI / 操作 路由意图 应记录的边界
content://lab.provider/public + query 公开教学信息 只返回白名单列,不连带账户私有列
content://lab.provider/records/7 + query 单条记录 调用者是否有权限读取 id=7
同一路径 + update 修改记录 写权限、资源归属与可修改列分别检查
content://lab.provider/files/demo.txt + openFile 共享指定文件 URI grant、打开模式与最终文件位置

不能只在 URI 层检查路径,而在 selection 中允许把查询范围重新扩成全部记录。对于多账户数据,合法记录 ID 和已授权记录 ID 也不是同一个集合。

精确路径和路径前缀

Android 的 path 表示完整路径匹配,pathPrefix 表示前缀匹配,pathPattern 使用特定模式规则;它不是完整的 PCRE 正则表达式。比如 path="/Keys" 与 /Keys/ 的关系,需要同时对照系统权限匹配和 Provider 内部路由,不能只看两者“长得差不多”。定义见 path-permission 文档。

Provider 的路径权限判断与内部 URI 路由必须指向相同的受保护数据

某个权限规则没有匹配,不自动等于访问被允许;还可能有 Provider 全局权限、临时 URI grant 和方法内检查。真正有问题的是这些检查组合后留下了一条未授权可达的数据路径。反过来,单纯把 path 改成更宽的 prefix,也要检查是否把本来公开的数据一并封住。

SQL 值绑定只解决其中一层

selectionArgs 能把输入作为值绑定,避免它变成 SQL 语法,但列名、表名和排序结构不能这样处理。即使查询全部参数化,如果调用者可以选择本不应公开的 password_hash 列,依然可能泄露信息。

在测试版数据集里,可以先建立一条公开 marker 和一条受保护 marker,分别验证:合法条件只能读到公开行;不合法列名被拒绝;排序值在限定列表内;空条件不会意外变成整个数据库的查询。选择 SQL 报错作为线索时,保留 Provider 抛出的异常类型及参数位置,别只截一个“扫描发现 injection”标题。

修复通常是组合措施:固定或白名单化投影与排序,值用绑定参数,业务层强制追加调用者的资源范围,写入字段逐项校验。某个扫描器不再报错,不能替代这些输入维度的回归检查。

文件 Provider 的读取路径

文件型 Provider 常见的调用链是 URI→匹配规则→文件相对名→后端文件→文件描述符。前面的名称校验通过后,还要看路径规范化、可用目录、打开模式以及符号链接等文件系统语义。

一次成功读取公开系统文件,只能说明那条路径可读。要证明超出允许目录,应在自己控制的测试目录中放置非敏感 marker,并同时做“允许目录内可读、允许目录外不可读”的对照。报告的影响范围应以目标进程身份实际可读的资源为界,不能从任意一个 read 成功推导出“全文件系统读取”。

Service:Started、Messenger 和 AIDL 分别看

服务接口 Android 入口 Drozer 的作用与边界
Started Service startService 等入口 构造 Intent,验证输入与启动约束
Messenger Binder 上的 Message 在模块支持的消息格式下设置 what/args/Bundle 并观察回复
AIDL / 自定义 Binder 约定接口、事务与序列化 通常需要按接口定义准备最小测试客户端

app.service.send 不是“任意 Service 都能发一条字符串”的通用接口。报文格式、响应方式与线程模型都由服务实现决定。绑定成功也不等于方法级授权通过,必须继续追到实际业务方法。

Messenger 的 Handler 把消息排到队列中执行;AIDL 则可能在 Binder 线程池中并发进入。两者的共享状态、身份来源和锁设计不能照抄。官方 绑定服务说明 与 AIDL 文档对这两种模型分别说明。

对于需要测试自定义接口的场景,我会先写只完成一个方法调用的客户端,并把参数固定为测试记录,而不是先尝试自动枚举所有事务。这样更容易复核访问权限,也能分清反序列化失败和业务拒绝。

Receiver:静态枚举看不见的部分

Manifest 中没有某个 Receiver,不代表运行时一定不存在。注册可能发生在登录后、某个 Activity 可见时、后台服务启动后,并在生命周期结束时注销。复现要记录注册条件和应用状态。

发送端限制接收者、接收端限制发送者是两条不同方向的控制。敏感广播应确认谁能接收;带敏感动作的 Receiver 则应确认谁能发送并触发。只看 action 字符串是否独特,不构成调用者认证。

还要把系统版本与 targetSdk 写出来。Android 14 对符合条件、target 34+ 的运行时注册要求显式指定导出行为,并存在仅接收系统广播等例外。不能继续沿用“所有动态 Receiver 默认都导出”的笼统结论。Android 14 的运行时 Receiver 变更给出了适用条件。

收到广播后没出现动作,可能是未注册、权限拒绝、action/type/data 不匹配、参数检查失败或后台限制。测试结果要定位到其中一层,不能只有“命令已发送,所以安全/不安全”。

为什么 scanner 阴性结果不能结束审计

自动模块只覆盖它知道的输入和可以观察到的对象。目标动态生成 URI、自定义 AIDL、按账户状态切换组件,都会让静态枚举或通用扫描遗漏入口。受包可见性和平台限制影响,Agent 看到的清单也可能不完整。

最有效的补充通常来自静态代码:把公开入口对应到 onCreate/onNewIntent、onReceive、onStartCommand/onBind、Provider 的 CRUD/openFile 等处理点,然后检查跨进程输入到敏感操作之间是否有完整授权。扫描器用于缩小范围,代码与对照实验用于解释范围内发生了什么。

复测时保留同一个 Agent 身份、同一个用户/profile、同一组前置状态和测试记录。否则修改前来自普通 App、修改后改用 shell,结果即使不同,也无法单独归因于修复。

一份可直接使用的实验记录

可以下载 Drozer 组件验证记录模板。模板把“命令执行结果”和“允许得出的结论”分开,避免扫描输出直接变成漏洞标题。

每个发现至少保留三份记录:合法流程、原始异常路径、修复后相同输入。对于数据访问,记录资源 ID 与非敏感 marker;对于写入,记录修改前后状态和还原方式;对于崩溃,记录实际发生异常的进程和可重复条件。不要用同一段“有风险”覆盖这些不同结果。

本次补充依据公开 API、模块源码和教学应用流程整理,没有在用户手机上执行 Drozer,也没有新增冒充设备操作的截图。文章中的旧安装终端输出是原始笔记保留下来的记录;新增图用来解释进程身份、URI 权限与调用路径。

把模块输出落到组件结论

入口枚举、最小调用、结果对照、代码定位与修复复测的证据链

我更愿意按下面的粒度记录一个发现,而不是只贴整屏扫描结果:

1
2
3
4
5
6
7
8
9
环境:系统版本 / target SDK / APK 哈希 / Console 与 Agent 版本
身份:Agent 的 UID 与已授予权限
入口:组件完整名称或精确 URI
前置状态:未登录;测试账户 A;已有一条 marker 记录
输入:完整模块名与参数
观察:是否成功,返回哪些非敏感字段,是否发生状态变化
对照:正常授权流程;缺少权限或不同用户时的结果
根因:Manifest 配置与实际代码中的检查位置
复测:相同输入被拒绝,合法流程仍通过

例如“Provider 可导出”只描述配置;“Agent 未获得目标读取权限,仍能通过某个 URI 查询到测试账户的 marker 行”才说明访问边界被越过。结论越具体,复测时越容易分辨修复是否覆盖了同一条路径。

模块说明与原命令速查

以下保留原笔记的完整模块分类和命令。模块是否存在、是否支持相同参数,以当前安装版的 list、help 为准;下方删除、更新、插入等模块会改变数据。

模块说明

Activity

1
2
3
4
app.activity.forintent # 找到可以处理已指定的包
app.activity.info # 获取activity组件信息
app.activity.start # 开启activity组件
scanner.activity.browsable # 获取可从web浏览器调用的所有可浏览的activity组件

Service

1
2
3
4
app.service.info # 获取service组件信息
app.service.send # 向服务组件发送消息并显示答复
app.service.start # 开启service组件
app.service.stop # 停止service组件

Content Provider

1
2
3
4
5
6
7
8
9
10
11
12
13
app.provider.columns # 在内容提供程序中列出列
app.provider.delete # 在内容提供程序中删除
app.provider.download # 在内容提供程序中下载支持文件
app.provider.finduri # 在包中查找引用的内容URIS
app.provider.info # 获取Content Provider组件信息
app.provider.insert # 插入到Content Provider组件中
app.provider.query # 查询Content Provider组件
app.provider.read # 从支持文件的Content Provider读取
app.provider.update # 更新Content Provider的记录
scanner.provider.finduris # 搜索可从上下文中查询的Content Provider
scanner.provider.injection # 测试Content Provider的注入漏洞
scanner.provider.sqltables # 查找可通过SQL注入漏洞访问的表
scanner.provider.traversal # 测试Content Provider的基本目录遍历漏洞

Broadcast Receivers

1
2
3
app.broadcast.info # 获取有关广播接收器的信息
app.broadcast.send # 带目的发送广播
app.broadcast.sniff # 注册一个能嗅出特定意图的广播接收器

获取APP包信息

1
2
3
4
5
6
7
8
9
app.package.attacksurface #获取包攻击面
app.package.backup #列出使用备份API的包(在标记“允许备份”时返回true)
app.package.debuggable #查找可调试包
app.package.info #获取有关已安装软件包的信息
app.package.launchintent #获取包的启动意图
app.package.list #列出程序包
app.package.manifest #获取包的AndroidManifest.xml
app.package.native #查找嵌入在应用程序中的本地库
app.package.shareduid #查找具有共享uid的包

原笔记命令速查

通过app.package.list模块获取要检测的app的包名
dz> run app.package.list -f filter_str
查看指定 apk 信息(版本,数据库存储目录,权限信息等)
dz> run app.package.info -a <package_name>
使用app.package.attacksurface模块识别攻击面
dz> run app.package.attacksurface <package_name>
使用app.activity.info模块查看activity组件信息
dz> run app.activity.info -a <package_name>
使用app.provider.info模块查看content provider组件信息
dz> run app.provider.info -a <package_name>
使用app.service.info模块查看service组件信息
dz> run app.service.info -a <package_name>
使用scanner.provider.injection 扫描是否有 sql 注入
dz> run scanner.provider.injection -a <package_name>
使用scanner.provider.finduris扫描访问 content provider 的URI(数据泄露)
dz> run scanner.provider.finduris -a <package_name>
获取指定应用的导出组件
dz> run app.activity.info -a <package_name>
dz> run app.service.info -a <package_name>
dz> run app.broadcast.info -a <package_name>
dz> run scanner.provider.finduris -a <package_name>

参考


Drozer指北
https://g1at.github.io/2024/01/27/Drozer/
作者
g0at
发布于
2024年1月27日
许可协议