pwnlib.elf.elf — ELF Files

Exposes functionality for manipulating ELF files

Stop hard-coding things! Look them up at runtime with pwnlib.elf.

Example Usage

>>> e = ELF('/bin/cat')
>>> print(hex(e.address)) 
0x400000
>>> print(hex(e.symbols['write'])) 
0x401680
>>> print(hex(e.got['write'])) 
0x60b070
>>> print(hex(e.plt['write'])) 
0x401680

You can even patch and save the files.

>>> e = ELF('/bin/cat')
>>> e.read(e.address+1, 3)
b'ELF'
>>> e.asm(e.address, 'ret')
>>> e.save('/tmp/quiet-cat')
>>> disasm(open('/tmp/quiet-cat','rb').read(1))
'   0:   c3                      ret'

Module Members

class pwnlib.elf.elf.ELF(path, checksec=True)[source]

Bases: ELFFile

Encapsulates information about an ELF file.

Example

>>> bash = ELF(which('bash'))
>>> hex(bash.symbols['read'])
0x41dac0
>>> hex(bash.plt['read'])
0x41dac0
>>> u32(bash.read(bash.got['read'], 4))
0x41dac6
>>> print(bash.disasm(bash.plt.read, 16))
0:   ff 25 1a 18 2d 00       jmp    QWORD PTR [rip+0x2d181a]        # 0x2d1820
6:   68 59 00 00 00          push   0x59
b:   e9 50 fa ff ff          jmp    0xfffffffffffffa60
__getitem__(name)[source]

Implement dict-like access to header entries

__init__(path, checksec=True)[source]
__repr__()[source]

Return repr(self).

static _decompress_dwarf_section(section)[source]

Returns the uncompressed contents of the provided DWARF section.

_get_section_header(n)[source]

Find the header of section #n, parse it and return the struct

_get_section_header_stringtable()[source]

Get the string table section corresponding to the section header table.

_get_section_name(section_header)[source]

Given a section header, find this section’s name in the file’s string table

_get_segment_header(n)[source]

Find the header of segment #n, parse it and return the struct

_identify_file()[source]

Verify the ELF file and identify its class and endianness.

_make_gnu_verdef_section(section_header, name)[source]

Create a GNUVerDefSection

_make_gnu_verneed_section(section_header, name)[source]

Create a GNUVerNeedSection

_make_gnu_versym_section(section_header, name)[source]

Create a GNUVerSymSection

_make_section(section_header)[source]

Create a section object of the appropriate type

_make_segment(segment_header)[source]

Create a Segment object of the appropriate type

_make_sunwsyminfo_table_section(section_header, name)[source]

Create a SUNWSyminfoTableSection

_make_symbol_table_index_section(section_header, name)[source]

Create a SymbolTableIndexSection object

_make_symbol_table_section(section_header, name)[source]

Create a SymbolTableSection

_parse_elf_header()[source]

Parses the ELF file header and assigns the result to attributes of this object.

_patch_elf_and_read_maps()[source]

patch_elf_and_read_maps(self) -> dict

Read /proc/self/maps as if the ELF were executing.

This is done by replacing the code at the entry point with shellcode which dumps /proc/self/maps and exits, and actually executing the binary.

Returns:

A dict mapping file paths to the lowest address they appear at. Does not do any translation for e.g. QEMU emulation, the raw results are returned.

If there is not enough space to inject the shellcode in the segment which contains the entry point, returns {}.

Doctests:

These tests are just to ensure that our shellcode is correct.

>>> for arch in CAT_PROC_MAPS_EXIT:
...   context.clear()
...   with context.local(arch=arch):
...     sc = shellcraft.cat2("/proc/self/maps")
...     sc += shellcraft.exit()
...     sc = asm(sc)
...     sc = enhex(sc)
...     assert sc == CAT_PROC_MAPS_EXIT[arch], (arch, sc)
_populate_functions()[source]

