Back to course

Common Sections

15 minintermediateModule 4: SectionsContinues from pe-section-table
15 min · Module 4: Sections · Structures

Why This Matters

Open almost any Windows binary and you meet the same cast: .text, .rdata, .data, .pdata, .rsrc, .reloc. Knowing what belongs where turns PE reading from byte-hunting into navigation — import names live in read-only data, mutable globals are the only writable section, exception unwind info sits in its own table the dispatcher can find by directory index rather than by name.

But the names are conventions chosen by the linker, not rules enforced by the format. The loader never reads them; it reads the Characteristics flags. Learn both: names for orientation, flags for truth.

A Simple Model

Sort sections by what they are allowed to do, not by what they are called:

The model's missing piece: the directory index, not the name, is the contract. Sample A's import table lives in .rdata, and there is no .idata section in either sample — the Import directory (RVA 0x3A04) points wherever the linker put it.

The Actual Detail

The spec's flag bits that matter for images, and what each sample sets:

MaskFlagMeaning
0x00000020IMAGE_SCN_CNT_CODESection contains executable code
0x00000040IMAGE_SCN_CNT_INITIALIZED_DATASection contains initialized data
0x02000000IMAGE_SCN_MEM_DISCARDABLEMay be discarded once the loader is done with it
0x20000000IMAGE_SCN_MEM_EXECUTEPage may be executed
0x40000000IMAGE_SCN_MEM_READPage may be read
0x80000000IMAGE_SCN_MEM_WRITEPage may be written

Both samples, decoded from their real Characteristics fields:

SectionPurposeA / B CharacteristicsDecode
.textMachine code; entry point lives here0x60000020CODE | EXECUTE | READ
.rdataConstants, import names, IAT, load config, debug pointers0x40000040INITIALIZED_DATA | READ
.dataMutable globals; zero-fill tail beyond SizeOfRawData0xC0000040INITIALIZED_DATA | READ | WRITE
.pdatax64 RUNTIME_FUNCTION entries for the unwinder (directory 3)0x40000040INITIALIZED_DATA | READ
.rsrcResource tree (directory 2): icons, version info, manifests0x40000040INITIALIZED_DATA | READ
.relocBase relocations (directory 5); loader may discard after fixups0x42000040MEM_DISCARDABLE | INITIALIZED_DATA | READ

Sample A has .pdata because it is x64 (exception directory 0x6000/0x1EC points straight at it); Sample B is 32-bit and has no .pdata at all — its exception directory is zero. Sample B also drops .pdata while keeping the same flags on the five sections both files share.

Linkers do add their own sections beyond the classic six. Verified on binaries in this very environment: OpenConsole.exe carries .didat and a populated Delay Import directory (RVA 0xE8CB8, size 0x40); dbghelp.dll carries .didat and .mrdata; rg.exe carries .fptable; libasyncProfiler.dll carries .idata, .tls and .00cfg. Every one of those was linked by MSVC 14.x — treat unfamiliar names as data-plus-flags to decode, never as assumptions about content.

Common mistakes. Expecting an .idata section — modern MSVC folds import descriptors, names and the IAT into .rdata (directory 12, the IAT, starts at RVA 0x3000 = the first byte of .rdata in Sample A). Dispatching on section names in code — the spec only promises the order the linker chose, and says tables need not begin at section starts. And assuming .reloc must stay resident — its MEM_DISCARDABLE bit tells the loader it may unmap those pages after rebasing.

A Real Example

Decode .reloc's 0x42000040 by peeling bits off, highest first:

0x42000040
  0x80000000 MEM_WRITE          not set  -- loader will map it read-only
  0x40000000 MEM_READ           set
  0x20000000 MEM_EXECUTE        not set  -- data, not code
  0x02000000 MEM_DISCARDABLE    set      -- safe to drop after relocations
  0x00000040 CNT_INITIALIZED_DATA set    -- initialized data class
  0x00000020 CNT_CODE           not set
= read-only, discardable, initialized data

Then .data's 0xC0000040 — the only writable row in either sample:

0xC0000040 = 0x80000000 (MEM_WRITE) | 0x40000000 (MEM_READ) | 0x00000040 (CNT_INITIALIZED_DATA)

The consequences are visible without a debugger: with NX_COMPAT set in both samples (DllCharacteristics 0x8160 for A, 0x8140 for B), .text gets execute-plus-read and no write, .data gets read-plus-write and no execute, and a program that tries to store a variable into .text raises an access violation — the section table said so first.

Sample A's section table tail, where the flags live (last 8 bytes of each row are two zero dwords plus Characteristics):

00000228: 0000 0000 2000 0060 2e72 6461 7461 0000  .... ..`.rdata..
00000278: 0000 0000 4000 00c0 2e70 6461 7461 0000  ....@....pdata..
000002e8: 0000 0000 0000 0000 0000 0000 4000 0042  ............@..B

Read them backwards, little-endian: 20 00 00 60 = 0x60000020, 40 00 00 c0 = 0xC0000040, 40 00 00 42 = 0x42000040.

Try It

List the sections, then decode the flags yourself:

$ objdump -h cli-64.exe
Idx Name          Size      VMA               File off
  0 .text         000017bc  0000000140001000  00000400
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .rdata        0000132c  0000000140003000  00001c00
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
...
  5 .reloc        00000030  0000000140008000  00003600
                  CONTENTS, LOAD, READONLY, DATA

A bit-decoder for any Characteristics value:

$ python3 -c "ch=0x42000040; flags=[(0x80000000,'WRITE'),(0x40000000,'READ'),(0x20000000,'EXECUTE'),(0x02000000,'DISCARDABLE'),(0x00000040,'INIT_DATA'),(0x00000020,'CODE')]; print([n for m,n in flags if ch & m])"
['READ', 'DISCARDABLE', 'INIT_DATA']

Run it on 0x60000020, 0x40000040 and 0xC0000040 — you should get CODE/EXECUTE/READ, READ/INIT_DATA, and WRITE/READ/INIT_DATA respectively. Then try objdump -h cli-32.exe and notice .pdata is missing on the 32-bit build.

Check Your Understanding

Without looking back: decode 0x42000040 into named flags, and say which section of Sample A carries it.

Apply It

A program needs a global counter that several functions increment. Which section of Sample A will the linker place it in, and how do you know from the flags alone — without reading the name?

The pattern: name for humans, flags for the loader, directory index for finding structures. When they disagree, the flags and directories win.

Connect

You have seen the two sizes per section (0x17BC vs 0x1800) and the two rulers that produce them — content length versus padded disk length. The next lesson makes both rulers explicit: FileAlignment for the disk, SectionAlignment for memory, and the SizeOfImage / SizeOfHeaders arithmetic that ties them together.

Next: Alignment: FileAlignment vs SectionAlignment — why the file is 14336 bytes but the image claims 0x9000, and how to prove a file has no overlay.