Skip to content

Add TIBX archive parser - #30

Open
falklindner wants to merge 7 commits into
fox-it:mainfrom
falklindner:feature/tibx-parser
Open

Add TIBX archive parser#30
falklindner wants to merge 7 commits into
fox-it:mainfrom
falklindner:feature/tibx-parser

Conversation

@falklindner

Copy link
Copy Markdown

Add a parser for the Acronis CyberBackup/CyberProtect TIBX ("archive3") backup format as a dissect.archive.tibx subpackage.

Closes #20

Proposed changes

The parser handles the full TIBX format:

  • Page store: 4 KiB pages with CRC-32C verification, ARCH commit-root superblocks, live-root / commit-root selection
  • LSM B-tree index: TLV directory, L-SB superblocks, LEAF/LDIR cell decoding, data_map / segment_map with mem-tree and on-disk ctree layers, tombstone masking for backup-version cleanup
  • Compression: zstd and linked-LZ4 (dictionary-seeded block chain)
  • Encryption: AES-256-CBC with PBKDF2 key derivation
  • Chain reconstruction: full incremental/differential chain with recovery-point selection
  • Volume reconstruction: NTFS, FAT32, exFAT, FAT12/16 and ext boot-sector detection for accurate volume sizing
  • Archive variants: split archives (auto-stitched), version sets (TLV[18] file table with compacted bases), multi-disk archives

TIBX is materially more complex than VBK (page store + LSM index + crypto + chain reconstruction), so a subpackage is proposed rather than a flat module. Happy to consolidate if preferred.

Dependencies

pycryptodome and backports.zstd added to the full optional extra; lz4 added to the test dependency group.

Testing

70 unit tests covering all parser layers (page store, LSM trees, segments, codecs, crypto, volume reconstruction, version sets, tombstone masking). Tests are fully hermetic — they build synthetic CRC-correct archives via tests/_synth.py, no binary fixtures required.

Validated byte-for-byte against real Acronis True Image 2026 (Build 42980) output across: single-partition, multi-partition, whole-disk (MBR and GPT), multi-disk, split, incremental, differential, and encrypted archives; NTFS, FAT32, and exFAT volumes.

Acknowledgments

Thanks to @mniedermaier for his contributions and support in developing this parser.

Checklist

falklindner and others added 3 commits July 13, 2026 09:06
Add a parser for the Acronis CyberBackup/CyberProtect TIBX
("archive3") backup format as a dissect.archive.tibx subpackage.

The parser handles the 4 KiB page store, CRC-32C verification,
ARCH commit-root superblocks, TLV directory, LSM B-tree index
(data_map / segment_map with mem-tree and on-disk ctree layers),
zstd and linked-LZ4 decompression, AES-256-CBC decryption, and
full incremental/differential chain reconstruction with
recovery-point selection.

Volume reconstruction supports NTFS, FAT32, exFAT, FAT12/16 and
ext boot-sector detection for accurate volume sizing, split
archives, version sets (TLV[18] file table with compacted bases),
multi-disk archives, and tombstone masking for backup-version
cleanup.

Validated byte-for-byte against real Acronis True Image 2026
(Build 42980) output across single-partition, multi-partition,
whole-disk (MBR and GPT), multi-disk, split, incremental,
differential, and encrypted archives.

Closes fox-it#20

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a parser for the Acronis CyberBackup/CyberProtect TIBX
("archive3") backup format as a dissect.archive.tibx subpackage.

The parser handles the 4 KiB page store, CRC-32C verification,
ARCH commit-root superblocks, TLV directory, LSM B-tree index
(data_map / segment_map with mem-tree and on-disk ctree layers),
zstd and linked-LZ4 decompression, AES-256-CBC decryption, and
full incremental/differential chain reconstruction with
recovery-point selection.

Volume reconstruction supports NTFS, FAT32, exFAT, FAT12/16 and
ext boot-sector detection for accurate volume sizing, split
archives, version sets (TLV[18] file table with compacted bases),
multi-disk archives, and tombstone masking for backup-version
cleanup.

Validated byte-for-byte against real Acronis True Image 2026
(Build 42980) output across single-partition, multi-partition,
whole-disk (MBR and GPT), multi-disk, split, incremental,
differential, and encrypted archives.

Closes fox-it#20

Co-Authored-By: Matthias Niedermaier <matthias.niedermaier@hs-augsburg.de>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yunzheng

Copy link
Copy Markdown
Member

Very nice work, and thanks for sharing it! I haven't started reviewing yet, but I pushed a commit filling in a few of the unknown fields based on some of our own reversing efforts.

falklindner and others added 2 commits July 22, 2026 21:48
Reduce the random-access parsing gap versus a raw disk image with
caching and batched I/O on the segment read path. Output is
byte-identical: every real-sample volume hash and file check is
unchanged.

- Segment cache: replace the fixed 32-entry limit with a 256 MiB
  byte-budget LRU, so random-access workloads (MFT, registry
  hives) that touch thousands of segments stop thrashing.
- Multi-page segments: read the contiguous continuation pages in
  a single I/O instead of one read per page, and retain the
  segment header page so it is not read twice.
- Add a small LRU page cache to the page store.
- Use the C lz4 library for the linked-LZ4 decoder when it is
  importable (byte-identical, including the growing-dictionary
  case), falling back to the pure-Python decoder otherwise; add
  lz4 to the full extra.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Treating "a keymap tree with records exists" as proof of encryption
over-reports: an archive whose keymap holds no password-wrapped key
would prompt for a password that could never unwrap anything.

Confirm the keymap actually carries a password-wrapped data key by
locating one structurally -- a known algorithm id, a plausible PBKDF2
iteration exponent, a full salt and an AES-block-multiple wrapped key.
No password is involved and no key is derived, so this stays cheap
enough to run on every open. AES-GCM archives are still reported as
encrypted, since they are: they now fail with a clear "not supported"
instead of being mistaken for plaintext. Detection never raises -- a
missed one still surfaces as an explicit InvalidPasswordError from the
segment reader rather than a silent misread.

The keymap region is extracted once and shared with unwrap_data_key,
and pycryptodome is imported where it is used rather than at module
import time, so detection no longer pulls in an optional dependency.

Validated against the real corpus: the encrypted sample is detected
and still decrypts file-for-file, with no false positive on any of the
ten unencrypted archives.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@yunzheng

Copy link
Copy Markdown
Member

Regarding the last commit, can the TIBX encryption not be detected via the encr_alg in the superblock header?

@falklindner

Copy link
Copy Markdown
Author

Regarding the last commit, can the TIBX encryption not be detected via the encr_alg in the superblock header?

Good point — checked against the full real-sample corpus (12 archives): encr_alg in the ARCH superblock agrees with the keymap detection everywhere (AES_256_CBC on the encrypted sample, NONE on the rest), so yes, it does indicate encryption.

The keymap scan answers a narrower question though: not "is this encrypted" but "does a password-wrapped data key exist" — which is what drives the password prompt / keychain lookup. Acronis also supports certificate-wrapped keys (format byte 0x02 next to the 0x01 we locate), where encr_alg would be set but asking for a password can't help. Detection via the keymap also can't disagree with unlock(), since both parse the same structure, and it doesn't depend on the superblock byte layout, which we've only verified for header_version 8.

The two compose nicely, and encr_alg is essentially free: happy to use encr_alg != NONE as the archive-level signal and the keymap scan to decide password vs. certificate — that would let a certificate-wrapped archive fail with "encrypted, but not password-protected" instead of a generic missing-password error.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for TIB and TIBX format (Acronis backup)

2 participants