LIEF extended exposes a user-friendly API to disassemble code in different places of executable formats for the following architectures: x86/x86-64, ARM, AArch64, RISC-V, Mips, PowerPC, 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 (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());
}
Thus, when calling elf.disassemble_address(0x400)
, nothing is disassembled until the iterator is processed.
In Python, one 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 can be done 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 architectures x86/x86-64
and AArch64
we can also iterate over the 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());
}
}
}
The disassembler is based on the LLVM’s MC layer which is known to be efficient and accurate for disassembling code. This LLVM’s MC layer has already been used by other projects like capstone or more recently Nyxstone.
Compared to Capstone, LIEF uses a mainstream LLVM version with limited modifications on 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 19.1.2.
Compared to Nyxstone’s disassembler, LLVM is hidden from the public API which means 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