NOIR:跑在电脑上的手机隐私审计工具

2026-11-05 11:49:50 2 311 1
  ███╗   ██╗ ██████╗ ██╗██████╗
  ████╗  ██║██╔═══██╗██║██╔══██╗
  ██╔██╗ ██║██║   ██║██║██████╔╝
  ██║╚██╗██║██║   ██║██║██╔══██╗
  ██║ ╚████║╚██████╔╝██║██║  ██║
  ╚═╝  ╚═══╝ ╚═════╝ ╚═╝╚═╝  ╚═╝

NOIR

Github:https://github.com/Verlintas/NOIR/

把「这台手机到底能泄露什么」变成一份可复现的报告。

NOIR 是一个跑在电脑上的审计工具,通过 adb 读取已连接 Android 设备的应用清单、权限和 APK 里的网络端点,给出分类、风险评分和可执行的处置建议。它也可以在需要时调用 jadx 对单个应用做反编译和关键字检索。

定位:审计与自我防护。面向自己的设备、自己的应用、已授权的安全研究。

官网 / Website: https://verlintas.dev/noir/ · 中文

AtomGit 镜像 / Mirror: https://atomgit.com/Verlintas/NOIR


它做什么

  1. 盘点:列出所有「有网络权限 + 敏感权限」的应用,以及它们各自拿到了什么。
  2. 抽取端点:在设备端用 unzip + strings 从每个 APK 的 dex 里提取 URL,只回传几 KB,不把整包拉回电脑。
  3. 分类与打分:按包名和域名识别厂商(Xiaomi / Google / 腾讯 / 高德 …)与类别(遥测、广告、定位、输入法 …),结合权限和上报端点计算风险分。
  4. 数据关联:把权限翻译成"能拿到什么数据"(位置/通讯录/短信/通话/麦克风/相机/屏幕/应用列表/设备标识/文件/日历/账号),再和上报域名配对,得出"哪类数据可能流向哪里"。
  5. 共享 UID:识别 android.uid.system 这类共享用户,列出哪些包实际上共享同一套权限并集。
  6. 清单层:解析 AndroidManifest.xml 二进制 AXML,找出没有权限保护的导出组件、明文流量、备份、debuggable、targetSdk。
  7. 给建议:区分「可安全自动执行」「需人工确认」「不建议动」,并为每条动作生成回滚命令。
  8. 必要时深挖:deep 会把单个 APK 拉到本地,用 jadx 反编译后检索 Runtime.exec、setApplicationEnabledSetting、DexClassLoader、MediaProjection 等高风险调用。

它不做什么

  • 不联网、不上传任何数据、不需要账号。
  • 不修改系统分区,不需要 root。
  • 不会自动卸载任何东西,除非你显式加 --aggressive。

第一次运行

noir

不带参数会进入向导:

  1. 检查 adb,没有就问你要不要自动下载(~/.noir/tools)
  2. 检查设备,没有就打印开发者模式 / USB 调试的开通步骤,然后等你插线(最多 180 秒)
  3. 问一句「开始扫描?」,扫完直接出仪表盘
  4. 问一句「执行这 19 条安全处置?」,y 就执行,并写好回滚文件

全程只会问两三个 y/n,其余都是自动的。

环境自检

noir doctor
NOIR doctor
  platform  Mac OS X aarch64
  java      17.0.19
  adb       /opt/homebrew/.../platform-tools/adb
            Version 37.0.1-15733141
  device    178b0d2f  device  2509FPN0BC
  jadx      /opt/homebrew/bin/jadx
noir setup          # 缺什么下什么
noir setup --yes    # 不询问,适合脚本

出现问题时

所有错误都会带「怎么办」,而不是抛异常栈:

╭─ ERROR ──────────────────────────────────────────────────────────╮
│ 指定的 adb 不存在:/nonexistent/adb                              │
├──────────────────────────────────────────────────────────────────┤
│ 检查路径,或运行 noir setup 自动安装。                           │
╰──────────────────────────────────────────────────────────────────╯

没有设备时给出的是可执行步骤:

╭─ 没有检测到设备 ──────────────────────────────────────────────╮
│  1. 打开开发者模式                                           │
│     小米 / HyperOS:设置 → 我的设备 → 全部参数 → 连点「MIUI 版本」7 次
│  2. 开启 USB 调试:设置 → 系统 → 开发者选项 → USB 调试        │
│  3. 插线后手机弹「允许 USB 调试吗?」→ 勾选「一律允许」        │
│  4. 还不行就换一根数据线(很多线只能充电)                    │
╰──────────────────────────────────────────────────────────────╯

已连接但没授权时,会单独提示「撤销 USB 调试授权后重插」。

支持平台

系统 状态
macOS (Intel / Apple Silicon) ✅
Windows 10 / 11 ✅
Debian / Ubuntu ✅

