Repository navigation
feat(pe): inspect Rich header linker records - #8923
Plastic-time wants to merge 6 commits into
Conversation
Add inspect_pe_rich_header and the inspect-pe-rich-header command. They read the Rich header between the DOS stub and the PE signature without a provider. - Take the last DWORD-aligned Rich footer before the PE signature and walk back to its DanS marker. - Report each product ID, build and use count, the decoded padding and the record's file range. - Recompute the linker checksum and report whether it reproduces the stored key. - Report an absent record, and a footer without DanS or with a partial entry as malformed. The verifier compares key and entries with pefile 2024.8.26.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits. |
[Medium impact] The PR appears safe to merge; no blocking code issue remains from this review. SummaryAdds
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["CLI or MCP: explicit local path"] --> B["PeRichHeaderService: check input"]
B --> C["PeRichHeaderProvider: readStableArtifact"]
C --> D["Keep path, SHA-256 and byte count"]
D --> E["parsePeRichHeader: admit PE headers"]
E --> F["Find last aligned Rich footer"]
F --> G["Find encoded DanS and decode entries"]
G --> H["Recompute checksum"]
H --> I["Check report and create Evidence"]
E --> J["On failure, retain completed file identity"]
F --> J
G --> J
Reviews (5) · Last reviewed commit: "chore: rerun CI after an unrelated test ..." · Reviewed by Greptile |
- Scan aligned DWORDs for the Rich footer and DanS marker in one loop that yields and checks cancellation every MiB. A stub full of unaligned Rich strings no longer holds the event loop. - Declare the padding words with fixedTupleSchema and cover them in the tuple parity tests. - Import the binary diagnostics contract family in the focused CLI and MCP tests. - In the pefile verifier, exclude files only by pefile's own layout result, fail any other state difference, refuse a run that compares nothing, and give the CLI check the same read budget.
A PE whose e_lfanew points inside the 64-byte DOS header passes readPeHeaders, and the parser reports no Rich record for it. The service then refused the report as malformed provider output, because it required pe_header_offset to be at least 64. Drop that lower bound. The footer checks still reject any record reported before 0x40.
|
Very interesting! |
…ct-pe-rich-header # Conflicts: # docs/index.md
- Once the stable read completes, a later decoding failure or cancellation keeps the file's path, SHA-256 and byte count as a partial observation. - The pefile verifier now fails on a file that pefile reads but REA rejects. An input both reject, such as an empty file, is listed apart.
Summary
This adds
inspect_pe_rich_headerandrea inspect-pe-rich-header. They read the Rich header of a local PE32 or PE32+ image with no provider, and never run the target.The Rich header is the record Microsoft's linker writes between the DOS stub and the PE signature. It lists which compiler, assembler, linker and import-library builds produced the image, and how many objects each contributed. Today REA has no reader for it.
No issue is linked. This is a proposed new read-only tool, and whether REA wants it is for maintainers to decide.
Problem and expected behavior
Use case: an analyst wants to know which Microsoft toolchain built a Windows binary, or whether its Rich header was copied or edited after linking. The binary has no DWARF to ask, so #8523 does not cover it.
Observable outcome:
rich_header.stateispresent,malformedorabsent.product_id,buildandcount.checksum.computedis the linker checksum recomputed from the DOS header, stub and entries.checksum.matchessays whether it reproduces the stored key.DanSmarker, or a record that ends inside an entry, ismalformedwith a reason. A missing record isabsentand carries a limitation saying absence does not show how the image was built.absent.Change and scope
src/native/pe/PeRichHeaderParser.tsdoes the reading. It takes the last DWORD-alignedRichfooter whose key ends at or before the PE signature, then walks back to the nearestDanSencoded with that key.src/domain/native/peRichHeader.tsowns the report schema. The provider, service, CLI command and MCP registration followinspect_pe_load_configandinspect_pe_certificates.readPeHeaders. No existing reader changes.Two choices differ from pefile, on purpose:
DanSsits at0x80. On this host it is at0x58in twovk*.sysdrivers and at0x200inWeGameDriverXP.sys.Richmarker.tcblaunch.exehas a custom DOS stub that is itself a PE image, with its own record at0xC0. The image's record is the last one, at0x2D20.Not included: names for product IDs or Visual Studio releases, the "rich hash", or any edit of the record.
Follow the contribution scope.
Unresolved scope, authority, side effects, or required verification block
integration. Link the required maintainer scope decision for new providers or
capabilities involving target execution, injection, mutation, or protection
recovery; ordinary fixes within existing boundaries may state "not applicable".
Draft PRs may leave the scope decision pending and the readiness and merge checklists
unchecked. Maintainers can help resolve open questions.
Contract and boundary impact
src/native/pe/PeRichHeaderParser.ts. It consumes the existingreadPeHeaders.inspect_pe_rich_header, and one new command,inspect-pe-rich-header.readStableArtifact. No resources are retained.docs/public/product-catalog.json), package, or installation impact (CI generates and validates ignored catalogs andskills/; edit authored instructions in.agents/skills/reverse-engineer-anything/and never commit build outputs): one newverify:pe:rich-headerscript inpackage.json. No runtime dependency and no build output.Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.
Evidence and regression coverage
src/native/pe/PeRichHeaderParser.test.ts: 17 cases on synthetic PE32 and PE32+ images. The expected key comes from a separate BigInt checksum in the fixture. One case fills a 4 MiB stub with unalignedRichstrings and checks that the scan yields and observes cancellation.src/contracts/tupleSchemaParity.test.ts: thepaddingwords reject empty, short, overlong, null-element and out-of-range values in both JSON Schema drafts and both projections.src/application/binaryDiagnostics/PeRichHeaderService.test.ts: input validation, cancellation, Evidence locations, anabsentreport for a PE header at0x30inside the DOS header, and 5 kinds of malformed provider output rejected.src/native/pe/PeRichHeaderProvider.test.ts: once the read completes, an exhausted entry budget and a malformed PE header both keep the file's path, SHA-256 and byte count as the partial observation. Cancellation during decoding takes the same path.localToolContracts,stableArtifactConsumersandtoolEffectslists.rea inspect-pe-rich-headerisCOMMAND_NOT_FOUND, andgit grepfinds noDanSor Rich header reader undersrc.scripts/verify-pe-rich-header.mjscompares key and entries with pefile 2024.8.26parse_rich_header. It leaves a file out only on pefile's own result, and fails on any other state difference, a file only REA rejects, or a run that compares nothing. Over 17,896 PE files inC:\Windows\System32,C:\Program FilesandC:\Program Files (x86), 17,890 agree, 0 differ, and 5 fall outside pefile's0x80assumption. One 0-byte file is rejected by both REA and pefile.docs/verification/pe-rich-header-2026-11-04.mdholds the record.vk*.sysdrivers, three Riot Vanguard executables andtcblaunch.exe.rea inspect-pe-rich-header ... --jsonon this commit.EPERM. The existingpeCertificatesboundary test fails the same way on this host.For boundary fixes, identify the applicable boundary regressions
and lifecycle regressions
covered by the evidence above, and explain relevant gaps.
For evidence-bearing changes:
Validation performed
Run on Windows 11 10.0.26200 with Node 24.22.1. The branch starts from main
63b66fa49and was not rebased.c1db5e5ecmerges main0a99c2a58. That adds two main commits. They share only three docs files with this PR. The source tests, the provider comparison and the pefile run are on155b2c700. Some other runs predate the merge. CI passes on the head01aa387c5, an empty commit with the same tree as155b2c700.npm run test:localon the 5 new or changed source tests, includingtupleSchemaParityand the provider test: 72 passed.PeRichHeaderProvider.test.tswith the provider fromc1db5e5ec: 2 failed. On this commit: 2 passed.PeRichHeaderService.test.tswith the service from4af9a5243: 1 failed, 10 passed. On this commit: 11 passed.0x30): on4af9a5243,inspect-pe-rich-header --jsonexits 1 withunreadable_output. On this commit it exits 0 withabsentandpe_header_offset48.inspect-pe-resourcesreads the same file with exit 0.npm run test:focusedon the new CLI, MCP and filesystem tests pluslocalToolContractsandstableArtifactConsumers: 173 passed, 1 failed (the symlinkEPERMabove).toolSchemaValidity,clientSchemaPatternsandcliInputSchemaRequired: passed.npm run test:localon the 59 test files undersrc/contracts,src/composition,src/server,src/cli,src/native/peandsrc/application/binaryDiagnostics: 823 passed, 1 failed, 2 skipped. The failure is aByteEntropyServicecase that renames a parent directory while streaming, the Windows class from [Bug] Windows: five artifact tests fail because they rename a parent directory while its admitted file is open #7846. This PR does not touch that service or its test.node scripts/verify-pe-rich-header.mjswith pefile 2024.8.26: passed on this commit, numbers above.typecheck,lint(0 errors),lint:typed(0 errors),format:check,metadata:check,verify:architecture-guards,verify:test-discovery,knip,knip:productionanddocs:check: passed.Compatibility, safety, and release
package.jsonscript. No new dependency.max_file_bytes(default 64 MiB), decoding bymax_entries(default 1024), and long scans yield and check cancellation every 1 MiB. Exhausted budgets fail without partial success.Author readiness
Human owner: @Plastic-time
Before merge
type(scope): outcome.AI was used for assistance.