PE Changelog for LIEF 0.17.0

Compared to LIEF v0.16.X, version 0.17.0 includes a significant refactoring of the PE parser and builder. These changes were necessary to align the PE functionalities of LIEF with those of ELF and Mach-O. If your codebase uses LIEF to process PE binaries, you may encounter issues when upgrading to LIEF 0.17.0. This page provides a comprehensive overview of the various changes, but it cannot cover every detail. If you encounter an API issue that is not described here, you can:

  1. Join Discord and ask your questions: https://discord.gg/jGQtyAYChJ

  2. Review the git diff of the tests. Most of these changes are covered by tests, so you should be able to find a relevant example.

  3. Email me at: me[at]romainthomas.fr

PE Modifications

To explore the new modifications features, you can check the following pages:

COFF String & COFF Symbol

As described in issue: #1043, if a section’s name exceeds 8 bytes, the linker may allocate a longer name in the COFF string pool while setting the section’s name with the offset in the COFF string pool.

You can access this COFF string using , while the string representing the offset (e.g. /230) remains in the section’s name.

Additionally, optional COFF symbols, along with any auxiliary symbols, are now processed correctly by LIEF:

import lief

pe = lief.PE.parse("libLIEF.dll")
section = pe.sections[10]
print(section.name) # --> '/19'
print(section.coff_string) # --> '.debug_info'

for symbol in pe.symbols:
    print(symbol.name)
    for aux in symbol.auxiliary_symbols:
        print(aux)

Although this example is written in Python, the API is also available in C++ and Rust.

Load Configuration

The IMAGE_LOAD_CONFIG_DIRECTORY structures, represented in LIEF by , regularly evolves with new Windows releases. A new version of Windows may introduce new attributes to accommodate additional functionalities like Control Flow Guard. To manage these changes, previously used a form of inheritance. For each new attribute, a new class, such as LoadConfigurationV12, would inherit from LoadConfigurationV11. This approach, though systematic, resulted in numerous modifications – especially in Rust – raising questions about the decision to use inheritance.
Starting from LIEF v0.17.0, has adopted a flat representation. In this new model, version-dependent attributes are represented with an option<>-like container.
import lief

pe = lief.PE.parse("demo.exe")
offset: Optional[int] = pe.load_configuration.hotpatch_table_offset
Additionally, the now provides an API that allows access to the internal data associated with this structure:

