Point it at a Windows setupapi.dev.log and get back severity-graded external-device anomalies — the DMA-capable Thunderbolt/FireWire/PCIe attack surfaces, the removable mass storage, the BadUSB-shaped HID devices, and the OS-synthesized serials that weaken attribution — as forensicnomicon::report::Findings.
Two crates, one workspace:
peripheral-core— the reader: parsessetupapi.dev.log(Vista+) andsetupapi.log(XP) device-installation logs into a uniform [DeviceConnection] stream — bus-classified, VID/PID/iSerial extracted, and every timestamp tagged authoritative-vs-inferred. Pure Rust, nounsafe, no regex engine, no date library.peripheral-forensic— the analyzer: turns the connection stream into severity-gradedforensicnomicon::report::Findings, so external-device evidence aggregates uniformly with the rest of the forensic fleet.
[dependencies]
peripheral-forensic = "0.1" # pulls in peripheral-coreuse peripheral_core::setupapi::parse_setupapi;
use peripheral_forensic::{audit, source};
let log = std::fs::read_to_string(r"C:\Windows\INF\setupapi.dev.log")?;
let devices = parse_setupapi(&log, "setupapi.dev.log");
for anomaly in audit(&devices) {
let finding = anomaly.to_finding(source("evidence-host"));
println!("[{:?}] {} — {}", finding.severity, finding.code, finding.note);
// e.g. [Some(High)] PERIPHERAL-DMA-CAPABLE-DEVICE — a Thunderbolt device … consistent with a direct-memory-access attack surface (MITRE T1200)
}
# Ok::<(), std::io::Error>(())audit(&devices) returns the typed [DeviceAnomaly] stream; audit_findings(&devices, scope) does the parse-to-Finding conversion in one call. A malformed or garbled log degrades line-by-line, never a panic.
One record per device-install section header. The forensic cautions are baked into the type, not just the docs:
device_serialis the USB iSerial — a distinct field fromvolume_serial. A filesystem volume serial and a device's hardware serial are different things; keeping them separate fields means the two can never be conflated in correlation.serial_is_os_generated: bool—truewhen the instance-id serial's 2nd character is&(e.g.7&1c2c4f0a&0). Windows synthesized the serial because the device exposed no real iSerial, so attribution back to a specific physical device is weaker. The OS-generated value is not reported as a realdevice_serial.- Every timestamp is a
Stamp { value, confidence }.first_installfrom the setupapi section header isAuthoritative; the registry-derivedlast_arrival/last_removal(the undocumented0066/0067device properties) areInferred— and arrive only in v0.2. - Correlation join keys (
parent_id_prefix,volume_guid,drive_letter,volume_serial,disk_signature) and a threat lens (dma_capable,mitre) round out the record.
The enumerator (the leading token of a device instance id) classifies the [Bus], which drives two threat lenses:
| Class | Buses | Lens | MITRE |
|---|---|---|---|
| DMA-capable | FireWire, Thunderbolt, PCIe, ExpressCard | Bus-mastering direct-memory-access attack surface | T1200 |
| Mass storage (NOT DMA) | USB mass storage, eSATA, SD/MMC, SCSI/SAS, NVMe | Data staging/exfiltration, autorun payload | T1052.001 / T1091 |
| HID / wireless | USB-HID, Bluetooth | Keystroke-injection (BadUSB) | T1200 |
eSATA is a SATA/storage transport and is explicitly not DMA-capable. (Caveat: SD-Express tunnels PCIe and can be DMA-capable; v0.1 treats bare SD as the legacy non-DMA SD/MMC bus — distinguishing SD-Express needs the device-capability bits the v0.2 registry source carries.)
Each anomaly is an observation ("consistent with …"); the examiner draws the conclusions. Codes are a stable, published contract.
| Code | Severity | Category | What it observes |
|---|---|---|---|
PERIPHERAL-DMA-CAPABLE-DEVICE |
High | Threat | A FireWire / Thunderbolt / PCIe / ExpressCard device connected — consistent with a direct-memory-access attack surface (MITRE T1200) |
PERIPHERAL-MASS-STORAGE-CONNECTED |
Medium | Threat | Removable mass storage connected — consistent with data staging/exfiltration or autorun payload delivery (MITRE T1052.001 / T1091) |
PERIPHERAL-HID-DEVICE |
Medium | Threat | A human-interface device connected — consistent with keystroke-injection hardware such as BadUSB (MITRE T1200) |
PERIPHERAL-OS-GENERATED-SERIAL |
Low | Integrity | The device exposed no real iSerial (Windows synthesized one) — consistent with weaker device attribution |
parse_setupapi(text, file) handles both header grammars, with the real-world >>> / <<< section markers stripped:
- Vista+ — description first, timestamp last:
[Device Install (Hardware initiated) - USB\VID_0781&PID_5583\<serial> 2023/04/15 14:23:11.456] - XP — timestamp first:
[2005/05/12 12:34:56 632.5] Device Install - USB\VID_...
VID/PID, enumerator, and iSerial are extracted from the device instance id; the section-header time becomes the authoritative first_install. Lines that match neither grammar are skipped — never a panic.
The richest source — the Windows registry SYSTEM\CurrentControlSet\Enum\ keys (USBSTOR/USB serials, ParentIdPrefix), MountedDevices (volume serial / drive-letter / disk-signature correlation), the undocumented 0066 / 0067 Last-Arrival / Last-Removal device-property FILETIMEs, and the EVTX device-connection events — requires the (unpublished) winreg-core and winevt-forensic fleet crates. They are deferred to v0.2; v0.1 is scoped to the fully self-contained setupapi.dev.log source and the complete data model.
Built for untrusted logs acquired from potentially compromised systems:
#![forbid(unsafe_code)]across both crates — no FFI, no C bindings. It reads a log authored on Windows from any OS.- Panic-free on malicious input — parsing is lenient (lossy UTF-8) and bounds-checked; the workspace denies
clippy::unwrap_used/expect_usedin production code. A truncated or garbled log degrades line-by-line, never a crash. - Fuzzed — two
cargo-fuzztargets (setupapiparse,forensicfull parse→audit pipeline); afuzz.ymlCI workflow builds and smoke-runs each. - Validated against spec-exact fixtures — the analyzer is exercised end-to-end against
setupapi.dev.log/setupapi.logfixtures matching the Microsoft SetupAPI text-log grammar, with planted DMA / mass-storage / HID / OS-generated-serial traces re-surfaced (seeforensic/tests/real_data.rs).
cargo test
cargo +nightly fuzz run forensic # requires nightly + cargo-fuzzperipheral-forensic is one analyzer in the SecurityRonin forensic fleet. The reader/analyzer split mirrors ntfs-core/ntfs-forensic; findings are emitted in the shared forensicnomicon::report vocabulary so issen can correlate external-device evidence with disk, memory, and log artifacts.
Privacy Policy · Terms of Service · © 2026 Security Ronin Ltd