Add compression and hash algorithm support#129
Conversation
Add compress functions to lzxpress, lzxpress_huffman, lz4 and lznt1. Add XPRESS9 codec (lzxpress9) with full compress and decompress. Add raw DEFLATE and ZLIB stream codecs. Add CRC-64/NVME hash. Fix lzxpress_huffman decompress: handle EOF symbol (256) and the v10.0 uint32 match-length escape when uint16 is zero. Add max_size parameter to lzxpress and lzxpress_huffman decompress for output size limiting.
|
Can you add tests in a similar style as the existing ones? |
|
@StrongWind1 thank you for your contribution! As this is your first code contribution, please read the following Contributor License Agreement (CLA). If you agree with the CLA, please reply with the following information:
Contributor License Agreement
Contribution License AgreementThis Contribution License Agreement ("Agreement") governs your Contribution(s) (as defined below) and conveys certain license rights to Fox-IT B.V. ("Fox-IT") for your Contribution(s) to Fox-IT"s open source Dissect project. This Agreement covers any and all Contributions that you ("You" or "Your"), now or in the future, Submit (as defined below) to this project. This Agreement is between Fox-IT B.V. and You and takes effect when you click an “I Accept” button, check box presented with these terms, otherwise accept these terms or, if earlier, when You Submit a Contribution.
|
Add tests for the new compress functions, lzxpress9, deflate, zlibstream and crc64, plus max_size on lzxpress and lzxpress_huffman decompress. Include regression tests for the lzxpress_huffman EOF symbol and v10.0 uint32 match-length escape fixes. Decompress vectors are real RtlCompressBuffer and esent.dll output.
New module lzxpress9_compact implementing the undocumented ntdll.dll compression format 0x0005 (Server 2022 Build 20348+). Same canonical-Huffman LZ77 algorithm as the ESE XPRESS9 codec in lzxpress9.py but with a streamlined 10-byte header (magic 0xC039E510) instead of 32-byte block headers. Reuses BitReader, CanonicalHuffman, and LZ77 internals from lzxpress9 -- no code duplication. Decompressor verified against 11 test vectors captured from RtlCompressBuffer on Server 2022 and Server 2025. Reverse-engineered from ntdll.dll Build 20348 (decompressor at RVA 0x111810, Huffman builder at 0x114DA8, header parser at 0x115AB0).
e902046 to
bf86877
Compare
|
@DissectBot agree |
Done. Let me know if you need any other changes. |
Adds compress functions to
lzxpress,lzxpress_huffman,lz4, andlznt1(which previously only had decompress). Adds three new codecs:lzxpress9,deflate, andzlibstream. Adds acrc64hash module for CRC-64/NVME. Fixes two bugs inlzxpress_huffmandecompress.@Schamper asked in fox-it/dissect.database#60 (comment) for the compression algorithms to go into dissect.util rather than pulling in an external dependency.
The code is ported from ntcompress, which has 982 tests and 107 gold vectors checked against Windows APIs across 16 builds (XP SP3 through Server 2025).
What changed
New files:
compression/lzxpress9.pycompression/deflate.pyzlibwithwbits=-15.compression/zlibstream.pyzlib.hash/crc64.py0x9A6C9329AC4BC9B5. Needed for ESE XPRESS10 payload verification.Updated files:
compression/lzxpress.pycompress(). Added optionalmax_sizeparameter todecompress().compression/lzxpress_huffman.pycompress(). Added optionalmax_sizeparameter todecompress(). Fixed EOF symbol (256) handling and the v10.0uint32match-length escape. See bugfixes section below.compression/lz4.pycompress()for headerless LZ4 blocks.compression/lznt1.pycompress()with 4096-byte chunk processing per [MS-XCA] S2.5.3.compression/__init__.pydeflate,lzxpress9,zlibstreamto__all__.hash/__init__.pycrc64export.Algorithm coverage
lzxpressmax_sizelzxpress_huffmanmax_size+ bugfixeslznt1lz4lzxpress9deflatezlibstreamsevenbitBugfixes in lzxpress_huffman decompress
Two bugs in the existing
decompress(), found while porting the encoder and cross-testing:EOF symbol (256) was not handled. The decoder treated symbol 256 as a match (
symbol -= 256, then decoded offset/length), which produced 3 garbage bytes at the end of any stream terminated by EOF. Fixed by returning early when symbol 256 is decoded.The v10.0 uint32 match-length escape was missing. When the uint16 match-length continuation is 0, [MS-XCA] S2.2 says to read a uint32 instead. The existing code fell through without reading the extra bytes. Fixed by adding the
if length == 0branch.Closes #44
How to test
Testing done
0xAE8B14860A799888forb"123456789"matches specruff formatandruff checkat 120 charactersNote
Claude was used to help research and write this code. I have read all source, all documentation, and verified the output.