Some of these structures are important to correctly support ARM64EC/ARM64X PE Binary (#teasing).

Debug Information

LIEF version 0.17.0 can manage larger types of debug entries and provides a user-friendly API:

As described in the section: Debug Modification you can modify debug entries to perform various actions:

pe = lief.PE.parse("some.dll")
# Changing the PDB file path into an URL
pe.codeview_pdb.filename = "https://lief.re/"
pe.write("out.dll")

Resources

Some interfaces exposed by the have been redesigned to make them more consistent with their underlying representation in the PE format.
For instance, lief.PE.LangCodeItem has been renamed and refactored into .

Resources-related enums have been re-scoped to avoid definition conflicts between Windows’s headers and LIEF:

lief.PE.EXTENDED_WINDOW_STYLES

lief.PE.ResourceDialog.WINDOW_EXTENDED_STYLES

lief.PE.WINDOW_STYLES

lief.PE.ResourceDialog.WINDOW_STYLES

lief.PE.DIALOG_BOX_STYLES

lief.PE.ResourceDialog.DIALOG_STYLES

lief.PE.FIXED_VERSION_OS

lief.PE.ResourceVersion.fixed_file_info_t.VERSION_OS

lief.PE.FIXED_VERSION_FILE_FLAGS

lief.PE.ResourceVersion.fixed_file_info_t.FILE_FLAGS

lief.PE.FIXED_VERSION_FILE_SUB_TYPES

lief.PE.ResourceVersion.fixed_file_info_t.FILE_TYPE_DETAILS

lief.PE.ACCELERATOR_FLAGS

lief.PE.ResourceAccelerator.FLAGS

lief.PE.ACCELERATOR_VK_CODES

lief.PE.ACCELERATOR_CODES

Exception Info

For x86-64 and ARM64 PE binaries, stack unwinding information is referenced by the data directory: IMAGE_DIRECTORY_ENTRY_EXCEPTION. Even if your code is compiled without exception support, this information may still be generated by the linker.

The entries of this data directory are represented by the structure which is specialized for x64 and ARM64 functions. Regardless of the underlying architecture, each entry in this table exposes a function RVA. From a reverse engineering perspective, these RVAs offer a valuable set of function start addresses that can be used to begin disassembling the binary..
pe = lief.PE.parse("Windows.Media.Protection.PlayReady.dll", lief.PE.ParserConfgi.all)
print(pe.exceptions[10])
RuntimeFunctionX64 {
  RVA: [0x0c9350, 0x0c9382] (50 bytes)
  Unwind info RVA: 0x68eb5c
  unwind_info_t {
    Version: 1
    Flags: 0
    Size of prologue: 20
    Nb opcodes: 8 (6)
    Opcodes: [
      0x14 SAVE_NONVOL reg=RDI, offset=0x000078
      0x14 SAVE_NONVOL reg=RBX, offset=0x000070
      0x14 ALLOC_SMALL size=64
      0x10 PUSH_NONVOL reg=R15
      0x0e PUSH_NONVOL reg=R14
      0x0c PUSH_NONVOL reg=R12
    ];
  }
}
pe = lief.PE.parse("win11_arm64x_Windows.Media.Protection.PlayReady.dll", lief.PE.ParserConfgi.all)
print(pe.exceptions[10])
Runtime Unpacked AArch64 Function {
  Range(RVA): 0x00822bb8 - 0x00822c08
  Unwind location (RVA): 0x00d63838
  Length=80 Vers=0 X=0 E=0, CodeWords=8
  Epilogs=1
  Prolog unwind:
    0x0000 e1...... mov fp, sp
    0x0001 81...... stp x29, x30, [sp, #-16]!
    0x0002 e6...... save next
    0x0003 e6...... save next
    0x0004 e6...... save next
    0x0005 e6...... save next
    0x0006 e76689.. stp q6, q7, [sp, #-160]!
    0x0009 fc...... pacibsp
    0x000a e4...... end
  Epilog #1 unwind:  (Offset=10, Index=11, Reserved=0)
    0x0000 81...... ldp x29, x30, [sp], #16
    0x0001 e74e88.. ldp q14, q15, [sp, #128]
    0x0004 e74c86.. ldp q12, q13, [sp, #96]
    0x0007 e74a84.. ldp q10, q11, [sp, #64]
    0x000a e74882.. ldp q8, q9, [sp, #32]
    0x000d e76689.. ldp q6, q7, [sp], #160
    0x0010 fc...... autibsp
    0x0011 e3...... nop
    0x0012 e3...... nop
    0x0013 e4...... end
}

ARM64 - SAVE_ANY_REG

Thank you to Eli Friedman and Martin Storsjö for their work on reverse engineering this undocumented unwind opcode.

LIEF provides a detailed API for the underlying structures involved in PE exception and stack unwinding support.

Performance Considerations

In-depth analysis of exceptions metadata can introduce significant overhead. Therefore, must be explicitly enabled.

ARM64EC / ARM64X

With the recent release of SnapDragon X, ARM64-based Windows computers are becoming increasingly popular. This shift introduces some changes to the PE format. One notable change is the introduction of ARM64X binaries, which can be compared to the Mach-O FAT concept, where a single binary contains multiple architectures.

In the case of ARM64X, a single PE file encapsulates ARM64 and ARM64EC architectures. LIEF v0.17.0 exposes the following helpers to determine whether the binary is ARM64X or ARM64EC: , .

Performance Considerations

Parsing nested ARM64EC binary can introduce additional overhead compared to the 0.16.X version. Therefore, feature must be explicitly enabled.

Other Changes

The workaround involving undef.h has been removed. This file was used to #undef certain Windows defines that conflicted with LIEF enum definitions. You can now safely include Windows headers alongside LIEF headers. If you encounter any conflicts, please report the issue..

The builder engine has been refactored to resolve numerous errors and bugs associated with PE modifications. It now uses a more conservative approach. For more details, please refer to the PE page.

The “PE from Scratch” feature is no longer available as it had significant bugs that often resulted in corrupted binaries. This feature will be reintroduced later in the project. If you rely on this feature, please reach out.

List of changes: