Lecture2 - ELF File
Understanding ELF Format
The focus for today's lecture is on the internal structure of binaries used in reverse engineering. Understanding this structure is critical for effectively analyzing, debugging, and reverse-engineering software.
Binaries, unlike human-readable source code or text files, contain complex data, including machine instructions, initialized/uninitialized data, and various metadata, which presents as non-printable bytes.
ELF (Executable and Linkable Format):
This a standard file format widely used on Unix-like systems, including Linux, for describing application binaries, shared libraries, object files, and core dumps.
Provides a structured, standardized representation, including headers, sections, and segments, that compilers, assemblers, and linkers use to create and manage binary files.
ELF Characteristics
Comparison with Source Code:
Source codes are typically simple streams of bytes consisting of printable characters; binaries are significantly more complex, containing non-printable bytes.
Binaries serve as direct, machine-encoded instructions for the CPU to execute code, whereas source code is primarily written for human developers.
Components of Binaries:
Code: Contains the executable machine instructions (e.g., found in the section).
Data: Includes initialized global and static variables (e.g., in the section) and uninitialized global and static variables (e.g., in the section).
Metadata: Encompasses various information about the file's structure, dependencies, symbols, and debugging information (e.g., ELF header, program headers, section headers, symbol table, relocation table).
Classes of Binaries in ELF
ELFs can describe multiple types of files:
Executable Programs: These are runnable applications containing an entry point (such as
_startormain) and all necessary code and data to run independently, directly invocable by the operating system.Shared Libraries: These are dynamically linked libraries (e.g., files on Linux) that are loaded into memory at runtime and can be shared among multiple programs, thereby reducing memory footprint and enabling modular updates.
Static Libraries: These are archives of object files (e.g., files on Linux) that are statically linked into an executable during compilation. This means their code is copied directly into the final binary.
Object Files: Also known as relocatable object files (e.g., files on Linux), these are the intermediate output of compilation for each source file. They contain machine code and data but are not yet fully linked into an executable or shared library.
Core Dumps: A snapshot of the memory image of a running process at the time of a crash, invaluable for post-mortem debugging to diagnose software failures.
Executable vs Library
Executable: A program that can be run as-is; it contains a specific entry point (e.g.,
_startor themainfunction) where execution begins, and can be directly invoked by the operating system.Library: A collection of reusable functions and resources; it must be linked with an executable or another library to be utilized.
Static libraries integrate into the executable at compile time (their code merges directly into the final binary), resulting in a larger executable but eliminating runtime dependencies on external library files.
Dynamic libraries link during the loading phase of the program (either at program startup by the dynamic linker or dynamically at runtime using functions like
dlopen). This approach reduces the executable's size and allows for library updates without recompiling the executable.
Object Files
Defined as the intermediate output of compilation for each individual source file. An object file is the result of compiling a single source file before linking it with other object files and libraries.
Every C/C++ file typically generates its corresponding object file (e.g., ).
Object files contain code and data only for that specific translation unit, and often include symbol tables and relocation information that the linker uses to resolve references between different parts of the program.
Example: Hello World Program
This example illustrates the detailed flow from source code to object files and eventually to the final executable, demonstrating the compilation and linking process in a straightforward manner.
Features two functions in different source files, showing how modular programming works and how separate functions are compiled into their own object files before being linked together into a single program.
The main function executes a call to a function named "do it" from another source file.
The "do it" function may access global variables and inner workings, highlighting the necessity for the linker to resolve references between different compilation units to create a cohesive executable.