Builds a dict of ‘functions’ (i.e. symbols of type ‘STT_FUNC’) by function name that map to a tuple consisting of the func address and size in bytes.

_populate_got()[source]

Loads the symbols for all relocations.

>>> libc = ELF(which('bash')).libc
>>> assert 'strchrnul' in libc.got
>>> assert 'memcpy' in libc.got
>>> assert libc.got.strchrnul != libc.got.memcpy
_populate_libraries()[source]
>>> from os.path import exists
>>> bash = ELF(which('bash'))
>>> all(map(exists, bash.libs.keys()))
True
>>> any(map(lambda x: 'libc' in x, bash.libs.keys()))
True
_populate_plt()[source]

Loads the PLT symbols

>>> path = pwnlib.data.elf.path
>>> for test in glob(os.path.join(path, 'test-*')):
...     test = ELF(test)
...     assert '__stack_chk_fail' in test.got, test
...     if test.arch != 'ppc':
...         assert '__stack_chk_fail' in test.plt, test
_populate_symbols()[source]
>>> bash = ELF(which('bash'))
>>> bash.symbols['_start'] == bash.entry
True
_populate_synthetic_symbols()[source]

Adds symbols from the GOT and PLT to the symbols dictionary.

Does not overwrite any existing symbols, and prefers PLT symbols.

Synthetic plt.xxx and got.xxx symbols are added for each PLT and GOT entry, respectively.

Example:bash.

>>> bash = ELF(which('bash'))
>>> bash.symbols.wcscmp == bash.plt.wcscmp
True
>>> bash.symbols.wcscmp == bash.symbols.plt.wcscmp
True
>>> bash.symbols.stdin  == bash.got.stdin
True
>>> bash.symbols.stdin  == bash.symbols.got.stdin
True
_read_dwarf_section(section, relocate_dwarf_sections)[source]

Read the contents of a DWARF section from the stream and return a DebugSectionDescriptor. Apply relocations if asked to.

_section_offset(n)[source]

Compute the offset of section #n in the file

_segment_offset(n)[source]

Compute the offset of segment #n in the file

asm(address, assembly)[source]

Assembles the specified instructions and inserts them into the ELF at the specified address.

This modifies the ELF in-place. The resulting binary can be saved with ELF.save()

bss(offset=0) int[source]
Returns:

Address of the .bss section, plus the specified offset.

checksec(banner=True, color=True)[source]

Prints out information in the binary, similar to checksec.sh.

Parameters:
  • banner (bool) – Whether to print the path to the ELF binary.

  • color (bool) – Whether to use colored output.

close() None[source]

Close the ELF file and release all resources associated with it.

debug(argv=[], *a, **kw) tube[source]

Debug the ELF with gdb.debug().

Parameters:
  • argv (list) – List of arguments to the binary

  • *args – Extra arguments to gdb.debug()

  • **kwargs – Extra arguments to gdb.debug()

Returns:

tube – See gdb.debug()

disable_nx()[source]

Disables NX for the ELF.

Zeroes out the PT_GNU_STACK program header p_type field.

disasm(address, n_bytes) str[source]

Returns a string of disassembled instructions at the specified virtual memory address

dynamic_by_tag(tag) tag[source]
Parameters:

tag (str) – Named DT_XXX tag (e.g. 'DT_STRTAB').

Returns:

elftools.elf.dynamic.DynamicTag

dynamic_string(offset) bytes[source]

Fetches an enumerated string from the DT_STRTAB table.

Parameters:

offset (int) – String index

Returns:

str – String from the table as raw bytes.

dynamic_value_by_tag(tag) int[source]

Retrieve the value from a dynamic tag a la DT_XXX.

If the tag is missing, returns None.

fit(address, *a, **kw)[source]

Writes fitted data into the specified address.

See: packing.fit()

flat(address, *a, **kw)[source]

Writes a full array of values to the specified address.

See: packing.flat()

static from_assembly(assembly) ELF[source]

