LIEF v0.12.0

ionicons-v5-kRomain Thomas March 27, 2022

We are thrilled to announce that LIEF v0.12.0 is released! You can find the complete changelog here.

LIEF v0.12.0: What’s New?

LIEF v0.12.0 is a balanced mix of new features, internal refactoring, and performance improvement.

New Features

Regarding the new features, we added support for recomputing the PE’s rich header and the PE’s checksum. The PE’s rich header is a well-known-hidden1 feature that can be helpful to fingerprint a PE binary.

LIEF enables – since the version v0.7.0 – to access this part of the PE file with the following API:

1import lief
2pe_file = lief.parse("hello.exe")
4rich_header = pe_file.rich_header
5print(f"XOR Key: {rich_header.key}")
6for e in rich_header.entries:
7  print(f"{e.id}: {e.build_id} {e.count}")

In LIEF v0.12.0, we added two functions:

  1. LIEF::PE::RichHeader::raw: To generate the rich header blob with or without a xor key.
  2. LIEF::PE::RichHeader::hash: To generate the MD5/SHA-1/SHA-256/(…) of the rich header blob.

For those who are looking for PE’s markers or tracking PE binaries, these two functions could be used to generate a characteristic of the binary, regardless of the xor-key:

1# [...]
2rich_header = pe_file.rich_header
4marker = bytes(rich_header.hash(lief.PE.ALGORITHMS.SHA_1)).hex()

Still about the PE format, we added LIEF::PE::OptionalHeader::computed_checksum() which returns the re-computed value of the PE’s checksum (LIEF::PE::OptionalHeader::checksum()).

For regular binaries, the verification of the OptionalHeader’s checksum is not enforced by Windows and the integrity checks are usually deferred to the PE’s Authenticode. Nonetheless, verifying the checksum() value with the output of computed_checksum() could help identify binaries that would have been modified after the compilation.

Finally, we added the support for the PE’s delayed imports in LIEF and Luca Moro added the support of the LC_FILESET_ENTRY command in the Mach-O format.

Refactoring & Performance Improvement

We also refactored and enhanced LIEF’s internal codebase. Among those changes, we started to get rid of the C++ exceptions as described in this blog post: LIEF RTTI & Exceptions

We also introduced a std::span like interface (based on tcbrindle/span) to avoid returning and potentially copying std::vector<uint8_t>. For instance, LIEF::Section::content now uses the span interface. Regarding the Python API, functions or properties that bind a function which returns a span, are now returning a py::memoryview instead of the list of bytes. The original list of bytes can be recovered as follows:

1bin = lief.parse("/bin/ls")
2section = bin.get_section(".text")
4if section is not None:
5  memory_view = section.content
6  list_of_bytes = list(memory_view)

About the performances, we did a global refactoring of the ELF builder as described in this blog post: New ELF Builder. We also reduced the memory footprint of the ELF parser. For instance, in LIEF v0.11.5 a binary of 1.5G takes 3G or RAM2 while in LIEF v0.12.0, it takes quite the same memory as the file size.

Memory profiling

Eric Kilmer also did a nice and complete cleaning of the LIEF CMake integration

In February 2022, tmp.0ut v2 has been released and @netspooky presented interesting tricks on the ELF format 3 4. We fixed the ELF parser to make sure we handle these tricks.

What’s Next?

We started to implement Rust bindings for LIEF thanks to cxx and google/autocxx. These bindings are in their early stages and we can’t confirm they will be present in the next release. In the current development stage, the API looks like this:

 1let mut path: String = "/bin/ls";
 3match Binary::parse(&path) {
 4    Binary::ELF(elf) => {
 5        println!("ELF binary");
 6        for segment in elf.segments() {
 7            println!("Address: {:x}", segment.virtual_address);
 8        }
 9    },
10    Binary::PE(pe) => {
11        println!("PE binary");
12        let text_section = pe.get_section(".text");
13        text_section.name        = ".foo";
14        text_section.file_offset = 0x123;
16        text_section.commit(); // Commit the changes
17    },
18    Binary::MachO(macho) => {
19        println!("MachO binary");
20        for command in macho.commands() {
21          match command {
22            Commands::Dylib(dylib) => {
23              ...
24            },
25            Commands::Main(main_cmd) => {
26              ...
27            },
28          }
29        }
31    },
32    Binary::Unknown(x) => {
33        println!("Unknown");
34    },

We will also merge the (still private) branch that enables to parse Mach-O from memory as well as the global improvement of the Mach-O’s builder.

Regarding LIEF’s experimentations and work in progress, here is a list of topics on which we are working or we would like to work:

Parsing ELF files from memoryNot started yet
Parsing DART/Flutter snapshotsPoC
Creating an ELF from scratchPoC
Parsing PE’s private authenticode: MS Counter SignatureNot started yet
Refactoring the PE’s builderNot started yet, priority undefined
Supporting the Mach-O’s commands: LC_DYLD_CHAINED_FIXUPS / LC_DYLD_EXPORTS_TRIEDone, under testing
Supporting the archive format (AR)Early stage

If you are interested in supporting some of these topics, feel free to reach out.


Romain Thomas Posted on March 27, 2022