依赖:

  • JDK 17+
  • Android platform-tools(adb)
  • jadx(仅 deep 命令需要)

程序会自动在常见安装位置查找 adb 和 jadx;找不到时可以用 --adb / --jadx 指定。

# macOS
brew install --cask android-platform-tools jadx

# Debian / Ubuntu
sudo apt install adb
# jadx: https://github.com/skylot/jadx/releases

# Windows (scoop)
scoop install adb jadx

安装

方式一:下载即用(推荐)

到 Releases 下载对应平台的 portable 包:

noir-v0.1.1-macOS-portable.zip
noir-v0.1.1-Windows-portable.zip
noir-v0.1.1-Linux-portable.zip
SHA256SUMS.txt

解压后双击启动器即可,包内自带运行时,不需要安装 Java:

系统 双击这个 如果被拦
macOS Start NOIR.command 右键 → 打开;或 xattr -dr com.apple.quarantine "Start NOIR.command" NOIR.app
Windows Start NOIR.bat SmartScreen → 更多信息 → 仍要运行(批处理会切到 UTF-8 并调用包内 JRE)
Linux Start-NOIR.sh 先 chmod +x Start-NOIR.sh,或在终端执行

启动器会打开一个终端窗口跑完向导,并在结束时停住(不会一闪而过)。

adb 和 jadx 不在包里——首次运行会询问是否自动下载到 ~/.noir/tools。

方式二:从源码构建

git clone https://github.com/Verlintas/NOIR
cd NOIR
./gradlew :cli:installDist
./cli/build/install/noir/bin/noir --about

构建

git clone <repo> && cd NOIR
./gradlew :cli:installDist

产物:

cli/build/install/noir/bin/noir        # macOS / Linux
cli/build/install/noir/bin/noir.bat    # Windows

也可以直接:

./gradlew :cli:run --args="devices"

界面

NOIR.webp

真机输出(macOS Terminal):渐变 logo、CJK 对齐的表格、按风险着色的分数、 每处理完一个 APK 就刷新一次的进度条。

noir ui 会把结果渲染成一块等宽仪表盘;颜色和边框都会按终端能力自动降级:

  • --ascii:纯 ASCII 边框,给旧版 Windows 控制台
  • --no-color / --color:关闭或强制 ANSI 颜色(默认按是否为 TTY 自动判断)
  • --quiet:只留仪表盘,不打印扫描过程
  • --no-clear:不清屏(方便把输出重定向到文件)

用法

noir --about              # 它是什么、会写什么、许可
noir --version
noir devices
noir scan                 # 全量扫描,输出 JSON / Markdown / 域名清单
noir ui                   # 扫描并渲染仪表盘
noir auto                 # 扫描 + 分类 + 生成处置计划(dry-run)
noir auto --apply         # 执行安全的 appops 动作(可回滚)
noir auto --apply --aggressive   # 额外卸载被标记的用户应用
noir deep com.example.app # 拉包 + jadx + 关键字检索
noir block com.example.app        # 打印处置命令
noir block com.example.app --apply

常用参数:

参数 说明
--serial <id> 指定设备
--out <dir> 输出目录(默认 noir-out)
--min <score> 只保留分数 ≥ N 的应用
--limit <n> 最多输出 N 条
--only <regex> 只扫描匹配的包名,例如 --only 'com\.miui\.'
--adb <path> / --jadx <path> 手动指定工具路径
--yes 自动化场景下跳过确认

DeepScan:一条命令反编译 + 处置

noir deepscan                  # 反编译风险最高的 15 个应用
noir deepscan --top 30         # 更多
noir deepscan --all            # 全部被标记应用(慢,占磁盘)
noir deepscan --jobs 4         # 并行 jadx 数量

流程是:扫描 → 按风险排序 → 逐个 adb pull + jadx → 统计代码级信号 (Runtime.exec、DexClassLoader、setApplicationEnabledSetting、 enabled_accessibility_services、MediaProjection、SystemProperties.set、 短信/设备标识、WebView 桥、无障碍/通知监听/设备管理员 …)→ 解析清单 (导出组件、明文流量、备份、debuggable)→ 识别 SDK(Umeng/Firebase/AppsFlyer…) → 输出 deepscan.md / deepscan.tsv → 询问是否清理。

noir deepscan --apply          # 分析后执行安全 appops
noir deepscan --apply --purge  # 再逐个确认卸载高风险用户应用

反编译结果按 包名 + APK 路径 缓存,第二次跑同一个应用会直接复用。

输出

