Assembler


Introduction

In addition to regular file formats modifications, we might want to patch code with custom assembly. This functionality is available thanks to the function:
import lief

elf = lief.ELF.parse("/bin/hello")

syscall_addresses = [
  inst.address for inst in elf.disassemble(0x400090) if inst.is_syscall
]

for syscall_addr in syscall_addresses:
    elf.assemble(syscall_addr, """
    mov x1, x0;
    str x1, [x2, #8];
    """)

Warning

The assembler is working decently for AArch64/ARM64E and x86/x86-64 but the support is highly limited for the other architectures.

Technical Details

In the same way that the disassembler is based on the LLVM MC layer, this assembler is also based on this component of LLVM.

The assembly text is consumed by the llvm::MCAsmParser object and we intercept the raw generated assembly bytes from the llvm::MCObjectWriter.

Currently, llvm::MCFixup are not resolved such as if an assembly instruction needs some kind of relocation, you can get a warning and the issued bytes be corrupted:

import lief

macho = lief.MachO.parse("my-ios-app").take(lief.MachO.Header.CPU_TYPE.ARM64)
macho.assemble(0x01665c, "bl _my_function")
warning: Fixup not resolved: bl _my_function
LIEF is going to progressively support these fixups and more importantly, it will provide the binary context of to the assembler.

This means that we the binary defines the symbol _my_function, the assembly engine will be aware of this symbol and could be used in your assembly listing:

ldr x0, =_my_function;
mov x1, #0xAABB;
str x1, [x0];
bl _my_function

Python API

C++ API

Rust API