Files

20 KiB

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

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:

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:

rpcscope scan targets.txt -u 'ACME\auditor' --password-prompt \
  --shares --i-am-authorized -o engagement-a

Enable the non-exploit MS17-010 auxiliary scanner:

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:

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.

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 and response 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, Zerologon, and noPac implementations.

LongLogon is included in --checks recommended (12 checks total). It remains a precondition-only check and can also run separately:

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; 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:

rpcscope scan 10.10.10.0/24 -u '' -p '' --shares --dry-run

Analyze previously captured artifacts:

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.

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.

[########--------] 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.

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.

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 remains patch-review metadata, separate from the precondition measurement. The original 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

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.