Executable Formats, Loaders and Dynamic Linking — DedSec Project
Executable Formats, Loaders and Dynamic Linking Purpose: Understand how operating systems transform executable files into running processes and how imports, relocations, shared libraries…
Purpose: Understand how operating systems transform executable files into running processes and how imports, relocations, shared libraries, and loader policy affect security analysis.
Learning objectives
- Explain the difference between file format, loader, linker, and runtime dynamic linker.
- Read the high-level structure of ELF and PE files.
- Understand sections versus segments, imports/exports, relocations, symbols, and position-independent code.
- Recognize security implications of library search paths, writable code locations, and loader configuration.
- Inspect binaries you compiled yourself using read-only tools.
From source to process
Source code is compiled or interpreted into forms the runtime can execute. For native code, the path commonly includes compilation into object files, linking into an executable or shared library, storage in a platform file format, and loading/mapping by the operating system. Dynamic dependencies may be resolved at process start or lazily later.
Each stage introduces metadata that is valuable to an analyst: symbols, relocation records, import tables, section permissions, debug information, build IDs, dependencies, and hardening flags.
ELF mental model
ELF is common on Linux, Android native code, and many Unix-like systems. It contains headers describing the file and program loading. Sections organize link-time information such as code, data, symbols, and relocations. Program headers/segments tell the loader what to map into memory and with what permissions.
A frequent beginner error is treating sections and segments as identical. A stripped production binary can still be loaded even when much link-time naming information is absent because the loader primarily follows program headers.
PE/COFF mental model
Windows executables and DLLs use the PE/COFF family. Relevant structures include DOS/PE headers, section table, import/export data, relocation information, resources, debug directories, and optional-header fields describing image layout and security characteristics.
For security analysis, imports often provide behavioral clues: networking, cryptography, process creation, registry access, or file operations. Imports are evidence, not proof—a program can resolve functions dynamically or contain unused imports.
Static versus dynamic linking
Static linking copies required library code into the final binary. Dynamic linking leaves dependencies to shared libraries/DLLs that are resolved at runtime. Dynamic linking reduces duplication and supports centralized library updates but creates dependency-resolution and search-path considerations.
Security questions include:
- where does the loader search?
- can an unprivileged user influence that search path?
- are dependencies signed or otherwise trusted?
- can an application directory be written by a lower-privilege principal?
- does the binary use an absolute, relative, or platform-defined lookup?
Study these questions defensively on software you own; do not attempt library hijacking on third-party systems.
Imports, exports, and symbols
An import is an external symbol a module expects another component to provide. An export is a symbol made available to others. Symbol tables may include function and variable names; stripped binaries remove much human-friendly naming but must retain whatever runtime resolution still requires.
C++ and other languages may mangle names to encode type information. Demangling can recover readable signatures. Debug symbols can provide rich type and source mappings when available.
Relocations and ASLR
Code and data often contain references that depend on where the image is loaded. Relocations describe places that need adjustment. Position-independent code minimizes fixed-address assumptions and supports address randomization.
On ELF systems, concepts such as the GOT and PLT participate in dynamic symbol resolution. On Windows, import-address tables play a related role. Learn the architecture as a data-flow mechanism rather than memorizing offensive techniques around it.
W^X and memory permissions
A strong design goal is that memory should not be writable and executable at the same time unless absolutely required. Loaders map code and data with different permissions. Inspecting segment/section flags reveals whether an image follows expected separation.
The loader may also enforce stack execute policy, relocation protections, control-flow features, signatures, or platform entitlements depending on the OS.
Initialization and entry points
Execution does not always begin directly at main. Runtime startup code initializes the process, dynamic loader, thread-local storage, constructors, language runtime, and environment before application logic runs. Reverse engineers therefore distinguish the image entry point from the high-level program entry function.
Likewise, shared libraries can have initialization routines. Security review should consider initialization order because privileged state or environment may exist before normal application checks.
File hashing and provenance
When analyzing a binary, record a cryptographic hash, file size, architecture, timestamp metadata, source/provenance if known, and tool versions. Hashes identify exact bytes; they do not prove trustworthiness. Signing and reproducible builds provide additional provenance signals.
Safe ELF lab
Compile the benign program from Module 63. Then inspect it:
file ./demo
readelf -h ./demo
readelf -l ./demo
readelf -S ./demo
readelf -d ./demo
If available:
objdump -p ./demo
nm ./demo | head
Write down architecture, entry point, loadable segments, dependencies, and which regions are executable versus writable.
Safe Windows/PE lab
On a Windows VM, compile a hello-world application you own. Use platform tools such as dumpbin /headers when available, or a trusted PE-inspection utility in read-only mode. Identify sections, imports, architecture, and security-related characteristics. Do not modify system binaries.
Loader security review
For an application you maintain, review:
- executable and library directory ACLs/permissions;
- whether untrusted directories are in search paths;
- whether runtime environment variables can alter dependency lookup;
- signature/notarization expectations;
- dependency pinning and update provenance;
- whether debug symbols or sensitive paths leak unnecessarily;
- whether executable pages are also writable;
- ASLR/PIE support and control-flow hardening.
Android native libraries
Android packages can contain native .so libraries for one or more ABIs. The application sandbox still applies, while native code introduces memory-safety and JNI boundary concerns. APK signing establishes package/update identity, while ELF hardening affects native components separately.
Guided study workflow
Before you begin
Complete Modules 53, 62, and 63.
Practice task
Compile one tiny program and one shared library you own. Inspect headers, dependencies, symbols, and memory permission layout. Draw the path from file bytes to mapped process segments.
Evidence to keep
Keep hashes, tool output excerpts, a loader diagram, and a brief hardening inventory.
Common mistakes to avoid
- confusing sections with loadable segments;
- assuming stripped means encrypted or obfuscated;
- treating imports as definite behavior;
- ignoring runtime initialization before
main; - modifying system binaries for a learning task.
Mastery check
Explain how a dynamic dependency becomes mapped code, how ASLR changes load addresses, and where loader trust decisions can fail.
Continue with
Modules 65, 66, 67, 73, 79, and 82.