A valid signature establishes integrity over a defined set of content. It does not automatically establish which program representation the runtime consumed. VBA projects store both compressed source and a compiled cache; if they disagree, signature coverage and execution selection become separate questions.
The example is a 2020 Word/VBA test document. Its static views show different source and P-Code semantics, but the evidence lacks a full Office build number, file hash, and signature-verification log. It therefore does not establish cache execution across all Office versions or attribute a successful run to a failure of macro policy.
Module streams store two representations
A module stream begins with PerformanceCache, followed by CompressedSourceCode. Both have variable length. MODULEOFFSET marks the start of the source region, not a universal file offset.
| Region | Range | Meaning |
|---|---|---|
| PerformanceCache | [0, MODULEOFFSET) |
Implementation- and version-dependent cache |
| CompressedSourceCode | [MODULEOFFSET, EOF) |
VBA source compressed by the specified algorithm |
Microsoft's format specification describes the cache as implementation- and version-dependent and requires format readers to ignore it. That interoperability requirement is distinct from the execution representation selected by a particular Office implementation. Runtime behavior requires evidence from the specific environment. MS-OVBA module stream
_VBA_PROJECT and __SRP_* locations also relate to compilation state. The cache is not a stable, standalone executable format across versions. Changing a few bytes in one module can invalidate it and cause recompilation from source.
Contents hash is not a hash of the entire file
MS-OVBA defines a contents hash over a specified subset of information in VBA Storage. The module-processing part of ContentNormalizedData reads and decompresses CompressedSourceCode, then applies prescribed line processing. It does not simply hash the raw module stream from start to finish. Contents Hashes
flowchart TD A["CompressedSourceCode"] --> B["Decompress to source text"] B --> C["Apply specified line handling"] C --> D["Skip lines starting with Attribute"] D --> E["Append to normalized buffer"] E --> F["Contribute to contents hash"]
This diagram summarizes only the module-source portion of that algorithm version, not a complete signature implementation. Project names, constants, references, and other information have their own handling. The Attribute test is case-insensitive; it should not be carried over unchanged to signature versions with different normalization rules. Content Normalized Data
Two claims need independent proof: that a cache change left the actual signed input unchanged, and that the runtime accepted and consumed the changed cache. One concerns integrity coverage; the other concerns execution selection. Neither alone establishes the behavioral divergence.
Keep source and P-Code in the same evidence chain
The source extracted from example-08b.docm retains the message-box logic. a3 is the stream selector for this document. Enumerate streams first rather than assuming every DOCM uses that location.
$ oledump.py -s a3 -v example-08b.docm
Attribute VB_Name = "Module1"
Sub autoopen()
MsgBox "Hello"
End SubThe pcodedmp output describes a different operation. The excerpt preserves module lengths, the string, and the call name while omitting the local tool-installation path:
$ pcodedmp example-08b.docm | tail
Module streams:
VBA/ThisDocument - 1060 bytes
VBA/Module1 - 1400 bytes
Line #0:
FuncDefn (Sub autoopen())
Line #1:
LitStr 0x0004 "calc"
ArgsCall Shell 0x0001
Line #2:
EndSubThe difference is explicit:
| Layer | Visible content | Supported conclusion |
|---|---|---|
| Source | MsgBox "Hello" |
The decompressed program text calls a message box |
| Cache | LitStr "calc", ArgsCall Shell |
Disassembled P-Code contains an external-program call |
| Runtime | Separate capture required | Establishes which representation actually executed |
Reading only the VBA editor or a source extractor misses the second row. Reading only P-Code can also mislead if that cache is stale and the runtime recompiles the source. Saving, editing, or changing the Office environment may alter the choice, so the inspection sequence itself belongs in the record.
These outputs establish a static semantic mismatch. They do not independently prove that Calculator launched or that the digital signature remained valid. Those claims need a runtime record and a signature-verification result, respectively.
Reduce variables with a one-byte control
To isolate representation selection, both paths can call a message box but display Hello and Hallo, respectively. The following offline check uses an illustrative byte sequence. It does not locate bytes in a real document, modify a cache, or calculate a signature.
prefix0x00–0x01string0x02–0x06
before = bytes.fromhex("05 00 48 65 6c 6c 6f")
after = bytes.fromhex("05 00 48 61 6c 6c 6f")
changed = [i for i, (a, b) in enumerate(zip(before, after)) if a != b]
assert len(before) == len(after) == 7
assert changed == [3]
assert before[2:].decode("ascii") == "Hello"
assert after[2:].decode("ascii") == "Hallo"
print("PASS: one changed byte at offset 3; Hello -> Hallo")$ python verify_string_diff.py
PASS: one changed byte at offset 3; Hello -> HalloOffset 3 is relative to this fragment, not an absolute DOCM offset. A real cache-level experiment must first establish that candidate bytes belong to the intended string, rather than compressed source, another module, or unrelated data. This model does not perform that step.
Track four independent states in the test matrix
Combining signature validity, certificate trust, permission to run macros, and cache selection into one boolean makes both success and failure difficult to interpret.
| Document or environment | Hold constant | Observe |
|---|---|---|
| Original signed document | Office build and policy | Baseline source, P-Code, signature, and behavior |
| Copy differing only in cache | Comparable source and signature material | Whether coverage and execution representation diverge |
| Copy that triggers recompilation | Record the trigger and preserve separate files | Whether replacing the cache restores source semantics |
| Different build or bitness | Identical input document | How cache compatibility affects the outcome |
For each cell, record the file hash, Office product and full build, 32/64-bit architecture, certificate chain, macro policy, signature-scheme version, verification result, and displayed text. A signature badge does not replace those records. Preserve negative results too: prompts, cache regeneration, and cases where the macro never entered its execution path.
Later product boundaries and practical checks
Microsoft subsequently introduced V3 VBA signatures and deployment guidance covering re-signing and a policy that trusts only V3 signatures. Compatibility defaults can still accept older signatures. Updating Office therefore does not establish that every document has been re-signed with V3 or that the corresponding policy is enforced. Microsoft KB5000676
The specific build of this 2020 test document is missing, so the example is not assigned directly to a CVE or used to characterize arbitrary current releases. Review the deployed build, signature format, cache handling, and macro policy instead of carrying forward an unversioned claim.
Keep source, cache disassembly, and execution-selection evidence together. The broader integrity question is: does validation bind the original input, or the derived object actually consumed at runtime? Identify the protected object before interpreting what a valid signature means.