Skip to main content
Contact us: blog@ukatemi.com
TECHNICAL BLOG ukatemi.com

Windows catalog updates

Microsoft Cabinet archive files have MSCF magic at the beginning:

00000000: 4d53 4346 0000 0000 e3aa 0100 0000 0000  MSCF............
00000010: 4400 0000 0000 0000 0301 0100 1200 0400  D...............
00000020: 0000 0000 1400 0000 0000 1000 e3aa 0100  ................
00000030: e025 0000 0000 0000 0000 0000 2d07 0000  .%..........-...

Windows updates use forward and reverse differentials. They are located in the /f/ and /r/ folders respectively. Microsoft docs describe how it works.

In short there is a base version (a selected major software release) of an updateable file against which the deltas are calculated. An update for a specific version of Windows first applies the r (reverse) delta to obtain the base version, then it applies the f (forward) delta to produce the target (updated) version of the file.

File delta update process using forward and reverse differentials
File delta update process using forward and reverse differentials

For example here is a part of the directory structure from one of the updates (KB5012170):

├── amd64_microsoft-windows-s..boot-firmwareupdate_31bf3856ad364e35_10.0.19041.1880_none_294d9e3cbae1ff57
│   ├── f
│   │   ├── dbupdate.bin
│   │   └── dbxupdate.bin
│   └── r
│       ├── dbupdate.bin
│       └── dbxupdate.bin
└── amd64_microsoft-windows-s..boot-firmwareupdate_31bf3856ad364e35_10.0.19041.1880_none_294d9e3cbae1ff57.manifest

(yes those are .. characters in the directory name).

  46 Jul 13  2022 f/dbupdate.bin
7169 Jul 13  2022 f/dbxupdate.bin
  46 Jul 13  2022 r/dbupdate.bin
1064 Jul 13  2022 r/dbxupdate.bin

As you can see, the f and r directories contain files with the same names, but dbxupdate.bin files have different sizes. The reverse differential is much shorter (1064) than the forward (7169). This doesn't necessarily mean that the file changed, because the reverse differential may just describe the following operation: delete COUNT bytes from offset X. Whereas the forward diff needs to contain the specific bytes that are needed to be appended or inserted to the source file.

The *.manifest file describes how the related files need to be treated:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<assembly ...>
  <!--
    reducted for sake of simplicity
  -->
  <directories>
    <directory destinationPath="$(runtime.system32)\SecureBootUpdates" owner="true">
      <securityDescriptor name="WRP_DIR_DEFAULT_SDDL" />
    </directory>
  </directories>
  <file name="dbupdate.bin" destinationPath="$(runtime.system32)\SecureBootUpdates\" sourceName="dbupdate.bin" importPath="$(build.nttree)\" sourcePath=".\">
    <securityDescriptor name="WRP_FILE_DEFAULT_SDDL" />
    <asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
      <dsig:Transforms>
        <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
      </dsig:Transforms>
      <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
      <dsig:DigestValue>/6Yl+eMBve0mGQA+aiFg4qvQ7GUCV5eGvIcoRf4mEPw=</dsig:DigestValue>
    </asmv2:hash>
  </file>
  <file name="dbxupdate.bin" destinationPath="$(runtime.system32)\SecureBootUpdates\" sourceName="dbxupdate.bin" importPath="$(build.nttree)\" sourcePath=".\">
    <securityDescriptor name="WRP_FILE_DEFAULT_SDDL" />
    <asmv2:hash xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
      <dsig:Transforms>
        <dsig:Transform Algorithm="urn:schemas-microsoft-com:HashTransforms.Identity" />
      </dsig:Transforms>
      <dsig:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha256" />
      <dsig:DigestValue>UlftZMySTmmlvwwOF9qD/RzOLIowjtvzkrluVmhhTqA=</dsig:DigestValue>
    </asmv2:hash>
  </file>
  <!--
    reducted for sake of simplicity
  -->
</assembly>

It describes where the related files can be found and what their target SHA256 hash is.

echo -n -E 'UlftZMySTmmlvwwOF9qD/RzOLIowjtvzkrluVmhhTqA=' | base64 -d | xxd -p -c 0
5257ed64cc924e69a5bf0c0e17da83fd1cce2c8a308edbf392b96e5668614ea0

One can use the delta_patch.py script (Windows only as it loads ApplyDeltaB and DeltaFree from msdelta.dll) to apply patches. So the patch operation may be the following:

delta_patch.py -i /tmp/dbxupdate-original.bin -o /tmp/dbxupdate-base.bin "${local}/r/dbxupdate.bin"
delta_patch.py -i /tmp/dbxupdate-base.bin -o /tmp/dbxupdate-new.bin "${update}/f/dbxupdate.bin"
# 5257ed64cc924e69a5bf0c0e17da83fd1cce2c8a308edbf392b96e5668614ea0 dbxupdate-original.bin
# 528728c4a643d366445d953c6357a45656795396c09ac93b8a984b74c4bda9c3 dbxupdate-base.bin
# 5257ed64cc924e69a5bf0c0e17da83fd1cce2c8a308edbf392b96e5668614ea0 dbxupdate-new.bin

The -original and -new versions match. Why?! Probably because r version is stored locally so that on a new update, the client can calculate the base version. But because in our case, the original version was in fact the target version as well, our local r matches the one in the patch. We can even check the hashes on Winbindex, they refer to consecutive versions of dbxupdate.bin.

There are n (null) updates that don't have base versions, or more precisely it's the b"" (empty file). For example dbxupdate2024.bin is such a file in KB5036892 introducing Microsoft 2023 DB and KEK certificates.

Want to message us? Contact us: blog@ukatemi.com