Skip to content

Lesson 2.4 · Binary Formats· 35 min

Resources, Overlays and Other Hiding Places

Where PE files carry data outside their code: the resource tree, version info, overlays, Authenticode signatures, and the .NET and TLS directories.

Objectives

  • Navigate the three-level resource tree and recognise the resource types that matter in triage
  • Use version information as metadata and IOC while knowing how easily it is forged
  • Compute where a PE's overlay starts and explain why droppers and installers use it
  • Interpret an Authenticode signature correctly, including signed-but-malicious files
  • Spot the .NET and TLS data directories and know what each one means for your next step

So far the PE format has looked tidy: headers describe sections, sections hold code and data, and the import table says which APIs the program needs. But a PE file has plenty of room for bytes that are neither code nor ordinary data. Icons, version strings, manifests, certificates, whole archives glued to the end of the file. Legitimate software uses every one of these, and so does malware, because a second executable hidden in an "icon" or appended after the last section is invisible to anyone who only looks at .text.

This lesson covers those places. They matter mostly during triage: finding an embedded payload early can save hours of reversing the code that unpacks it.

The data directories, revisited

In PE Headers you met the data directory array at the end of the optional header. Each entry is an (address, size) pair pointing at a structure the loader or other Windows components consume. Four of them are the subject of this lesson:

IndexDirectoryAddress is…Why an analyst cares
2ResourceRVAIcons, dialogs, version info — and embedded payloads
4Certificate (Security)File offsetAuthenticode signature
9TLSRVACallbacks that run before the entry point
14CLR Runtime HeaderRVAMarks a .NET assembly — switch tools

Note the odd one out: the certificate directory's "address" is a raw file offset, not an RVA. The signature is never mapped into memory, so an RVA would be meaningless. Tools that blindly convert it through the section table produce garbage.

The resource tree

The resource directory (usually in a section named .rsrc) is a small tree with three fixed levels:

