LIEF Extended provides a user-friendly API for disassembling code within various parts of executable formats for the following architectures: x86/x86-64, ARM, AArch64, RISC-V, MIPS, PowerPC, and eBPF.
import lief
elf = lief.ELF.parse("/bin/hello")
inst: lief.assembly.Instruction = ...
for inst in elf.disassemble(0x400120):
print(inst)
auto pe = LIEF::PE::Parser::parse("cmd.exe");
std::unique_ptr<LIEF::assembly::Instruction> inst;
for (const auto& inst : pe->disassemble("_WinRT")) {
std::cout << inst->to_string() << '\n';
}
let elf = lief::elf::Binary::parse("/bin/ls");
for inst in elf.disassemble_address(0x400) {
println!("{}", inst.to_string());
}
Consequently, when calling elf.disassemble_address(0x400), no disassembly occurs until the iterator is advanced.
In Python, you can check the effective type of a lief.assembly.Instruction with isinstance(...):
inst: lief.assembly.Instruction = ...
if isinstance(inst, lief.assembly.riscv.Instruction):
opcode: lief.assemble.riscv.OPCODE = inst.opcode
In C++, downcasting is performed using the function: LIEF::assembly::Instruction::as():
std::unique_ptr<LIEF::assembly::Instruction> inst = ...;
if (const auto* riscv_inst = inst->as<LIEF::assembly::riscv::Instruction>())
{
LIEF::assembly::riscv::OPCODE opcode = riscv_inst->opcode();
}
In Rust, instructions are represented by the enum lief::assembly::Instructions. Thus, you can write:
fn check_opcode(inst: &lief::assembly::Instructions) {
if let lief::assembly::Instructions::RiscV(riscv) = inst {
println!("{:?}", riscv.opcode());
}
}
Note
You can also check the assembler documentation here: Assembler
For the x86/x86-64 and AArch64 architectures, you can also iterate over an instruction’s operands:
import lief
inst: lief.assembly.aarch64.Instruction
for inst in macho.disassemble(0x400120):
print(inst)
# Check inst properties
if inst.is_branch:
print(f"Resolved: {inst.branch_target}")
for idx, operand in enumerate(inst.operands):
if isinstance(operand, lief.assembly.aarch64.operands.Register):
print(f"op[{idx}]: REG - {operand.value}")
if isinstance(operand, lief.assembly.aarch64.operands.Memory):
print(f"op[{idx}]: MEM - {operand.base}")
if isinstance(operand, lief.assembly.aarch64.operands.PCRelative):
print(f"op[{idx}]: PCR - {operand.value}")
if isinstance(operand, lief.assembly.aarch64.operands.Immediate):
print(f"op[{idx}]: IMM - {operand.value}")
import lief
inst: lief.assembly.x86.Instruction
for inst in elf.disassemble(0x1000200):
print(inst)
# Check inst properties
if inst.is_branch:
print(f"Resolved: {inst.branch_target}")
for idx, operand in enumerate(inst.operands):
if isinstance(operand, lief.assembly.x86.operands.Register):
print(f"op[{idx}]: REG - {operand.value}")
if isinstance(operand, lief.assembly.x86.operands.Memory):
print(f"op[{idx}]: MEM - {operand.base}")
if isinstance(operand, lief.assembly.x86.operands.PCRelative):
print(f"op[{idx}]: PCR - {operand.value}")
if isinstance(operand, lief.assembly.x86.operands.Immediate):
print(f"op[{idx}]: IMM - {operand.value}")
You can check the documentation of these architectures for more details about the exposed API.
Warning
.dSYM files.import lief
elf = lief.ELF.parse("/bin/hello")
main = elf.debug_info.find_function("main")
for inst in .instructions:
print(inst)
auto elf = LIEF::ELF::Parser::parse("/bin/hello");
if (const auto* dwarf = elf->debug_info()->as<LIEF::dwarf::DebugInfo>())
{
std::unique_ptr<LIEF::dwarf::Function> _main = dwarf->find_function("main");
for (const auto& inst : _main->instructions()) {
std::cout << inst->to_string() << '\n';
}
}
let elf = lief::elf::Binary::parse("/bin/ls");
if let Some(lief::DebugInfo::Dwarf(dwarf)) = elf.debug_info() {
if Some(func) = dwarf.find_function("main") {
for inst in func.instructions() {
println!("{}", inst.to_string());
}
}
}
For more details, please check the COFF Disassembler section
The disassembler is based on LLVM’s MC layer, which is known to be efficient and accurate for disassembling code. This LLVM MC layer is already used by other projects like capstone or, more recently, Nyxstone.
Compared to Capstone, LIEF uses a mainstream LLVM version with limited modifications to the MC layer. On the other hand, it does not expose a C API, supports fewer architectures than Capstone, and does not expose a standalone API.
Note
The current LLVM version is 22.x.
Unlike Nyxstone’s disassembler, LIEF hides LLVM from the public API, meaning that LLVM does not need to be installed on the system. On the other hand, it does not expose a standalone API.
Rust API: lief::assembly