When working with binary exploitation and reverse engineering, particularly within the Python Exploitation Framework (pwntools), understanding how to retrieve the runtime address of a function is critical for developing reliable exploits. The ability to determine the exact location of a function in memory allows an attacker to bypass Address Space Layout Randomization (ASLR) and construct precise Return-Oriented Programming (ROP) chains. This process is fundamental to advanced exploit development and is a core concept for any serious practitioner of ethical hacking.
Understanding Memory Layouts and Symbol Resolution
The journey to finding a function's address begins with understanding how binaries are structured. Modern executables contain sections like `.text` for code and `.data` for initialized variables, but the runtime memory layout is dynamic. When a program loads, the operating system maps these sections into virtual memory. Functions like `main` or library calls such as `system` are assigned virtual addresses. Pwntools interfaces with these structures, providing methods to parse Executable and Linkable Format (ELF) files to locate the static offset of a symbol, which is the address before ASLR is applied.
Static vs. Dynamic Addressing
It is essential to distinguish between static and dynamic addresses. A static address is the hardcoded offset found in the binary file itself, which remains constant. A dynamic address is the actual location in RAM when the program is running, which changes due to ASLR. To get the function address that the CPU uses, you must often calculate the dynamic address by adding the static offset to the base address of the module. Pwntools streamlines this by handling the base address adjustments automatically once the process is attached or the leak is obtained.

Using the `elf.symbols` Dictionary
The most straightforward method provided by pwntools is accessing the `.symbols` dictionary attached to an ELF object. When you load a binary using `ELF('vulnerable_binary')`, pwntools parses the symbol table and creates a dictionary mapping function and variable names to their static addresses. Accessing `elf.symbols['function_name']` returns the offset. This is the go-to approach for functions defined in the binary or imported from shared libraries where the offset is known statically.
Practical Code Example
To illustrate, assume you have an ELF file object named `elf`. To get the address of a function called `win`, you would simply use the following syntax. This command queries the internal symbol table and returns the relative virtual address (RVA) of that function.
| Method | Description |
|---|---|
elf.symbols['win'] | Retrieves the static address of the 'win' function from the ELF symbol table. |
elf.sym['win'] | A shorthand alias for the full symbols dictionary entry. |
Handling Position-Independent Executables (PIE)
Modern Linux distributions enforce PIE, which randomizes the base address of the main executable, similar to ASLR for libraries. This adds a layer of complexity because the static offset is no longer the final answer; you must determine the runtime base address. If a leak is available—such as from a format string vulnerability or an info leak exploit—you can calculate the base by subtracting the known offset from the leaked address. Pwntools allows you to assign this base address to the ELF object, at which point `elf.symbols` will automatically reflect the correct runtime function address.

Retrieving Library Function Addresses
Exploits often rely on functions provided by shared libraries like `libc`. Obtaining the address of a libc function like `system` requires identifying the correct libc version and resolving its base address. If you have a memory leak that points to a known libc function (e.g., `puts`), you can calculate the base of libc by subtracting the known offset of that function. Once the base is established, you can calculate the address of `system` by adding the offset of `system` within that specific libc version. Pwntools handles the version detection and offset calculations seamlessly through its `Dynelf` system or by leveraging pre-built libc databases.
Utilizing `libc.symbols` for Automation
Similar to the `elf.symbols` dictionary, pwntools provides the `libc.symbols` dictionary for resolved libraries. After loading a libc file (e.g., `libc = ELF('libc.so.6')`) and determining its base address, you can retrieve the address of any function by adding the base to the libc offset. The recommended approach is to use the `.symbols` attribute directly. If the base is set correctly on the libc object, `libc.symbols['system']` will return the fully resolved runtime address, eliminating manual arithmetic and reducing the potential for calculation errors.
The `pwnlib.util.functions` Module
For highly dynamic scenarios where functions are resolved at runtime without symbols, pwntools includes lower-level utilities. The `pwnlib.util.functions` module is designed for just-in-time resolution. While less common than using the symbol dictionaries, this module is powerful for custom mods or when dealing with stripped binaries where standard symbol lookup fails. It allows you to scan memory regions and identify function prologues, providing an address based on pattern matching rather than symbol tables, ensuring you can find the function even in the most obfuscated binaries.























![How to Switch to Personal Account on Instagram [2024 Updated]](https://i.pinimg.com/originals/92/46/dc/9246dc796ac2fc340febba3fff7889d4.webp)