Given an assembly listing, return a fully loaded ELF object which contains that assembly at its entry point.

Parameters:
  • assembly (str) – Assembly language listing

  • vma (int) – Address of the entry point and the module’s base address.

Example

>>> e = ELF.from_assembly('nop; foo: int 0x80', vma = 0x400000)
>>> e.symbols['foo'] = 0x400001
>>> e.disasm(e.entry, 1)
'  400000:       90                      nop'
>>> e.disasm(e.symbols['foo'], 2)
'  400001:       cd 80                   int    0x80'
static from_bytes(bytes) ELF[source]

Given a sequence of bytes, return a fully loaded ELF object which contains those bytes at its entry point.

Parameters:
  • bytes (str) – Shellcode byte string

  • vma (int) – Desired base address for the ELF.

Example

>>> e = ELF.from_bytes(b'\x90\xcd\x80', vma=0xc000)
>>> print(e.disasm(e.entry, 3))
    c000:       90                      nop
    c001:       cd 80                   int    0x80
get_ehabi_infos()[source]

Generally, shared library and executable contain 1 .ARM.exidx section. Object file contains many .ARM.exidx sections. So we must traverse every section and filter sections whose type is SHT_ARM_EXIDX.

get_machine_arch()[source]

Return the machine architecture, as detected from the ELF header.

get_section_by_name(name)[source]

Get a section from the file, by name. Return None if no such section exists.

get_section_index(section_name)[source]

Gets the index of the section by name. Return None if no such section name exists.

get_segment_for_address(address, size=1) Segment[source]

Given a virtual address described by a PT_LOAD segment, return the first segment which describes the virtual address. An optional size may be provided to ensure the entire range falls into the same segment.

Parameters:
  • address (int) – Virtual address to find

  • size (int) – Number of bytes which must be available after address in both the file-backed data for the segment, and the memory region which is reserved for the data.

Returns:

Either returns a segments.Segment object, or None.

get_shstrndx()[source]

Find the string table section index for the section header table

get_supplementary_dwarfinfo(dwarfinfo)[source]

Read supplementary dwarfinfo, from either the standared .debug_sup section or the GNU proprietary .gnu_debugaltlink.

has_ehabi_info()[source]

Check whether this file appears to have arm exception handler index table.

has_phantom_bytes()[source]

The XC16 compiler for the PIC microcontrollers emits DWARF where all odd bytes in all DWARF sections are to be discarded (“phantom”).

We don’t know where does the phantom byte discarding fit into the usual chain of section content transforms. There are no XC16/PIC binaries in the corpus with relocations against DWARF, and the DWARF section compression seems to be unsupported by XC16.

iter_notes()[source]
Yields:

All the notes in the PT_NOTE segments. Each result is a dictionary- like object with n_name, n_type, and n_desc fields, amongst others.

iter_properties()[source]
Yields:

All the GNU properties in the PT_NOTE segments. Each result is a dictionary- like object with pr_type, pr_datasz, and pr_data fields.

iter_segments_by_type(t)[source]
Yields:

Segments matching the specified type.

classmethod load_from_path(path)[source]

Takes a path to a file on the local filesystem, and returns an ELFFile from it, setting up a correct stream_loader relative to the original file.

num_sections()[source]

Number of sections in the file

num_segments()[source]

Number of segments in the file

offset_to_vaddr(offset) int[source]

Translates the specified offset to a virtual address.

Parameters:

offset (int) – Offset to translate

Returns:

int – Virtual address which corresponds to the file offset, or None.

Examples

This example shows that regardless of changes to the virtual address layout by modifying ELF.address, the offset for any given address doesn’t change.

>>> bash = ELF('/bin/bash')
>>> bash.address == bash.offset_to_vaddr(0)
True
>>> bash.address += 0x123456
>>> bash.address == bash.offset_to_vaddr(0)
True
p16(address, data, *a, **kw)[source]

