谁在偷偷连你的手机?拆解安卓 USB 调试入侵"自动拍照取证"的完整实现
上一篇文章发了我的开源手机安全六件套(GitHub 传送门),评论区有朋友对「ADB 入侵自动拍照」最感兴趣,这篇就把它的实现掰开揉碎讲清楚。
先说风险:USB 调试就是一个常开的门
很多开发者(包括曾经的我)手机的开发者选项常年开着、USB 调试永远不关。这意味着:
任何一台装了 adb 的电脑,只要拿到数据线接触你手机的机会,就能静默安装应用、拉取文件、截取屏幕,甚至执行 shell 命令
部分恶意公共充电桩也走 USB 通道做数据嗅探
更麻烦的是:这个过程在手机上几乎无感知,被连了你都不知道
传统安全软件只做「提醒」,但提醒的问题在于——事发时你可能根本不在手机旁。所以 AdbGuard 的思路是:不仅告警,还要取证。谁连的、什么时候连的、人在哪,全部留下证据。
整体设计:双通道监听 + 防抖
要抓住"USB 设备接入"这个瞬间,安卓给了两条路:
Manifest 静态注册 UsbDeviceAttachedReceiver:App 被杀也能收到系统广播,做兜底
前台服务内运行时注册:服务存活时实时响应,做主通道
同一个 U 盘插入,两个通道可能同时收到广播。所以需要一个统一的处理入口 + 防抖去重:
kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// UsbAlertHelper.kt(节选)
object UsbAlertHelper {
private var lastAction = ""
private var lastEventAt = 0L
fun handleUsbEvent(context: Context, action: String?, deviceName: String): Boolean {
if (action != UsbManager.ACTION_USB_DEVICE_ATTACHED &&
action != UsbManager.ACTION_USB_DEVICE_DETACHED
) return false
// 防抖:双通道可能同时收到同一广播
val now = System.currentTimeMillis()
if (action == lastAction && now - lastEventAt < 2000) return false
lastAction = action
lastEventAt = now
// ... 告警 + 取证
}
}
静态注册的接收器只负责转发:
kotlin
1
2
3
4
5
6
7
8
9
10
11
12
13
// UsbPlugReceiver.kt —— Manifest 静态注册兜底通道
class UsbPlugReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
UsbAlertHelper.handleUsbEvent(
context.applicationContext,
intent.action,
UsbAlertHelper.deviceNameOf(intent)
)
} catch (_: Exception) { }
}
}
一个容易被忽略的细节:ACTION_USB_DEVICE_ATTACHED 是系统受保护广播,只有系统才能发送,所以 exported=true 也不用担心伪造——第三方 App 伪造不了这个 action。
设备名提取:一个小坑
告警里要告诉用户"什么东西插进来了",从 Intent 里取 UsbDevice。安卓 13 前后取法不同,要兼容:
kotlin
1
2
3
4
5
6
7
8
9
10
11
@Suppress("DEPRECATION")
fun deviceNameOf(intent: Intent): String {
val device: UsbDevice? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
}
return device?.productName?.takeIf { it.isNotBlank() } ?: "未知设备"
}
这个函数能直接把"某人的电脑""充电宝""钓鱼线"区分出来——至少在告警里你能看到设备的 productName。
关键一步:拉起前置拍照取证
确认是"接入"事件后,通过自定义 action 拉起前台服务执行拍照:
kotlin
1
2
3
4
5
6
7
8
9
10
const val ACTION_USB_PHOTO = "com.phonGuard.adbguard.action.USB_PHOTO"
// 事件处理中:立即告警(不等轮询)+ 写时间线 + 触发取证
if (config.usbPlugAlertEnabled) {
NotificationHelper.sendAlertNotification(
context, AdbGuardApp.CHANNEL_ADB_GUARD,
title, message, USB_ALERT_ID
)
}
拍照环节有几个实打实的坑,都踩过:
后台启动相机受限:Android 9+ 对后台进程打开相机有严格限制,必须走前台服务(startForegroundService)并在通知栏可见,这正是把监听逻辑放进常驻服务的原因
相机权限与隐私声明:取证拍照必须在隐私政策里明确声明"安全取证用途",上架应用商店时这是审核点
连拍而非单拍:单张照片可能拍糊/闭眼,连拍 3-5 张间隔抓拍,取证成功率直线上升
写入要快:拍照后立即落盘到应用私有目录(MODE_PRIVATE),再更新 UI 列表——取证数据第一时间固化,防止进程被杀丢证据
再补两道保险:TTS 语音 + 事件时间线
TTS 语音播报:连接事件确认后,直接语音喊出来——"检测到 USB 设备接入"。人在旁边时这是最即时的威慑;不在旁边,语音也会吓退操作者
事件时间线:每次事件(接入/断开/拍照)写入 SecurityLogger,带时间戳和严重等级,事后在 App 里可以完整回溯"那天下午到底发生了什么"
kotlin
1
2
3
4
5
6
7
8
9
10
// 事件入时间线(节选)
app.securityLogger.logEvent(
SecurityLogger.SecurityEvent(
type = "usb",
subType = if (attached) "attached" else "detached",
message = message,
severity = if (attached) 2 else 0
)
)
小结
整套方案的核心思路一句话:感知(双通道监听)→ 确认(防抖去重)→ 告警(通知+TTS)→ 取证(连拍落盘)→ 留痕(事件时间线) 。
安全工具最难的不是某个 API 怎么调,而是把"事发瞬间"的完整链条都接住——任何一个环节断了,证据就没了。
开源仓库(觉得思路有用就点个 Star ⭐):
https://github.com/hnpdsyn/PhoneGuard1
Releases 里可以直接下载 6 个 APK,AdbGuard 对应 adbguard-release.apk。
下篇预告:应用锁「假崩溃 + 图标伪装」是怎么骗过窥屏者的——假弹窗的绘制时机、伪装图标的 activity-alias 配置,都会拆开讲。关注不迷路。