# NOIR (Verlintas/NOIR)

> NOIR is a desktop audit tool for Android. Over adb it reads each installed app's permissions and the network endpoints inside its APK, then gives categories, risk scores and reversible fix suggestions.

- GitHub: https://github.com/Verlintas/NOIR
- GitHub.Locker page: https://github.locker/Verlintas/NOIR
- Stars: 1.3K
- Language: Kotlin
- License: MIT
- Last push: 2026-11-02

## Questions and answers

Written by GitHub.Locker from the README and the repository; check the README below for details.

**Is it an app I install on my phone or a tool on my computer?**

A tool on your computer. It reads the connected phone over USB through adb, so it is not an app you install on the phone.

**How do I get it running?**

Download the portable package for your system from Releases, unzip it and double-click the launcher. Run without arguments and a wizard checks adb, asks you to plug in the phone with USB debugging on (up to 180 seconds), then offers to scan and to apply the suggested fixes.

**Do I need Java or adb installed first?**

Not for the portable package, since it bundles its own runtime. adb and jadx are not included: the first run asks whether to download adb to ~/.noir/tools, and jadx is only needed for the deep command.

**Does a finding prove that data was actually sent?**

No. A finding shows what an app can do and which endpoints its code contains. Proving a real transfer needs network-level evidence, which you would have to collect yourself.

**Is it only meant for my own phone?**

It is meant for your own devices and apps, or devices you have explicit permission to audit. Its output is static evidence: an app has a capability and its code contains an endpoint.

**Which computers can run it?**

macOS (Intel and Apple Silicon), Windows 10 and 11, and Debian or Ubuntu Linux are listed as supported.

**What does the deep command do?**

It pulls one app's APK to your computer, decompiles it with jadx and searches for risky calls such as Runtime.exec, DexClassLoader and MediaProjection. It also checks the manifest for exported components, cleartext traffic, backups and debuggable flags.

**Why does it only export DNS block lists?**

Changing DNS across platforms can easily cut off the phone's network, so NOIR exports domain lists instead: trackers, other and functional. Functional domains cover push, login, updates, payments and app store traffic, so blocking them breaks features.

**How is the risk score calculated?**

It combines the app's permissions with the endpoints found in its APK and with vendor and category classification. It also links permissions to the data they could expose and to the domains the app reports to.


---

## README (checked against GitHub 2026-11-03)

Source: https://github.com/Verlintas/NOIR/blob/main/README.md

```
  ███╗   ██╗ ██████╗ ██╗██████╗
  ████╗  ██║██╔═══██╗██║██╔══██╗
  ██╔██╗ ██║██║   ██║██║██████╔╝
  ██║╚██╗██║██║   ██║██║██╔══██╗
  ██║ ╚████║╚██████╔╝██║██║  ██║
  ╚═╝  ╚═══╝ ╚═════╝ ╚═╝╚═╝  ╚═╝
```

# NOIR

[![build](https://github.com/Verlintas/NOIR/actions/workflows/build.yml/badge.svg)](https://github.com/Verlintas/NOIR/actions/workflows/build.yml)
[![release](https://img.shields.io/github/v/release/Verlintas/NOIR)](https://github.com/Verlintas/NOIR/releases)
[![license](https://img.shields.io/github/license/Verlintas/NOIR)](LICENSE)
![platforms](https://img.shields.io/badge/platforms-macOS%20%7C%20Windows%20%7C%20Linux-2ea44f)

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

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

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

**官网 / Website:** https://verlintas.dev/noir/ · [中文](https://verlintas.dev/noir/zh/)

---

## 它做什么

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

## 它不做什么

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

---

## 第一次运行

```bash
noir
```

不带参数会进入向导：

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

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

## 环境自检

```bash
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
```

```bash
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` 指定。

```bash
# 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](https://github.com/Verlintas/NOIR/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`。

**方式二：从源码构建**

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

## 构建

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

产物：

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

也可以直接：

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

---

## 界面

![NOIR dashboard on a Xiaomi 17 Pro Max](docs/screenshot.png)

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

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

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

## 用法

```bash
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：一条命令反编译 + 处置

```bash
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` → 询问是否清理。

```bash
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
└── rollback.txt              # 回滚命令
```

## 隐私

- 工具本身**不联网**：没有遥测、没有版本检查、没有崩溃上报
- 扫描结果只写在你指定的 `--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，每条都能用 `default` 还原，`rollback.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` 的域名会被自动剔除。

**支持 iOS 吗？**
不支持。`core` 是按 Kotlin Multiplatform 组织的，但审计逻辑依赖 Android 的
`dumpsys` 和 APK 格式。

## 参与

- [CONTRIBUTING.md](CONTRIBUTING.md) — 构建、测试、规则放在哪
- [SECURITY.md](SECURITY.md) — 漏洞报告方式（请不要开公开 issue）
- [Issues](https://github.com/Verlintas/NOIR/issues) — bug 请附上 `noir doctor` 输出

## 路线图

- [x] 等宽仪表盘（`noir ui`）
- [x] DeepScan（批量反编译 + 代码级信号）
- [x] 自带运行时的双击即用包（jpackage）
- [ ] 图形界面（Compose Multiplatform）
- [ ] 规则文件可配置（YAML），支持社区规则包
- [ ] 与 `apktool` / `jadx` 的批量流水线
- [ ] 设备端 DNS/VPN 拦截方案导出
- [ ] 扫描结果签名与版本对比

## 免责声明

NOIR 只应用于**你自己的设备**，或你获得明确授权的设备。它给出的是静态证据
（"这个应用具备这个能力，并且代码里有这个端点"），**不能证明**某次具体传输发生过。
请结合网络层证据自行判断。

## 许可

MIT © 2026 Verlintas

