Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Correctness and evidence-integrity pass, followed by the features a competitive
gap analysis showed every established tool has and Cinder lacked.

### Added — $LogFile, AFF4, VHD/VHDX

- **`$LogFile` parser and tool.** `NtfsLogFile` reads the NTFS transaction
journal: restart-page geometry, fixup-protected `RCRD` pages, records
reassembled across page boundaries, the LSN ↔ offset check that separates live
records from circular-log slack (kept, flagged stale — that slack is often
where a long-deleted name survives), buffer-page duplicates dropped. Names,
parent records and `$FILE_NAME` timestamps are recovered from the payloads
that carry them: file-record initialisation, index-entry add / delete
(delete's undo side), attribute create / delete. Operation names match
Microsoft's. New **$LogFile** tool in Examine reads it from every NTFS volume
in an image or from an extracted file.
- **AFF4 in-process, both ways.** `Aff4Writer` produces AFF4 Standard v1.0
containers — ZIP volume, `container.description`, `version.txt`,
`information.turtle`, zlib-compressed bevies with standard (offset, length) indexes, a
single-range `Map`, and an `Image` object with MD5 / SHA-1 / SHA-256 and case
metadata — the structure pyaff4 and Evimetry emit, verified readable by pyaff4.
`Aff4Reader` opens what Cinder, pyaff4, libaff4 and Evimetry write for disk
images: standard and pre-standard (Evimetry) index layouts, `Image → Map → ImageStream` chains with
multiple ranges and zero / unreadable targets, zlib, **snappy**, **LZ4** and
stored chunks (both decoders in-tree, checked against the reference
implementations), rdflib-style turtle. `EvidenceOpener` detects AFF4, so every
tool that takes an image takes an `.aff4`; the Verify tool checks all three
recorded digests.
- **VHD / VHDX output** through DiscUtils' managed writers (dynamic disks).
- **Convert tool: any → any** across raw, E01, AFF4, VHD and VHDX, with the
output re-read and hashed after writing and a container source checked
against its recorded hash.
- The Python imager sidecar is no longer needed for any format.

### Added — EWF writer, trusted timestamps

- **E01 acquisition in-process.** `EwfWriter` produces EnCase 6-layout chains:
Expand Down
8 changes: 8 additions & 0 deletions LIMITATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ fixture-vs-reference diff is in CI.
- **Blockers:** PDF/A-specific conformance (embedded font subsetting, XMP metadata, the
PDF/A-2b identifier) is not implemented — output is ordinary PDF, not archival PDF/A.

## AFF4

Read support covers physical disk images as pyaff4, libaff4 and Evimetry write them: `Image →
Map → ImageStream` chains with zlib / snappy / LZ4 / stored chunks in either index layout.
Logical containers (one object per file), encrypted volumes and striped multi-volume sets are
not read. The writer produces v1.0 containers with a single map range; unreadable sectors are
zero-filled in the stream and listed in the `.log.json` rather than as a separate map target.

## <a id="custody-anchor"></a> Chain-of-custody log has no external anchor

- **Where:** [`Cinder.Core.Custody.CustodyLog`](src/Cinder.Core/Custody/CustodyLog.cs)
Expand Down
26 changes: 15 additions & 11 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,19 @@ Not in the original plan; landed alongside Phase 1.
UI surfaces exist for every tool. Image acquisition / mounting / shadow
copies are placeholders until the platform-specific drivers ship.

- ✅ Disk imager (raw, E01) — `InProcessImager`: file / block device / E01
chain → `.dd` or an EnCase 6 `.E01` chain (`EwfWriter`, libewf-verified)
with hash-on-read, retry + sector-level fallback with bad-sector offsets
logged (and in the E01's error section), `.log.json` companion, custody
entry. Needs Administrator / root for devices. 🟡 AFF4 still via sidecar.
- ✅ Image verify — in-process: E01 against recorded digests, raw against a
companion digest or SHA256SUMS; verified / failed / unverifiable.
- ✅ Disk imager (raw, E01, AFF4, VHD, VHDX) — `InProcessImager`: file /
block device / E01 / AFF4 → any of the five, all in-process. E01 via
`EwfWriter` (libewf-verified), AFF4 via `Aff4Writer` (pyaff4-verified),
VHD/VHDX via DiscUtils. Hash-on-read, retry + sector-level fallback with
bad-sector offsets logged (and in the E01's error section), `.log.json`
companion, custody entry. Needs Administrator / root for devices.
- ✅ Image verify — in-process: E01 and AFF4 against recorded digests, raw
against a companion digest or SHA256SUMS; verified / failed / unverifiable.
- 🟡 Mount image — VHD/VHDX/ISO via PowerShell `Mount-DiskImage` on Windows
works; E01 mount requires Arsenal Image Mounter (free, external).
- ✅ Convert format — E01 ↔ raw in-process; E01 → raw compared with the
container's recorded hash, raw → E01 re-read and checked after writing.
- ✅ Convert format — raw / E01 / AFF4 / VHD / VHDX in any direction; output
re-read and checked after writing, container sources checked against
their recorded hash.
- 🟡 Write-blocker (Windows) — placeholder, real version blocked on a
signed kernel driver in `drivers/cinder-wb-windows`.
- 🟡 Write-blocker (Linux) — `blockdev --setro` wrapper, works.
Expand All @@ -114,8 +116,10 @@ copies are placeholders until the platform-specific drivers ship.
surfaces name, size, MAC/creation times, MFT index and sequence as
`IsDeleted` rows. Names and timestamps only; contents via the carver.
- ✅ $UsnJrnl:$J — USN_RECORD_V2/V3 parser + USN journal tool (image or
extracted `$J`); timeline ingest of `$J` from triage folders. $LogFile
still tracked.
extracted `$J`); timeline ingest of `$J` from triage folders.
- ✅ $LogFile — transaction-log parser + tool: records reassembled across
pages, stale slack kept and flagged, names / parents / timestamps
recovered from FILE-record and index-entry payloads.

## Phase 4 — Windows artifacts ✅ (most) / 🟡 (some)

Expand Down
12 changes: 7 additions & 5 deletions docs/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ long tail (PST via pypff, Volatility 3, pytsk for non-DiscUtils filesystems).
|---|---|---|
| `Cinder.App` | Avalonia shell, every view-model, every tool implementation, app services | ~55% of all code. **Parsing logic lives here**, in `ViewModels/Tools/*.cs`, not in the library projects — the biggest structural debt (see §9). |
| `Cinder.Core` | Case store (SQLite + Dapper + migrations), custody log, hash service (MD5/SHA-1/SHA-256/BLAKE3 streaming), signature scanner (60+ magics), encrypted-container heuristic, `ExecutableResolver`, `TabularExporter`, `FeatureExtractor` | The genuinely reusable core. |
| `Cinder.Imaging` | `EwfReader`/`EwfStream` (in-process E01, multi-segment, verification), `EvidenceOpener` (E01-or-raw → `Stream`), mounters (VHD/VHDX/ISO via PowerShell; Linux loop), shadow-copy enumeration, write-blocker wrappers, sidecar imager/verifier | EWF reader is hardened against malformed input; damaged chunks are zero-filled and reported, never silently truncated. |
| `Cinder.Imaging` | `EwfReader`/`EwfStream` (in-process E01, multi-segment, verification), `Aff4Reader`/`Aff4Writer` (AFF4 v1.0/v1.1, zlib/snappy/LZ4), `EwfWriter`, `InProcessImager` (raw/E01/AFF4/VHD/VHDX), `EvidenceOpener` (E01 / AFF4 / raw → `Stream`), mounters (VHD/VHDX/ISO via PowerShell; Linux loop), shadow-copy enumeration, write-blocker wrappers, sidecar imager/verifier | EWF reader is hardened against malformed input; damaged chunks are zero-filled and reported, never silently truncated. |
| `Cinder.Carving` | `FileCarver` (header/footer, vectorised, 30+ signatures), `SlackUnallocCarver` | |
| `Cinder.Hex` | `HexViewer` control (virtualised, `ILogicalScrollable`), `MmapHexBuffer`, `HexSearch` (streaming find), bookmarks, overlays | |
| `Cinder.Search` | Lucene.NET `CaseIndex`, `SuperTimeline` + `TimelineExporter` + `MitreTagger`, `HashSetService` (NSRL, SQLite), `CommunicationGraph`, `GeoPoint` index, `VirusTotalClient`, `YaraScanner` (sidecar stub — real scanning is `Cinder.App/Services/YaraLite`) | |
Expand Down Expand Up @@ -87,6 +87,7 @@ unkeyed and stored beside the data; SECURITY.md explains.
| **Gallery** | works | Image viewer with EXIF panel (MetadataExtractor); GPS → Map | `GalleryTool.cs` |
| **Documents** | works | Text extraction for DOCX/DOCM, XLSX/XLSM, PPTX, ODT/ODS/ODP, EPUB, RTF, PDF (PdfPig), HTML/XML, 20+ text/code formats; 50 MB in / 2 MB out caps; XXE closed | `Services/DocumentReader` |
| **Filesystem** | works | `DiscUtilsWalker` (in `Cinder.Filesystems`, tested against a checked-in NTFS image on Windows and Linux): NTFS / FAT / ext2-4 / ISO9660, whole-disk images per partition, VHD/VHDX, E01 via `EwfReader`; all timestamps; **deleted-file recovery** from the NTFS $MFT (`IsDeleted = true`); **optional hashing + hash-set verdict** per file (Settings ▸ Hash sets) so `Unknown` in the row filter is the known-good filter; metadata row shows recorded E01 hashes labelled UNVERIFIED | `Cinder.Filesystems/DiscUtilsWalker.cs`, `Phase3To10ParserTools.cs` |
| **$LogFile** | works | `NtfsLogFile` parses the NTFS transaction log from any NTFS volume in an image or an extracted file: fixups, multi-page records, LSN ↔ offset validation, stale slack flagged, names / parent / `$FILE_NAME` timestamps from InitializeFileRecordSegment, Add/DeleteIndexEntry and Create/DeleteAttribute payloads; Microsoft's operation names | `Cinder.Filesystems/NtfsLogFile.cs`, `LogFileTool.cs` |
| **USN journal** | works | `UsnJournal` parses `$UsnJrnl:$J` (V2/V3) from any NTFS volume in an image or an extracted `$J`; reason flags in MFTECmd/Plaso naming, MFT + parent references; 200k-row budget with banner. Also ingested into the timeline from triage folders | `Cinder.Filesystems/UsnJournal.cs`, `UsnJournalTool.cs` |
| **Registry** | works | Eric Zimmerman `Registry` lib walk of any hive (NTUSER/SYSTEM/SOFTWARE/SAM/Amcache) with transaction-log replay; row and depth budgets with truncation banner | `WindowsParserTools.cs` |
| **Event Log** | works | `evtx` lib; every record with time, channel, provider, id, level, user, computer, mapped description | " |
Expand Down Expand Up @@ -131,10 +132,10 @@ annotation; every run is written to the custody log.

| Tool | Status | What it does |
|---|---|---|
| **Disk imager** | works (raw, E01) | `InProcessImager`: file / block device / E01 chain → `.dd` or an EnCase 6 `.E01` chain (`EwfWriter`: zlib chunks, segments, metadata header, MD5+SHA-1 inside, `error2` bad-sector section; verified readable by libewf). Hash-on-read, retry then sector-level fallback with bad-sector offsets in `.log.json`, custody entry. Devices need Administrator/root. AFF4 still via the Python sidecar |
| **Disk imager** | works (raw, E01, AFF4, VHD, VHDX) | `InProcessImager`: file / block device / E01 / AFF4 → `.dd`, EnCase 6 `.E01` chain (`EwfWriter`, libewf-verified), AFF4 v1.0 container (`Aff4Writer`, pyaff4-verified), or a dynamic VHD/VHDX (DiscUtils). Hash-on-read, retry then sector-level fallback with bad-sector offsets in `.log.json` (and the E01 error section), custody entry. Devices need Administrator/root. No sidecar for any format |
| **Image verify** | works | In-process. E01: `EwfReader.VerifyAsync` re-reads the decoded media and compares to the recorded MD5/SHA-1. Raw: hashes and compares to a `.sha256/.sha1/.md5` companion or a `SHA256SUMS` line. Three distinct outcomes — verified / failed / **unverifiable** — and the result is written to custody with digests |
| **Mount image** | partial | VHD/VHDX/ISO via `Mount-DiskImage` (Windows); Linux `losetup`+`mount` read-only; E01 needs Arsenal Image Mounter |
| **Convert format** | works (E01 ↔ raw) | `ImageConverter.EwfToRawAsync` decodes, hashes, writes flat, compares with the recorded acquisition hash; `RawToEwfAsync` writes the chain then re-reads it and checks the digest reproduces. Both directions double as a verification |
| **Convert format** | works (any ↔ any) | `ImageConverter.ConvertAsync`: raw / E01 / AFF4 / VHD / VHDX in any direction; output re-read through its own reader and checked against the digest computed while writing; a container source is checked against its recorded acquisition hash too |
| **Shadow copies** | works | `vssadmin` (Windows), btrfs/LVM/ZFS snapshots (Linux) |
| **RAM capture** | shell | winpmem / LiME fallbacks when present; no bundled driver |
| **File carver** | works | Vectorised header/footer carving, 30+ signatures, validators for JPEG/PNG/PE, exact window ownership (no duplicate hits), slack/unallocated regions |
Expand Down Expand Up @@ -207,8 +208,9 @@ publish unless the suite passes on both platforms; Windows binary is currently u
`Cinder.Filesystems`; the same move for registry/EVTX/etc. is what would make them testable.
3. **Custody attestations need a configured TSA to bind time**; without one the examiner's
own key is the only anchor, and publishing the export out of their reach is a process step.
4. **Deleted-file recovery is names only**; contents via the carver. `$LogFile` is not parsed.
5. **Imaging writes raw and E01**; AFF4 output still needs the Python sidecar.
4. **Deleted-file recovery is names only**; contents via the carver.
5. **AFF4 read support covers disk images**, not logical (file-per-object) containers; encrypted
AFF4 is not supported.
6. **IOC matching is folder-scoped**, not against the Lucene index or an open case's parsed
grids; hash indicators require exact digests.
7. RAM capture and cloud pull are shells or externally dependent; VirusTotal needs a key.
Expand Down
29 changes: 19 additions & 10 deletions src/Cinder.App/ViewModels/Tools/BuiltInToolsHelp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -882,7 +882,13 @@ and the run is recorded in the custody log.
header, MD5 + SHA-1 in the container, unreadable sectors in its error
section. Verified readable by libewf, which is what FTK Imager, Autopsy,
X-Ways and EnCase-compatible tools use.
- **AFF4** is handed to the Python imager sidecar, which must be installed.
- **AFF4** in-process: AFF4 Standard v1.0 ZIP volume, zlib-compressed 32 KiB
chunks in 64 MiB bevies, `Image → Map → ImageStream` objects, MD5 / SHA-1 /
SHA-256 and case metadata in `information.turtle`. Verified readable by
pyaff4.
- **VHD / VHDX** in-process: dynamic disks through DiscUtils' managed
writers, mountable in Hyper-V or Disk Management. These need the source
size up front, so a device that does not report one goes raw or E01.
4. Read errors: Cinder retries, then re-reads the failing block one sector at a
time so a bad sector costs 512 zero bytes rather than a megabyte. Every
zero-filled sector is counted, and its offset is listed in the log.
Expand Down Expand Up @@ -910,8 +916,9 @@ defensive sanity check that catches storage corruption.
1. Pick the image file.
2. Cinder reads every block and compares against a reference digest. For an E01
that reference is the MD5 / SHA-1 the acquisition tool wrote inside the
container; for a raw image it's a .sha256 / .sha1 / .md5 companion file, or a
SHA256SUMS line naming the image, sitting next to it.
container; for an AFF4 container it is the MD5 / SHA-1 / SHA-256 recorded in
its `information.turtle`; for a raw image it's a .sha256 / .sha1 / .md5
companion file, or a SHA256SUMS line naming the image, sitting next to it.

## Reading the result
There are three distinct outcomes, and the difference matters.
Expand Down Expand Up @@ -952,10 +959,10 @@ public sealed partial class ConvertTool
{
public override string HelpMarkdown => """
## What this is
Conversion between raw and E01 in both directions, in-process. E01 → raw decodes
every chunk of the EWF container, hashes as it streams, and writes flat. Raw →
E01 chunks, compresses and hashes the source into an EnCase 6 chain, then
re-reads the finished chain and checks it reproduces the digest.
Conversion between forensic image formats — raw, E01, AFF4, VHD, VHDX — in any
direction, in-process. The source is decoded through whichever reader
understands it, hashed as it streams, and written through the target's writer;
the finished output is then re-read and checked against that digest.

## When you'd use it
When the tool you need next only understands raw images — a loop-mount, a
Expand All @@ -980,9 +987,11 @@ the count is in the result and the log.
the faithful decode of what was in the E01, but nothing ties it to the media.
4. The conversion is recorded in the custody log with both digests.

## Not yet
AFF4 in either direction; VHD/VHDX output. AFF4 acquisition still goes through
the Python sidecar from the Imager tool.
## Formats
Source: raw, E01 chain, AFF4, VHD, VHDX. Target: any of the same five. Every
target is re-read through its own reader after writing and must hash back to
what was written; a container source is also checked against its recorded
acquisition hash.
""";
}

Expand Down
Loading
Loading