Writes a 16-bit integer data to the specified address

p32(address, data, *a, **kw)[source]

Writes a 32-bit integer data to the specified address

p64(address, data, *a, **kw)[source]

Writes a 64-bit integer data to the specified address

p8(address, data, *a, **kw)[source]

Writes a 8-bit integer data to the specified address

pack(address, data, *a, **kw)[source]

Writes a packed integer data to the specified address

static patch_custom_libraries(exe_path, custom_library_path, create_copy=True, suffix='_remotelibc') ELF[source]

Looks for the interpreter binary in the given path and patches the binary to use it if available. Also patches the RUNPATH to the given path using the patchelf utility.

Parameters:
  • exe_path (str) – Path to the binary to patch.

  • custom_library_path (str) – Path to a folder containing the libraries.

  • create_copy (bool) – Create a copy of the binary and apply the patches to the copy.

  • suffix (str) – Suffix to append to the filename when creating the copy to patch.

Returns:

A new ELF instance is returned after patching the binary with the external patchelf tool.

Example

>>> tmpdir = tempfile.mkdtemp()
>>> linker_path = os.path.join(tmpdir, 'ld-mock.so')
>>> write(linker_path, b'loader')
>>> ls_path = os.path.join(tmpdir, 'ls')
>>> _ = shutil.copy(which('ls'), ls_path)
>>> e = ELF.patch_custom_libraries(ls_path, tmpdir)
>>> e.runpath.decode() == tmpdir
True
>>> e.linker.decode() == linker_path
True
process(argv=[], *a, **kw) process[source]

Execute the binary with process. Note that argv is a list of arguments, and should not include argv[0].

Parameters:
  • argv (list) – List of arguments to the binary

  • *args – Extra arguments to process

  • **kwargs – Extra arguments to process

Returns:

process

read(address, count) bytes[source]

Read data from the specified virtual address

Parameters:
  • address (int) – Virtual address to read

  • count (int) – Number of bytes to read

Returns:

A bytes object, or None.

Examples

The simplest example is just to read the ELF header.

>>> bash = ELF(which('bash'))
>>> bash.read(bash.address, 4)
b'\x7fELF'

ELF segments do not have to contain all of the data on-disk that gets loaded into memory.

First, let’s create an ELF file has some code in two sections.

>>> assembly = '''
... .section .A,"awx"
... .global A
... A: nop
... .section .B,"awx"
... .global B
... B: int3
... '''
>>> e = ELF.from_assembly(assembly, vma=False)

By default, these come right after eachother in memory.

>>> e.read(e.symbols.A, 2)
b'\x90\xcc'
>>> e.symbols.B - e.symbols.A
1

Let’s move the sections so that B is a little bit further away.

>>> objcopy = pwnlib.asm._objcopy()
>>> objcopy += [
...     '--change-section-vma', '.B+5',
...     '--change-section-lma', '.B+5',
...     e.path
... ]
>>> subprocess.check_call(objcopy)
0

Now let’s re-load the ELF, and check again

>>> e = ELF(e.path)
>>> e.symbols.B - e.symbols.A
6
>>> e.read(e.symbols.A, 2)
b'\x90\x00'
>>> e.read(e.symbols.A, 7)
b'\x90\x00\x00\x00\x00\x00\xcc'
>>> e.read(e.symbols.A, 10)
b'\x90\x00\x00\x00\x00\x00\xcc\x00\x00\x00'

Everything is relative to the user-selected base address, so moving things around keeps everything working.

>>> e.address += 0x1000
>>> e.read(e.symbols.A, 10)
b'\x90\x00\x00\x00\x00\x00\xcc\x00\x00\x00'
save(path=None)[source]

Save the ELF to a file

>>> bash = ELF(which('bash'))
>>> bash.save('/tmp/bash_copy')
>>> copy = open('/tmp/bash_copy', 'rb')
>>> bash = open(which('bash'), 'rb')
>>> bash.read() == copy.read()
True
search(needle, writable=False, executable=False) generator[source]

