{"id":51015064,"url":"https://github.com/securityronin/iso9660-forensic","last_synced_at":"2026-06-21T09:02:39.844Z","repository":{"id":360405487,"uuid":"1249724515","full_name":"SecurityRonin/iso9660-forensic","owner":"SecurityRonin","description":"Forensic ISO 9660 reader \u0026 tamper analyzer in pure Rust — analyse() surfaces 23 anomaly findings (redundancy, slack, EDC/ECC, concealment) across multi-session, Rock Ridge, Joliet, El Torito \u0026 raw CD images","archived":false,"fork":false,"pushed_at":"2026-06-16T01:41:10.000Z","size":791,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-16T03:20:16.882Z","etag":null,"topics":["dfir","digital-forensics","disk-image","ecma-119","el-torito","file-format","forensics","incident-response","iso","iso9660","joliet","optical-disc","parser","rock-ridge","rust","security","udf"],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SecurityRonin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-26T01:40:36.000Z","updated_at":"2026-06-16T01:41:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/SecurityRonin/iso9660-forensic","commit_stats":null,"previous_names":["securityronin/iso","securityronin/iso9660-forensic"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/SecurityRonin/iso9660-forensic","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fiso9660-forensic","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fiso9660-forensic/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fiso9660-forensic/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fiso9660-forensic/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SecurityRonin","download_url":"https://codeload.github.com/SecurityRonin/iso9660-forensic/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SecurityRonin%2Fiso9660-forensic/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34603644,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dfir","digital-forensics","disk-image","ecma-119","el-torito","file-format","forensics","incident-response","iso","iso9660","joliet","optical-disc","parser","rock-ridge","rust","security","udf"],"created_at":"2026-06-21T09:02:35.914Z","updated_at":"2026-06-21T09:02:39.838Z","avatar_url":"https://github.com/SecurityRonin.png","language":"Rust","funding_links":["https://github.com/sponsors/h4x0r"],"categories":[],"sub_categories":[],"readme":"[![Crates.io](https://img.shields.io/crates/v/iso9660-forensic.svg)](https://crates.io/crates/iso9660-forensic)\n[![docs.rs](https://img.shields.io/docsrs/iso9660-forensic)](https://docs.rs/iso9660-forensic)\n[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)\n[![CI](https://github.com/SecurityRonin/iso9660-forensic/actions/workflows/ci.yml/badge.svg)](https://github.com/SecurityRonin/iso9660-forensic/actions)\n[![Sponsor](https://img.shields.io/badge/sponsor-h4x0r-ea4aaa?logo=github-sponsors)](https://github.com/sponsors/h4x0r)\n\n**Hand an optical disc image to `analyse()` and get back a ranked list of tamper, corruption, and concealment findings — plus the provenance breadcrumbs that say who, what, and when built it.**\n\nA pure-Rust ISO 9660 reader *and* forensic analyzer. The reader handles the extensions that trip up basic parsers (multi-session, Rock Ridge, Joliet, El Torito, raw 2352-byte CD sectors). The analyzer turns that parsing into **23 anomaly findings** — the redundant copies ISO 9660 keeps everywhere are diffed, and every non-file byte is carved.\n\n## 30 seconds to a finding\n\n```toml\n[dependencies]\niso9660-forensic = \"0.4\"\n```\n\n```rust\nuse iso9660_forensic::analyse;\nuse std::fs::File;\n\nlet mut img = File::open(\"evidence.iso\")?;\nlet report = analyse(\u0026mut img)?;\n\n// Provenance — what a report leads with (observed facts, never conclusions)\nlet v = \u0026report.volume;\nprintln!(\"label={:?}  mastered-by={:?}  created={:?}\",\n         v.volume_label, v.data_preparer_id, v.creation_time);\n\n// Anomalies — ranked by severity, each with a stable code and a plain-language note\nfor a in \u0026report.anomalies {\n    println!(\"[{}] {} — {}\", a.severity, a.code, a.note);\n}\n```\n\n```text\nlabel=\"INSTALL_CD\"  mastered-by=\"MKISOFS 2.01\"  created=Some(\"2026-01-14 09:02:11\")\n[High]   ISO-PATHTABLE-ENDIAN  — path-table entry 3: LBA mismatch L=412 M=88231 …\n[High]   ISO-DISGUISED-EXEC    — `docs/readme.txt` content begins with a PE executable …\n[Medium] ISO-TRAILING-DATA     — 1.2 MB of non-zero data past the declared volume end …\n[Medium] ISO-SUPERSEDED-FILE   — `setup.ini` exists in session 0 but not the active tree …\n```\n\nEvery finding derives its `severity`, `code`, and `note` from a single classified `kind`, so they can't drift — the same shape the sibling `gpt-forensic` / `mbr-forensic` crates use, ready to fold into one uniform report.\n\n## What it detects\n\nThe engine is **redundancy + slack**: ISO 9660 stores most things twice (both-endian fields, two path tables, primary + Joliet trees, per-session descriptors) — diff every copy; then carve every byte no file claims. Each finding distinguishes an *observed fact* from a *\"consistent with\"* inference and leaves conclusions to the examiner.\n\n| Category | Findings |\n|----------|----------|\n| **Cross-redundancy** (tamper) | both-endian field mismatch · L↔M path table · path-table↔tree (phantom/ghost dirs) · primary↔Joliet tree |\n| **Slack \u0026 appended data** | non-zero file slack · trailing payload past volume end · pre-system-area payload · non-zero PVD reserved fields |\n| **Structural** | out-of-bounds extent · overlapping extents · directory cycle · orphaned (unlinked) file |\n| **Temporal** | file recorded after volume · mixed timezones · implausible volume date (pre-1985 / future) · ISO ↔ Rock Ridge time mismatch |\n| **History** | superseded / recoverable content across sessions |\n| **Identity \u0026 escape** | symlink path-traversal \u0026 absolute-target leak |\n| **Concealment \u0026 authenticity** | Rock Ridge ↔ Joliet filename divergence · executable disguised by document extension · invalid/zero EDC · invalid Reed-Solomon P/Q ECC |\n\n…and the provenance summary surfaces mastering-tool fingerprint, volume timestamps, the authoring time-window, Rock Ridge owner UIDs/GIDs/inodes, El Torito boot platforms + boot-image SHA-256, and the Rock Ridge / Joliet / ISO 9660:1999 extension flags.\n\nIt also **degrades gracefully on damaged evidence**: out-of-bounds extents, directory cycles, and truncated images are *reported as findings* rather than crashing the analysis.\n\n## Feed it any optical container\n\n`open()` resolves the common image containers to a `Read + Seek` over the ISO 9660 data track, so the same `analyse()` works on all of them:\n\n```rust\nuse iso9660_forensic::{analyse, open};\n\nlet mut src = open(\"image.cue\")?;   // .iso .cue .ccd .nrg .mds .toc\nlet report = analyse(\u0026mut src)?;\n```\n\n## Browsing the volume\n\nBeyond analysis, `IsoReader` is a full navigator:\n\n```rust\nuse iso9660_forensic::IsoReader;\nuse std::fs::File;\n\nlet mut reader = IsoReader::open(File::open(\"image.iso\")?)?;\nprintln!(\"sessions={}  rock_ridge={}  joliet={}\",\n         reader.session_count(), reader.has_rock_ridge(), reader.has_joliet());\n\nfor entry in reader.walk()? {\n    println!(\"  {}  ({} bytes, LBA {})\", entry.path, entry.record.size, entry.record.lba);\n}\n\nlet entry = reader.find_entry(\"docs/readme.txt\")?;\nlet bytes = reader.read_file_entry(\u0026entry)?;\n```\n\n| Extension | Basic reader | `iso9660-forensic` |\n|-----------|:-----------:|:------:|\n| Multi-session / multi-track | last session only | all sessions (+ per-session walk) |\n| Rock Ridge (RRIP) NM / PX / TF / SL | no | yes |\n| Joliet UCS-2 filenames | no | yes |\n| El Torito boot catalog | no | yes (BIOS + UEFI, multi-section) |\n| ISO 9660:1999 Enhanced Volume Descriptor | no | yes |\n| Raw 2352-byte Mode-1 sectors | no | yes (auto-detected) |\n| Path-traversal / cycle / OOB guards | rarely | always |\n\n`serde` is behind the `serde` feature — every output type derives `Serialize` for JSON / DFXML reporting.\n\n## Validation\n\n- The PVD fields and root listing are reconciled value-for-value against **cdrtools `isoinfo`** (an independent decoder) on the published libcdio `multi_extent_8k.iso` — a Tier 1 check on real third-party bytes.\n- Extension detection is exercised on **independent real-world discs** from distinct sources, so the parser can't share a blind spot with any single fixture generator — Microsoft VL pressing (plain ISO 9660), TinyCore Linux (Rock Ridge + Joliet + El Torito), Windows Server 2019 FOD (UDF NSR02 negative case), and Debian netinst (BIOS+UEFI hybrid boot).\n- Every anomaly was proven *silent on the clean corpus* before shipping; the EDC/ECC validators (ECMA-130 §14) are round-trip and tamper-detection tested.\n- Large images skip automatically when absent; run `bash corpus/fetch.sh` to enable them locally.\n\nEach capability is tagged with its evidence tier — see the [validation report](https://securityronin.github.io/iso9660-forensic/validation/) ([source](docs/validation.md)) for the oracle/corpus backing every claim and the steps to reproduce it. The [supported-format matrix](docs/formats.md) lists every parsed structure.\n\n## Where it fits\n\nThis crate reads **ISO 9660 + its optical layers only**. Other filesystems, partition schemes, and acquisition containers that may co-reside on or wrap a disc are separate single-responsibility crates — compose them at your orchestrator (e.g. `disk-forensic`) rather than expecting this one to know about them.\n\n| Crate | Layer |\n|-------|-------|\n| [`udf-forensic`](https://github.com/SecurityRonin/udf-forensic) · [`hfsplus-forensic`](https://github.com/SecurityRonin/hfsplus-forensic) | Co-resident optical filesystems (UDF, Apple HFS+) |\n| [`apm-forensic`](https://github.com/SecurityRonin/apm-forensic) · [`gpt-forensic`](https://github.com/SecurityRonin/gpt-forensic) · [`mbr-forensic`](https://github.com/SecurityRonin/mbr-forensic) | Partition schemes (same `analyse()` contract) |\n| [`ewf`](https://github.com/SecurityRonin/ewf) · [`aff4`](https://github.com/SecurityRonin/aff4) · [`vmdk`](https://github.com/SecurityRonin/vmdk) · [`vhdx`](https://github.com/SecurityRonin/vhdx) · [`dmg`](https://github.com/SecurityRonin/dmg) | Acquisition / virtual-disk containers |\n\n---\n\n[Privacy Policy](https://securityronin.github.io/iso9660-forensic/privacy/) · [Terms of Service](https://securityronin.github.io/iso9660-forensic/terms/) · © 2026 Security Ronin Ltd\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsecurityronin%2Fiso9660-forensic","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsecurityronin%2Fiso9660-forensic","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsecurityronin%2Fiso9660-forensic/lists"}