Resource Directory¶
Data directory 2
IMAGE_DIRECTORY_ENTRY_RESOURCE: DataDirectory[2] in the Optional header. All data directories
Resources hold data compiled into the binary: icons, dialogs, version information, manifests, and any blob the author wants. That makes the resource section the favorite hiding place for dropper payloads, encrypted configs and second stages, and version info is also the first thing a masquerading sample fakes.
How the structure works¶
The directory is a three-level tree:
Type (RT_RCDATA, RT_ICON, "IMAGE", …)
└── Name or ID (DATA.DLL, #101, …)
└── Language (#1033 = en-US, #0 = neutral) ──► IMAGE_RESOURCE_DATA_ENTRY: RVA, size, code page
| Standard type | ID | What's in it |
|---|---|---|
RT_ICON / RT_GROUP_ICON | 3 / 14 | Icons. Stolen icons (PDF, Word) are a classic lure |
RT_VERSION | 16 | Company, product, original filename, all attacker-controlled |
RT_MANIFEST | 24 | UAC level, DPI, dependencies (see Manifest) |
RT_RCDATA | 10 | Raw application data, the usual place for payloads |
RT_DIALOG / RT_STRING / RT_MENU | 5 / 6 / 4 | UI |
| Custom string types | For example "IMAGE", "MUI", "TYPELIB", "REGISTRY" |
What PPEE shows¶
- Tree: every type under DIR_ENTRY_RESOURCE (n) (
#16 (RT_VERSION),"IMAGE", …). - Upper list for a type: Resource Name/ID · OffsetToData · Type Detected · Size ratio (each resource's share of the type's bytes).
- Lower list for a resource: each language with OffsetToData, Size, CodePage, Entropy, MD5, Type Detected and a first-bytes hex/ASCII preview.
- Dump… on a language row saves the raw resource. PE payloads found in resources (and in appended data) are labelled with their bitness and kind, such as 32-bit EXE, not just PE File, so you can tell a 32-bit implant from a 64-bit one at a glance. Follow in Hex View (Ctrl+H) selects its bytes.
Content-based type detection¶
PPEE identifies each blob from its bytes, not from its declared type, so an executable disguised as a bitmap still shows up. Detected types include PE File, Zip Archive, Rar Archive, GZIP Compressed file, Microsoft Cabinet file, AutoIt compiled script file, Rich Text Format, XML, SVG, PNG, JPEG, GIF Image, BMP Image, Icon, Cursor, WAV, AVI, Macromedia flash compressed/uncompressed, Borland Delphi Form, OLE or Visual C++ type library file, Registry script, Version Resource, Security Certificate, Text string and more. Unknown content shows as null in JSON, which is itself interesting when combined with high entropy.
Reading resources like an analyst¶
| Observation | What it suggests |
|---|---|
RT_RCDATA (or a custom type) with entropy > 7.5, typeDetected: null, tens of KB or more | Encrypted or compressed payload: a dropper, an encrypted config, or a crypter stub's inner PE |
typeDetected: "PE File" inside any type | Embedded executable, dropped or injected at run time |
Resource names like DATA.DLL, API.DLL, PAYLOAD, random strings | The author's own labels for what they unpack |
AutoIt compiled script file | AutoIt-compiled malware (the script can be decompiled) |
DVCLAL, PACKAGEINFO, Borland Delphi Form | Built with Delphi, which tells you what kind of code and RTTI to expect |
| Resource data RVA not backed by file data | The resource directory survived packing, but the data was moved into a packed section (UPX does this) |
RT_VERSION claiming Microsoft/Adobe, but the file is unsigned or signed by someone else | Masquerading. Cross-check with Security |
| An icon of a document type on an EXE | Social-engineering lure |
Walkthrough: a dropper's embedded executable
3c6b036f2eebc124c17db51960d9f6c9b39e236e9a33d5ac0c3a3a2cabe36833.exe (RemusStealer, 1.8 MB):
$ ppee-cli --resources 3c6b036f….exe
Resources: 4 type(s), 9 name(s), 9 language variant(s):
#10 (RT_RCDATA) [...]
#100 [...]
lang=#1033 offset=4C500 size=1515568 codePage=0 entropy=6.49614 md5=AC7A167EE7269BD790F220BB104CCA22 typeDetected=PE File
first bytes: 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 | MZ.........ÿÿ..
- What is it?
typeDetected=PE FileandMZin the first bytes: a 1.5 MB executable, 83% of the file. MCPextract_payloadaddsembeddedPe: {machine: 0x8664, bitness: 64-bit (PE32+), kind: EXE}. - Is it used?
ppee-cli --xrefs FindResourceW 3c6b036f….exefinds one call, withmov r8d, 0xA(lpType = RT_RCDATA) just before it. The code loads this resource (details). - Analyze it where it is:
ppee-cli --analysis '3c6b036f….exe#resource:RT_RCDATA/100'reads the next stage straight from the resource (layers). Look it up by MD5AC7A167EE7269BD790F220BB104CCA22, or carve it (below) if you need the file.
More samples
- QuasarRAT builds (
2eac9624….exe,37d82058….exe) carry two small PEs (6 KB and 9 KB) inRT_RCDATA #101and#102: helper modules dropped at run time. - A protected crackme carries
RT_RCDATAentries namedAPI.DLL,ASMG.DLL,17.DLL, … and a 38,607-byteDATA.DLLwith entropy 7.89 and no recognizable type: encrypted modules the protector unpacks at run time. - A UPX-packed Rufus build lists 50+
RT_RCDATAentries with entropy ≈ 7.99. Their data RVAs fall insideUPX0, a section with no raw data: the directory stayed readable, the bytes were packed. - A Delphi binary contains
RT_RCDATA/DVCLAL, the Delphi license-check resource.
High entropy is not proof
PNG icons are compressed, so entropy 7.6–7.98 is normal for them. That's why PPEE shows Type Detected next to entropy. The signal is high entropy + unknown type + large size, especially in RT_RCDATA.
Dialogs, version info and string tables, decoded¶
Selecting a resource shows its language variants; for a dialog, version info or string table, the rows end with what it holds: the dialog's caption, font and every control (ID, class, text), the version values (CompanyName, OriginalFilename, …) and file versions, and the strings by ID. PPEE reads these formats itself, so it works the same on Windows and Linux.
A fake antivirus whose version info names another program
[email protected] presents itself as "Security Essentials 2011". Its version info says Kernel Mode Driver Manager by Four-F, OriginalFilename KmdManager.exe: a freeware driver tool's identity, reused. Compare it with the file name, the signer and the code.
For an assistant, get_resources returns the same, and gives each control or string ID its codeUses: the instructions using the ID, which get_xrefs with imm: lists.
Carving resources out¶
The GUI's Dump… saves one resource. From the command line, the resource's RVA and size are in the JSON, and ppee-dump-resource.sh converts the RVA to a file offset using the section table, carves the bytes, and verifies the MD5 against what PPEE reported:
$ ppee-dump-resource.sh explorer.exe IMAGE 100 icon.png
icon.png: 120 bytes at file offset 0x4FE980, md5 927E8608714F8F45FD337184873D56C9 OK
$ file icon.png
icon.png: PNG image data, 16 x 16, 8-bit gray+alpha, non-interlaced
$ ppee-dump-resource.sh explorer.exe RT_MANIFEST 1 manifest.xml
manifest.xml: 1327 bytes at file offset 0x45D500, md5 48C1E399D28E49E2E457C8AAC64FB5E3 OK
$ ppee-dump-resource.sh 3c6b036f….exe RT_RCDATA 100 stage2.bin
stage2.bin: 1515568 bytes at file offset 0x47300, md5 AC7A167EE7269BD790F220BB104CCA22 OK
$ file stage2.bin
stage2.bin: PE32+ executable (GUI) x86-64, for MS Windows, 7 sections
$ ppee-dump-resource.sh rufus.exe RT_RCDATA 300
resource RT_RCDATA/300 is not backed by file data (packed/virtual section); dump it from memory instead
Carved payloads are live malware
A resource carved from a sample is as dangerous as the sample. Carve inside an isolated analysis VM, never on a machine you work on.
Source of ppee-dump-resource.sh
#!/usr/bin/env bash
# ppee-dump-resource.sh FILE TYPE NAME [OUT] -- carve one resource (first language) out of a PE using ppee-cli's JSON.
# TYPE/NAME are as ppee-cli prints them: RT_RCDATA / DATA.DLL, RT_VERSION / 1, "IMAGE" -> IMAGE, …
set -euo pipefail
f=$1 type=$2 name=$3 out=${4:-"$name.bin"}
read -r off size md5 < <(ppee-cli --no-update-check --no-similarity --json --resources --sections "$f" | jq -r --arg t "$type" --arg n "$name" '
def h: ascii_downcase | explode | reduce .[] as $c (0; . * 16 + (if $c >= 97 then $c - 87 else $c - 48 end));
.sections as $s
| [range(0; 96) | select($s["Section[\(.)].VirtualAddress"] != null)
| {va: ($s["Section[\(.)].VirtualAddress"] | h), raw: ($s["Section[\(.)].PointerToRawData"] | h),
size: ($s["Section[\(.)].SizeOfRawData"] | h)}] as $secs
| .resources.types[] | select(((.typeName // .name // .id) | tostring) == $t)
| .names[] | select(((.name // .id) | tostring) == $n) | .languages[0]
| (.offsetToData | h) as $rva
| [$secs[] | select($rva >= .va and $rva < .va + .size)][0] as $sec
| if $sec == null then "NOTINFILE \(.size) \(.md5)" else "\($rva - $sec.va + $sec.raw) \(.size) \(.md5)" end' | head -1) || true
[ -n "${off:-}" ] || { echo "resource $type/$name not found (check the names with: ppee-cli --resources $f)" >&2; exit 1; }
[ "$off" != NOTINFILE ] || { echo "resource $type/$name is not backed by file data (packed/virtual section); dump it from memory instead" >&2; exit 2; }
dd if="$f" of="$out" bs=1 skip="$off" count="$size" status=none
got=$(md5sum "$out" | cut -c1-32 | tr a-f A-F)
echo "$out: $size bytes at file offset 0x$(printf %X "$off"), md5 $got $([ "$got" = "$md5" ] && echo OK || echo "MISMATCH (expected $md5)")"
CLI and JSON¶
$ ppee-cli --resources explorer.exe
Resources: 7 type(s), 683 name(s), 683 language variant(s):
"IMAGE" [... NumberOfIdEntries=425]
#100 [...]
lang=#1033 offset=507580 size=120 codePage=0 entropy=5.66008 md5=927E8608714F8F45FD337184873D56C9 typeDetected=PNG
first bytes: 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 | PNG........IHDR
(explorer.exe: an ordinary icon, for comparison with the dropper above.) offset is the resource's RVA, as the format stores it.
JSON: resources.types[] → id/name, typeName, names[] → id/name, languages[] → id, offsetToData (an RVA), size, codePage, entropy, md5, firstBytesHex, firstBytesAscii, typeDetected.
Hunting recipes¶
# Payload candidates: big, high-entropy, unrecognized blobs
ppee-cli --json --resources f.exe | jq -r '.resources.types[] as $t | $t.names[] as $n | $n.languages[]
| select(.entropy > 7.5 and .size > 10000 and (.typeDetected == null or .typeDetected == "PE File"))
| "\($t.typeName // $t.name // $t.id)/\($n.name // $n.id)\t\(.size)\t\(.entropy)\t\(.typeDetected)"'
# Embedded executables anywhere in a folder
for f in samples/*; do
ppee-cli --no-similarity --json --resources "$f" 2>/dev/null | jq -r --arg f "$f" \
'.resources.types[]? as $t | $t.names[] as $n | $n.languages[] | select(.typeDetected == "PE File")
| "\($f)\t\($t.typeName // $t.name // $t.id)/\($n.name // $n.id)\t\(.md5)"'
done
# MD5s of every resource, for IOC lookups of dropped components
ppee-cli --json --resources f.exe | jq -r '[.resources.types[].names[].languages[] | .md5] | unique[]'
# Delphi?
ppee-cli --json --resources f.exe | jq -e '[.resources.types[].names[] | .name // empty] | index("DVCLAL")' >/dev/null && echo Delphi
Related: --resources · Manifest · Strings · Hashes & entropy
References¶
- Microsoft PE format specification, resource data: the three-level resource tree.
- Resource types (Microsoft): the predefined resource type IDs such as RT_ICON and RT_RCDATA.
