Skip to content

Rich Header & Manifest

Rich header

Microsoft's linker writes an undocumented, XOR-masked block between the DOS stub and the PE header, the Rich header. It records each tool (compiler, assembler, linker, import library) that contributed objects, with its product ID, build number and object count.

PPEE decodes it and names the Visual Studio version:

$ ppee-cli --richheader explorer.exe
Rich Header:
  CheckSum(XOR key)            = D34DFA25
  DanS sign                    = 536E6144   DanS
  MD5                          = 92C3A2A89B45668A5013AD79762FDAB4
  Product ID                   = 0093       Import, VS2008
  Minor Compiler Version       = 7809       Build 30729
  Count                        = 00000104
  Product ID                   = 0104       C object, VS2015
  Minor Compiler Version       = 6B14       Build 27412
  Count                        = 00000020

Windows screenshot: Rich header of a shellcode loader: VS2015-family C, C++, ASM and import objects from two compiler builds

Rich header of a shellcode loader: VS2015-family C, C++, ASM and import objects from two compiler builds

In the GUI each entry is a group of three rows. This shellcode loader (86c6bd80….exe) was built from C++, C and assembler objects by two toolset builds, 33145 and 35721. The pair, with the counts and the MD5 row, is a fingerprint to search for in other samples.

Why it matters:

  • Attribution and clustering: samples built on the same machine or toolchain share Rich headers. The MD5 row gives you a searchable fingerprint.
  • Tamper detection: a Rich header that lists tools inconsistent with the rest of the file (for example VS2008 objects in a binary whose linker version is 14.x) suggests it was forged or transplanted from another binary.
  • Files not linked by Microsoft's toolchain (MinGW, Go, Rust with the GNU toolchain, Delphi) have no Rich header: "present": false.

Application manifest

The RT_MANIFEST resource tells Windows how to run the program. PPEE parses the XML into rows:

Row Why it matters
RequestedPrivileges → Level asInvoker, highestAvailable or requireAdministrator (triggers UAC)
uiAccess true lets the app drive higher-integrity UI; it requires a signed binary in a secure location
AssemblyIdentity Name, version, architecture
SupportedOS Windows version GUIDs the app declares
dpiAware / dpiAwareness High-DPI behavior
Dependencies Side-by-side assemblies (Common Controls v6, …)

Windows screenshot: Application manifest of a ransomware sample requesting requireAdministrator

Application manifest of a ransomware sample requesting requireAdministrator

A NativeAOT ransomware (d1337711….exe) asks for requireAdministrator, shown in the warning color, and the AppManifest node gets a warning dot. It needs those rights for what its method names say it does, such as deleting shadow copies (see NativeAOT). The identity is Visual Studio's default MyApplication.app, left unchanged.

ppee-cli --json --appmanifest f.exe | jq -r '.appManifest.rows[] | select(.member|test("Level")) | .value'

Related: --richheader · --appmanifest · CI: forbid requireAdministrator

References