Files

192 lines
20 KiB
Markdown

# RPCScope
RPCScope is a small Python orchestrator and correlation engine for authorized SMB and Windows RPC audits. It combines evidence from NetExec, enum4linux-ng, Impacket's `rpcdump`, and an optional Metasploit **scanner** into one JSON schema and a readable report.
The normal CLI scan uses NetExec plus RPC endpoint collection. Full enum4linux-ng collection is now explicit with `--deep`, so broad enumeration does not delay dedicated checks. Add `--no-rpcdump` for SMB/check-only work. The Python API retains its earlier collector defaults for compatibility.
The differentiator is not “run four tools.” It is the evidence layer between collection and conclusions:
- normalize changing console output into host, SMB, share, and RPC-interface facts;
- match RPC UUIDs to security-relevant Windows protocols without claiming that exposure proves a CVE;
- distinguish a confirmed scanner result from an inferred risk signal;
- retain collector artifacts with supplied passwords redacted and report the evidence behind each finding;
- support offline analysis and dry runs for safe development and demos.
## Scope and safety
Use RPCScope only on systems you own or are explicitly authorized to assess. Live collection requires `--i-am-authorized`. Version banners and exposed interfaces are leads for validation, not automatic proof of vulnerability. The initial Metasploit integration uses only `auxiliary/scanner/smb/smb_ms17_010`; it does not run an exploit or payload.
NDRaider is useful architectural inspiration, but it solves a different problem: it extracts NDR grammars from Windows PE files and performs structure-aware RPC fuzzing. RPCScope starts with remote enumeration and correlation. A future integration could ingest NDRaider JSON from authorized lab-side binary analysis, but it should not be a hard dependency.
## Install
```bash
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
rpcscope doctor
```
External collectors are optional for offline analysis. Install the adapters you intend to use: `nxc` (or `netexec`), `enum4linux-ng`, and `impacket-rpcdump`; `msfconsole` is required only when its check is enabled. The native LongLogon precondition check does not require NetExec.
## Usage
Null-session discovery plus shares across one or more IPs/CIDRs:
```bash
rpcscope scan 10.10.10.25 10.10.20.0/24 \
--shares --i-am-authorized -o engagement-a
```
No credential flags are required: omitted `-u/-p` values intentionally select null/anonymous collection.
Authenticated collection without placing the password in shell history:
```bash
rpcscope scan targets.txt -u 'ACME\auditor' --password-prompt \
--shares --i-am-authorized -o engagement-a
```
Enable the non-exploit MS17-010 auxiliary scanner:
```bash
rpcscope scan 10.10.10.0/24 -u '' -p '' --shares \
--msf-ms17-010 --i-am-authorized -o engagement-a
```
Run the credentialless vulnerability-check preset:
```bash
rpcscope scan 10.10.10.0/24 --checks credentialless --shares \
--authorised -o engagement-a
```
List the checker registry and credential requirements with `rpcscope catalog`. The CLI batches the same NetExec module across live hosts, while unrelated modules run in separate processes so a module exception cannot suppress later checks. `safe` remains an alias for `credentialless`; it describes the authentication requirement, not a guarantee about probe impact.
For the expanded audit, `--checks recommended` adds these adapters to the original four credentialless checks:
| Selector | Evidence collected | Positive label |
| --- | --- | --- |
| `petitpotam` | EFSRPC interface binding associated with PetitPotam | `EXPOSURE` |
| `dfscoerce` | DFS namespace RPC binding | `EXPOSURE` |
| `shadowcoerce` | File Server VSS Agent RPC binding | `EXPOSURE` |
| `printerbug` | Print Spooler RPC binding | `EXPOSURE` |
| `mseven` | Event Log RPC binding | `EXPOSURE` |
| `spooler` | Registered MS-RPRN endpoints | `INFO` |
| `oxid` | Anonymous IObjectExporter advertised interface IPs | `INFO` |
| `longlogon` | Guarded CLDAP naming-precondition estimate | `PRECONDITION` |
The five bind adapters use NetExec `coerce_plus` with only `METHOD=...`. When all five are selected, the batch engine uses one `METHOD=All` invocation and attributes each method's evidence separately; subsets run only their selected methods. RPCScope never supplies `LISTENER` or `ALWAYS`. Upstream's “VULNERABLE” bind markers are normalized to `exposed`, since an accepted bind does not prove an authenticated callback, relay opportunity, or CVE. The current PetitPotam lookup may activate EFS through endpoint resolution. Anonymous method access still depends on server policy. The spooler/OXID requests use TCP/135 through NetExec's SMB wrapper, so those two adapters require both ports reachable. OXID-reported addresses are recorded only and never become scan targets. [Upstream implementation](https://github.com/Pennyw0rth/NetExec/blob/main/nxc/modules/coerce_plus.py).
```bash
rpcscope scan 10.10.10.0/24 --checks recommended --shares --authorised -o audit-new
rpcscope scan 10.10.10.0/24 --checks exposure,info --authorised -o inventory-new
rpcscope scan 10.10.10.0/24 --checks recommended --deep --authorised -o deep-new
```
Omitting `--checks` still runs enumeration only. Presets `exposure` and `info` can be combined with explicit checks. Inventory results never cause `NOT VULNERABLE` by themselves. Each check attempt retains its backend and evidence; contradictory results appear as `CONFLICT` in minimal output.
The `smbghost` selector now performs one native, unauthenticated SMB2 negotiation instead of invoking NetExec's login-dependent heuristic. It parses the actual compression context, including frame bounds, response identity, dialect and algorithm array. The result is `EXPOSED[SMB3-COMPRESSION]` or `NOT-EXPOSED[SMB3-COMPRESSION]`, never a CVE or patch verdict. Patched servers may support compression. No compressed transform, exploit, login or remote write is sent. The wire format follows Microsoft's [negotiate request](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/e14db7ff-763a-4263-8b10-0c3944f52fc5) and [response](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/63abf97c-0d09-47e2-88d6-6bfa552949a5) specifications. Its absolute deadline is at most five seconds, including fragmented reads; malformed replies/timeouts are unknown.
PrintNightmare's driver-permission probe and noPac's ticket-size heuristic remain `SUSPECTED` in saved reports (`REVIEW` in the compact CLI). They do not prove code execution. An explicit RPC bind denial is `skipped-access-denied`, not vulnerable or patched. NetExec's explicit exhausted-attempts Zerologon result is now recognized as a scoped, probabilistic negative; generic connection errors are not evidence that a host is not a DC. Several NetExec adapters use `on_login`, so credentialless means no supplied credentials, not guaranteed anonymous access. An attributed failed SMB login is shown as an authentication skip. There are no automatic credential guesses, payloads or unsafe retries. The removed NTLM reflection module remains disabled. See the upstream [PrintNightmare](https://github.com/Pennyw0rth/NetExec/blob/main/nxc/modules/printnightmare.py), [Zerologon](https://github.com/Pennyw0rth/NetExec/blob/main/nxc/modules/zerologon.py), and [noPac](https://github.com/Pennyw0rth/NetExec/blob/main/nxc/modules/nopac.py) implementations.
LongLogon is included in `--checks recommended` (12 checks total). It remains a precondition-only check and can also run separately:
```bash
rpcscope scan 10.10.10.25 --checks longlogon --authorised -o longlogon-lab
```
It uses unauthenticated UDP/389 CLDAP requests: an EX response identifies the DNS domain, then a guarded legacy baseline uses only the fixed tiny `User=x` value. It never sends an oversized payload. The response-size estimate is reported as `PRECONDITION: [LONGLOGON:MET]`, `NOT-MET`, or `BORDERLINE`; missing or invalid evidence is `UNKNOWN`. None of these results establishes patch state, code execution, or a confirmed CVE, and none becomes `VULN` or `NOT VULNERABLE`. This adapter is informed by the MIT-licensed [ADScanPro tester at revision 5c0b86f](https://github.com/ADScanPro/CVE-2026-41089-LongLogon/tree/5c0b86fb957e387c9c3f6d56f7d8ee35e159c2); attribution is retained in `THIRD_PARTY_NOTICES.md`.
`-l/--lhost` is recorded in report metadata for future callback-based checks. It is deliberately unused by the current collectors because share enumeration, endpoint mapping, and the MS17-010 scanner do not need a callback listener.
Preview collection without sending traffic:
```bash
rpcscope scan 10.10.10.0/24 -u '' -p '' --shares --dry-run
```
Analyze previously captured artifacts:
```bash
rpcscope analyze \
--nxc evidence/nxc.txt \
--rpcdump-dir evidence/rpcdump \
--enum4linux-dir evidence/enum4linux \
-o analysis
```
Outputs include a one-line-per-reachable-host `report.txt`, detailed `report.json` and `report.md`, and collector files under `raw/`. Progress, TXT, and Markdown omit unreachable addresses by default; JSON retains the expanded scope and its port outcomes. Unreachable hosts have no scheduled checks. Passwords are never included in report identity labels. Each live run requires an empty output directory to prevent stale evidence from earlier runs being mixed in. Files are written atomically, and reports refresh as hosts finish, with updates capped at twice per second.
Execution coverage and evidence certainty are separate. A completed heuristic check is `SUSPECTED`; an executed check without a reliable verdict is `UNKNOWN`. Both count as completed. Skips appear separately, for example `CHECKS: 9/9 SKIPPED: 2` for eleven selected checks. Only real failures, unfinished work, or interruption produce partial coverage. A skipped or uncertain check still prevents an all-selected-checks clean claim: `NOT VULNERABLE (SELECTED CHECKS)` requires explicit negative vulnerability results and successful collection, with no unresolved selected checks. RPC tags describe endpoint-mapper registrations; bindings have not been independently tested for reachability. Conflicting evidence is preserved in the detailed report.
```text
192.0.2.10 (LAB) CHECKED EXPOSURE: [SMB3-COMPRESSION] [SMB-SIGNING-NOT-REQUIRED] PORTS: 135/open,445/open CHECKS: 9/9 SKIPPED: 2
192.0.2.20 CHECKED UNKNOWN: [MS17-010] PORTS: 135/open,445/open CHECKS: 1/1
```
## Progress and performance
The default CLI uses one updating progress bar on stderr and a compact final report on stdout. It does not print every start/finish event. Color is automatic on terminals; `--color never` or `NO_COLOR` disables it, and `--color always` forces it. Redirected compact progress emits only phase/final lines, never animation escapes. Discovery and audit have separately labelled phase percentages: finishing hundreds of dead targets does not imply the checks are almost finished. The audit denominator is established after live-host discovery; the bar is not an ETA. Dead addresses advance counts silently and never enter a host-check pipeline.
```text
[########--------] 50% audit [25/50] batch:ms17-010 | 12s
192.0.2.10 (LAB-DC) VULN[MS17-010] EXPOSED[SMB3-COMPRESSION]
192.0.2.20 (WS01) NOT-VULN[MS17-010] SKIP[access-denied:1] ERROR[rpcdump]
```
`VULN` means a dedicated checker signature, not proof of remote code execution. `NOT-VULN` is negative for the named probe and identity only. `REVIEW` needs validation. `UNKNOWN` includes a short reason/count instead of repeating every CVE; `SKIP` shows unmet prerequisites; `ERROR` names the failing collection stage. A failed RPC dump no longer hides a separately successful check behind an unexplained per-host `PARTIAL` tag. Full coverage and all check evidence remain in `report.json`, `report.md`, and the uncolored `report.txt`. `--verbose` includes per-check evidence/collection notes; `--format full` restores the longer terminal host lines. `--progress auto` or `--progress plain` restores per-task progress.
Each external invocation and native check records duration in JSON; Markdown groups summed task time by stage (parallel task time is not wall-clock duration). Errors now retain a bounded diagnostic excerpt. A recognized local RPC-dump dependency/argument failure disables further queued copies of that collector; already running tasks finish and the stage remains explicitly skipped/failed. It does not disable checks or hide remote failures. This is not a general circuit breaker for access denial.
RPC dump must return parsed endpoints or an explicit empty inventory without a protocol error; a zero exit code alone is insufficient. A missing requested collector makes coverage partial. Conflicting check attempts remain in JSON, but show `CONFLICT` instead of a positive/clean verdict in reports and are excluded from the compact confirmed count. Optional signing is labelled `SIGNING-OPTIONAL`, not disabled signing.
Use `--progress plain` for log files or `--no-progress` for quiet operation. Lightweight TCP and NetExec inventory discovery use `--discovery-workers 256`, matching the reviewed NetExec thread default. `--jobs 8` limits heavier checks and host collectors separately. Each NetExec check batch contains at most `--jobs` live hosts (capped at 128 targets), with the same NetExec thread limit. Only one check batch runs at a time, avoiding multiplied/nested concurrency. Dedicated checks precede optional broad enumeration; native checks, optional MSF, RPC dump and finally `--deep` enum4linux run in bounded host pipelines.
`--timeout 120` bounds each collector or single-check batch. The five combined bind checks receive five check budgets in total; this is an invocation ceiling, not an independently enforced per-method deadline. `--discovery-timeout 600` bounds discovery separately. Output is spooled to disk; the runner actively stops a noisy collector at its next poll once output exceeds 16 MiB, retains bounded output, and marks the invocation partial.
`--jobs` means maximum concurrent heavy hosts, whether handled by NetExec's batch threads or Python's collector pool. It is optional: omitting it uses eight; `--jobs 1` runs heavy hosts sequentially. Tune discovery separately. Increasing either limit uses more local/network resources; it changes throughput, not detection criteria. `--isolated-checks` retains per-host/per-check execution for diagnosing backend compatibility, without automatic retries.
The installed NetExec SMB help is inspected once and cached. Supported versions use `--no-admin-check` to avoid unnecessary Service Control Manager checks; none of our adapters needs an admin-only callback. Share enumeration uses legacy `--no-write-check` where supported, or current ACL-only defaults where `--file-write-check` is an explicit opt-in. RPCScope never enables file-write testing; unknown share semantics cause an explicit skip/error rather than a guessed flag. Existing filenames supplied as literal credentials are rejected because NetExec would treat them as credential lists.
Accuracy safeguards include host/module/port-indexed output, explicit negative signatures, caught-host-exception detection even on exit zero, and IPv4-only gating for the upstream MS17-010 adapter. The native SMB negotiation supports IPv4 and IPv6. Missing output never implies patched. Failed batches are retained as unknown/partial without replaying probes. Local socket-resource exhaustion is a scan failure, not thousands of supposedly dead hosts.
The offline 32-host `recommended` test uses 24 NetExec **check** processes (six module groups, four batches each), plus native SMB negotiation and LongLogon checks, retaining twelve per-host check records. The prior all-NetExec orchestrator needed 352 check processes; the first batching version needed 28. This measures orchestration work, not live throughput or CVE-detection accuracy. NetExec and the other external collectors are not installed in the development environment, so live version interoperability and network timing still require an authorized lab. A twelve-check audit plus RPC/optional deep inventory cannot be expected to take the same time as a bare `nxc smb` banner scan. See [the source review](docs/NETEXEC_REVIEW.md).
Default discovery expands explicit IPs, CIDRs, DNS names, and UTF-8 target files, deduplicates them, and checks TCP/135 and TCP/445 independently. A host enters collection only if either port is open, retaining RPC-only systems. Closed and timed-out connections are completed reachability outcomes, not blanket partial coverage. For a reachable host, unavailable ports skip only dependent adapters; hosts with neither service reachable receive no collectors/checks. JSON retains all expanded addresses and discovery results. Files allow one IP/CIDR/hostname per line and `#` comments. Use `--connect-timeout 1` to tune connection probing and `--max-hosts 4096` to control the expansion cap. Discovery time is bounded by `--discovery-timeout`, including per-connection timeout clamping; unfinished probes remain distinguishable from completed timeouts. `--discovery smb` keeps an SMB-only workflow for environments where direct TCP probing is inappropriate. The Python `ScanConfig` API retains SMB-only discovery and isolated checks by default for compatibility; set `tcp_discovery=True, batch_checks=True, use_enum4linux=False` for the CLI's normal execution strategy.
Discovery does not test UDP/389: a UDP-only domain controller with neither TCP/135 nor TCP/445 reachable is outside this workflow's discovery coverage, even when `longlogon` is selected.
## Recent advisory review and metadata sync
`rpcscope catalog --advisories` lists seven patch-review advisories: CVE-2020-1206, CVE-2022-26809, CVE-2022-37958, CVE-2024-49112, CVE-2025-47981, CVE-2025-49716, and CVE-2026-41089. These include SMBleed, RPC/NEGOEX, adjacent Active Directory LDAP, and newer Netlogon issues. The registry supplies metadata, never automatic per-host vulnerability claims. LongLogon has the precondition adapter above, included in `recommended`; it does not remotely verify patch state. The other entries have no executable remote adapter. No CVE is assigned to DFSCoerce or ShadowCoerce merely from its technique name.
```bash
rpcscope catalog --advisories
rpcscope sync-cves -o rpcscope-advisories.json
```
Sync refreshes the fixed CVE Program Microsoft CNA JSON records and preserves affected-version metadata and vendor references. It validates record identity and publisher, bounds response size, and replaces the cache only after every record validates. Downloads never install or execute scanner code. A published unauthenticated RCE advisory does not imply a validated scanner or working exploit; the Netlogon [CVE-2026-41089 record](https://www.cve.org/CVERecord?id=CVE-2026-41089) remains patch-review metadata, separate from the precondition measurement. The [original SMBleed scanner](https://github.com/jamf/SMBGhost-SMBleed-scanner) warns that it can crash some unpatched systems, so it has not been added as a default probe.
Ctrl+C stops active collector process groups and retains completed evidence. TCP/135 RPC enumeration runs without credentials, which also avoids an interactive prompt for named accounts with blank passwords. `--shares` uses the verified read-only mode described above, never file-write tests. With `--deep`, enum4linux JSON is exported in a private temporary directory and scrubbed before being retained. The external tools may maintain their own logs or credential databases outside RPCScope's output directory.
Exit codes: `0` completed, `1` real execution/collection failures or unfinished work, `2` invalid input, `130` interrupted. Findings, inconclusive results, normal skips, and an empty reachable-host set do not by themselves cause a nonzero exit. Offline analysis returns `1` for malformed inputs while retaining any valid artifacts it could analyze.
## Tests
```bash
python3 -B -m unittest discover -s tests -v
```
Tests use synthetic artifacts, mocked tool invocations, and local subprocesses. No live network target is required.
## Roadmap
1. Harden parsers against a larger fixture corpus from different tool versions.
2. Move UUID knowledge into signed/versioned data files with protocol references and tests.
3. Add SARIF output and “compare two scans” drift reporting.
4. Add explicit profiles (`safe`, `standard`, `active`) rather than silently adding intrusive modules.
5. Optionally ingest NDRaider grammar JSON to correlate a remotely exposed UUID with a lab-analyzed vendor binary.
6. Add a plugin API for new collectors and rules, keeping the core dependency-free.