---
documentID: "ce4def58194691d52ba5d885d5285c10ec7e272e16d656397f3f1cc2620deb88"
docname: "extended/assembler/index"
title: "Assembler - LIEF Documentation"
description: "Patch executable code with LIEF Extended, choose virtual addresses and patch sizes, and resolve symbols through an assembler configuration."
canonical: "https://lief.re/doc/latest/extended/assembler/index.html"
markdownURL: "https://lief.re/doc/latest/extended/assembler/index.md"
documentationVersion: "2.0.0"
documentationChannel: "latest"
language: "en"
contentHash: "6bd6bc0bb47a7fc6be5bbc69decb11ea6f7f6c6ae822be97507495c558a489cf"
---

# [Assembler](<https://lief.re/doc/latest/extended/assembler/index.html#assembler>)

API

- [C++](<https://lief.re/doc/latest/extended/assembler/cpp.html>)
- [Python](<https://lief.re/doc/latest/extended/assembler/python.html>)
- [Rust](<https://lief.re/doc/latest/extended/assembler/rust.html>)

## [Introduction](<https://lief.re/doc/latest/extended/assembler/index.html#introduction>)

LIEF Extended can assemble instructions and patch them into a parsed binary with  `lief.Binary.assemble()` ( [`lief::generic::Binary::assemble`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/generic/trait.Binary.html#method.assemble>) ;  [`LIEF::Binary::assemble()`](<https://lief.re/doc/latest/api/binary_abstraction/cpp.html#_CPPv4N4LIEF6Binary8assembleE8uint64_tRKNSt6stringERN8assembly15AssemblerConfigE>) ;  [`lief.Binary.assemble()`](<https://lief.re/doc/latest/api/binary_abstraction/python.html#lief.Binary.assemble>) ).

## [Patch a binary](<https://lief.re/doc/latest/extended/assembler/index.html#patch-a-binary>)

Pass a virtual address in the binary and assembly text for its architecture. The following example assumes an AArch64 ELF binary:

**Python**

```python
elf: lief.ELF.Binary

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];
        """,
    )
```

**C++**

```cpp
std::unique_ptr<LIEF::ELF::Binary> elf;

std::vector<uint64_t> syscall_addresses;
for (const auto& inst : elf->disassemble(0x400090)) {
  if (inst.is_syscall()) {
    syscall_addresses.push_back(inst.address());
  }
}

for (uint64_t addr : syscall_addresses) {
  elf->assemble(addr, R"asm(
    mov x1, x0;
    str x1, [x2, #8];
  )asm");
}
```

**Rust**

```rust
let elf: &mut lief::elf::Binary = some_elf;

let syscall_addresses: Vec<u64> = elf
    .disassemble_address(0x400090)
    .filter(|inst| inst.is_syscall())
    .map(|inst| inst.address())
    .collect();

for addr in syscall_addresses {
    elf.assemble(
        addr,
        r#"
        mov x1, x0;
        str x1, [x2, #8];
        "#,
    );
}
```

The call returns the generated bytes and patches the binary object. Save the result with the format’s write API, such as  `lief.ELF.Binary.write()` ( [`lief::elf::Binary::write`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/elf/struct.Binary.html#method.write>) ;  [`lief::elf::Binary::write_with_config`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/elf/struct.Binary.html#method.write_with_config>) ;  [`lief.ELF.Binary.write()`](<https://lief.re/doc/latest/formats/elf/python.html#lief.ELF.Binary.write>) ;  [`LIEF::ELF::Binary::write()`](<https://lief.re/doc/latest/formats/elf/cpp.html#_CPPv4N4LIEF3ELF6Binary5writeERKNSt6stringE>) ),  `lief.PE.Binary.write()` ( [`lief::pe::Binary::write`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/pe/struct.Binary.html#method.write>) ;  [`lief.PE.Binary.write()`](<https://lief.re/doc/latest/formats/pe/python.html#lief.PE.Binary.write>) ;  [`LIEF::PE::Binary::write()`](<https://lief.re/doc/latest/formats/pe/cpp.html#_CPPv4N4LIEF2PE6Binary5writeERKNSt6stringE>) ), or  `lief.MachO.Binary.write()` ( [`lief::macho::Binary::write`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/macho/struct.Binary.html#method.write>) ;  [`lief::macho::Binary::write_with_config`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/macho/struct.Binary.html#method.write_with_config>) ;  [`lief.MachO.Binary.write()`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Binary.write>) ;  [`LIEF::MachO::Binary::write()`](<https://lief.re/doc/latest/formats/macho/cpp.html#_CPPv4N4LIEF5MachO6Binary5writeERKNSt6stringE>) ).

> **Warning**
> 
> The assembler works well for `AArch64/ARM64E`, `x86/x86-64`, and `RISC-V` but support for other architectures is currently limited.

## [Technical Details](<https://lief.re/doc/latest/extended/assembler/index.html#technical-details>)

Similar to the [disassembler](<https://lief.re/doc/latest/extended/disassembler/index.html#extended-disassembler>), this assembler is based on the LLVM MC layer.

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

We also resolve `llvm::MCFixup` for a vast majority of the generated fixups. An important feature introduced in LIEF 0.17.0 is support for resolving symbols or labels **on the fly**.

## [Contextual Assembly Patching](<https://lief.re/doc/latest/extended/assembler/index.html#contextual-assembly-patching>)

Given assembly code and a target address, we might want to use a **context** to resolve symbols referenced in the assembly listing.

For example, consider the following x86-64 patch:

**Python**

```python
elf: lief.ELF.Binary

elf.assemble(
    elf.entrypoint,
    """
    mov rdi, rax;
    call a_custom_function;
    """,
)
```

**C++**

```cpp
std::unique_ptr<LIEF::ELF::Binary> elf;
elf->assemble(elf->entrypoint(), R"asm(
  mov rdi, rax;
  call a_custom_function;
)asm");
```

**Rust**

```rust
let elf: &mut lief::elf::Binary = some_elf;

elf.assemble(
    elf.entrypoint(),
    r#"
    mov rdi, rax;
    call a_custom_function;
    "#,
);
```

In this example, `a_custom_function` is undefined, so the assembler engine cannot resolve it and reports an unresolved fixup:

```text
warning: Fixup not resolved:
    call a_custom_function
```

LIEF exposes a  `lief.assembly.AssemblerConfig` ( [`lief::assembly::AssemblerConfig`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/assembly/struct.AssemblerConfig.html>) ;  [`LIEF::assembly::AssemblerConfig`](<https://lief.re/doc/latest/extended/assembler/cpp.html#_CPPv4N4LIEF8assembly15AssemblerConfigE>) ;  [`lief.assembly.AssemblerConfig`](<https://lief.re/doc/latest/extended/assembler/python.html#lief.assembly.AssemblerConfig>) ) interface that can be used to configure the engine and to **dynamically** resolve symbols used in the assembly listing:

**Python**

```python
class MyConfig(lief.assembly.AssemblerConfig):
    def __init__(self):
        super().__init__()  # Important!

    @override
    def resolve_symbol(self, name: str) -> int | None:
        if name == "a_custom_function":
            return 0x1000
        return None

elf: lief.ELF.Binary

elf.assemble(
    elf.entrypoint,
    """
    mov rdi, rax;
    call a_custom_function;
    """,
    MyConfig(),
)
```

**C++**

```cpp
class MyConfig : public LIEF::assembly::AssemblerConfig {
  public:
  std::optional<uint64_t> resolve_symbol(const std::string& name) override {
    if (name == "a_custom_function") {
      return 0x1000;
    }
    return std::nullopt;
  }
};

MyConfig myconfig;

std::unique_ptr<LIEF::ELF::Binary> elf;

elf->assemble(elf->entrypoint(), R"asm(
  mov rdi, rax;
  call a_custom_function;
)asm",
              myconfig);
```

**Rust**

```rust
let elf: &mut lief::elf::Binary = some_elf;

let mut config = lief::assembly::AssemblerConfig::default();

let resolver = Arc::new(|symbol: &str| {
    if symbol == "a_custom_function" {
        return Some(0x1000);
    }
    None
});

config.symbol_resolver = Some(resolver);

elf.assemble_with_config(
    elf.entrypoint(),
    r#"
    mov rdi, rax;
    call a_custom_function;
    "#,
    &config,
);
```

Return the symbol’s address in the same address space as the patch, or no value when it cannot be resolved. An unresolved symbol diagnostic needs to be handled before using the resulting patch.

This interface can be used to wrap a context, such as a generic  `lief.abstract.Binary` ( [`lief::generic::Binary`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/generic/trait.Binary.html>) ;  [`lief.Binary`](<https://lief.re/doc/latest/api/binary_abstraction/python.html#lief.Binary>) ;  [`LIEF::Binary`](<https://lief.re/doc/latest/api/binary_abstraction/cpp.html#_CPPv4N4LIEF6BinaryE>) ):

**Python**

```python
import lief
class MyConfig(lief.assembly.AssemblerConfig):
    def __init__(self, target: lief.Binary):
        super().__init__()  # Important!

        self._target = target

    @override
    def resolve_symbol(self, name: str) -> int | None:
        addr = self._target.get_function_address(name)
        if isinstance(addr, lief.lief_errors):
            return None
        return addr

elf: lief.ELF.Binary
config = MyConfig(elf)

elf.assemble(
    elf.entrypoint,
    """
    mov rdi, rax;
    call a_custom_function;
    """,
    config,
)
```

**C++**

```cpp
class MyConfig : public LIEF::assembly::AssemblerConfig {
  public:
  MyConfig() = delete;
  MyConfig(LIEF::Binary& target) :
    LIEF::assembly::AssemblerConfig(),
    target_(&target) {}

  std::optional<uint64_t> resolve_symbol(const std::string& name) override {
    if (auto addr = target_->get_function_address(name)) {
      return *addr;
    }
    return std::nullopt;
  }

  ~MyConfig() override = default;

  private:
  LIEF::Binary* target_ = nullptr;
};

std::unique_ptr<LIEF::ELF::Binary> elf;
MyConfig myconfig(*elf);

elf->assemble(elf->entrypoint(), R"asm(
  mov rdi, rax;
  call a_custom_function;
)asm",
              myconfig);
```

The Rust bindings do not offer the same flexibility to capture the  `lief.abstract.Binary` ( [`lief::generic::Binary`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/generic/trait.Binary.html>) ;  [`lief.Binary`](<https://lief.re/doc/latest/api/binary_abstraction/python.html#lief.Binary>) ;  [`LIEF::Binary`](<https://lief.re/doc/latest/api/binary_abstraction/cpp.html#_CPPv4N4LIEF6BinaryE>) ). Nevertheless, the closure associated with the [`lief::assembly::AssemblerConfig::symbol_resolver`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/assembly/struct.AssemblerConfig.html#structfield.symbol_resolver>) can capture most of its context:

**Rust**

```rust
let elf: &mut lief::elf::Binary = some_elf;

let mut config = lief::assembly::AssemblerConfig::default();

let sym_map: HashMap<String, u64> = elf
    .exported_symbols()
    .map(|sym| (sym.name(), sym.value()))
    .collect();

let resolver = Arc::new(move |symbol: &str| sym_map.get(symbol).copied());

config.symbol_resolver = Some(resolver);

elf.assemble_with_config(
    elf.entrypoint(),
    r#"
    mov rdi, rax;
    call a_custom_function;
    "#,
    &config,
);
```

## [Use Cases](<https://lief.re/doc/latest/extended/assembler/index.html#use-cases>)

Patching code with the assembler is a fast alternative to editing raw bytes by hand: it is useful for bypassing a check while reverse engineering, hot-patching a bug in a shipped executable, or inserting instrumentation.

### [Disabling an Instruction](<https://lief.re/doc/latest/extended/assembler/index.html#disabling-an-instruction>)

To strip a single instruction like a call to a telemetry or anti-debugging routine *without* shifting the rest of the function we can overwrite it with as many `nop` bytes as it occupied. Pairing the [disassembler](<https://lief.re/doc/latest/extended/disassembler/index.html#extended-disassembler>) with  `lief.Binary.assemble()` ( [`lief::generic::Binary::assemble`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/generic/trait.Binary.html#method.assemble>) ;  [`LIEF::Binary::assemble()`](<https://lief.re/doc/latest/api/binary_abstraction/cpp.html#_CPPv4N4LIEF6Binary8assembleE8uint64_tRKNSt6stringERN8assembly15AssemblerConfigE>) ;  [`lief.Binary.assemble()`](<https://lief.re/doc/latest/api/binary_abstraction/python.html#lief.Binary.assemble>) ) lets you do this:

**Python**

```python
elf: lief.ELF.Binary

# Overwrite the first call in the region (e.g. a call to an anti-debugging
# routine) with nops
for inst in elf.disassemble(0x401200):
    if inst.is_call:
        elf.assemble(inst.address, "nop\n" * inst.size)
        break

elf.write("patched.bin")
```

**C++**

```cpp
std::unique_ptr<LIEF::ELF::Binary> elf;

// Overwrite the first call in the region (e.g. a call to an anti-debugging
// routine) with nops
for (const auto& inst : elf->disassemble(0x401200)) {
  if (inst.is_call()) {
    std::string nops;
    for (size_t i = 0; i < inst.size(); ++i) {
      nops += "nop\n";
    }
    elf->assemble(inst.address(), nops);
    break;
  }
}

elf->write("patched.bin");
```

**Rust**

```rust
let elf: &mut lief::elf::Binary = some_elf;

// Overwrite the first call in the region (e.g. a call to an anti-debugging
// routine) with nops
let target = elf
    .disassemble_address(0x401200)
    .find(|inst| inst.is_call())
    .map(|inst| (inst.address(), inst.size()));

if let Some((address, size)) = target {
    elf.assemble(address, &"nop\n".repeat(size as usize));
}

elf.write("patched.bin");
```

> **Note**
> 
> The snippets above assume `x86/x86-64`, where a `nop` is a single byte, so `inst.size` of them fill the slot exactly. On a fixed-width instruction set such as `AArch64` every instruction is 4 bytes, so we would emit `inst.size / 4` instead.

## [In-Memory Assembler](<https://lief.re/doc/latest/extended/assembler/index.html#in-memory-assembler>)

In addition to patching binaries on disk or within standard file formats, the assembly engine is also available for JIT compilation and in-memory operations. This is exposed through the runtime API  `lief.runtime.assemble()` ( [`lief::runtime::assemble`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/runtime/fn.assemble.html>) ;  [`lief::runtime::assemble_with_config`](<https://lief-rs.s3.fr-par.scw.cloud/doc/latest/lief/runtime/fn.assemble_with_config.html>) ;  [`lief.runtime.assemble()`](<https://lief.re/doc/latest/runtime/python.html#lief.runtime.assemble>) ;  [`LIEF::runtime::assemble()`](<https://lief.re/doc/latest/runtime/cpp.html#_CPPv4N4LIEF7runtime8assembleE8uint64_tRKNSt6stringERN8assembly15AssemblerConfigE>) ), as detailed in the [Runtime Memory](<https://lief.re/doc/latest/runtime/components/memory.html#runtime-memory>) documentation.

## [API](<https://lief.re/doc/latest/extended/assembler/index.html#api>)

[Python API](<https://lief.re/doc/latest/extended/assembler/python.html>)

[C++ API](<https://lief.re/doc/latest/extended/assembler/cpp.html>)

[Rust API](<https://lief.re/doc/latest/extended/assembler/rust.html>)