text
  Resource directory (root)
  ├── Type        e.g. RT_ICON (3), RT_RCDATA (10), RT_VERSION (16), or a name "CONFIG"
  │   └── Name    numeric ID (#1) or string name ("PAYLOAD")
  │       └── Language   e.g. 0x0409 (en-US), 0x0000 (neutral)
  │           └── Data entry → RVA + size of the raw bytes

Each level is an IMAGE_RESOURCE_DIRECTORY followed by entries that are either an integer ID or an offset to a Unicode name. The leaves are IMAGE_RESOURCE_DATA_ENTRY records holding the RVA and size of the actual data.

The types you will meet most often in triage:

TypeIDNormal useSeen in malware as…
RT_ICON / RT_GROUP_ICON3 / 14Application iconStolen icons (PDF, Word) to trick users
RT_RCDATA10Arbitrary binary dataEncrypted config, second-stage payload
RT_VERSION16File/product version infoImpersonation of a legitimate vendor
RT_MANIFEST24Side-by-side and UAC settingsrequireAdministrator requests
RT_STRING6Localised stringsRarely interesting
Custom named types—App-specificAnything; names like BIN, DATA, X

Programs read their own resources at runtime with FindResource, LoadResource, LockResource and SizeofResource. Seeing that sequence in the imports of a small binary with one large RT_RCDATA blob is a strong hint that the blob is the real payload — typically decrypted or decompressed in memory before use.

Tip: Check the entropy and the first bytes of every large resource. A resource beginning with MZ is an embedded executable; one with entropy near 8.0 is compressed or encrypted. Detect It Easy and PE-bear both show per-resource details.

Version information: useful, but trivially forged

The RT_VERSION resource holds a VS_VERSIONINFO structure: fixed numeric versions plus a StringFileInfo table with keys such as CompanyName, FileDescription, InternalName, OriginalFilename and ProductName.

For an analyst it is worth reading for two opposite reasons:

  • As an IOC and clustering hint. Builders often reuse the same odd strings across a campaign — a misspelled company name or a stable InternalName makes a good YARA condition.
  • As a red flag. A file claiming CompanyName: Microsoft Corporation that is unsigned, compiled with MinGW and sitting in %APPDATA% is lying. OriginalFilename that differs from the name on disk is also worth noting — it often reveals the name the author used.

Never treat version info as evidence of origin. Anyone can write any string into it with a resource compiler or an editor like Resource Hacker.

Overlays: data after the last section

The loader maps only what the section table describes. Anything appended after the end of the last section's raw data is ignored at load time but is still part of the file on disk. That tail is called the overlay.

Computing where it starts is simple:

text
overlay_start = max(section.PointerToRawData + section.SizeOfRawData)
overlay_size  = file_size - overlay_start        (if positive)

Legitimate users of overlays are common: installers (NSIS, Inno Setup) append their compressed archives, self-extracting archives do the same, and signed files carry the Authenticode blob there. Malicious uses mirror them: droppers append an encrypted payload, builders append a configuration block, and some samples pad themselves with hundreds of megabytes of junk to exceed the size limits of sandboxes and scanners.

A program finds its own overlay by opening its own file (GetModuleFileName + CreateFile/ReadFile) and seeking past the sections. That self-read pattern in a small binary is another dropper hint.

Warning: Two things inflate the apparent overlay. The certificate table of a signed file lives after the sections, so a naive calculation reports it as overlay data. And some toolchains (MinGW without -s) leave a COFF symbol table after the last section. Always look at what the overlay contains before concluding anything.

Authenticode: what a signature does and does not prove

The certificate directory points at one or more WIN_CERTIFICATE structures containing a PKCS#7 SignedData blob. The signature covers a hash of the file computed while skipping the checksum field, the certificate directory entry and the certificate table itself.

What a valid signature tells you:

  • The file has not been modified since it was signed (within the hashed ranges).
  • The signer held the private key for that certificate at signing time.

What it does not tell you: that the file is benign. Signed malware is a recurring reality — certificates are stolen from real companies, bought through shell companies, or issued to fraudulent developers. Treat a signature as attribution evidence to check (who is the signer? is the certificate revoked? does the signer make sense for this file?), not as a verdict.

On Windows, sigcheck -a from Sysinternals or the file's Digital Signatures tab shows the chain; on any platform, osslsigncode verify or a pefile script can extract the blob for inspection.

Two more directories that change your plan

TLS directory

The TLS directory (index 9) describes thread-local storage — and, crucially, an array of TLS callbacks the loader calls before the entry point runs. A debugger that stops at the entry point has already missed them, which is why malware uses them for anti-debugging and early unpacking. If this directory is present in a small binary, list its callbacks before you do anything else. The TLS callbacks technique page covers the details, and How a Binary Is Loaded and Run shows where they fit in the loader's sequence.

CLR Runtime Header (.NET)

If data directory 14 is non-empty, the file is a .NET assembly. Its "real" code is CIL bytecode plus metadata, not x86. The native entry point is a tiny stub that jumps into mscoree.dll. Stop reading disassembly: switch to a .NET decompiler such as dnSpyEx or ILSpy, which recover near-source C#. Detect It Easy flags this immediately.

Lab: build, hide and find

This lab builds a harmless Windows program that embeds a small text "config" as a resource and carries appended data as an overlay — the same structures a dropper uses, with nothing malicious inside. You need mingw-w64 (brew install mingw-w64 or apt install mingw-w64) and Python with pefile.

  1. Create config.txt:

    text
    server=update.example.com
    interval=3600
    mode=demo
  2. Create res.rc, which embeds the file as RT_RCDATA and adds version info:

    c
    #include <windows.h>
    
    CONFIG RCDATA "config.txt"
    
    1 VERSIONINFO
    FILEVERSION     1,2,3,4
    PRODUCTVERSION  1,2,3,4
    FILEOS          VOS_NT_WINDOWS32
    FILETYPE        VFT_APP
    BEGIN
      BLOCK "StringFileInfo"
      BEGIN
        BLOCK "040904B0"
        BEGIN
          VALUE "CompanyName",      "Contoso Lab Tools"
          VALUE "FileDescription",  "Resource demo"
          VALUE "OriginalFilename", "resdemo.exe"
          VALUE "ProductName",      "Lab Resource Demo"
        END
      END
      BLOCK "VarFileInfo"
      BEGIN
        VALUE "Translation", 0x0409, 1200
      END
    END
  3. Create resdemo.c, which reads its own resource at runtime:

    c
    #include <windows.h>
    #include <stdio.h>
    
    int main(void) {
        HRSRC h = FindResourceA(NULL, "CONFIG", MAKEINTRESOURCEA(10)); /* RT_RCDATA */
        if (!h) { puts("resource not found"); return 1; }
        DWORD size = SizeofResource(NULL, h);
        const char *data = LockResource(LoadResource(NULL, h));
        printf("embedded config (%lu bytes):\n%.*s", size, (int)size, data);
        return 0;
    }
  4. Build it. -s strips the COFF symbol table so it does not masquerade as an overlay:

    bash
    x86_64-w64-mingw32-windres res.rc -O coff -o res.o
    x86_64-w64-mingw32-gcc -s -o resdemo.exe resdemo.c res.o
  5. Create an overlay by appending bytes:

    bash
    cp resdemo.exe resdemo-overlay.exe
    printf 'OVERLAY-MARKER:this-data-is-never-mapped' >> resdemo-overlay.exe
  6. Find everything with a short pefile script, carve.py:

    python
    import sys
    import pefile
    
    pe = pefile.PE(sys.argv[1])
    
    # Resource tree: type -> name -> language
    for rtype in pe.DIRECTORY_ENTRY_RESOURCE.entries:
        tname = pefile.RESOURCE_TYPE.get(rtype.id, str(rtype.name))
        for rname in rtype.directory.entries:
            name = str(rname.name) if rname.name else f"#{rname.id}"
            for rlang in rname.directory.entries:
                rva, size = rlang.data.struct.OffsetToData, rlang.data.struct.Size
                print(f"{tname:<12} {name:<8} lang=0x{rlang.id:04x} rva=0x{rva:x} size={size}")
                if tname == "RT_RCDATA":
                    open(f"rsrc_{name}.bin", "wb").write(pe.get_data(rva, size))
    
    # Version strings
    for fileinfo in getattr(pe, "FileInfo", []):
        for entry in fileinfo:
            if entry.Key == b"StringFileInfo":
                for table in entry.StringTable:
                    for k, v in table.entries.items():
                        print(f"  {k.decode():<18} {v.decode()}")
    
    # Overlay
    end = max(s.PointerToRawData + s.SizeOfRawData for s in pe.sections)
    print(f"file size {len(pe.__data__)}, sections end at {end}")
    if pe.get_overlay_data_start_offset() is not None:
        open("overlay.bin", "wb").write(pe.get_overlay())
        print("overlay written to overlay.bin")
    bash
    python3 carve.py resdemo-overlay.exe
    cat rsrc_CONFIG.bin overlay.bin
  7. If you have a Windows VM, open resdemo-overlay.exe in Resource Hacker, PE-bear and Detect It Easy and find the same three artefacts in each GUI. Run it and confirm it prints the embedded config — the overlay has no effect on execution.

Questions to answer: Does strings on the executable find the config text? Would it if the resource were XOR-encoded? Which imports in resdemo.exe would tip you off that it reads its own resources? Run carve.py on the build without -s — what does it report as overlay, and why is that misleading?

Key takeaways

  • The resource tree has three levels (type, name, language); large RT_RCDATA or custom-named resources are prime candidates for embedded payloads.
  • Version info is useful for clustering and spotting impersonation, but it is attacker-controlled text, never proof of origin.
  • The overlay starts at the end of the last section's raw data; installers, droppers and signatures all live there, so inspect its contents before judging.
  • A valid Authenticode signature proves integrity and key possession, not innocence.
  • A TLS directory means code runs before the entry point; a CLR header means you should switch to a .NET decompiler.