Search the ELF’s virtual address space for the specified string.

Notes

Does not search empty space between segments, or uninitialized data. This will only return data that actually exists in the ELF file. Searching for a long string of NULL bytes probably won’t work.

Parameters:
  • needle (bytes) – String to search for.

  • writable (bool) – Search only writable sections.

  • executable (bool) – Search only executable sections.

Yields:

An iterator for each virtual address that matches.

Examples

An ELF header starts with the bytes \x7fELF, so we sould be able to find it easily.

>>> bash = ELF('/bin/bash')
>>> bash.address + 1 == next(bash.search(b'ELF'))
True

We can also search for string the binary.

>>> len(list(bash.search(b'GNU bash'))) > 0
True

It is also possible to search for instructions in executable sections.

>>> binary = ELF.from_assembly('nop; mov eax, 0; jmp esp; ret')
>>> jmp_addr = next(binary.search(asm('jmp esp'), executable = True))
>>> binary.read(jmp_addr, 2) == asm('jmp esp')
True
section(name) bytes[source]

Gets data for the named section

Parameters:

name (str) – Name of the section

Returns:

str – String containing the bytes for that section

static set_interpreter(exepath, interpreter_path) ELF[source]

Patches the interpreter of the ELF to the given binary using the patchelf utility.

When running the binary, the new interpreter will be used to load the ELF.

Parameters:
  • exepath (str) – Path to the binary to patch.

  • interpreter_path (str) – Path to the ld.so dynamic loader.

Returns:

A new ELF instance is returned after patching the binary with the external patchelf tool.

Example

>>> tmpdir = tempfile.mkdtemp()
>>> ls_path = os.path.join(tmpdir, 'ls')
>>> _ = shutil.copy(which('ls'), ls_path)
>>> e = ELF.set_interpreter(ls_path, '/tmp/correct_ld.so')
>>> e.linker == b'/tmp/correct_ld.so'
True
static set_runpath(exepath, runpath) ELF[source]

Patches the RUNPATH of the ELF to the given path using the patchelf utility.

The dynamic loader will look for any needed shared libraries in the given path first, before trying the system library paths. This is useful to run a binary with a different libc binary.

Parameters:
  • exepath (str) – Path to the binary to patch.

  • runpath (str) – Path containing the needed libraries.

Returns:

A new ELF instance is returned after patching the binary with the external patchelf tool.

Example

>>> tmpdir = tempfile.mkdtemp()
>>> ls_path = os.path.join(tmpdir, 'ls')
>>> _ = shutil.copy(which('ls'), ls_path)
>>> e = ELF.set_runpath(ls_path, './libs')
>>> e.runpath == b'./libs'
True
string(address) str[source]

Reads a null-terminated string from the specified address

Returns:

A str with the string contents (NUL terminator is omitted), or an empty string if no NUL terminator could be found.

u16(address, *a, **kw)[source]

Unpacks an integer from the specified address.

u32(address, *a, **kw)[source]

Unpacks an integer from the specified address.

u64(address, *a, **kw)[source]

Unpacks an integer from the specified address.

u8(address, *a, **kw)[source]

Unpacks an integer from the specified address.

unpack(address, *a, **kw)[source]

Unpacks an integer from the specified address.

vaddr_to_offset(address) int[source]

Translates the specified virtual address to a file offset

Parameters:

address (int) – Virtual address to translate

Returns:

int – Offset within the ELF file which corresponds to the address, or None.

Examples

>>> bash = ELF(which('bash'))
>>> bash.vaddr_to_offset(bash.address)
0
>>> bash.address += 0x123456
>>> bash.vaddr_to_offset(bash.address)
0
>>> bash.vaddr_to_offset(0) is None
True
write(address, data)[source]

Writes data to the specified virtual address

Parameters:
  • address (int) – Virtual address to write

  • data (str) – Bytes to write

