- What: Release of LIEF 1.0.0 with improved security and stability
- Impact: Developers and security researchers may benefit from the new features
I’m really happy to announce the release ofLIEF 1.0.0. Compared to previous versions, this release represents an important milestones for this project: stability, usability, and security. LIEF started almost ten years ago. While the journey was driven by adding new features, it also involved correcting a handful of early design decisions that turned out to be wrong and have been corrected, release after release. I won’t pretend this version is bug-free or that it does not need further improvements, but it now rests on solid foundations, and the project has been adopted across a range of industries with positive feedback. A special thanks goes toQuansight, which generously sponsored this project. Many thanks as well toHolepunchfor their feature-based sponsorship, and toCaliffor reviewing LIEF against frontier models (aka Mythos & GPT‑5‑Cyber). The LIEF Python wheels are now built against thestable ABIand now offer afree-threadedvariant. Building against the stable ABI lets a single wheel serve multiple interpreter versions, while the free-threaded variant lets you use LIEF with the GIL disabled. This variant is available for Python 3.14 and 3.15 onward. The C++ core is now thread-safe with respect to its few static variables, so it behaves correctly under free-threading. On the Rust side, I refactored the bindings to drop the unmaintainedautocxxdependency. They are now built directly on top ofcxx, without any extra wrapper. This change dramatically reduces compilation and iteration time. The crates also moved fromapi/rust/cargo/toapi/rust/crates/, and the minimum supported Rust version is now1.85.0. I1have started annotating the LIEF API with Clang lifetime annotations[[clang::lifetimebound]]to strengthen the compile-time verification of the codebase. These annotations enable the compiler to catch issues like this one: When compiled under strict lifetime-analysis flags, Clang raises the following error: Mach-O support has been extended to include load commands introduced in recent versions of dyld:LC_LAZY_LOAD_DYLIB_INFO,LC_FUNCTION_VARIANTS, andLC_FUNCTION_VARIANT_FIXUPS. LIEF can now alsocreate a FAT (universal) binaryfrom several thin Mach-O binaries, select a specific architecture out of an existing FAT binary, and writebig-endianMach-O files. The ELF rewriter has been reworked to reduce the memory footprint of modified binaries: a rewritten ELF is now smaller than in previous releases, and LIEF can safely modify a binary it hasalreadymodified. Finally, thanks thanks to a security review by Calif, the various parsers are now safer and keep tighter control over how much memory they allocate when facing malformed inputs. This release introduces pre-compiled packages for several new architectures and platforms: One of the most exciting additions in this release is the newRuntime API. The motivation came from a recurring need: parsing ELF, Mach-O and PE binaries directly from memory. LIEF already had everything required to parse a binary from a raw pointer thanks to itsBinaryStreamabstraction, but it lacked a friendly, high-level bridge to reach it. The Runtime API is that bridge: These runtime features go well beyond parsing an executable from memory. They provide cross-platform, cross-language access to inspect and manipulate the process in which LIEF is loaded. To make this concrete, here is a Python example that JIT-compiles and executes a Windows ARM64 “Hello World” using the new Runtime API. First, we allocate a chunk of memory to hold the assembled code: Then, we assemble our code straight into thechunkwithassemble(...). This function is backed by LIEF’sassembly engine: While we could implement the “Hello World” as pure, low-level shellcode issuing raw syscalls, we can also leverage theContextual Assembly Patchingfeature to resolve symbols likeGetStdHandleandWriteFileon the fly: The same mechanism can also resolvedatasymbols. Let’s expose our message buffer and its length through the config, then actually call the two functions to print the string: Once the code is assembled in memory, we flush the instruction cache, flip the region to read-execute, and call it like a regular function: