Back to course

Base Relocations

15 minintermediateModule 6: Relocations & HardeningContinues from pe-delay-loads
15 min · Module 6: Relocations & Hardening · Structures

Why This Matters

The linker lays out cli-64.exe as if it will live at 0x140000000, and bakes that assumption into every absolute address — pointers in .data, jump tables, vtable slots. But another process may already have claimed 0x140000000, and Windows would rather load your image somewhere else. If the loader cannot move those baked-in addresses, it cannot randomize your image, and ASLR — the single most basic exploit mitigation on Windows — is off.

The base relocation table is the repair list. It records, for each absolute address the linker emitted, the page it sits on and how many bits to patch. The loader computes one number — the difference between where the image wanted to be and where it landed — and adds it to every recorded location. Without this table an image must load at its exact preferred base or not at all: the spec says the loader reports an error when the base address is unavailable and the file has no relocations.

A Simple Model

The table is a list of pages, and each page holds a list of tiny instructions:

To apply: delta = actual base - preferred base, then for each entry, target = page RVA + offset, and patch according to the type. If the image landed exactly at ImageBase, delta is zero and the loader may skip the work entirely — the spec notes the loader is not required to process relocations unless the image cannot load at its preferred base.

The model's missing piece: type 0 is not a patch at all. IMAGE_REL_BASED_ABSOLUTE means "skip" — padding to align the next block. A table full of type 0 is normal, not corruption.

The Actual Detail

Base relocation block header (8 bytes), from the spec's "Base Relocation Block":

OffsetSizeFieldWhat it says
04Page RVAImage base plus this page RVA, plus each entry's offset, gives the VA to patch
44Block SizeTotal bytes in the block, including the header and all type/offset words
82 eachType/Offset entriesHigh 4 bits = type, low 12 bits = offset within the page

The relocation types you will actually meet, from the spec's "Base Relocation Types" table:

ValueConstantWhat it patches
0IMAGE_REL_BASED_ABSOLUTESkipped — used to pad a block
1IMAGE_REL_BASED_HIGHAdds the high 16 bits of delta to a 16-bit field
2IMAGE_REL_BASED_LOWAdds the low 16 bits of delta to a 16-bit field
3IMAGE_REL_BASED_HIGHLOWAdds all 32 bits of delta to a 32-bit field — the PE32 workhorse
4IMAGE_REL_BASED_HIGHADJHigh 16 bits plus a following adjustment word (consumes two slots)
10IMAGE_REL_BASED_DIR64Applies delta to a 64-bit field — the PE32+ workhorse
Common mistake. Assuming every EXE ships relocations, or that DYNAMIC_BASE alone enables ASLR. The spec explicitly notes that the linker's default behavior is to strip base relocations from executable (EXE) files, and COFF Characteristics bit 0x0001 (IMAGE_FILE_RELOCS_STRIPPED) is the on-disk record of exactly that: no relocations, must load at the preferred base, loader reports an error otherwise. DllCharacteristics 0x0040 (DYNAMIC_BASE) promises "this image can be relocated at load time" — a promise it cannot keep without this table. Check both fields, and remember type 3 is a 32-bit patch (PE32) while type 10 is a 64-bit patch (PE32+); mixing them up silently corrupts half an address.

A Real Example

The entire .reloc section of cli-64.exe — data directory index 5 says RVA 0x8000, size 0x30, and the section header puts it at file offset 0x3600:

00003600: 0030 0000 3000 0000 50a2 58a2 60a2 68a2  .0..0...P.X.`.h.
00003610: 70a2 80a2 90a2 a8a2 b0a2 a8a3 b0a3 28a4  p.............(.
00003620: 40a4 48a4 d0a4 e8a4 f0a4 f8a4 00a5 08a5  @.H.............
  1. 0030 0000 = 0x00003000 — Page RVA, the .rdata page.
  2. 3000 0000 = 0x00000030 — Block Size 48 bytes. Header is 8, so (0x30 - 8) / 2 = 20 entries, and the section is exactly this one block.
  3. First entry 50 a2 = word 0xA250: high nibble 0xA = 10 = IMAGE_REL_BASED_DIR64, low 12 bits = 0x250. Patch target RVA = 0x3000 + 0x250 = 0x3250.
  4. Last entry 08 a5 = 0xA508 → DIR64 at offset 0x508, RVA 0x3508.

All twenty entries decode as type 10 — this is a 64-bit image patching 64-bit addresses. The COFF Characteristics of this file are 0x0022 (EXECUTABLE_IMAGE | LARGE_ADDRESS_AWARE): bit 0x0001 is clear, so relocations were kept, and DllCharacteristics 0x8160 includes DYNAMIC_BASE. The three facts line up: the binary claims it can move, and the repair list to make that true is present.

Try It

objdump decodes the blocks for you — types, offsets, and the resulting RVAs:

$ objdump -x cli-64.exe | sed -n '/PE File Base Relocations/,+10p'
PE File Base Relocations (interpreted .reloc section contents)

Virtual Address: 00003000 Chunk size 48 (0x30) Number of fixups 20
	reloc    0 offset  250 [3250] DIR64
	reloc    1 offset  258 [3258] DIR64
	reloc    2 offset  260 [3260] DIR64
	reloc    3 offset  268 [3268] DIR64
	reloc    4 offset  270 [3270] DIR64
	reloc    5 offset  280 [3280] DIR64
	reloc    6 offset  290 [3290] DIR64
	reloc    7 offset  2a8 [32a8] DIR64
	reloc    8 offset  2b0 [32b0] DIR64

Or decode the block yourself:

$ python3 -c "
import struct
d=open('cli-64.exe','rb').read()
b=d[0x3600:0x3630]
va,sz=struct.unpack('<II',b[:8])
n=(sz-8)//2
ents=struct.unpack('<'+'H'*n, b[8:])
print(hex(va), hex(sz), 'entries', n, 'first', 'type', ents[0]>>12, 'off', hex(ents[0]&0xfff))"
0x3000 0x30 entries 20 first type 10 off 0x250

What to look for: does every entry's high nibble match the image's bitness, does (SizeOfBlock - 8) divide evenly by 2, and does the sum of all blocks equal the directory size? A mismatch there means you are looking at corrupted or stripped relocation data.

Check Your Understanding

Without looking back: decode the relocation word 0xA250.

Apply It

A relocation block reports Page RVA = 0x9000 and SizeOfBlock = 0x18. How many type/offset entries does it contain, and what is the last entry's page offset range?

The pattern: SizeOfBlock always includes its own 8-byte header. Subtract first, then divide — that one subtraction is the difference between a correct parser and one that reads two garbage entries past every block.

Connect

Now that the image can move, the hardening questions begin: did the binary opt into ASLR and DEP? Which subsystem runs it? Is it signed? Those answers live in three small header fields — COFF Characteristics, Subsystem, and DllCharacteristics — plus the certificate table at the end of the file.

Next: Security & Subsystem Flags — every bit that decides how (and whether) Windows will run you.