Note

This routine does not check the bounds on the write to ensure that it stays in the same segment.

Examples

>>> bash = ELF(which('bash'))
>>> bash.read(bash.address+1, 3)
b'ELF'
>>> bash.write(bash.address, b"HELO")
>>> bash.read(bash.address, 4)
b'HELO'
__weakref__[source]

list of weak references to the object

property address[source]

Address of the lowest segment loaded in the ELF.

When updated, the addresses of the following fields are also updated:

However, the following fields are NOT updated:

Example

>>> bash = ELF('/bin/bash')
>>> read = bash.symbols['read']
>>> text = bash.get_section_by_name('.text').header.sh_addr
>>> bash.address += 0x1000
>>> read + 0x1000 == bash.symbols['read']
True
>>> text == bash.get_section_by_name('.text').header.sh_addr
True
Type:

int

arch[source]

Architecture of the file (e.g. 'i386', 'arm').

See: ContextType.arch

Type:

str

property asan[source]

Whether the current binary was built with Address Sanitizer (ASAN).

Type:

bool

property aslr[source]

Whether the current binary is position-independent.

Type:

bool

bits = 32[source]

Bit-ness of the file

Type:

int

build[source]

Linux kernel build commit, if this is a Linux kernel image

Type:

str

property buildid[source]

GNU Build ID embedded into the binary

Type:

bytes

bytes = 4[source]

Pointer width, in bytes

Type:

int

property canary[source]

Whether the current binary uses stack canaries.

Type:

bool

config[source]

Linux kernel configuration, if this is a Linux kernel image

Type:

dict

property data[source]

Raw data of the ELF file.

See:

get_data()

Type:

bytes

property debuginfo[source]

Whether the current binary has debug information

Type:

bool

property dwarf[source]

DWARF info for the elf

property elftype[source]

ELF type (EXEC, DYN, etc)

Type:

str

endian = 'little'[source]

Endianness of the file (e.g. 'big', 'little')

Type:

str

property entry[source]

Address of the entry point for the ELF

Type:

int

property entrypoint[source]

Address of the entry point for the ELF

Type:

int

property execstack[source]

Whether dynamically loading the current binary will make the stack executable.

This is based on the presence of a program header PT_GNU_STACK, its setting, and the default stack permissions for the architecture.

If PT_GNU_STACK is persent, the stack permissions are set according to it:

case PT_GNU_STACK:
  stack_flags = ph->p_flags;
  break;

Else, the stack permissions are set according to the architecture defaults as defined by DEFAULT_STACK_PERMS:

/* On most platforms presume that PT_GNU_STACK is absent and the stack is
 * executable.  Other platforms default to a nonexecutable stack and don't
 * need PT_GNU_STACK to do so.  */
uint_fast16_t stack_flags = DEFAULT_STACK_PERMS;

By searching the source for DEFAULT_STACK_PERMS, we can see which architectures have which settings.

$ git grep '#define DEFAULT_STACK_PERMS' | grep -v PF_X
sysdeps/aarch64/stackinfo.h:    #define DEFAULT_STACK_PERMS (PF_R|PF_W)
sysdeps/arc/stackinfo.h:        #define DEFAULT_STACK_PERMS (PF_R|PF_W)
sysdeps/csky/stackinfo.h:       #define DEFAULT_STACK_PERMS (PF_R|PF_W)
sysdeps/ia64/stackinfo.h:       #define DEFAULT_STACK_PERMS (PF_R|PF_W)
sysdeps/loongarch/stackinfo.h:  #define DEFAULT_STACK_PERMS (PF_R | PF_W)
sysdeps/nios2/stackinfo.h:      #define DEFAULT_STACK_PERMS (PF_R|PF_W)
sysdeps/riscv/stackinfo.h:      #define DEFAULT_STACK_PERMS (PF_R | PF_W)
Type:

bool

executable[source]

True if the ELF is an executable

