Hello Veeam Community and R&D team,
As a Data Recovery engineer, I regularly encounter cases of VBK/VIB/VRB archive corruption due to storage failures or ransomware attacks.
When dealing with partially encrypted files or read errors, native Veeam tools often simply throw an error and abort, without attempting to extract any surviving data. To address this, I began reverse-engineering the binary structure of Veeam files and am developing custom software for their recovery.
During this process, I identified the following key structural groups:
- Header
- SnapShotSlot 0 and SnapShotSlot 1 (SSS0, SSS1)
- Metabanks + DataBlocks
The Header contains basic info and is easy to recover. The SnapShotSlot contains critical data required to begin parsing Metabanks. This block can also be reconstructed, but only after successfully analyzing the Metabanks themselves. Next comes the Metabank table (CRC32, file offset, size). Its loss is fatal, as without it, a parser has no knowledge of the physical location of Metabanks within the file.
Questions regarding Metabanks:
- What was the rationale behind Veeam's choice of a fixed field size for entries in the bank table?
- What determines the number of pages in a Metabank? I have encountered cases where Metabanks are of uniform size, and others where they vary significantly. (Currently, I handle this via heuristic structure scanning, but understanding the internal logic would greatly improve parsing reliability for multi-gigabyte files).
Following the two SnapShotSlot copies, the primary Metabanks and their backups appear (SSS0 points to copy 0, SSS1 to copy 1). These are followed by DataBlocks. As space in the current Metabank is exhausted for describing new DataBlocks, space is allocated for new Metablocks, followed again by DataBlocks. This cycle repeats until the backup is complete.
Questions regarding Meta-maps and DataBlocks:
- Meta-object maps: The 8-byte records with relative addressing (page+bank) seem to follow this logic: the zero record points to the next map page or serves as an end-of-chain marker. Following this, object descriptors are interleaved with map descriptors. Currently, my parser analyzes these records by checking their pointers (if it points to a map structure, it's a map descriptor; otherwise, it's an object descriptor). This heuristic works, but I would like to know: is there a strictly deterministic logic for distributing these descriptors, and how might it differ between Veeam versions?
- LZ4 Overhead: DataBlocks are typically LZ4-compressed (standard Header: 0F 00 00 F8, CRC, Source Size / Unpack_Size). What is the rationale for wrapping blocks in an LZ4 container when LZ4 compression actually makes them larger than the original size? (For context, in systems like ZFS, compression is only applied when it yields an actual size reduction).
- DataBlock Header Architecture: What dictates the modest 12-byte size of the DataBlock header? Would it be feasible to store more metadata here to aid in disaster recovery scenarios? For example, adding a BlockID field from the FIB would enable rapid recovery in the event of a total STG (block store) structure failure. Adding "file number" and "sequential block number within the stored file" fields would allow object reconstruction even if Metabanks are completely destroyed (assuming no encryption). Increasing the DataBlock header from 12 to 24-36 bytes would potentially make rescuing customer data much more straightforward. This would consume negligible space in the archive file (currently, padding for block address alignment consumes far more), and would not impact performance.
I would greatly appreciate any insights, especially from the Veeam R&D team.
