Skip to content

hibernation: Windows 11 24H2+ support (build-dependent counter offset) and a decoder size fix - #1

Merged
forensicxlab merged 2 commits into
forensicxlab:feature/hibernation-layerfrom
Ozer0x777:hibernation-24h2-autodetect
Oct 16, 2026
Merged

forensicxlab merged 2 commits into
forensicxlab:feature/hibernation-layerfrom
Ozer0x777:hibernation-24h2-autodetect

Conversation

@Ozer0x777

Copy link
Copy Markdown

Target: forensicxlab/volatility3, branch feature/hibernation-layer (upstream PR volatilityfoundation#1036).
Follow-up of my comment on volatilityfoundation#1036: volatilityfoundation#1036 (comment)

Summary

Two independent problems of the hibernation layer, found while converting Windows 11 Fast Startup hiberfil.sys files, with a fix for each and the test campaign that validates them on Windows 11 21H2, 22H2, 23H2, 24H2, 25H2 and 26H1 lab VMs (official ISOs, kernel builds 22000.318, 22621.382, 22621.2428, 26100.1742, 26100.6584, 28000.1340).

  1. KernelPagesProcessed is not at 0x230 on build 26100 and later, and its offset depends on the build. With --version 0 the layer reads 0x230, which on those files holds another counter (or 0), so the kernel section is either skipped (image almost empty, no error) or mis-walked (conversion refused). This PR adds --version 4 (Windows 11 24H2 and later): the counter is looked for at the offsets seen so far (0x238, 0x250) and the candidate is accepted only if the chain of compression sets of the kernel section adds up to exactly that number of pages. A file where no candidate matches is refused with an explicit message instead of producing a truncated image. The compression-set header of those builds is parsed with the size in bits 8-28 and bit 30 selecting LZ77+Huffman (bit 29, set on most sets, is not part of the size).

  2. A LZ77+Huffman set of fewer than 16 pages aborts the whole conversion ("EOF Error"). _uncompress always asked the decoder for 65536 bytes. Every Windows 11 21H2/22H2/23H2/24H2 Fast Startup file of the campaign has one such set at the end of the kernel section, so the existing --version 0 fails on 21H2-23H2 files (ValueError: EOF Error, exit 1). The layer now passes the size of the set (pages x 4096). No behaviour change for sets of 16 pages.

Versions 0 to 3 are otherwise untouched.

Offsets seen (counter that bounds the kernel section)

Windows kernel build KernelPagesProcessed how it was found
10 22H2 (lab VM) 19045 0x230 layer's existing value, chain matches
11 21H2 / 22H2 / 23H2 (lab VMs) 22000.318 / 22621.382 / 22621.2428 0x230 chain matches, 4 files each
11 24H2 / 25H2 / 26H1 (lab VMs) 26100.1742 / 26100.6584 / 28000.1340 0x238 chain matches, 3-4 files each; matches the Vergilius _PO_HIBER_PERF layout for 24H2
11 24H2, a file from a later update (not shareable) 26100.9445 0x250 chain matches (reported in the volatilityfoundation#1036 comment)

So a fixed offset per --version cannot cover build 26100: the two 26100 builds differ. The chain check picks the right one without a build number, and refuses the file otherwise.

What is in the PR

  • volatility3/framework/layers/hib.py: WINDOWS_11_24H2 = 4; _find_kernel_pages_processed / _kernel_chain_has_pages (chain-validated counter); 24H2 set-header rules; _uncompress takes the output size of the set (fix 2, applies to every version).
  • volatility3/framework/plugins/windows/hibernation.py: --version help text.
  • contrib/hibernation_checks/: standalone checking tools (no Volatility needed, counters only, nothing identifying printed by default), 16 synthetic tests of the tools, 3 tests of the real layer on synthetic files (test_hib_layer.py: every page reads back for the three layouts, a 4-page Huffman set converts, a 24H2 file with no matching counter is refused), and the logs of the campaign below.

Test campaign (logs in contrib/hibernation_checks/campaign_2026-10/)

Method, one VM at a time: restore the after-install-cold snapshot of the VM, boot, inject cmd.exe /c powercfg /h on & shutdown /s /hybrid /t 5 into the logged-in session (DRAKVUF, -m createproc), wait for the domain to power off by itself, mount the disk image read-only and copy hiberfil.sys. Each file is identified by its own SystemTime (windows.hibernation.Info; the guest clocks are 2 h behind UTC) and its sha256, which differ between rounds. Three generations per build plus the six files of the day before (round 0); three generations where the VM resumed from its own Fast Startup file and the injection failed are logged and excluded (signature WAKE, no hybrid shutdown), and were redone.

For every file: hib_check.py --decompress (chain walk with the 3 layouts, header bits, decompression of every compressed set with the output size checked), then windows.hibernation.Dump with this branch and the right --version (exit code, size and sha256 of the raw image, number of non-zero pages), a second identical run (sha256 identical: deterministic), the original branch head (603b53f) with --version 0 and --version 4, and this branch with the wrong --version (must refuse, not produce a truncated image). Unit tests run 5 times, once more with fix 2 reverted (the 3 layer tests then fail with EOF Error), and ruff check / ruff format --check as in the repository workflow (the two reports on the whole tree are pre-existing on the base branch: codecs/__init__.py, linux/sockscan.py).

Rounds: r0 = files of 2026-10-13 (same method; the 26H1 r0 file turned out to be the one written at install time, it is kept as a genuine 26H1 file but not counted as a generation), r1-r3 = 2026-10-14, r4 = replacement of the three failed generations. The hiberfil.sys of rounds 2-3 were deleted after their checks (disk space), so their hib_check.py output comes from the earlier version of the tool, which did not report the kernel-section bit statistics for the 0x230/0x238 layouts (n/a); the decompression counters are the same in both versions.

Files and counter offset (hib_check.py --decompress)

build kernel round SystemTime of the file (UTC, guest clock) KernelPagesProcessed found at boot sets/pages kernel sets/pages Huffman sets with < 16 pages decompression of the kernel section (hib_check.py)
21H2 22000.318 r0 2026-10-13 15:41:05+00:00 0x230 1571/25134 18447/295146 1 18369 sets: LZ77 22 ok / 0 failed, Huffman 18346 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
21H2 22000.318 r1 2026-10-14 02:17:00+00:00 0x230 1569/25095 18445/295105 1 18347 sets: LZ77 21 ok / 0 failed, Huffman 18325 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
21H2 22000.318 r2 2026-10-14 04:24:40+00:00 0x230 1571/25124 18489/295820 n/a (earlier tool, see last column) 18401 sets: LZ77 22 ok / 0 failed, Huffman 18378 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
21H2 22000.318 r3 2026-10-14 05:53:25+00:00 0x230 1563/24999 22015/352225 n/a (earlier tool, see last column) 21965 sets: LZ77 24 ok / 0 failed, Huffman 21940 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
22H2 22621.382 r0 2026-10-13 15:50:06+00:00 0x230 1578/25235 18655/298470 1 18572 sets: LZ77 23 ok / 0 failed, Huffman 18548 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
22H2 22621.382 r1 generation failed (file not used)
22H2 22621.382 r2 2026-10-14 04:35:58+00:00 0x230 1577/25218 18151/290401 n/a (earlier tool, see last column) 18061 sets: LZ77 24 ok / 0 failed, Huffman 18036 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
22H2 22621.382 r3 2026-10-14 06:06:13+00:00 0x230 1577/25232 18002/288019 n/a (earlier tool, see last column) 17935 sets: LZ77 22 ok / 0 failed, Huffman 17912 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
22H2 22621.382 r4 2026-10-14 07:19:56+00:00 0x230 1576/25210 17937/286984 1 17851 sets: LZ77 24 ok / 0 failed, Huffman 17826 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
23H2 22621.2428 r0 2026-10-13 15:53:54+00:00 0x230 1580/25270 18517/296270 1 18437 sets: LZ77 23 ok / 0 failed, Huffman 18413 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
23H2 22621.2428 r1 2026-10-14 03:06:15+00:00 0x230 1578/25237 16745/267919 1 16646 sets: LZ77 23 ok / 0 failed, Huffman 16622 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
23H2 22621.2428 r2 2026-10-14 04:46:37+00:00 0x230 1578/25235 18552/296824 n/a (earlier tool, see last column) 18483 sets: LZ77 23 ok / 0 failed, Huffman 18459 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
23H2 22621.2428 r3 2026-10-14 06:21:33+00:00 0x230 1578/25239 18931/302894 n/a (earlier tool, see last column) 18857 sets: LZ77 22 ok / 0 failed, Huffman 18834 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
24H2 26100.1742 r0 2026-10-13 15:57:34+00:00 0x238 1896/30328 19507/312106 1 19345 sets: LZ77 17 ok / 0 failed, Huffman 19327 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
24H2 26100.1742 r1 2026-10-14 03:28:05+00:00 0x238 1900/30394 18847/301546 1 18701 sets: LZ77 18 ok / 0 failed, Huffman 18682 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
24H2 26100.1742 r2 generation failed (file not used)
24H2 26100.1742 r3 2026-10-14 06:32:11+00:00 0x238 1899/30375 19638/314207 n/a (earlier tool, see last column) 19492 sets: LZ77 18 ok / 0 failed, Huffman 19473 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
24H2 26100.1742 r4 2026-10-14 07:41:17+00:00 0x238 1898/30364 19486/311771 1 19350 sets: LZ77 18 ok / 0 failed, Huffman 19331 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
25H2 26100.6584 r0 2026-10-13 16:01:38+00:00 0x238 2157/34506 21630/346065 0 21183 sets: LZ77 20748 ok / 0 failed, Huffman 435 ok / 0 failed; 0 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
25H2 26100.6584 r1 2026-10-14 03:45:56+00:00 0x238 2153/34439 21607/345712 0 21143 sets: LZ77 20709 ok / 0 failed, Huffman 434 ok / 0 failed; 0 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
25H2 26100.6584 r2 2026-10-14 05:21:46+00:00 0x238 2175/34786 21247/339946 n/a (earlier tool, see last column) 20763 sets: LZ77 20334 ok / 0 failed, Huffman 429 ok / 0 failed; 0 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
25H2 26100.6584 r3 2026-10-14 06:43:46+00:00 0x238 2148/34362 19916/318642 n/a (earlier tool, see last column) 19445 sets: LZ77 19044 ok / 0 failed, Huffman 401 ok / 0 failed; 0 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
26H1 28000.1340 r0 2026-09-27 19:51:37+00:00 (written at install time, not a generation) 0x238 1980/31669 24474/391575 0 24307 sets: LZ77 23819 ok / 0 failed, Huffman 488 ok / 0 failed; 0 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
26H1 28000.1340 r1 2026-10-14 04:03:05+00:00 0x238 1766/28246 26849/429581 1 26788 sets: LZ77 16 ok / 0 failed, Huffman 26771 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
26H1 28000.1340 r2 2026-10-14 05:29:55+00:00 0x238 1985/31747 25695/411106 n/a (earlier tool, see last column) 25626 sets: LZ77 17 ok / 0 failed, Huffman 25608 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set
26H1 28000.1340 r3 generation failed (file not used)
26H1 28000.1340 r4 2026-10-14 07:59:15+00:00 0x238 1987/31785 25486/407772 1 25431 sets: LZ77 17 ok / 0 failed, Huffman 25413 ok / 1 failed; 1 Huffman set(s) fail with the fixed 65536 and decode with the size of the set

windows.hibernation.Dump

build round this branch, right --version second run original code --version 0 / --version 4 / this branch with the wrong --version
21H2 r0 OK, 308394 non-zero pages identical v0: ValueError: EOF Error; v4: refused (not converted); wrong --version on the fix: refused (not converted)
21H2 r1 OK, 306883 non-zero pages identical v0: ValueError: EOF Error; v4: refused (not converted); wrong --version on the fix: refused (not converted)
21H2 r2 OK, 307087 non-zero pages - v0: -; v4: -; wrong --version on the fix: -
21H2 r3 OK, 362847 non-zero pages - v0: -; v4: -; wrong --version on the fix: -
22H2 r0 OK, 308402 non-zero pages identical v0: ValueError: EOF Error; v4: refused (not converted); wrong --version on the fix: refused (not converted)
22H2 r1 generation failed (file not used)
22H2 r2 OK, 301804 non-zero pages - v0: -; v4: -; wrong --version on the fix: -
22H2 r3 OK, 298952 non-zero pages - v0: -; v4: -; wrong --version on the fix: -
22H2 r4 OK, 298042 non-zero pages identical v0: ValueError: EOF Error; v4: refused (not converted); wrong --version on the fix: refused (not converted)
23H2 r0 OK, 308817 non-zero pages identical v0: ValueError: EOF Error; v4: refused (not converted); wrong --version on the fix: refused (not converted)
23H2 r1 OK, 278116 non-zero pages identical v0: ValueError: EOF Error; v4: refused (not converted); wrong --version on the fix: refused (not converted)
23H2 r2 OK, 308798 non-zero pages - v0: -; v4: -; wrong --version on the fix: -
23H2 r3 OK, 313341 non-zero pages - v0: -; v4: -; wrong --version on the fix: -
24H2 r0 OK, 325586 non-zero pages identical v0: refused (not converted); v4: refused (not converted); wrong --version on the fix: refused (not converted)
24H2 r1 OK, 313942 non-zero pages identical v0: refused (not converted); v4: refused (not converted); wrong --version on the fix: refused (not converted)
24H2 r2 generation failed (file not used)
24H2 r3 OK, 327167 non-zero pages - v0: -; v4: -; wrong --version on the fix: -
24H2 r4 OK, 325002 non-zero pages identical v0: refused (not converted); v4: refused (not converted); wrong --version on the fix: refused (not converted)
25H2 r0 OK, 363514 non-zero pages identical v0: refused (not converted); v4: refused (not converted); wrong --version on the fix: refused (not converted)
25H2 r1 OK, 359170 non-zero pages identical v0: refused (not converted); v4: refused (not converted); wrong --version on the fix: refused (not converted)
25H2 r2 OK, 355900 non-zero pages - v0: -; v4: -; wrong --version on the fix: -
25H2 r3 OK, 334667 non-zero pages - v0: -; v4: -; wrong --version on the fix: -
26H1 r0 OK, 407227 non-zero pages identical v0: refused (not converted); v4: refused (not converted); wrong --version on the fix: refused (not converted)
26H1 r1 OK, 444349 non-zero pages identical v0: refused (not converted); v4: refused (not converted); wrong --version on the fix: refused (not converted)
26H1 r2 OK, 427697 non-zero pages - v0: -; v4: -; wrong --version on the fix: -
26H1 r3 generation failed (file not used)
26H1 r4 OK, 424453 non-zero pages identical v0: refused (not converted); v4: refused (not converted); wrong --version on the fix: refused (not converted)

Unit tests: 19/19 on each of the 5 runs (final-unittest-*.log); with fix 2 reverted the 3 layer tests fail with 6 EOF Error (final-unittest-fix-reverted.log). Ruff: clean on the files of this PR (final-ruff.log).

Not covered

  • Full hibernation (S4) on Windows 11, x86, and other 26100 builds than 26100.1742 / 26100.6584 / 26100.9445.
  • The 0x250 offset comes from one file only (the 26100.9445 one), which cannot be shared.
  • The meaning of bit 29 of the set header is unknown to me; it is simply not part of the size.

…uffman set size

Two independent problems of the hibernation layer on Windows 11 Fast Startup
files, validated on 21H2, 22H2, 23H2, 24H2, 25H2 and 26H1 lab VMs (see
contrib/hibernation_checks/campaign_2026-10).

1. KernelPagesProcessed is not at 0x230 on build 26100 and later, and its
   offset depends on the build: 0x238 in 26100.1742, 26100.6584 and 28000.1340,
   0x250 in a 26100.9445 file. With --version 0 the kernel section is skipped
   or mis-walked. New --version 4 (Windows 11 24H2 and later): the counter is
   read at the known offsets and accepted only if the chain of compression
   sets of the kernel section adds up to exactly that number of pages; a file
   where no candidate matches is refused with an explicit message. On those
   builds the set header carries the size in bits 8-28 (bit 29 is not part of
   it) and bit 30 selects LZ77+Huffman.

2. _uncompress asked the LZ77+Huffman decoder for 65536 bytes for every set.
   A set of fewer than 16 pages (present at the end of the kernel section of
   every Windows 11 21H2-24H2 file of the campaign) fails with "EOF Error"
   and aborts the whole conversion, including with the existing --version 0.
   The layer now passes the size of the set (pages * 4096).

Versions 0 to 3 are otherwise unchanged.
…campaign logs)

- hib_check.py: walks the chain of compression sets of a hiberfil.sys with the
  23h2, 24h2-early (0x238) and 24h2 (0x250) layouts, finds the header qwords
  that bound the kernel section, reports set kinds and header bits, and with
  --decompress decodes every compressed set with the output size checked.
  Counters only, nothing identifying by default.
- compare_outputs.py, count_nonzero_pages.py: page-by-page comparison of two
  Dump outputs, non-zero page count of a raw image.
- test_hib_layout.py (16 tests of the tools) and test_hib_layer.py (3 tests of
  the real layer) on synthetic files generated in memory, no sample data.
- campaign_2026-10/: logs of the campaign on Windows 11 21H2/22H2/23H2/24H2/
  25H2/26H1 lab VMs: per file hib_check output, Dump with this branch (twice),
  with the original code and with the wrong --version; unit tests x5, with the
  decoder fix reverted, and ruff.
@forensicxlab
forensicxlab merged commit 365e13a into forensicxlab:feature/hibernation-layer Oct 16, 2026
@Ozer0x777
Ozer0x777 deleted the hibernation-24h2-autodetect branch October 24, 2026 20:27
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.

2 participants