property executable_segments[source]

List of all segments which are executable.

See:

ELF.segments

Type:

list

file[source]

Open handle to the ELF file on disk

Type:

file

property fortify[source]

Whether the current binary was built with Fortify Source (-DFORTIFY).

Type:

bool

functions = {}[source]

dotdict of name to Function for each function in the ELF

got = {}[source]

dotdict of name to address for all Global Offset Table (GOT) entries

property ibt[source]

Whether the current binary was built with Indirect Branch Tracking (IBT)

Type:

bool

property libc[source]

If this ELF imports any libraries which contain 'libc[.-], and we can determine the appropriate path to it on the local system, returns a new ELF object pertaining to that library. Prints the checksec output of the library if it was printed for the original ELF too.

If not found, the value will be None.

Type:

ELF

property libc_start_main_return[source]

Address of the return address into __libc_start_main from main.

>>> bash = ELF(which('bash'))
>>> libc = bash.libc
>>> libc.libc_start_main_return > 0
True

Try to find the return address from main into __libc_start_main. The heuristic to find the call to the function pointer of main is to list all calls inside __libc_start_main, find the call to exit after the call to main and select the previous call.

Type:

int

library[source]

True if the ELF is a shared library

property libs[source]

Dictionary of {path: address} for every library loaded for this ELF.

linker = None[source]

Path to the linker for the ELF

property maps[source]

Dictionary of {name: address} for every mapping in this ELF’s address space.

memory[source]

IntervalTree which maps all of the loaded memory segments

mmap[source]

Memory-mapped copy of the ELF file on disk

Type:

mmap.mmap

property msan[source]

Whether the current binary was built with Memory Sanitizer (MSAN).

Type:

bool

native[source]

Whether this ELF should be able to run natively

property non_writable_segments[source]

List of all segments which are NOT writeable.

See:

ELF.segments

Type:

list

property nx[source]

Whether the current binary uses NX protections.

Specifically, we are checking for READ_IMPLIES_EXEC being set by the kernel, as a result of honoring PT_GNU_STACK in the kernel.

READ_IMPLIES_EXEC is set, according to a set of architecture specific rules, that depend on the CPU features, and the presence of PT_GNU_STACK.

Unfortunately, ELF is not context-aware, so it’s not always possible to determine whether the process of a binary that’s missing PT_GNU_STACK will have NX or not.

The rules are as follows:

ELF arch

linux

GNU_STACK

other

NX

i386

< 5.8 [1]

non-exec

enabled

exec / missing

disabled

>= 5.8 [2]

exec / non-exec

enabled

missing

disabled

amd64

< 5.8 [1]

non-exec

enabled

exec / missing

disabled

>= 5.8 [2]

exec / non-exec / missing

enabled

arm

< 5.8 [3]

non-exec*

enabled

exec / missing

disabled

>= 5.8 [4]

exec / non-exec*

enabled

missing

disabled

mips

< 5.18 [5]

non-exec*

enabled

exec / missing

disabled

>= 5.18 [6]

exec / non-exec*

enabled

missing

disabled

powerpc

[7]

non-exec / exec

enabled

missing

disabled

powerpc64

[7]

exec / non-exec / missing

enabled

ia64

[8]

non-exec

enabled

exec / missing

e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK == 0

enabled

e_flags & EF_IA_64_LINUX_EXECUTABLE_STACK != 0

disabled

the rest

[9]

exec / non-exec / missing

enabled

* Hardware limitations are ignored.

If READ_IMPLIES_EXEC is set, then all readable pages are executable.
if (elf_read_implies_exec(loc->elf_ex, executable_stack))
    current->personality |= READ_IMPLIES_EXEC;
Type:

bool

os[source]

Operating system of the ELF

property packed[source]

Whether the current binary is packed with UPX.

Type:

bool

path = '/path/to/the/file'[source]

Path to the file

Type:

str

property pie[source]

Whether the current binary is position-independent.