noir-out/
├── report.json               # 结构化结果(应用、权限、端点、建议)
├── report.md                 # 人类可读报告
├── domains-trackers.txt      # 统计/广告/崩溃上报,可整份导入
├── domains-other.txt         # 用途不明,导入前逐条判断
├── domains-functional.txt    # 推送/登录/更新/支付/商店/云同步,禁止拦截
├── domains.txt               # 等于 trackers(兼容旧脚本)
├── plan.txt                  # 本次可执行的 appops
├── applied.txt               # 实际执行的命令、退出码、完整 stdout/stderr
└── rollback.txt              # 回滚命令(成功后写入修改前的 AppOps 模式)

隐私

  • 工具本身不联网:没有遥测、没有版本检查、没有崩溃上报
  • 扫描结果只写在你指定的 --out 目录
  • 唯一的外部请求发生在 noir setup(下载 adb/jadx)和 noir deep(jadx 是本地进程)
  • 报告里可能包含你设备上的包名和端点,分享前自行检查

缓存

扫描结果按 包名 + APK 路径 缓存在 ~/.noir/endpoints.tsv。应用更新后路径变化会自动重新分析,重复扫描通常只需几秒。


架构

adb ──▶ pm / dumpsys ──▶ Dumpsys parser ──┐
                                          ├─▶ cache (pkg + apk path)
adb ──▶ unzip/strings ──▶ endpoints ──────┘        │
                                                   ▼
                                          Classifier ─▶ score ─▶ Advisor
                                                   │              │
                                                   ▼              ▼
                                              report.json    plan.txt
                                              report.md      rollback.txt
                                                   │
                                                   ▼
                                                 Tui / CLI

deep 走另一条支线:adb pull → jadx → 源码 grep。

设计取舍

  • 端点抽取在设备端完成:adb shell 内跑 unzip | strings | grep,避免把几百个 APK 拉回电脑。设备缺少 unzip/strings 时自动退化为本地解析。
  • 共享 UID 特殊处理:Android 上 android.uid.system 下的应用(常见 50+ 个)权限记录在 dumpsys package 尾部的 Shared users 段。解析器会把这些权限合并回每个包,否则最该被看见的应用反而会消失。
  • 建议分级:只有 appops 级别的动作标记为可自动执行;卸载必须显式开启。
  • 无遥测:程序不包含任何上报逻辑。

常见问题

需要 root 吗? 不需要。全部通过 adb 的 shell 权限完成;卸载只是 --user 0 级别,APK 仍在系统分区。

会改坏手机吗? 默认完全只读。--apply 只改 app-ops;执行前会查询原模式,成功执行的动作会写入 rollback.txt,按记录恢复。完整 stdout/stderr 和退出码写入 applied.txt。 noir block 的卸载也会先打印 pm install-existing 的恢复命令。

为什么有些应用只给"人工复核"? 系统应用不能真正卸载(--user 0 卸载也会被 OTA 或恢复出厂装回来),所以只建议 app-ops 或 DNS 层拦截。

DNS 拦截为什么只导出不执行? 跨平台改 DNS 很容易把设备网断掉(我们在开发中踩过),所以只导出清单:

  • domains-trackers.txt —— 统计/广告/崩溃上报,可以整份导入
  • domains-other.txt —— 用途不明,导入前逐条判断
  • domains-functional.txt —— 推送、登录、更新、支付、应用商店、云同步,拦了会掉功能
  • domains.txt —— 现在只包含 trackers,兼容旧脚本

IP、localhost 和带 test/staging 的域名会被自动剔除。

关于作者

whoami128篇文章212篇回复

勤快的搬运工。

评论2次

