ELF


Introduction

import lief

# Using filepath
elf: lief.ELF.Binary = lief.ELF.parse("/bin/ls")

# Using a Path from pathlib
elf: lief.ELF.Binary = lief.ELF.parse(pathlib.Path(r"C:\Users\test.elf"))

# Using a io object
with open("/bin/ssh", 'rb') as f:
  elf: lief.ELF.Binary = lief.ELF.parse(f)

Note

In Python, you can also use lief.parse() which returns a lief.ELF.Binary object.

From this parsed ELF binary you can use all the API exposed by the object to inspect or modify the binary itself.
elf: lief.ELF.Binary = ...

print(elf.header.entrypoint)

for section in elf.sections:
    print(section.name, len(section.content))
elf: lief.ELF.Binary = ...

elf.add_library("libdemo.so")
elf.write("new.elf")

Adding a Section/Segment

The ELF format uses two tables to represent the different slices of the binary:

  1. The sections table

  2. The segments table

While the sections table offers a detailed view of the binary, it is primarily needed by the compiler and the linker. In particular, this table is not required for loading and executing an ELF file. The Android loader enforces the existence of a sections table and requires certain specific sections but from a loading perspective, this table is not used.

If you intend to modify an ELF file to load additional content into memory (such as code or data), it is recommended to add a instead of a section:
elf: lief.ELF.Binary = ...

segment = lief.ELF.Segment()
segment.type = lief.ELF.Segment.TYPES.LOAD
segment.content = list(b'Hello World')

new_segment: lief.ELF.Segment = elf.add(segment)

elf.write("new.elf")
You can also achieve this modification by creating a that will implicitly create an associated PT_LOAD segment:
elf: lief.ELF.Binary = ...

section = lief.ELF.Section(".lief_demo")
section.content = list(b'Hello World')

new_section: lief.ELF.Section = elf.add(section, loaded=True)

elf.write("new.elf")

As mentioned above, the segments table matters from a loading perspective over the sections table. Therefore, it makes more sense to explicitly add a new segment rather than adding a section that implicitly adds a segment.

On the other hand, for debugging purposes or specific tools, one might want to add a non-loaded section. In this case, the data of the section is inserted at the end of the binary right after all the data wrapped by the segments:

elf: lief.ELF.Binary = ...

section = lief.ELF.Section(".metadata")
section.content = list(b'version: 1.2.3')

# /!\ Note that loaded is set to False here
# ------------------------------------------
new_section: lief.ELF.Section = elf.add(section, loaded=False)

elf.write("new.elf")

Advance Parsing/Writing

parser_config = lief.ELF.ParserConfig()
parser_config.parse_overlay = False

elf: lief.ELF.Binary = lief.ELF.parse("my.elf", parser_config)

builder_config = lief.ELF.Builder.config_t()
builder_config.gnu_hash = False

elf.write("new.elf", builder_config)

DWARF Support

Note that this support is only available in the extended version of LIEF.