Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .agents/skills/reverse-engineer-anything/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,13 @@ explicit path or endpoint and do not need it.
linker declarations, not proof of enforcement. A missing directory is observed
absence of that declaration. See the
[PE load configuration guide](https://github.com/morluto/rea/blob/main/docs/pe-load-config.md).
- PE Rich header: `inspect_pe_rich_header` with the explicit local PE32/PE32+
file. This built-in target-free inspection requires no Windows SDK. Preserve
the record range, key, each product ID, build and use count, and whether the
checksum reproduces the key. Product IDs are not tool or Visual Studio names.
A missing record is observed absence, not proof of a non-Microsoft toolchain.
See the
[PE Rich header guide](https://github.com/morluto/rea/blob/main/docs/pe-rich-header.md).
- ELF note tables: `inspect_elf_notes` with the explicit local ELF32/ELF64
file. This built-in target-free inspection requires no disassembler. Preserve
PT_NOTE and SHT_NOTE ranges, owners, types, descriptor SHA-256, and GNU
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,18 @@ mitigations carry `evidence_kind: "inferred"`; enforcement remains unobserved.
See
[PE TLS callbacks and load configuration](https://github.com/morluto/rea/blob/main/docs/pe-load-config.md).

## PE Rich header

Use `inspect_pe_rich_header` for an explicit local PE32/PE32+ image when the
question is which Microsoft toolchain built it, or whether its Rich header was
edited after linking. No `open_binary`, Windows SDK, or target execution is
required. The CLI equivalent is `inspect-pe-rich-header PATH`. Preserve artifact
SHA-256, the record's file range, the key, and each product ID, build and use
count. `checksum.matches` compares the stored key with the linker checksum over
the DOS header, stub and entries; a mismatch does not show which bytes changed.
Product IDs are not mapped to tool names. See
[PE Rich header](https://github.com/morluto/rea/blob/main/docs/pe-rich-header.md).

## ELF note tables

Use `inspect_elf_notes` for an explicit local ELF32/ELF64 image when the
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export default defineConfig({
{ text: "PE certificates", link: "/pe-certificates" },
{ text: "PE imports", link: "/pe-imports" },
{ text: "PE TLS/load config", link: "/pe-load-config" },
{ text: "PE Rich header", link: "/pe-rich-header" },
{ text: "ELF notes", link: "/elf-notes" },
{ text: "Apple IPS crash reports", link: "/apple-ips" },
{ text: "Linux kernel modules", link: "/kernel-modules" },
Expand Down
10 changes: 10 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,16 @@ Reports TLS callbacks and size-covered load-config fields without execution.
Use `--max-callbacks` and `--max-file-bytes` to set read budgets. See
[PE TLS and load configuration](pe-load-config.md).

### PE Rich header

```sh
rea inspect-pe-rich-header /absolute/path/to/application.exe --json
```

Reports the Rich header's product IDs, builds and use counts, and whether its
checksum reproduces the stored key, without execution. Use `--max-entries` and
`--max-file-bytes` to set read budgets. See [PE Rich header](pe-rich-header.md).

Bun standalone executables use the existing `inspect-artifact` and
`extract-artifact` commands to inventory and materialize embedded source,
sourcemaps and bytecode without running the executable. See
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ For runtime instrumentation, see the [Frida guide](./frida-instrumentation.md).
| Task | Guides |
| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Identify a desktop installation | [Runtime candidates, evidence and follow-up files](desktop-applications.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [ELF notes](elf-notes.md), [Linux kernel modules](kernel-modules.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Inspect native functions, references, or types | [Native binaries](native-investigation.md), [Go metadata](go-binaries.md), [Rust dependencies](rust-binaries.md), [CPython pyc files](python-pyc.md), [PE certificates](pe-certificates.md), [PE imports](pe-imports.md), [PE TLS/load config](pe-load-config.md), [PE Rich header](pe-rich-header.md), [ELF notes](elf-notes.md), [Linux kernel modules](kernel-modules.md), [Apple IPS](apple-ips.md), [Erlang BEAM](erlang-beam.md), [offline ELF layout](binary-diagnostics.md), [recorded crashes](recorded-crashes.md) |
| Select a native provider or import format | [Ghidra semantics](ghidra-provider.md), [Windows](windows-ghidra-p0.md), [DOS](ghidra-dos.md), [raw bytes](raw-binary.md), [GameCube DOL](ghidra-dol.md), [MIPS](ghidra-mips.md), [PSP](ghidra-psp.md), [NativeAOT](ghidra-nativeaot.md), [IDA](ida-provider.md), [Kuna](kuna-provider.md), [JEB](jeb-analysis.md) |
| Analyze JavaScript or Electron artifacts | [Static analysis](javascript-artifact-reconstruction.md), [tracing and comparisons](javascript-application-workflows.md), [application graph](javascript-application-graph.md), [source recovery](javascript-recovery.md) |
| Compare static JavaScript with runtime observations | [Runtime attribution](javascript-runtime-observation.md), [reconciliation](javascript-runtime-reconciliation.md), [Electron observation](electron-observation.md), [Electron fuses](electron-fuses.md) |
Expand Down
83 changes: 83 additions & 0 deletions docs/pe-rich-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# PE Rich header

Use `inspect_pe_rich_header` to read the Rich header of an explicit local PE32
or PE32+ image. Microsoft's linker writes this record between the DOS stub and
the PE signature. It counts the objects each compiler, assembler, linker and
import-library tool contributed, by product ID and build number. No
disassembler, Windows host, Visual Studio or target execution is required.

```sh
rea inspect-pe-rich-header /absolute/path/to/application.exe --json
rea inspect-pe-rich-header /absolute/path/to/application.exe --max-entries 4096 --json
```

MCP accepts `{ "path": "/absolute/path/to/application.exe" }`. CLI paths may be
relative to the current directory; MCP paths must be absolute.

## Report

`pe_header_offset` is `e_lfanew`, which ends the search. `rich_header.state` is
one of:

- `present`: the record decoded. `location` runs from `DanS` through the key
after `Rich`, and `footer_location` covers `Rich` and the key. Each entry in
`entries` is one `@comp.id`: `product_id` is its high word, `build` its low
word, and `count` its use count. `padding` holds the three DWORDs after
`DanS`, decoded with the key. Microsoft's linker writes zeros there.
- `malformed`: a `Rich` footer exists, but the record does not decode. `reason`
is `start_marker_missing` when no `DanS` encoded with that key precedes the
footer, or `partial_entry` when the record ends inside an entry.
- `absent`: no `Rich` footer precedes the PE signature. Other linkers omit the
record, and it can be stripped after linking, so absence does not show how
the image was built. A PE header that starts inside the 64-byte DOS header
leaves no room for a record, so it is `absent` too.

Evidence locations include the record's file range, or only the footer for a
malformed record.

## How the record is found

REA takes the last DWORD-aligned `Rich` marker whose key ends at or before the
PE signature. It then walks back one DWORD at a time to the nearest `DanS`
encoded with that key.

The last marker matters. A custom DOS stub can hold a whole image with its own
Rich header. On one Windows 11 host, `C:\Windows\System32\tcblaunch.exe` has a
footer at `0xE8` inside its stub and the image's own record at `0x2D20`.
`DanS` is not always at `0x80` either: drivers on the same host place it at
`0x58` and `0x200`.

## Checksum

The key is a checksum. REA recomputes it:

1. Start with the file offset of `DanS`.
2. Add every byte before `DanS`, rotated left by its offset modulo 32. Skip
`e_lfanew` at offsets `0x3C` to `0x3F`.
3. Add every `@comp.id`, rotated left by its count modulo 32.
4. Keep the low 32 bits.

`checksum.matches` reports whether that reproduces the stored key. A mismatch
means the DOS header, the stub or the entries differ from what was summed, for
example a record copied from another image or a stub edited after linking. It
does not show which bytes changed. A match says nothing about the rest of the
image.

## Limits

Product IDs and builds are reported as stored. REA does not name the tool or
Visual Studio release behind them.

The default read budget is 64 MiB (`max_file_bytes`, ceiling 512 MiB). The
default entry budget is 1024 (`max_entries`, ceiling 65536). Exceeding either
fails without partial success, as does a malformed PE header.

## Verification

`npm run verify:pe:rich-header -- PATH...` inspects each PE file under the given
files and directories, then compares key and entries with pefile's
`parse_rich_header` wherever pefile's layout assumption holds. Any other state
difference fails the run, as does a file only REA rejects or a run that
compares no file. Set
`REA_PEFILE_PYTHON` to a Python that imports pefile 2024.8.26. See the
[2026-11-04 verification record](verification/pe-rich-header-2026-11-04.md).
66 changes: 66 additions & 0 deletions docs/verification/pe-rich-header-2026-11-04.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# PE Rich header verification - 2026-11-04

All runs used Windows 11 (10.0.26200) x64 and Node 24.22.1. No target was
executed.

## Oracle

The oracle is pefile **2024.8.26**, installed without dependencies into a
separate virtual environment. Its `pefile.py` SHA-256 is
`0d0eb68f0f169182613dc64b3ab50b20855508c1c1d2faafe8fccf23edb6a345`.
`parse_rich_header` reads from file offset `0x80` up to the first `Rich`
marker and warns when the first DWORD there is not `DanS`.

## Installed images

```sh
REA_PEFILE_PYTHON=<python with pefile> node scripts/verify-pe-rich-header.mjs \
"C:\Windows\System32" "C:\Program Files" "C:\Program Files (x86)"
```

The script walked 17,896 `.exe`, `.dll`, `.sys`, `.ocx`, `.cpl`, `.efi` and
`.scr` files with `max_file_bytes` at its 512 MiB ceiling. A file is left out
of the comparison only on pefile's own result: pefile warned that `0x80` is not
`DanS`, or REA found a footer before `0x80`, where pefile never looks. Every
other state difference counts as a disagreement and fails the run. A file REA
rejects fails the run too, unless pefile also rejects it.

| Result | Files |
| ---------------------------------------------------- | ------ |
| Rich header present | 10,915 |
| Checksum reproduces the stored key | 10,901 |
| Checksum does not reproduce the key | 14 |
| No Rich footer before the PE signature | 6,980 |
| Malformed record | 0 |
| Rejected by REA and pefile (a 0-byte `eqnedt32.exe`) | 1 |
| Same key and entries as pefile, or both absent | 17,890 |
| Different from pefile | 0 |
| Outside pefile's `0x80` layout assumption | 5 |

The 14 checksum mismatches are seven NVIDIA driver files in one DriverStore
package, three `vk*.sys` drivers, three Riot Vanguard executables, and
`tcblaunch.exe`. Their entries still match pefile wherever pefile's layout
assumption holds.

The five images outside pefile's assumption:

| Image | `DanS` | Entries | Checksum | pefile |
| ------------------------ | -------- | ------- | -------- | ----------------------------------------- |
| `WeGameDriverXP.sys` | `0x200` | 5 | matches | 53 value pairs, "not in Microsoft format" |
| `vkdpi.sys` | `0x58` | 7 | differs | "not in Microsoft format" |
| `vktap.sys` (two copies) | `0x58` | 7 | differs | "not in Microsoft format" |
| `tcblaunch.exe` | `0x2D20` | 7 | differs | "not in Microsoft format" |

`tcblaunch.exe` 10.0.26100.9711 has a custom 11 KB DOS stub that is itself a
PE image. The stub's own record sits at `0xC0`, and its checksum reproduces
its key when summed from the stub image's start at `0x40`. The outer record
at `0x2D20` is the one reported.

## Synthetic fixtures

Parser tests build PE32 and PE32+ images and compute the expected key with a
separate BigInt checksum. They cover zero entries, nonzero padding, a stub
byte changed after linking, a wrong stored key, a footer whose key would
overlap the PE signature, an unaligned marker, an earlier footer embedded in
the stub, a missing `DanS`, a partial entry, the entry budget, a 2 MiB stub,
cancellation, and a non-PE input.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@
"verify:apktool": "npm run build:cached && node scripts/verify-apktool.mjs",
"verify:pcap": "npm run build:cached && node scripts/verify-pcap.mjs",
"verify:pe:load-config": "npm run build:cached && node scripts/verify-pe-load-config.mjs",
"verify:pe:rich-header": "npm run build:cached && node scripts/verify-pe-rich-header.mjs",
"verify:flutter": "npm run build:cached && node scripts/verify-flutter.mjs",
"verify:adb": "npm run build:cached && node scripts/verify-adb.mjs",
"verify:ghidra": "npm run build:cached && node scripts/verify-real-ghidra.mjs",
Expand Down
Loading
Loading