要评论?请先  登录  或  注册
  • 2楼
    1 小时前

    用 NOIR 做隐私风险处置时,建议采用“基线扫描 → 人工确认 → 最小权限处置 → 保留回滚 → 复扫验证”的闭环。NOIR 的风险分和代码关键字属于静态线索,不应直接等同于已经发生的数据泄露。

    1. 建立扫描基线

    noir doctornoir devicesnoir scan --out noir-beforenoir ui   --out noir-before

    重点检查:

    noir-before/report.mdnoir-before/report.jsonnoir-before/domains-trackers.txtnoir-before/domains-other.txtnoir-before/domains-functional.txt

    先按以下组合排序:

    1. 敏感权限 + 已知追踪/广告端点;
    2. 敏感权限 + 用途不明端点;
    3. 导出组件、明文流量、backup、debuggable;
    4. 仅命中追踪 SDK、但没有明显敏感权限的应用。

    2. 生成并审查处置计划

    noir auto 默认只生成 dry-run 计划:

    noir auto --out noir-planless noir-plan/plan.txt

    确认后再执行:

    noir auto --apply --out noir-plan

    执行结果和回滚信息位于:

    less noir-plan/applied.txtless noir-plan/rollback.txt

    NOIR 的 --apply 主要调整 Android AppOps。不同 Android 版本的 operation 名称存在差异,应优先执行 plan.txt 生成的命令,不要直接套用其他xi统版本的命令。

    验证某个应用的模式:

    adb shell cmd appops get --user 0 com.example.app CAMERAadb shell cmd appops get --user 0 com.example.app RECORD_AUDIO

    3. 按发现类型处置

    NOIR 发现建议处置
    定位、通讯录、短信、通话记录、麦克风、相机等权限 + 追踪域名优先撤销对应 AppOps;重新测试应用核心功能
    敏感权限 + domains-other.txt 域名先确认域名归属和数据流,再决定限制权限还是拦截域名
    仅追踪/广告/崩溃上报域名可将 domains-trackers.txt 加入 DNS sinkhole
    登录、支付、更新、推送、云同步、应用商店域名不应整组拦截;这些已归入 domains-functional.txt
    导出 Activity/Service/Receiver/Provider 且无权限保护在应用侧调整 Manifest
    usesCleartextTraffic=true迁移到 HTTPS,并使用 Network Security Config
    allowBackup=true按业务需要关闭备份或收紧 dataExtractionRules
    Release 包 debuggable=true从发布 Manifest 中移除该配置
    命中 DexClassLoader、Runtime.exec、无障碍、MediaProjection 等逐处人工审查,确认触发条件、权限来源和用户提示

    导出组件的典型修复方式:

    <activity    android:name=".ExportActivity"    android:exported="false" />

    确实需要跨应用调用时,优先使用签名级自定义权限,而不是仅依赖包名或 action:

    <permission    android:name="com.example.permission.CONTROL_SERVICE"    android:protectionLevel="signature" />

    DNS 拦截只能阻断上报,不能阻止应用继续收集权限数据。例如,屏蔽统计 SDK 的域名后,应用仍可能读取设备标识和位置。因此应同时处理权限和 SDK 配置,而不是只导入 domains-trackers.txt。

    4. 深度分析后再修改代码

    优先分析风险最高的应用:

    noir deepscan --top 30 --out noir-deep

    查看:

    noir-deep/deepscan.mdnoir-deep/deepscan.tsv

    也可以单独分析:

    noir deep com.example.app --out noir-deep-single

    重点人工确认:

    • addJavascriptInterface 是否能接触不可信网页;
    • WebView.loadUrl 是否加载远程或用户可控内容;
    • DexClassLoader 加载的代码来自哪里;
    • Runtime.exec 的命令参数是否可被外部输入控制;
    • MediaProjection、无障碍服务、设备管理员是否有明确授权流程;
    • sharedUserId 是否导致多个包共享权限并集。

    历史上的 Exploit-DB EDB-41675(CVE-2012-6636、CVE-2013-4710)是 Android 4.2 时代的 addJavascriptInterface 代码执行案例。NOIR 命中该关键字只能作为排查入口,不能直接给当前应用套用历史 CVE;还需确认当前版本、组件导出状态、JavaScript 来源和实际可达性。

    5. 卸载与恢复

    仅对确认不用的用户应用使用:

    noir auto --apply --aggressive --out noir-plan

    或者针对单个包:

    noir block com.example.app --out noir-singlenoir block com.example.app --apply --out noir-single

    --user 0 卸载后的常见恢复命令:

    adb shell pm install-existing com.example.app

    xi统应用通常不适合直接卸载,应使用 AppOps 或域名层处置。尤其是共享 UID 应用:修改其中一个包可能影响同一 UID 下的其他xi统组件,必须结合 NOIR 输出的共享权限并集逐项确认。

    6. 回滚和复扫

    处置前保留 plan.txt、applied.txt 和 rollback.txt。需要回滚时,核对并执行 rollback.txt 中记录的原始 AppOps 模式;卸载场景则执行对应的 pm install-existing。

    随后重新扫描:

    noir scan --out noir-after

    对比 report.md、report.json 和域名清单,并人工验证:

    • 相机、定位、输入法、推送等核心功能是否正常;
    • AppOps 是否处于预期模式;
    • 目标域名是否仍产生连接;
    • 高风险权限是否仍然保留;
    • 回滚命令能否恢复原状态。

    如果设备存在 Root、Magisk、Xposed 或 Bootloader 解锁,还应单独检查设备完整性。公开案例中的小米 8、MIUI 12.5.2、Android 10、Magisk、Xposed 与 JustTrustMe 组合说明:在被篡改或已 Root 的设备上,TLS pinning 和应用层隐私控制都可能被绕过;相关 Root/Xposed 隐藏案例也表明,仅依赖 Root 检测结果不足以证明设备可信。NOIR 本身不覆盖 Bootloader、Root 框架和xi统完整性。

  • 1楼
    3 天前 13:35

    能否加一些更高级的功能,比如导出聊天记录, 或者浏览器记录