PE Resources
How the .rsrc section stores icons, manifests, version info, and embedded files — the three-level resource directory tree, the API for reading resources at runtime, and how malware hides payloads inside resources
YARA scan hits on a suspicious binary because it has an unusually large resource section. You open it and find a resource of type "BINARY" with 2 MB of data and entropy 7.8 — clearly encrypted. Alongside it is a legitimate-looking RT_VERSION resource that claims the binary is svchost.exe version 10.0. The version info is spoofed; the binary blob is the encrypted payload. Knowing the resource tree structure, you can extract both for further analysis.
Resources Overview
The PE resource section (.rsrc) is a structured store of named data blocks embedded in the executable. Resources let Windows applications bundle non-code assets — icons, strings, dialogs, version metadata, application manifests — directly into the binary, eliminating the need for separate asset files.
The resource section is pointed to by Data Directory entry 2 (IMAGE_DIRECTORY_ENTRY_RESOURCE). Unlike most PE structures, the resource section uses a three-level tree of directory nodes rather than simple arrays.
Resource Tree Structure
The resource tree has exactly three levels:
Resource Tree — Three Levels ────────────────────────────────────────────────────────────────── Level 1: Resource Type ┌─────────────────────────────────────────────────────────────┐ │ Directory for RT_ICON (type 3) │ │ Directory for RT_VERSION (type 16) │ │ Directory for RT_MANIFEST (type 24) │ │ Directory for "BINARY" (custom type, named) │ └─────────────────────────────────────────────────────────────┘ Level 2: Resource ID (or name) ┌─────────────────────────────────────────────────────────────┐ │ Entry #1 (ID=1) ← first icon in the set │ │ Entry #2 (ID=2) ← second icon │ └─────────────────────────────────────────────────────────────┘ Level 3: Language ┌─────────────────────────────────────────────────────────────┐ │ Entry: LANGID=0x0409 (en-US) ← points to actual data leaf │ │ Entry: LANGID=0x0411 (ja-JP) ← Japanese variant │ └─────────────────────────────────────────────────────────────┘ Data Leaf (IMAGE_RESOURCE_DATA_ENTRY): ┌─────────────────────────────────────────────────────────────┐ │ OffsetToData: RVA of resource bytes │ │ Size: number of bytes │ │ CodePage: usually 0 │ └─────────────────────────────────────────────────────────────┘
Directory Entry Structure
Each level is described by IMAGE_RESOURCE_DIRECTORY followed by an array of IMAGE_RESOURCE_DIRECTORY_ENTRY records. Each entry is 8 bytes:
- Bits 31 (NameIsString): If set, the entry is identified by a string name; otherwise by an integer ID.
- Bits 30-0 (NameOffset or Id): Either an offset to a
IMAGE_RESOURCE_DIR_STRING_U(for named) or an integer resource ID. - OffsetToData high bit: If set, the offset points to another
IMAGE_RESOURCE_DIRECTORY(subdirectory); otherwise to aIMAGE_RESOURCE_DATA_ENTRY(leaf).
Resource API
Windows provides a clean API for loading resources from a PE without manually parsing the tree:
// Load and use a resource at runtime
// Step 1: Find the resource
HRSRC hRes = FindResourceA(
hModule, // module handle (or NULL for current EXE)
MAKEINTRESOURCE(101), // resource ID
"BINARY" // resource type (string for custom types)
);
if (!hRes) { /* not found */ }
// Step 2: Get size
DWORD size = SizeofResource(hModule, hRes);
// Step 3: Load into memory
HGLOBAL hMem = LoadResource(hModule, hRes);
if (!hMem) { /* failed */ }
// Step 4: Get pointer to data
LPVOID pData = LockResource(hMem);
// pData points to the raw resource bytes in the mapped image
// The data is directly mapped — no copy is made
// Common malware pattern: load + decrypt + execute
LPVOID pPayload = VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE);
memcpy(pPayload, pData, size);
decrypt_payload(pPayload, size, xor_key); // in-memory decryption
VirtualProtect(pPayload, size, PAGE_EXECUTE_READ, &oldProt);
((void(*)())pPayload)(); // execute shellcode
Application Manifest (RT_MANIFEST)
The application manifest is an XML document embedded as resource type 24 (RT_MANIFEST), resource ID 1. Windows reads this manifest before loading the application. Key fields it controls:
| Manifest Element | Effect |
|---|---|
<requestedExecutionLevel level="requireAdministrator"/> |
Triggers UAC prompt — user must approve elevation to administrator |
<requestedExecutionLevel level="asInvoker"/> |
Run with the caller's current token — no elevation |
<requestedExecutionLevel level="highestAvailable"/> |
Elevate to the highest available level without prompting (for admin accounts) |
<dpiAware>true</dpiAware> |
Marks the application as DPI-aware, suppressing Windows DPI virtualization |
<dependency> (SxS) |
Declares Side-by-Side assembly dependencies (Visual C++ runtime, etc.) |
A binary without a manifest or with a manifest declaring requireAdministrator combined with no code-signing certificate is worth examining. Legitimate software requesting admin elevation is expected to be signed. An unsigned binary asking for elevation that appears in a temp directory or user-writable path is a high-priority alert.
Version Information (RT_VERSION)
The RT_VERSION resource (type 16) stores the file's metadata: company name, product name, file version, legal copyright, original filename, and more. This is what Windows Explorer shows in the Details tab of a file's properties. The structure is VS_VERSIONINFO with nested StringFileInfo and VarFileInfo blocks.
Malware frequently copies version info from legitimate Windows system files to appear trusted. The company name might claim "Microsoft Corporation" and the original filename might be "svchost.exe", while the actual binary has no relation to either. Checking whether the version info matches the file's signature (Authenticode) is a reliable signal: if it claims to be Microsoft but has no Microsoft signature, the version info is spoofed.
import pefile
def get_version_info(filepath):
pe = pefile.PE(filepath)
if not hasattr(pe, 'FileInfo'):
print("No version info")
pe.close()
return
for fileinfo in pe.FileInfo:
for fi in fileinfo:
if fi.Key == b'StringFileInfo':
for st in fi.StringTable:
for key, val in st.entries.items():
print(f" {key.decode():25s}: {val.decode()}")
pe.close()
Malware Resource Abuse
Encrypted Payload in Custom Resource
The most common technique: store an encrypted PE or shellcode in a custom resource type ("BINARY", "PAYLOAD", "DATA", or any name). At runtime, the dropper calls FindResource / LoadResource, decrypts the data (commonly XOR or RC4), and executes it. The outer dropper may appear completely benign from import analysis alone; the payload is never visible until it's decrypted in memory.
Legitimate Binary as Host (PE Injection via Resource)
A legitimate binary (signed, trusted) can be modified to include a malicious resource. The attacker: (1) copies a legitimate signed binary, (2) adds a malicious resource, (3) re-signs with a stolen or self-signed cert. Windows will load the binary normally — the Authenticode check covers the entire file, so modifying the file after signing invalidates the signature. But if the cert check is skipped or the cert is trusted, the malicious resource is present.
Icon Spoofing
Malware commonly embeds an icon that makes it look like a PDF, Word document, folder, or control panel item. The icon is a legitimate RT_ICON resource; the binary just contains an image that makes users click it thinking it's a document. Combined with a filename like Invoice_2024.pdf.exe and Windows hiding extensions by default, the attack is effective.
Extracting Resources with Python
import pefile
import math
from collections import Counter
RT_NAMES = {
1: "RT_CURSOR", 2: "RT_BITMAP", 3: "RT_ICON",
4: "RT_MENU", 5: "RT_DIALOG", 6: "RT_STRING",
9: "RT_ACCELERATOR", 14: "RT_GROUP_ICON",
16: "RT_VERSION", 24: "RT_MANIFEST",
}
def entropy(data):
if not data: return 0
c = Counter(data); n = len(data)
return -sum((v/n)*math.log2(v/n) for v in c.values())
def audit_resources(filepath):
pe = pefile.PE(filepath)
if not hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
print("No resource directory")
pe.close(); return
for res_type in pe.DIRECTORY_ENTRY_RESOURCE.entries:
type_name = RT_NAMES.get(res_type.id, f"custom:{res_type.id}")
if res_type.name: type_name = f"named:{res_type.name}"
for res_id in res_type.directory.entries:
for res_lang in res_id.directory.entries:
leaf = res_lang.data.struct
offset = leaf.OffsetToData - pe.OPTIONAL_HEADER.ImageBase
size = leaf.Size
try:
data = pe.get_data(leaf.OffsetToData - pe.OPTIONAL_HEADER.ImageBase, size)
ent = entropy(data)
except:
ent = -1
flag = " ⚠ HIGH ENTROPY" if ent > 7.0 else ""
print(f" {type_name:20s} size={size:8d} entropy={ent:.2f}{flag}")
pe.close()
Q & A
How does Windows enforce that only privileged binaries can request elevation via the manifest?
Windows doesn't enforce anything about who can request elevation — any executable can put requireAdministrator in its manifest. The enforcement happens through the UAC (User Account Control) prompt: when a process with a manifest requesting elevation launches, Windows intercepts the creation, shows the UAC dialog to the user, and only creates the elevated process if the user approves. On systems where the current user is already an administrator with UAC enabled (Medium integrity), the prompt asks for confirmation. On systems where the current user is a standard user, the prompt asks for administrator credentials. Where it matters for malware: an attacker can trigger UAC prompts by launching a binary with requireAdministrator, hoping the user will click Yes. Modern UAC has a "secure desktop" mode where the prompt appears on a separate desktop that no user-space code can interact with programmatically — preventing automated UAC bypass via UI automation. But many organizations run users as local admins with UAC set to "notify only," meaning the prompt just needs a Yes click with no credential entry.
Can resources be modified without breaking the Authenticode signature?
No — Authenticode signs the entire PE file hash, including the .rsrc section. Modifying any resource after signing invalidates the signature. Windows will flag the binary as "The digital signature of this file couldn't be verified." However: (1) Not all Windows API calls verify signatures — LoadLibrary and CreateProcess load files without Authenticode validation by default on most configurations. (2) Application Control (AppLocker, WDAC) policies can enforce signature checks. (3) There's a special exception: the Authenticode spec historically excluded the IMAGE_DIRECTORY_ENTRY_SECURITY (the certificate table itself) and the checksum from the hash calculation. But this doesn't allow modifying resources without detection — modifying resources changes the file hash, which no longer matches the signed hash. The exception only covers the certificate table's own data so you can embed/remove a signature without self-reference issues.