ELF, PE, Mach-O binaries share similar characteristics, such as an entry point, imported/exported functions, etc.
These shared characteristics are represented in an abstract layer, which is defined by an inheritance relationship in C++/Python and by a trait in Rust.
target: lief.Binary = lief.parse("/tmp/some.elf")
target: lief.Binary = lief.parse("/Users/demo/some.macho")
target: lief.Binary = lief.parse(r"C:\some.pe.exe")
std::unique_ptr<LIEF::Binary> target = LIEF::Parser::parse("some.elf");
std::unique_ptr<LIEF::Binary> target = LIEF::Parser::parse("some.macho");
std::unique_ptr<LIEF::Binary> target = LIEF::Parser::parse("some.exe");
Due to Python’s dynamic polymorphism, the return value of lief.parse() is automatically cast into either: lief.ELF.Binary, lief.PE.Binary, or lief.MachO.Binary. To upcast this object into a lief.Binary object, one can use the lief.Binary.abstract attribute, which returns a lief.Binary instance:
import lief
target = lief.parse("some.elf")
assert type(target) is lief.ELF.Binary
abstract = target.abstract
assert type(abstract) is lief.Binary
In C++, a LIEF::Binary instance can be downcast to its underlying type using the classof idiom:
std::unique_ptr<LIEF::Binary> target = LIEF::Parser::parse("some.elf");
if (LIEF::ELF::Binary::classof(target.get())) {
auto& elf = static_cast<LIEF::ELF::Binary&>(*target);
}