Type:

bool

plt = {}[source]

dotdict of name to address for all Procedure Linkage Table (PLT) entries

property relro[source]

Whether the current binary uses RELRO protections.

This requires both presence of the dynamic tag DT_BIND_NOW, and a GNU_RELRO program header.

The ELF Specification describes how the linker should resolve symbols immediately, as soon as a binary is loaded. This can be emulated with the LD_BIND_NOW=1 environment variable.

DT_BIND_NOW

If present in a shared object or executable, this entry instructs the dynamic linker to process all relocations for the object containing this entry before transferring control to the program. The presence of this entry takes precedence over a directive to use lazy binding for this object when specified through the environment or via dlopen(BA_LIB).

(page 81)

Separately, an extension to the GNU linker allows a binary to specify a PT_GNU_RELRO program header, which describes the region of memory which is to be made read-only after relocations are complete.

Finally, a new-ish extension which doesn’t seem to have a canonical source of documentation is DF_BIND_NOW, which has supposedly superceded DT_BIND_NOW.

DF_BIND_NOW

If set in a shared object or executable, this flag instructs the dynamic linker to process all relocations for the object containing this entry before transferring control to the program. The presence of this entry takes precedence over a directive to use lazy binding for this object when specified through the environment or via dlopen(BA_LIB).

>>> path = pwnlib.data.elf.relro.path
>>> for test in glob(os.path.join(path, 'test-*')):
...     e = ELF(test)
...     expected = os.path.basename(test).split('-')[2]
...     actual = str(e.relro).lower()
...     assert actual == expected
Type:

bool

property rpath[source]

Whether the current binary has an RPATH.

Type:

bool

property runpath[source]

Whether the current binary has a RUNPATH.

Type:

bool

property rwx_segments[source]

List of all segments which are writeable and executable.

See:

ELF.segments

Type:

list

property sections[source]

A list of elftools.elf.sections.Section objects for the segments in the ELF.

Type:

list

property segments[source]

A list of elftools.elf.segments.Segment objects for the segments in the ELF.

Type:

list

property shadowstack[source]

Whether the current binary was built with Shadow Stack (SHSTK)

Type:

bool

property start[source]

Address of the entry point for the ELF

Type:

int

statically_linked[source]

True if the ELF is statically linked

property stripped[source]

Whether the current binary has been stripped of symbols

Type:

bool

property sym[source]

Alias for ELF.symbols

Type:

dotdict

symbols = {}[source]

dotdict of name to address for all symbols in the ELF

property ubsan[source]

Whether the current binary was built with Undefined Behavior Sanitizer (UBSAN).

Type:

bool

version[source]

Linux kernel version, if this is a Linux kernel image

Type:

tuple

property writable_segments[source]

List of all segments which are writeable.

See:

ELF.segments

Type:

list

class pwnlib.elf.elf.Function(name, address, size, elf=None)[source]

Encapsulates information about a function in an ELF binary.

Parameters:
  • name (str) – Name of the function

  • address (int) – Address of the function

  • size (int) – Size of the function, in bytes

  • elf (ELF) – Encapsulating ELF object

__init__(name, address, size, elf=None)[source]
__repr__()[source]

Return repr(self).

__weakref__[source]

list of weak references to the object

address[source]

Address of the function in the encapsulating ELF

elf[source]

Encapsulating ELF object

name[source]

Name of the function

size[source]

Size of the function, in bytes

class pwnlib.elf.elf.dotdict[source]

Wrapper to allow dotted access to dictionary elements.

Is a real dict object, but also serves up keys as attributes when reading attributes.

Supports recursive instantiation for keys which contain dots.

Example

>>> x = pwnlib.elf.elf.dotdict()
>>> isinstance(x, dict)
True
>>> x['foo'] = 3
>>> x.foo
3
>>> x['bar.baz'] = 4
>>> x.bar.baz
4
__weakref__[source]

list of weak references to the object