Disassembler¶
Introduction¶
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.
Disassemble a binary¶
elf: lief.ELF.Binary
for inst in elf.disassemble(0x400120):
print(inst)
std::unique_ptr<LIEF::PE::Binary> pe;
for (const auto& inst : pe->disassemble("_WinRT")) {
std::cout << inst.to_string() << '\n';
}
let elf: &lief::elf::Binary = some_elf;
for inst in elf.disassemble_address(0x400) {
println!("{}", inst);
}
Consequently, when calling elf.disassemble_address(0x400), no disassembly occurs until the iterator is advanced.
Inspect instructions and operands¶
isinstance(...) to select an architecture-specific :inst: lief.assembly.Instruction
match inst:
case lief.assembly.riscv.Instruction():
opcode: lief.assembly.riscv.OPCODE = inst.opcode
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();
}
pub fn check_opcode(some_inst: &lief::assembly::Instructions) {
let inst: &lief::assembly::Instructions = some_inst;
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
macho: lief.MachO.Binary
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):
match operand:
case lief.assembly.aarch64.operands.Register():
print(f"op[{idx}]: REG - {operand.value}")
case lief.assembly.aarch64.operands.Memory():
print(f"op[{idx}]: MEM - {operand.base}")
case lief.assembly.aarch64.operands.PCRelative():
print(f"op[{idx}]: PCR - {operand.value}")
case lief.assembly.aarch64.operands.Immediate():
print(f"op[{idx}]: IMM - {operand.value}")
import lief
elf: lief.ELF.Binary
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):
match operand:
case lief.assembly.x86.operands.Register():
print(f"op[{idx}]: REG - {operand.value}")
case lief.assembly.x86.operands.Memory():
print(f"op[{idx}]: MEM - {operand.base}")
case lief.assembly.x86.operands.PCRelative():
print(f"op[{idx}]: PCR - {operand.value}")
case lief.assembly.x86.operands.Immediate():
print(f"op[{idx}]: IMM - {operand.value}")
See the architecture-specific Python and C++ references for operand types and instruction properties. To write a patch using assembly text, continue with the assembler guide.
x86/x86-64¶
LOCK prefix of an instruction:import lief
inst: lief.assembly.x86.Instruction
if inst.has_lock_prefix or inst.is_atomic:
print(f"{inst} is atomic")
elif inst.is_lockable and (locked := inst.lock()) is not None:
print(f"atomic version of {inst}: {locked}")
std::unique_ptr<LIEF::assembly::x86::Instruction> inst;
if (inst->has_lock_prefix() || inst->is_atomic()) {
std::cout << inst->to_string() << " is atomic\n";
} else if (inst->is_lockable()) {
if (auto locked = inst->lock()) {
std::cout << "atomic version: " << locked->to_string() << '\n';
}
}
let inst: &lief::assembly::x86::Instruction = some_inst;
if inst.has_lock_prefix() || inst.is_atomic() {
println!("{} is atomic", inst);
} else if inst.is_lockable()
&& let Some(locked) = inst.lock()
{
println!("atomic version: {}", locked);
}
Use Cases¶
DWARF Function¶
Warning
.dSYM files.import lief
elf = lief.ELF.parse("/bin/hello")
main = elf.debug_info.find_function("main")
for inst in main.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").unwrap();
if let Some(lief::DebugInfo::Dwarf(dwarf)) = elf.debug_info()
&& let Some(func) = dwarf.function_by_name("main")
{
for inst in func.instructions() {
println!("{}", inst);
}
}
COFF Support¶
For more details, please check the COFF Disassembler section
In-Memory Disassembler¶
Technical Details¶
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.
API¶
Rust API: lief::assembly